github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/file/cataloger/filedigest/cataloger.go (about) 1 package filedigest 2 3 import ( 4 "context" 5 "crypto" 6 "errors" 7 "fmt" 8 9 "github.com/dustin/go-humanize" 10 11 stereoscopeFile "github.com/anchore/stereoscope/pkg/file" 12 "github.com/anchore/syft/internal" 13 "github.com/anchore/syft/internal/bus" 14 intFile "github.com/anchore/syft/internal/file" 15 "github.com/anchore/syft/internal/log" 16 "github.com/anchore/syft/syft/event/monitor" 17 "github.com/anchore/syft/syft/file" 18 intCataloger "github.com/anchore/syft/syft/file/cataloger/internal" 19 ) 20 21 var ErrUndigestableFile = errors.New("undigestable file") 22 23 type Cataloger struct { 24 hashes []crypto.Hash 25 } 26 27 func NewCataloger(hashes []crypto.Hash) *Cataloger { 28 return &Cataloger{ 29 hashes: intFile.NormalizeHashes(hashes), 30 } 31 } 32 33 func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) { 34 results := make(map[file.Coordinates][]file.Digest) 35 var locations []file.Location 36 37 if len(coordinates) == 0 { 38 locations = intCataloger.AllRegularFiles(ctx, resolver) 39 } else { 40 for _, c := range coordinates { 41 locs, err := resolver.FilesByPath(c.RealPath) 42 if err != nil { 43 return nil, fmt.Errorf("unable to get file locations for path %q: %w", c.RealPath, err) 44 } 45 locations = append(locations, locs...) 46 } 47 } 48 49 prog := catalogingProgress(int64(len(locations))) 50 for _, location := range locations { 51 result, err := i.catalogLocation(resolver, location) 52 53 if errors.Is(err, ErrUndigestableFile) { 54 continue 55 } 56 57 prog.AtomicStage.Set(location.Path()) 58 59 if internal.IsErrPathPermission(err) { 60 log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err) 61 continue 62 } 63 64 if err != nil { 65 prog.SetError(err) 66 return nil, fmt.Errorf("failed to process file %q: %w", location.RealPath, err) 67 } 68 69 prog.Increment() 70 71 results[location.Coordinates] = result 72 } 73 74 log.Debugf("file digests cataloger processed %d files", prog.Current()) 75 76 prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current()))) 77 prog.SetCompleted() 78 79 return results, nil 80 } 81 82 func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) { 83 meta, err := resolver.FileMetadataByLocation(location) 84 if err != nil { 85 return nil, err 86 } 87 88 // we should only attempt to report digests for files that are regular files (don't attempt to resolve links) 89 if meta.Type != stereoscopeFile.TypeRegular { 90 return nil, ErrUndigestableFile 91 } 92 93 contentReader, err := resolver.FileContentsByLocation(location) 94 if err != nil { 95 return nil, err 96 } 97 defer internal.CloseAndLogError(contentReader, location.AccessPath) 98 99 digests, err := intFile.NewDigestsFromFile(contentReader, i.hashes) 100 if err != nil { 101 return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err} 102 } 103 104 return digests, nil 105 } 106 107 func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { 108 info := monitor.GenericTask{ 109 Title: monitor.Title{ 110 Default: "File digests", 111 }, 112 ParentID: monitor.TopLevelCatalogingTaskID, 113 } 114 115 return bus.StartCatalogerTask(info, locations, "") 116 }