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  }