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 }