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 }