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 }