gopkg.in/essentialkaos/ek.v3@v3.5.1/fsutil/fs.go (about)

     1  // +build !windows
     2  
     3  // Package fsutil provides methods for working with files on posix compatible systems
     4  package fsutil
     5  
     6  // ////////////////////////////////////////////////////////////////////////////////// //
     7  //                                                                                    //
     8  //                     Copyright (c) 2009-2016 Essential Kaos                         //
     9  //      Essential Kaos Open Source License <http://essentialkaos.com/ekol?en>         //
    10  //                                                                                    //
    11  // ////////////////////////////////////////////////////////////////////////////////// //
    12  
    13  import (
    14  	"errors"
    15  	"os"
    16  	"strings"
    17  	"syscall"
    18  	"time"
    19  
    20  	PATH "pkg.re/essentialkaos/ek.v3/path"
    21  	"pkg.re/essentialkaos/ek.v3/system"
    22  )
    23  
    24  // ////////////////////////////////////////////////////////////////////////////////// //
    25  
    26  const (
    27  	_IFMT   = 0170000
    28  	_IFSOCK = 0140000
    29  	_IFLNK  = 0120000
    30  	_IFREG  = 0100000
    31  	_IFBLK  = 0060000
    32  	_IFDIR  = 0040000
    33  	_IFCHR  = 0020000
    34  	_IRUSR  = 00400
    35  	_IWUSR  = 00200
    36  	_IXUSR  = 00100
    37  	_IRGRP  = 00040
    38  	_IWGRP  = 00020
    39  	_IXGRP  = 00010
    40  	_IROTH  = 00004
    41  	_IWOTH  = 00002
    42  	_IXOTH  = 00001
    43  )
    44  
    45  // ////////////////////////////////////////////////////////////////////////////////// //
    46  
    47  // CheckPerms check many props at once.
    48  //
    49  // F - is file
    50  // D - is directory
    51  // X - is executable
    52  // L - is link
    53  // W - is writable
    54  // R - is readable
    55  // S - not empty (only for files)
    56  //
    57  func CheckPerms(props, path string) bool {
    58  	if len(props) == 0 || path == "" {
    59  		return false
    60  	}
    61  
    62  	path = PATH.Clean(path)
    63  	props = strings.ToUpper(props)
    64  
    65  	var stat = &syscall.Stat_t{}
    66  
    67  	err := syscall.Stat(path, stat)
    68  
    69  	if err != nil {
    70  		return false
    71  	}
    72  
    73  	var user *system.User
    74  
    75  	for _, k := range props {
    76  		switch k {
    77  
    78  		case 'F':
    79  			if stat.Mode&_IFMT != _IFREG {
    80  				return false
    81  			}
    82  
    83  		case 'D':
    84  			if stat.Mode&_IFMT != _IFDIR {
    85  				return false
    86  			}
    87  
    88  		case 'L':
    89  			if stat.Mode&_IFMT != _IFLNK {
    90  				return false
    91  			}
    92  
    93  		case 'X':
    94  			if user == nil {
    95  				user, err = system.CurrentUser()
    96  
    97  				if err != nil {
    98  					return false
    99  				}
   100  			}
   101  
   102  			if !isExecutableStat(stat, user.UID, getGIDList(user)) {
   103  				return false
   104  			}
   105  
   106  		case 'W':
   107  			if user == nil {
   108  				user, err = system.CurrentUser()
   109  
   110  				if err != nil {
   111  					return false
   112  				}
   113  			}
   114  
   115  			if !isWritableStat(stat, user.UID, getGIDList(user)) {
   116  				return false
   117  			}
   118  
   119  		case 'R':
   120  			if user == nil {
   121  				user, err = system.CurrentUser()
   122  
   123  				if err != nil {
   124  					return false
   125  				}
   126  			}
   127  
   128  			if !isReadableStat(stat, user.UID, getGIDList(user)) {
   129  				return false
   130  			}
   131  
   132  		case 'S':
   133  			if stat.Size == 0 {
   134  				return false
   135  			}
   136  		}
   137  	}
   138  
   139  	return true
   140  }
   141  
   142  // ProperPath return first proper path from given slice
   143  func ProperPath(props string, paths []string) string {
   144  	for _, path := range paths {
   145  		path = PATH.Clean(path)
   146  
   147  		if CheckPerms(props, path) {
   148  			return path
   149  		}
   150  	}
   151  
   152  	return ""
   153  }
   154  
   155  // IsExist check if target is exist in fs or not
   156  func IsExist(path string) bool {
   157  	if path == "" {
   158  		return false
   159  	}
   160  
   161  	path = PATH.Clean(path)
   162  
   163  	return syscall.Access(path, syscall.F_OK) == nil
   164  }
   165  
   166  // IsRegular check if target is regular file or not
   167  func IsRegular(path string) bool {
   168  	path = PATH.Clean(path)
   169  	mode := getMode(path)
   170  
   171  	if mode == 0 {
   172  		return false
   173  	}
   174  
   175  	return mode&_IFMT == _IFREG
   176  }
   177  
   178  // IsSocket check if target is socket or not
   179  func IsSocket(path string) bool {
   180  	path = PATH.Clean(path)
   181  	mode := getMode(path)
   182  
   183  	if mode == 0 {
   184  		return false
   185  	}
   186  
   187  	return mode&_IFMT == _IFSOCK
   188  }
   189  
   190  // IsBlockDevice check if target is block device or not
   191  func IsBlockDevice(path string) bool {
   192  	path = PATH.Clean(path)
   193  	mode := getMode(path)
   194  
   195  	if mode == 0 {
   196  		return false
   197  	}
   198  
   199  	return mode&_IFMT == _IFBLK
   200  }
   201  
   202  // IsCharacterDevice check if target is character device or not
   203  func IsCharacterDevice(path string) bool {
   204  	path = PATH.Clean(path)
   205  	mode := getMode(path)
   206  
   207  	if mode == 0 {
   208  		return false
   209  	}
   210  
   211  	return mode&_IFMT == _IFCHR
   212  }
   213  
   214  // IsDir check if target is directory or not
   215  func IsDir(path string) bool {
   216  	path = PATH.Clean(path)
   217  	mode := getMode(path)
   218  
   219  	if mode == 0 {
   220  		return false
   221  	}
   222  
   223  	return mode&_IFMT == _IFDIR
   224  }
   225  
   226  // IsLink check if file is link or not
   227  func IsLink(path string) bool {
   228  	path = PATH.Clean(path)
   229  	mode := getMode(path)
   230  
   231  	if mode == 0 {
   232  		return false
   233  	}
   234  
   235  	return mode&_IFMT == _IFLNK
   236  }
   237  
   238  // IsReadable check if file is readable or not
   239  func IsReadable(path string) bool {
   240  	if path == "" {
   241  		return false
   242  	}
   243  
   244  	path = PATH.Clean(path)
   245  
   246  	var stat = &syscall.Stat_t{}
   247  
   248  	err := syscall.Stat(path, stat)
   249  
   250  	if err != nil {
   251  		return false
   252  	}
   253  
   254  	user, err := system.CurrentUser()
   255  
   256  	if err != nil {
   257  		return false
   258  	}
   259  
   260  	return isReadableStat(stat, user.UID, getGIDList(user))
   261  }
   262  
   263  // IsWritable check if file is writable or not
   264  func IsWritable(path string) bool {
   265  	if path == "" {
   266  		return false
   267  	}
   268  
   269  	path = PATH.Clean(path)
   270  
   271  	var stat = &syscall.Stat_t{}
   272  
   273  	err := syscall.Stat(path, stat)
   274  
   275  	if err != nil {
   276  		return false
   277  	}
   278  
   279  	user, err := system.CurrentUser()
   280  
   281  	if err != nil {
   282  		return false
   283  	}
   284  
   285  	return isWritableStat(stat, user.UID, getGIDList(user))
   286  }
   287  
   288  // IsExecutable check if file is executable or not
   289  func IsExecutable(path string) bool {
   290  	if path == "" {
   291  		return false
   292  	}
   293  
   294  	path = PATH.Clean(path)
   295  
   296  	var stat = &syscall.Stat_t{}
   297  
   298  	err := syscall.Stat(path, stat)
   299  
   300  	if err != nil {
   301  		return false
   302  	}
   303  
   304  	user, err := system.CurrentUser()
   305  
   306  	if err != nil {
   307  		return false
   308  	}
   309  
   310  	return isExecutableStat(stat, user.UID, getGIDList(user))
   311  }
   312  
   313  // IsNonEmpty check if file is empty or not
   314  func IsNonEmpty(path string) bool {
   315  	if path == "" {
   316  		return false
   317  	}
   318  
   319  	path = PATH.Clean(path)
   320  
   321  	return GetSize(path) != 0
   322  }
   323  
   324  // IsEmptyDir check if directory empty or not
   325  func IsEmptyDir(path string) bool {
   326  	if path == "" {
   327  		return false
   328  	}
   329  
   330  	path = PATH.Clean(path)
   331  
   332  	fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
   333  
   334  	if err != nil {
   335  		return false
   336  	}
   337  
   338  	defer syscall.Close(fd)
   339  
   340  	n, err := syscall.ReadDirent(fd, make([]byte, 4096))
   341  
   342  	if n == 0x30 || err != nil {
   343  		return true
   344  	}
   345  
   346  	return false
   347  }
   348  
   349  // GetOwner return object owner pid and gid
   350  func GetOwner(path string) (int, int, error) {
   351  	if path == "" {
   352  		return -1, -1, errors.New("Path is empty")
   353  	}
   354  
   355  	path = PATH.Clean(path)
   356  
   357  	var stat = &syscall.Stat_t{}
   358  
   359  	err := syscall.Stat(path, stat)
   360  
   361  	if err != nil {
   362  		return -1, -1, err
   363  	}
   364  
   365  	return int(stat.Uid), int(stat.Gid), nil
   366  }
   367  
   368  // GetATime return time of last access
   369  func GetATime(path string) (time.Time, error) {
   370  	path = PATH.Clean(path)
   371  
   372  	atime, _, _, err := GetTimes(path)
   373  
   374  	return atime, err
   375  }
   376  
   377  // GetCTime return time of creation
   378  func GetCTime(path string) (time.Time, error) {
   379  	path = PATH.Clean(path)
   380  
   381  	_, _, ctime, err := GetTimes(path)
   382  
   383  	return ctime, err
   384  }
   385  
   386  // GetMTime return time of modification
   387  func GetMTime(path string) (time.Time, error) {
   388  	path = PATH.Clean(path)
   389  
   390  	_, mtime, _, err := GetTimes(path)
   391  
   392  	return mtime, err
   393  }
   394  
   395  // GetSize return file size in bytes
   396  func GetSize(path string) int64 {
   397  	if path == "" {
   398  		return 0
   399  	}
   400  
   401  	path = PATH.Clean(path)
   402  
   403  	var stat = &syscall.Stat_t{}
   404  
   405  	err := syscall.Stat(path, stat)
   406  
   407  	if err != nil {
   408  		return 0
   409  	}
   410  
   411  	return stat.Size
   412  }
   413  
   414  // GetPerm return file permissions
   415  func GetPerm(path string) os.FileMode {
   416  	path = PATH.Clean(path)
   417  	return os.FileMode(getMode(path) & 0777)
   418  }
   419  
   420  // ////////////////////////////////////////////////////////////////////////////////// //
   421  
   422  func getMode(path string) uint32 {
   423  	if path == "" {
   424  		return 0
   425  	}
   426  
   427  	var stat = &syscall.Stat_t{}
   428  
   429  	err := syscall.Stat(path, stat)
   430  
   431  	if err != nil {
   432  		return 0
   433  	}
   434  
   435  	return uint32(stat.Mode)
   436  }
   437  
   438  func isReadableStat(stat *syscall.Stat_t, uid int, gids []int) bool {
   439  	if uid == 0 {
   440  		return true
   441  	}
   442  
   443  	switch {
   444  	case stat.Mode&_IROTH == _IROTH:
   445  		return true
   446  	case stat.Mode&_IRUSR == _IRUSR && uid == int(stat.Uid):
   447  		return true
   448  	}
   449  
   450  	for _, gid := range gids {
   451  		if stat.Mode&_IRGRP == _IRGRP && gid == int(stat.Gid) {
   452  			return true
   453  		}
   454  	}
   455  
   456  	return false
   457  }
   458  
   459  func isWritableStat(stat *syscall.Stat_t, uid int, gids []int) bool {
   460  	if uid == 0 {
   461  		return true
   462  	}
   463  
   464  	switch {
   465  	case stat.Mode&_IWOTH == _IWOTH:
   466  		return true
   467  	case stat.Mode&_IWUSR == _IWUSR && uid == int(stat.Uid):
   468  		return true
   469  	}
   470  
   471  	for _, gid := range gids {
   472  		if stat.Mode&_IWGRP == _IWGRP && gid == int(stat.Gid) {
   473  			return true
   474  		}
   475  	}
   476  
   477  	return false
   478  }
   479  
   480  func isExecutableStat(stat *syscall.Stat_t, uid int, gids []int) bool {
   481  	if uid == 0 {
   482  		return true
   483  	}
   484  
   485  	switch {
   486  	case stat.Mode&_IXOTH == _IXOTH:
   487  		return true
   488  	case stat.Mode&_IXUSR == _IXUSR && uid == int(stat.Uid):
   489  		return true
   490  	}
   491  
   492  	for _, gid := range gids {
   493  		if stat.Mode&_IXGRP == _IXGRP && gid == int(stat.Gid) {
   494  			return true
   495  		}
   496  	}
   497  
   498  	return false
   499  }
   500  
   501  func getGIDList(user *system.User) []int {
   502  	if user == nil {
   503  		return []int{}
   504  	}
   505  
   506  	var result []int
   507  
   508  	for _, group := range user.Groups {
   509  		result = append(result, group.GID)
   510  	}
   511  
   512  	return result
   513  }