github.com/haraldrudell/parl@v0.4.176/pfs/traverser-next.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package pfs 7 8 import ( 9 "io/fs" 10 "path/filepath" 11 ) 12 13 // Next returns the next file-system entry 14 // - Next ends with [ResultEntry.DirEntry] nil, ie. [ResultEntry.IsEnd] returns true 15 // or ResultEntry.Reason == REnd 16 // - symlinks and directories can be skipped by invoking [ResultEntry.Skip]. 17 // Those have ResultEntry.Reason == RSkippable 18 // - symlinks have information based on the symlink source but [ResultEntry.Abs] is the 19 // fully resolved symlink target 20 // - [ResultEntry.ProvidedPath] is a path to the entry based upon the initially 21 // provided path. May be empty string 22 // - [ResultEntry.Abs] is an absolute symlink-free clean path but only available when 23 // [ResultEntry.Err] is nil 24 // - [ResultEntry.Err] holds any error associated with the returned entry 25 // - — 26 // - result.Err is from: 27 // - — process working directory cannot be read 28 // - — directory read error or [os.Readlink] or [os.Lstat] failed 29 func (t *Traverser) Next() (result ResultEntry) { 30 var entry ResultEntry 31 32 // if pending initial path, create its root and entry 33 if t.initialPath != "" { 34 entry = t.createInitialRoot() 35 } else { 36 for { 37 38 // process any pending returned entries 39 if len(t.skippables) > 0 { 40 entry = t.skippables[0] 41 t.skippables[0] = ResultEntry{} 42 t.skippables = t.skippables[1:] 43 44 // handle skip and error 45 if t.skipCheck(entry.No) { 46 entry = ResultEntry{} 47 continue // skip: not a directory that should be listed 48 } 49 50 // symlink that wasn’t skipped 51 if entry.Type()&fs.ModeSymlink != 0 { 52 t.processSymlink(entry.Abs) 53 entry = ResultEntry{} 54 continue // entry complete 55 } 56 57 // read directory that wasn’t skipped 58 if entry.Err = t.readDir(entry.Abs, entry.ProvidedPath); entry.Err == nil { 59 entry = ResultEntry{} 60 continue // directory read successfully 61 } 62 entry.Reason = RDirBad 63 // the directory is returned again for the error 64 65 // process pending directory entries 66 } else if len(t.dirEntries) > 0 { 67 var dir = t.dirEntries[0] 68 // number of directory entries typically ranges a dozen up to 3,000 69 // one small slice alloc equals copy of 3,072 bytes: trimleft_bench_test.go 70 // sizeof dirEntry is 48 bytes: 64 elements is 3,072 bytes 71 // alloc is once per directory, copy is once per directory entry 72 // number of copied elements for n is: [n(n+1)]/2: if 11 or more directory entries: alloc is faster 73 // do alloc here 74 t.dirEntries[0] = dirEntry{} 75 t.dirEntries = t.dirEntries[1:] 76 var name = dir.dirEntry.Name() 77 entry.ProvidedPath = filepath.Join(dir.providedPath, name) 78 entry.Abs = filepath.Join(dir.abs, name) 79 entry.DirEntry = dir.dirEntry 80 81 // process any additional roots 82 } else { 83 var root *Root2 84 for t.rootIndex+1 < t.rootsRegistry.ListLength() { 85 t.rootIndex++ 86 if root = t.rootsRegistry.GetValue(t.rootIndex); root != nil { 87 break 88 } 89 } 90 if root != nil { 91 entry.ProvidedPath = root.ProvidedPath 92 entry.Abs = root.Abs 93 // either DirEntry or Err will be non-nil 94 entry.DirEntry, entry.Err = AddDirEntry(entry.Abs) 95 } else { 96 97 // out of entries 98 return // REnd 99 } 100 } 101 102 // possibly return the entry 103 // - entry has ProvidedPath and (DirEntry/Abs or Err) 104 // - if entry is read from directory, IsDir/Type is always available 105 // - if entry.Err is non-nil, Abs and Name/IsDir/Type/Info are unavailable 106 // - entry may be any modeType including directory or symlink 107 108 // resolve error-free symlinks 109 if entry.Err == nil && entry.Type()&fs.ModeSymlink != 0 { 110 // the symlink can be: 111 // - broken: entry.Err is non-nil 112 // - matching or a descendant of an existing root: ignore 113 // - a separate location creating a new root 114 // - a parent directory obsoleting an existing root 115 116 // resolve all symlinks returning absolute, symlink-free, clean path 117 var abs string 118 if abs, entry.Err = filepath.EvalSymlinks(entry.Abs); entry.Err == nil { 119 var dirEntry fs.DirEntry 120 if dirEntry, entry.Err = AddDirEntry(abs); entry.Err == nil { 121 // ProvidedPath is symlink source 122 entry.Abs = abs 123 // DirEntry is symlink source 124 _ = dirEntry 125 entry.Reason = RSkippable 126 entry.No = t.skipNo.Add(1) 127 t.skippables = append(t.skippables, entry) 128 } 129 } 130 if entry.Err != nil { 131 entry.Reason = RSymlinkBad 132 } 133 } 134 135 // if entry is an obsolete root, it has already been traversed 136 if entry.Err == nil && t.obsoleteRoots.HasAbs(entry.Abs) { 137 entry = ResultEntry{} 138 continue 139 } 140 141 break 142 } 143 } 144 145 // the entry is to be returned 146 if entry.Err == nil && entry.IsDir() { 147 entry.No = t.skipNo.Add(1) 148 t.skippables = append(t.skippables, entry) 149 } 150 result = entry 151 if result.Reason == REnd { 152 if result.Err == nil { 153 if result.No != 0 { 154 result.Reason = RSkippable 155 result.SkipEntry = t.skip 156 } else { 157 result.Reason = REntry 158 } 159 } else { 160 result.Reason = RError 161 } 162 } 163 164 return // return of good or errored entry 165 }