github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/walker/fs.go (about) 1 package walker 2 3 import ( 4 "io/fs" 5 "os" 6 "path/filepath" 7 8 swalker "github.com/saracen/walker" 9 "golang.org/x/xerrors" 10 11 dio "github.com/aquasecurity/go-dep-parser/pkg/io" 12 "github.com/devseccon/trivy/pkg/log" 13 ) 14 15 type ErrorCallback func(pathname string, err error) error 16 17 type FS struct { 18 walker 19 parallel int 20 errCallback ErrorCallback 21 } 22 23 func NewFS(skipFiles, skipDirs []string, parallel int, errCallback ErrorCallback) FS { 24 if errCallback == nil { 25 errCallback = func(pathname string, err error) error { 26 // ignore permission errors 27 if os.IsPermission(err) { 28 return nil 29 } 30 // halt traversal on any other error 31 return xerrors.Errorf("unknown error with %s: %w", pathname, err) 32 } 33 } 34 35 return FS{ 36 walker: newWalker(skipFiles, skipDirs), 37 parallel: parallel, 38 errCallback: errCallback, 39 } 40 } 41 42 // Walk walks the file tree rooted at root, calling WalkFunc for each file or 43 // directory in the tree, including root, but a directory to be ignored will be skipped. 44 func (w FS) Walk(root string, fn WalkFunc) error { 45 // walk function called for every path found 46 walkFn := func(pathname string, fi os.FileInfo) error { 47 pathname = filepath.Clean(pathname) 48 49 // For exported rootfs (e.g. images/alpine/etc/alpine-release) 50 relPath, err := filepath.Rel(root, pathname) 51 if err != nil { 52 return xerrors.Errorf("filepath rel (%s): %w", relPath, err) 53 } 54 relPath = filepath.ToSlash(relPath) 55 56 switch { 57 case fi.IsDir(): 58 if w.shouldSkipDir(relPath) { 59 return filepath.SkipDir 60 } 61 return nil 62 case !fi.Mode().IsRegular(): 63 return nil 64 case w.shouldSkipFile(relPath): 65 return nil 66 } 67 68 if err = fn(relPath, fi, w.fileOpener(pathname)); err != nil { 69 return xerrors.Errorf("failed to analyze file: %w", err) 70 } 71 return nil 72 } 73 74 if w.parallel <= 1 { 75 // In series: fast, with higher CPU/memory 76 return w.walkSlow(root, walkFn) 77 } 78 79 // In parallel: slow, with lower CPU/memory 80 return w.walkFast(root, walkFn) 81 } 82 83 type fastWalkFunc func(pathname string, fi os.FileInfo) error 84 85 func (w FS) walkFast(root string, walkFn fastWalkFunc) error { 86 // error function called for every error encountered 87 errorCallbackOption := swalker.WithErrorCallback(w.errCallback) 88 89 // Multiple goroutines stat the filesystem concurrently. The provided 90 // walkFn must be safe for concurrent use. 91 log.Logger.Debugf("Walk the file tree rooted at '%s' in parallel", root) 92 if err := swalker.Walk(root, walkFn, errorCallbackOption); err != nil { 93 return xerrors.Errorf("walk error: %w", err) 94 } 95 return nil 96 } 97 98 func (w FS) walkSlow(root string, walkFn fastWalkFunc) error { 99 log.Logger.Debugf("Walk the file tree rooted at '%s' in series", root) 100 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { 101 if err != nil { 102 return w.errCallback(path, err) 103 } 104 info, err := d.Info() 105 if err != nil { 106 return xerrors.Errorf("file info error: %w", err) 107 } 108 return walkFn(path, info) 109 }) 110 if err != nil { 111 return xerrors.Errorf("walk dir error: %w", err) 112 } 113 return nil 114 } 115 116 // fileOpener returns a function opening a file. 117 func (w *walker) fileOpener(pathname string) func() (dio.ReadSeekCloserAt, error) { 118 return func() (dio.ReadSeekCloserAt, error) { 119 return os.Open(pathname) 120 } 121 }