github.com/anchore/syft@v1.38.2/syft/internal/fileresolver/filetree_resolver.go (about)

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