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

     1  package fileresolver
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     8  	"github.com/anchore/stereoscope/pkg/filetree"
     9  	"github.com/anchore/stereoscope/pkg/image"
    10  	"github.com/anchore/syft/syft/file"
    11  	"github.com/lineaje-labs/syft/internal/log"
    12  )
    13  
    14  var _ file.Resolver = (*ContainerImageAllLayers)(nil)
    15  
    16  // ContainerImageAllLayers implements path and content access for the AllLayers source option for container image data sources.
    17  type ContainerImageAllLayers struct {
    18  	img    *image.Image
    19  	layers []int
    20  }
    21  
    22  // NewFromContainerImageAllLayers returns a new resolver from the perspective of all image layers for the given image.
    23  func NewFromContainerImageAllLayers(img *image.Image) (*ContainerImageAllLayers, error) {
    24  	if len(img.Layers) == 0 {
    25  		return nil, fmt.Errorf("the image does not contain any layers")
    26  	}
    27  
    28  	var layers = make([]int, 0)
    29  	for idx := range img.Layers {
    30  		layers = append(layers, idx)
    31  	}
    32  	return &ContainerImageAllLayers{
    33  		img:    img,
    34  		layers: layers,
    35  	}, nil
    36  }
    37  
    38  // HasPath indicates if the given path exists in the underlying source.
    39  func (r *ContainerImageAllLayers) HasPath(path string) bool {
    40  	p := stereoscopeFile.Path(path)
    41  	for _, layerIdx := range r.layers {
    42  		tree := r.img.Layers[layerIdx].Tree
    43  		if tree.HasPath(p) {
    44  			return true
    45  		}
    46  	}
    47  	return false
    48  }
    49  
    50  func (r *ContainerImageAllLayers) fileByRef(
    51  	ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int,
    52  ) ([]stereoscopeFile.Reference, error) {
    53  	uniqueFiles := make([]stereoscopeFile.Reference, 0)
    54  
    55  	// since there is potentially considerable work for each symlink/hardlink that needs to be resolved, let's check to see if this is a symlink/hardlink first
    56  	entry, err := r.img.FileCatalog.Get(ref)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("unable to fetch metadata (ref=%+v): %w", ref, err)
    59  	}
    60  
    61  	if entry.Metadata.Type == stereoscopeFile.TypeHardLink || entry.Metadata.Type == stereoscopeFile.TypeSymLink {
    62  		// a link may resolve in this layer or higher, assuming a squashed tree is used to search
    63  		// we should search all possible resolutions within the valid source
    64  		for _, subLayerIdx := range r.layers[layerIdx:] {
    65  			resolvedRef, err := r.img.ResolveLinkByLayerSquash(ref, subLayerIdx)
    66  			if err != nil {
    67  				return nil, fmt.Errorf("failed to resolve link from layer (layer=%d ref=%+v): %w", subLayerIdx, ref, err)
    68  			}
    69  			if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
    70  				uniqueFileIDs.Add(*resolvedRef.Reference)
    71  				uniqueFiles = append(uniqueFiles, *resolvedRef.Reference)
    72  			}
    73  		}
    74  	} else if !uniqueFileIDs.Contains(ref) {
    75  		uniqueFileIDs.Add(ref)
    76  		uniqueFiles = append(uniqueFiles, ref)
    77  	}
    78  
    79  	return uniqueFiles, nil
    80  }
    81  
    82  // FilesByPath returns all file.References that match the given paths from any layer in the image.
    83  func (r *ContainerImageAllLayers) FilesByPath(paths ...string) ([]file.Location, error) {
    84  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
    85  	uniqueLocations := make([]file.Location, 0)
    86  
    87  	for _, path := range paths {
    88  		for idx, layerIdx := range r.layers {
    89  			ref, err := r.img.Layers[layerIdx].SearchContext.SearchByPath(path, filetree.FollowBasenameLinks, filetree.DoNotFollowDeadBasenameLinks)
    90  			if err != nil {
    91  				return nil, err
    92  			}
    93  			if !ref.HasReference() {
    94  				// no file found, keep looking through layers
    95  				continue
    96  			}
    97  
    98  			// don't consider directories (special case: there is no path information for /)
    99  			if ref.RealPath == "/" {
   100  				continue
   101  			} else if r.img.FileCatalog.Exists(*ref.Reference) {
   102  				metadata, err := r.img.FileCatalog.Get(*ref.Reference)
   103  				if err != nil {
   104  					return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err)
   105  				}
   106  				if metadata.Metadata.IsDir() {
   107  					continue
   108  				}
   109  			}
   110  
   111  			results, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
   112  			if err != nil {
   113  				return nil, err
   114  			}
   115  			for _, result := range results {
   116  				uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, result, r.img))
   117  			}
   118  		}
   119  	}
   120  	return uniqueLocations, nil
   121  }
   122  
   123  // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
   124  // nolint:gocognit
   125  func (r *ContainerImageAllLayers) FilesByGlob(patterns ...string) ([]file.Location, error) {
   126  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   127  	uniqueLocations := make([]file.Location, 0)
   128  
   129  	for _, pattern := range patterns {
   130  		for idx, layerIdx := range r.layers {
   131  			results, err := r.img.Layers[layerIdx].SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks, filetree.DoNotFollowDeadBasenameLinks)
   132  			if err != nil {
   133  				return nil, fmt.Errorf("failed to resolve files by glob (%s): %w", pattern, err)
   134  			}
   135  
   136  			for _, result := range results {
   137  				if !result.HasReference() {
   138  					continue
   139  				}
   140  				// don't consider directories (special case: there is no path information for /)
   141  				if result.RealPath == "/" {
   142  					continue
   143  				} else if r.img.FileCatalog.Exists(*result.Reference) {
   144  					metadata, err := r.img.FileCatalog.Get(*result.Reference)
   145  					if err != nil {
   146  						return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err)
   147  					}
   148  					// don't consider directories
   149  					if metadata.Metadata.IsDir() {
   150  						continue
   151  					}
   152  				}
   153  
   154  				refResults, err := r.fileByRef(*result.Reference, uniqueFileIDs, idx)
   155  				if err != nil {
   156  					return nil, err
   157  				}
   158  				for _, refResult := range refResults {
   159  					uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(result.RequestPath), refResult, r.img))
   160  				}
   161  			}
   162  		}
   163  	}
   164  
   165  	return uniqueLocations, nil
   166  }
   167  
   168  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
   169  // This is helpful when attempting to find a file that is in the same layer or lower as another file.
   170  func (r *ContainerImageAllLayers) RelativeFileByPath(location file.Location, path string) *file.Location {
   171  	layer := r.img.FileCatalog.Layer(location.Reference())
   172  
   173  	exists, relativeRef, err := layer.SquashedTree.File(stereoscopeFile.Path(path), filetree.FollowBasenameLinks)
   174  	if err != nil {
   175  		log.Errorf("failed to find path=%q in squash: %+w", path, err)
   176  		return nil
   177  	}
   178  	if !exists && !relativeRef.HasReference() {
   179  		return nil
   180  	}
   181  
   182  	relativeLocation := file.NewLocationFromImage(path, *relativeRef.Reference, r.img)
   183  
   184  	return &relativeLocation
   185  }
   186  
   187  // FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer.
   188  // If the path does not exist an error is returned.
   189  func (r *ContainerImageAllLayers) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
   190  	entry, err := r.img.FileCatalog.Get(location.Reference())
   191  	if err != nil {
   192  		return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err)
   193  	}
   194  
   195  	switch entry.Metadata.Type {
   196  	case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
   197  		// the location we are searching may be a symlink, we should always work with the resolved file
   198  		newLocation := r.RelativeFileByPath(location, location.AccessPath)
   199  		if newLocation == nil {
   200  			// this is a dead link
   201  			return nil, fmt.Errorf("no contents for location=%q", location.AccessPath)
   202  		}
   203  		location = *newLocation
   204  	case stereoscopeFile.TypeDirectory:
   205  		return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath)
   206  	}
   207  
   208  	return r.img.OpenReference(location.Reference())
   209  }
   210  
   211  func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Location, error) {
   212  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   213  	uniqueLocations := make([]file.Location, 0)
   214  
   215  	for idx, layerIdx := range r.layers {
   216  		refs, err := r.img.Layers[layerIdx].SearchContext.SearchByMIMEType(types...)
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  
   221  		for _, ref := range refs {
   222  			if !ref.HasReference() {
   223  				continue
   224  			}
   225  
   226  			refResults, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
   227  			if err != nil {
   228  				return nil, err
   229  			}
   230  			for _, refResult := range refResults {
   231  				uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img))
   232  			}
   233  		}
   234  	}
   235  
   236  	return uniqueLocations, nil
   237  }
   238  
   239  func (r *ContainerImageAllLayers) AllLocations() <-chan file.Location {
   240  	results := make(chan file.Location)
   241  	go func() {
   242  		defer close(results)
   243  		for _, layerIdx := range r.layers {
   244  			tree := r.img.Layers[layerIdx].Tree
   245  			for _, ref := range tree.AllFiles(stereoscopeFile.AllTypes()...) {
   246  				results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
   247  			}
   248  		}
   249  	}()
   250  	return results
   251  }
   252  
   253  func (r *ContainerImageAllLayers) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
   254  	return fileMetadataByLocation(r.img, location)
   255  }