pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/fsutil/list.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package fsutil
     5  
     6  // ////////////////////////////////////////////////////////////////////////////////// //
     7  //                                                                                    //
     8  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     9  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
    10  //                                                                                    //
    11  // ////////////////////////////////////////////////////////////////////////////////// //
    12  
    13  import (
    14  	PATH "path"
    15  	"syscall"
    16  )
    17  
    18  // ////////////////////////////////////////////////////////////////////////////////// //
    19  
    20  // ListingFilter is struct with properties for filtering listing output
    21  type ListingFilter struct {
    22  	MatchPatterns    []string // Slice with shell file name patterns
    23  	NotMatchPatterns []string // Slice with shell file name patterns
    24  
    25  	ATimeOlder   int64 // Files with ATime less or equal to defined timestamp (BEFORE date)
    26  	ATimeYounger int64 // Files with ATime greater or equal to defined timestamp (AFTER date)
    27  	CTimeOlder   int64 // Files with CTime less or equal to defined timestamp (BEFORE date)
    28  	CTimeYounger int64 // Files with CTime greater or equal to defined timestamp (AFTER date)
    29  	MTimeOlder   int64 // Files with MTime less or equal to defined timestamp (BEFORE date)
    30  	MTimeYounger int64 // Files with MTime greater or equal to defined timestamp (AFTER date)
    31  
    32  	SizeLess    int64 // Files with size less than defined
    33  	SizeGreater int64 // Files with size greater than defined
    34  	SizeEqual   int64 // Files with size equals to defined
    35  	SizeZero    bool  // Empty files
    36  
    37  	Perms    string // Permission (see fsutil.CheckPerms for more info)
    38  	NotPerms string // Permission (see fsutil.CheckPerms for more info)
    39  }
    40  
    41  // ////////////////////////////////////////////////////////////////////////////////// //
    42  
    43  func (lf ListingFilter) hasMatchPatterns() bool {
    44  	return len(lf.MatchPatterns) != 0
    45  }
    46  
    47  func (lf ListingFilter) hasNotMatchPatterns() bool {
    48  	return len(lf.NotMatchPatterns) != 0
    49  }
    50  
    51  func (lf ListingFilter) hasTimes() bool {
    52  	switch {
    53  	case lf.ATimeOlder != 0,
    54  		lf.ATimeOlder != 0,
    55  		lf.ATimeYounger != 0,
    56  		lf.CTimeOlder != 0,
    57  		lf.CTimeYounger != 0,
    58  		lf.MTimeOlder != 0,
    59  		lf.MTimeYounger != 0:
    60  		return true
    61  	}
    62  
    63  	return false
    64  }
    65  
    66  func (lf ListingFilter) hasPerms() bool {
    67  	return lf.Perms != "" || lf.NotPerms != ""
    68  }
    69  
    70  func (lf ListingFilter) hasSize() bool {
    71  	return lf.SizeZero || lf.SizeGreater > 0 || lf.SizeLess > 0 || lf.SizeEqual > 0
    72  }
    73  
    74  // ////////////////////////////////////////////////////////////////////////////////// //
    75  
    76  // List is lightweight method for listing directory
    77  func List(dir string, ignoreHidden bool, filters ...ListingFilter) []string {
    78  	var names = readDir(dir)
    79  
    80  	if ignoreHidden {
    81  		names = filterHidden(names)
    82  	}
    83  
    84  	if len(filters) != 0 {
    85  		names = filterList(names, dir, filters[0])
    86  	}
    87  
    88  	return names
    89  }
    90  
    91  // ListAll is lightweight method for listing all files and directories
    92  func ListAll(dir string, ignoreHidden bool, filters ...ListingFilter) []string {
    93  	if len(filters) == 0 {
    94  		return readDirRecAll(dir, "", ignoreHidden, ListingFilter{})
    95  	}
    96  
    97  	return readDirRecAll(dir, "", ignoreHidden, filters[0])
    98  }
    99  
   100  // ListAllDirs is lightweight method for listing all directories
   101  func ListAllDirs(dir string, ignoreHidden bool, filters ...ListingFilter) []string {
   102  	if len(filters) == 0 {
   103  		return readDirRecDirs(dir, "", ignoreHidden, ListingFilter{})
   104  	}
   105  
   106  	return readDirRecDirs(dir, "", ignoreHidden, filters[0])
   107  }
   108  
   109  // ListAllFiles is lightweight method for listing all files
   110  func ListAllFiles(dir string, ignoreHidden bool, filters ...ListingFilter) []string {
   111  	if len(filters) == 0 {
   112  		return readDirRecFiles(dir, "", ignoreHidden, ListingFilter{})
   113  	}
   114  
   115  	return readDirRecFiles(dir, "", ignoreHidden, filters[0])
   116  }
   117  
   118  // ListToAbsolute converts slice with relative paths to slice with absolute paths
   119  func ListToAbsolute(path string, list []string) {
   120  	for i, t := range list {
   121  		list[i] = path + "/" + t
   122  	}
   123  }
   124  
   125  // ////////////////////////////////////////////////////////////////////////////////// //
   126  
   127  func readDir(dir string) []string {
   128  	fd, err := syscall.Open(dir, syscall.O_CLOEXEC, 0644)
   129  
   130  	if err != nil {
   131  		return nil
   132  	}
   133  
   134  	defer syscall.Close(fd)
   135  
   136  	var size = 100
   137  	var n = -1
   138  
   139  	var nbuf int
   140  	var bufp int
   141  
   142  	var buf = make([]byte, 4096)
   143  	var names = make([]string, 0, size)
   144  
   145  	for n != 0 {
   146  		if bufp >= nbuf {
   147  			bufp = 0
   148  
   149  			var errno error
   150  
   151  			nbuf, errno = fixCount(syscall.ReadDirent(fd, buf))
   152  
   153  			if errno != nil {
   154  				return names
   155  			}
   156  
   157  			if nbuf <= 0 {
   158  				break
   159  			}
   160  		}
   161  
   162  		var nb, nc int
   163  		nb, nc, names = syscall.ParseDirent(buf[bufp:nbuf], n, names)
   164  		bufp += nb
   165  		n -= nc
   166  	}
   167  
   168  	return names
   169  }
   170  
   171  func readDirRecAll(path, base string, ignoreHidden bool, filter ListingFilter) []string {
   172  	var result []string
   173  
   174  	names := readDir(path)
   175  
   176  	for _, name := range names {
   177  		if name[0] == '.' && ignoreHidden {
   178  			continue
   179  		}
   180  
   181  		if !IsDir(path + "/" + name) {
   182  			if base == "" {
   183  				if isMatch(name, path+"/"+name, filter) {
   184  					result = append(result, name)
   185  				}
   186  			} else {
   187  				if isMatch(name, path+"/"+name, filter) {
   188  					result = append(result, base+"/"+name)
   189  				}
   190  			}
   191  		} else {
   192  			if base == "" {
   193  				if isMatch(name, path+"/"+name, filter) {
   194  					result = append(result, name)
   195  					result = append(result, readDirRecAll(path+"/"+name, name, ignoreHidden, filter)...)
   196  				}
   197  			} else {
   198  				if isMatch(name, path+"/"+name, filter) {
   199  					result = append(result, base+"/"+name)
   200  					result = append(result, readDirRecAll(path+"/"+name, base+"/"+name, ignoreHidden, filter)...)
   201  				}
   202  			}
   203  		}
   204  	}
   205  
   206  	return result
   207  }
   208  
   209  func readDirRecDirs(path, base string, ignoreHidden bool, filter ListingFilter) []string {
   210  	var result []string
   211  
   212  	names := readDir(path)
   213  
   214  	for _, name := range names {
   215  		if name[0] == '.' && ignoreHidden {
   216  			continue
   217  		}
   218  
   219  		if IsDir(path + "/" + name) {
   220  			if base == "" {
   221  				if isMatch(name, path+"/"+name, filter) {
   222  					result = append(result, name)
   223  					result = append(result, readDirRecDirs(path+"/"+name, name, ignoreHidden, filter)...)
   224  				}
   225  			} else {
   226  				if isMatch(name, path+"/"+name, filter) {
   227  					result = append(result, base+"/"+name)
   228  					result = append(result, readDirRecDirs(path+"/"+name, base+"/"+name, ignoreHidden, filter)...)
   229  				}
   230  			}
   231  		}
   232  	}
   233  
   234  	return result
   235  }
   236  
   237  func readDirRecFiles(path, base string, ignoreHidden bool, filter ListingFilter) []string {
   238  	var result []string
   239  
   240  	names := readDir(path)
   241  
   242  	for _, name := range names {
   243  		if name[0] == '.' && ignoreHidden {
   244  			continue
   245  		}
   246  
   247  		if IsDir(path + "/" + name) {
   248  			if base == "" {
   249  				result = append(result, readDirRecFiles(path+"/"+name, name, ignoreHidden, filter)...)
   250  			} else {
   251  				result = append(result, readDirRecFiles(path+"/"+name, base+"/"+name, ignoreHidden, filter)...)
   252  			}
   253  		} else {
   254  			if base == "" {
   255  				if isMatch(name, path+"/"+name, filter) {
   256  					result = append(result, name)
   257  				}
   258  			} else {
   259  				if isMatch(name, path+"/"+name, filter) {
   260  					result = append(result, base+"/"+name)
   261  				}
   262  			}
   263  		}
   264  	}
   265  
   266  	return result
   267  }
   268  
   269  // It's ok to have long function with many conditions to filter some entities
   270  // codebeat:disable[LOC,ABC,CYCLO]
   271  
   272  func isMatch(name, fullPath string, filter ListingFilter) bool {
   273  	var (
   274  		hasNotMatchPatterns = filter.hasNotMatchPatterns()
   275  		hasMatchPatterns    = filter.hasMatchPatterns()
   276  		hasTimes            = filter.hasTimes()
   277  		hasPerms            = filter.hasPerms()
   278  		hasSize             = filter.hasSize()
   279  	)
   280  
   281  	if !hasNotMatchPatterns && !hasMatchPatterns && !hasTimes && !hasPerms && !hasSize {
   282  		return true
   283  	}
   284  
   285  	var match = true
   286  
   287  	if hasNotMatchPatterns {
   288  		for _, pattern := range filter.NotMatchPatterns {
   289  			matched, _ := PATH.Match(pattern, name)
   290  
   291  			if matched {
   292  				match = false
   293  				break
   294  			}
   295  		}
   296  	}
   297  
   298  	if hasMatchPatterns {
   299  		for _, pattern := range filter.MatchPatterns {
   300  			matched, _ := PATH.Match(pattern, name)
   301  
   302  			if matched {
   303  				match = true
   304  				break
   305  			}
   306  
   307  			match = false
   308  		}
   309  	}
   310  
   311  	if !hasTimes && !hasPerms && !hasSize {
   312  		return match
   313  	}
   314  
   315  	if hasTimes {
   316  		atime, mtime, ctime, err := GetTimestamps(fullPath)
   317  
   318  		if err != nil {
   319  			return match
   320  		}
   321  
   322  		if filter.MTimeYounger != 0 {
   323  			match = match && mtime >= filter.MTimeYounger
   324  		}
   325  
   326  		if filter.MTimeOlder != 0 {
   327  			match = match && mtime <= filter.MTimeOlder
   328  		}
   329  
   330  		if filter.CTimeYounger != 0 {
   331  			match = match && ctime >= filter.CTimeYounger
   332  		}
   333  
   334  		if filter.CTimeOlder != 0 {
   335  			match = match && ctime <= filter.CTimeOlder
   336  		}
   337  
   338  		if filter.ATimeYounger != 0 {
   339  			match = match && atime >= filter.ATimeYounger
   340  		}
   341  
   342  		if filter.ATimeOlder != 0 {
   343  			match = match && atime <= filter.ATimeOlder
   344  		}
   345  	}
   346  
   347  	if !hasPerms && !hasSize {
   348  		return match
   349  	}
   350  
   351  	if hasPerms {
   352  		if filter.Perms != "" {
   353  			match = match && CheckPerms(filter.Perms, fullPath)
   354  		}
   355  
   356  		if filter.NotPerms != "" {
   357  			match = match && !CheckPerms(filter.NotPerms, fullPath)
   358  		}
   359  	}
   360  
   361  	if hasSize {
   362  		if filter.SizeZero {
   363  			match = match && GetSize(fullPath) == 0
   364  		} else {
   365  			if filter.SizeEqual > 0 {
   366  				match = match && GetSize(fullPath) == filter.SizeEqual
   367  			}
   368  
   369  			if filter.SizeGreater > 0 {
   370  				match = match && GetSize(fullPath) > filter.SizeGreater
   371  			}
   372  
   373  			if filter.SizeLess > 0 {
   374  				match = match && GetSize(fullPath) < filter.SizeLess
   375  			}
   376  		}
   377  	}
   378  
   379  	return match
   380  }
   381  
   382  // codebeat:enable[LOC,ABC,CYCLO]
   383  
   384  func filterList(names []string, dir string, filter ListingFilter) []string {
   385  	var filteredNames []string
   386  
   387  	for _, name := range names {
   388  		if isMatch(name, dir+"/"+name, filter) {
   389  			filteredNames = append(filteredNames, name)
   390  		}
   391  	}
   392  
   393  	return filteredNames
   394  }
   395  
   396  func filterHidden(names []string) []string {
   397  	var filteredNames []string
   398  
   399  	for _, name := range names {
   400  		if name[0] == '.' {
   401  			continue
   402  		}
   403  
   404  		filteredNames = append(filteredNames, name)
   405  	}
   406  
   407  	return filteredNames
   408  }
   409  
   410  func fixCount(n int, err error) (int, error) {
   411  	if n < 0 {
   412  		n = 0
   413  	}
   414  
   415  	return n, err
   416  }