pkg.re/essentialkaos/ek.v11@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 }