github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/internal/fileresolver/directory.go (about)

     1  package fileresolver
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  
     9  	stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    10  	"github.com/anchore/stereoscope/pkg/filetree"
    11  	"github.com/anchore/syft/syft/file"
    12  	"github.com/lineaje-labs/syft/internal/log"
    13  	"github.com/lineaje-labs/syft/syft/internal/windows"
    14  )
    15  
    16  var unixSystemRuntimePrefixes = []string{
    17  	"/proc",
    18  	"/dev",
    19  	"/sys",
    20  }
    21  
    22  var ErrSkipPath = errors.New("skip path")
    23  
    24  var _ file.Resolver = (*Directory)(nil)
    25  
    26  // Directory implements path and content access for the directory data source.
    27  type Directory struct {
    28  	path          string
    29  	chroot        ChrootContext
    30  	tree          filetree.Reader
    31  	index         filetree.IndexReader
    32  	searchContext filetree.Searcher
    33  	indexer       *directoryIndexer
    34  }
    35  
    36  func NewFromDirectory(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
    37  	r, err := newFromDirectoryWithoutIndex(root, base, pathFilters...)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	return r, r.buildIndex()
    43  }
    44  
    45  func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
    46  	chroot, err := NewChrootContextFromCWD(root, base)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("unable to interpret chroot context: %w", err)
    49  	}
    50  
    51  	cleanRoot := chroot.Root()
    52  	cleanBase := chroot.Base()
    53  
    54  	return &Directory{
    55  		path:    cleanRoot,
    56  		chroot:  *chroot,
    57  		tree:    filetree.New(),
    58  		index:   filetree.NewIndex(),
    59  		indexer: newDirectoryIndexer(cleanRoot, cleanBase, pathFilters...),
    60  	}, nil
    61  }
    62  
    63  func (r *Directory) buildIndex() error {
    64  	if r.indexer == nil {
    65  		return fmt.Errorf("no directory indexer configured")
    66  	}
    67  	tree, index, err := r.indexer.build()
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	r.tree = tree
    73  	r.index = index
    74  	r.searchContext = filetree.NewSearchContext(tree, index)
    75  
    76  	return nil
    77  }
    78  
    79  func (r Directory) requestPath(userPath string) (string, error) {
    80  	return r.chroot.ToNativePath(userPath)
    81  }
    82  
    83  // responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the directory resolver.
    84  func (r Directory) responsePath(path string) string {
    85  	return r.chroot.ToChrootPath(path)
    86  }
    87  
    88  // HasPath indicates if the given path exists in the underlying source.
    89  func (r *Directory) HasPath(userPath string) bool {
    90  	requestPath, err := r.requestPath(userPath)
    91  	if err != nil {
    92  		return false
    93  	}
    94  	return r.tree.HasPath(stereoscopeFile.Path(requestPath))
    95  }
    96  
    97  // Stringer to represent a directory path data source
    98  func (r Directory) String() string {
    99  	return fmt.Sprintf("dir:%s", r.path)
   100  }
   101  
   102  // FilesByPath returns all file.References that match the given paths from the directory.
   103  func (r Directory) FilesByPath(userPaths ...string) ([]file.Location, error) {
   104  	var references = make([]file.Location, 0)
   105  
   106  	for _, userPath := range userPaths {
   107  		userStrPath, err := r.requestPath(userPath)
   108  		if err != nil {
   109  			log.Warnf("unable to get file by path=%q : %+v", userPath, err)
   110  			continue
   111  		}
   112  
   113  		// we should be resolving symlinks and preserving this information as a AccessPath to the real file
   114  		ref, err := r.searchContext.SearchByPath(userStrPath, filetree.FollowBasenameLinks)
   115  		if err != nil {
   116  			log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err)
   117  			continue
   118  		}
   119  
   120  		if !ref.HasReference() {
   121  			continue
   122  		}
   123  
   124  		entry, err := r.index.Get(*ref.Reference)
   125  		if err != nil {
   126  			log.Warnf("unable to get file by path=%q : %+v", userPath, err)
   127  			continue
   128  		}
   129  
   130  		// don't consider directories
   131  		if entry.Metadata.IsDir() {
   132  			continue
   133  		}
   134  
   135  		if windows.HostRunningOnWindows() {
   136  			userStrPath = windows.ToPosix(userStrPath)
   137  		}
   138  
   139  		if ref.HasReference() {
   140  			references = append(references,
   141  				file.NewVirtualLocationFromDirectory(
   142  					r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root
   143  					r.responsePath(userStrPath),          // the path used to access this file, relative to the resolver root
   144  					*ref.Reference,
   145  				),
   146  			)
   147  		}
   148  	}
   149  
   150  	return references, nil
   151  }
   152  
   153  // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
   154  func (r Directory) FilesByGlob(patterns ...string) ([]file.Location, error) {
   155  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   156  	uniqueLocations := make([]file.Location, 0)
   157  
   158  	for _, pattern := range patterns {
   159  		refVias, err := r.searchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		for _, refVia := range refVias {
   164  			if !refVia.HasReference() || uniqueFileIDs.Contains(*refVia.Reference) {
   165  				continue
   166  			}
   167  			entry, err := r.index.Get(*refVia.Reference)
   168  			if err != nil {
   169  				return nil, fmt.Errorf("unable to get file metadata for reference %s: %w", refVia.Reference.RealPath, err)
   170  			}
   171  
   172  			// don't consider directories
   173  			if entry.Metadata.IsDir() {
   174  				continue
   175  			}
   176  
   177  			loc := file.NewVirtualLocationFromDirectory(
   178  				r.responsePath(string(refVia.Reference.RealPath)), // the actual path relative to the resolver root
   179  				r.responsePath(string(refVia.RequestPath)),        // the path used to access this file, relative to the resolver root
   180  				*refVia.Reference,
   181  			)
   182  			uniqueFileIDs.Add(*refVia.Reference)
   183  			uniqueLocations = append(uniqueLocations, loc)
   184  		}
   185  	}
   186  
   187  	return uniqueLocations, nil
   188  }
   189  
   190  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
   191  // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the
   192  // Directory, this is a simple path lookup.
   193  func (r *Directory) RelativeFileByPath(_ file.Location, path string) *file.Location {
   194  	paths, err := r.FilesByPath(path)
   195  	if err != nil {
   196  		return nil
   197  	}
   198  	if len(paths) == 0 {
   199  		return nil
   200  	}
   201  
   202  	return &paths[0]
   203  }
   204  
   205  // FileContentsByLocation fetches file contents for a single file reference relative to a directory.
   206  // If the path does not exist an error is returned.
   207  func (r Directory) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
   208  	if location.RealPath == "" {
   209  		return nil, errors.New("empty path given")
   210  	}
   211  
   212  	entry, err := r.index.Get(location.Reference())
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	// don't consider directories
   218  	if entry.Type == stereoscopeFile.TypeDirectory {
   219  		return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath)
   220  	}
   221  
   222  	// RealPath is posix so for windows directory resolver we need to translate
   223  	// to its true on disk path.
   224  	filePath := string(location.Reference().RealPath)
   225  	if windows.HostRunningOnWindows() {
   226  		filePath = windows.FromPosix(filePath)
   227  	}
   228  
   229  	return stereoscopeFile.NewLazyReadCloser(filePath), nil
   230  }
   231  
   232  func (r *Directory) AllLocations() <-chan file.Location {
   233  	results := make(chan file.Location)
   234  	go func() {
   235  		defer close(results)
   236  		for _, ref := range r.tree.AllFiles(stereoscopeFile.AllTypes()...) {
   237  			results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref)
   238  		}
   239  	}()
   240  	return results
   241  }
   242  
   243  func (r *Directory) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
   244  	entry, err := r.index.Get(location.Reference())
   245  	if err != nil {
   246  		return file.Metadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist)
   247  	}
   248  
   249  	return entry.Metadata, nil
   250  }
   251  
   252  func (r *Directory) FilesByMIMEType(types ...string) ([]file.Location, error) {
   253  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   254  	uniqueLocations := make([]file.Location, 0)
   255  
   256  	refVias, err := r.searchContext.SearchByMIMEType(types...)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  	for _, refVia := range refVias {
   261  		if !refVia.HasReference() {
   262  			continue
   263  		}
   264  		if uniqueFileIDs.Contains(*refVia.Reference) {
   265  			continue
   266  		}
   267  		location := file.NewVirtualLocationFromDirectory(
   268  			r.responsePath(string(refVia.Reference.RealPath)),
   269  			r.responsePath(string(refVia.RequestPath)),
   270  			*refVia.Reference,
   271  		)
   272  		uniqueFileIDs.Add(*refVia.Reference)
   273  		uniqueLocations = append(uniqueLocations, location)
   274  	}
   275  
   276  	return uniqueLocations, nil
   277  }