gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/pkg/find/find.go (about) 1 // Copyright 2015-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package find searches for files in a directory hierarchy recursively. 6 // 7 // find can filter out files by file names, paths, and modes. 8 package find 9 10 import ( 11 "context" 12 "fmt" 13 "os" 14 "path/filepath" 15 "regexp" 16 17 "github.com/u-root/u-root/pkg/ls" 18 ) 19 20 // File is a found file. 21 type File struct { 22 // Name is the path relative to the root specified in WithRoot. 23 Name string 24 25 os.FileInfo 26 Err error 27 } 28 29 // String implements a fmt.Stringer for File. 30 // 31 // String returns a string long-formatted like `ls` would format it. 32 func (f *File) String() string { 33 s := ls.LongStringer{ 34 Human: true, 35 Name: ls.NameStringer{}, 36 } 37 rec := ls.FromOSFileInfo(f.Name, f.FileInfo) 38 rec.Name = f.Name 39 return s.FileString(rec) 40 } 41 42 type finder struct { 43 root string 44 45 // Pattern is used with Match. 46 pattern string 47 48 // Match is a pattern matching function. 49 match func(pattern string, name string) (bool, error) 50 mode os.FileMode 51 modeMask os.FileMode 52 debug func(string, ...interface{}) 53 files chan *File 54 sendErrors bool 55 } 56 57 type Set func(*finder) 58 59 // WithRoot sets a root path for the file finder. Only descendants of the root 60 // will be returned on the channel. 61 func WithRoot(rootPath string) Set { 62 return func(f *finder) { 63 f.root = rootPath 64 } 65 } 66 67 // WithoutError filters out files with errors from being sent on the channel. 68 func WithoutError() Set { 69 return func(f *finder) { 70 f.sendErrors = false 71 } 72 } 73 74 // WithPathMatch sets up a file path filter. 75 // 76 // The file path passed to match will be relative to the finder's root. 77 func WithPathMatch(pattern string, match func(pattern string, path string) (bool, error)) Set { 78 return func(f *finder) { 79 f.pattern = pattern 80 f.match = match 81 } 82 } 83 84 // WithBasenameMatch sets up a file base name filter. 85 func WithBasenameMatch(pattern string, match func(pattern string, name string) (bool, error)) Set { 86 return WithPathMatch(pattern, func(patt string, path string) (bool, error) { 87 return match(pattern, filepath.Base(path)) 88 }) 89 } 90 91 // WithRegexPathMatch sets up a path filter using regex. 92 // 93 // The file path passed to regexp.Match will be relative to the finder's root. 94 func WithRegexPathMatch(pattern string) Set { 95 return WithPathMatch(pattern, func(pattern, path string) (bool, error) { 96 return regexp.Match(pattern, []byte(path)) 97 }) 98 } 99 100 // WithFilenameMatch uses filepath.Match's shell file name matching to filter 101 // file base names. 102 func WithFilenameMatch(pattern string) Set { 103 return WithBasenameMatch(pattern, filepath.Match) 104 } 105 106 // WithModeMatch ensures only files with fileMode & modeMask == mode are returned. 107 func WithModeMatch(mode, modeMask os.FileMode) Set { 108 return func(f *finder) { 109 f.mode = mode 110 f.modeMask = modeMask 111 } 112 } 113 114 // WithDebugLog logs messages to l. 115 func WithDebugLog(l func(string, ...interface{})) Set { 116 return func(f *finder) { 117 f.debug = l 118 } 119 } 120 121 // Find finds files according to the settings and matchers given. 122 // 123 // e.g. 124 // 125 // names := Find(ctx, 126 // WithRoot("/boot"), 127 // WithFilenameMatch("sda[0-9]"), 128 // WithDebugLog(log.Printf), 129 // ) 130 func Find(ctx context.Context, opt ...Set) <-chan *File { 131 f := &finder{ 132 root: "/", 133 debug: func(string, ...interface{}) {}, 134 files: make(chan *File, 128), 135 match: filepath.Match, 136 sendErrors: true, 137 } 138 139 for _, o := range opt { 140 if o != nil { 141 o(f) 142 } 143 } 144 145 go func(f *finder) { 146 _ = filepath.Walk(f.root, func(n string, fi os.FileInfo, err error) error { 147 if err != nil && !f.sendErrors { 148 // Don't send file on channel if user doesn't want them. 149 return nil 150 } 151 152 file := &File{ 153 Name: n, 154 FileInfo: fi, 155 Err: err, 156 } 157 if err == nil { 158 // If it matches, then push its name into the result channel, 159 // and keep looking. 160 f.debug("check pattern %q against name %q", f.pattern, n) 161 if f.pattern != "" { 162 m, err := f.match(f.pattern, n) 163 if err != nil { 164 f.debug("%s: err on matching: %v", n, err) 165 return nil 166 } 167 if !m { 168 f.debug("%s: name does not match %q", n, f.pattern) 169 return nil 170 } 171 } 172 m := fi.Mode() 173 f.debug("%s: file mode %v / want mode %s with mask %s", n, m, f.mode, f.modeMask) 174 if masked := m & f.modeMask; masked != f.mode { 175 f.debug("%s: mode %s (masked %s) does not match expected mode %s", n, m, masked, f.mode) 176 return nil 177 } 178 f.debug("Found: %s", n) 179 } 180 select { 181 case <-ctx.Done(): 182 return fmt.Errorf("should never be returned to user: stop walking") 183 184 case f.files <- file: 185 return nil 186 } 187 }) 188 close(f.files) 189 }(f) 190 191 return f.files 192 }