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  }