github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/binary/cataloger.go (about)

     1  package binary
     2  
     3  import (
     4  	"github.com/anchore/syft/internal/log"
     5  	"github.com/anchore/syft/syft/artifact"
     6  	"github.com/anchore/syft/syft/file"
     7  	"github.com/anchore/syft/syft/pkg"
     8  )
     9  
    10  const catalogerName = "binary-cataloger"
    11  
    12  func NewCataloger() *Cataloger {
    13  	return &Cataloger{}
    14  }
    15  
    16  // Cataloger is the cataloger responsible for surfacing evidence of a very limited set of binary files,
    17  // which have been identified by the classifiers. The Cataloger is _NOT_ a place to catalog any and every
    18  // binary, but rather the specific set that has been curated to be important, predominantly related to toolchain-
    19  // related runtimes like Python, Go, Java, or Node. Some exceptions can be made for widely-used binaries such
    20  // as busybox.
    21  type Cataloger struct{}
    22  
    23  // Name returns a string that uniquely describes the Cataloger
    24  func (c Cataloger) Name() string {
    25  	return catalogerName
    26  }
    27  
    28  // Catalog is given an object to resolve file references and content, this function returns any discovered Packages
    29  // after analyzing the catalog source.
    30  func (c Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    31  	var packages []pkg.Package
    32  	var relationships []artifact.Relationship
    33  
    34  	for _, cls := range defaultClassifiers {
    35  		log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
    36  		newPkgs, err := catalog(resolver, cls)
    37  		if err != nil {
    38  			log.WithFields("error", err, "classifier", cls.Class).Warn("unable to catalog binary package: %w", err)
    39  			continue
    40  		}
    41  	newPackages:
    42  		for i := range newPkgs {
    43  			newPkg := &newPkgs[i]
    44  			for j := range packages {
    45  				p := &packages[j]
    46  				// consolidate identical packages found in different locations or by different classifiers
    47  				if packagesMatch(p, newPkg) {
    48  					mergePackages(p, newPkg)
    49  					continue newPackages
    50  				}
    51  			}
    52  			packages = append(packages, *newPkg)
    53  		}
    54  	}
    55  
    56  	return packages, relationships, nil
    57  }
    58  
    59  // mergePackages merges information from the extra package into the target package
    60  func mergePackages(target *pkg.Package, extra *pkg.Package) {
    61  	// add the locations
    62  	target.Locations.Add(extra.Locations.ToSlice()...)
    63  	// update the metadata to indicate which classifiers were used
    64  	meta, _ := target.Metadata.(pkg.BinaryMetadata)
    65  	if m, ok := extra.Metadata.(pkg.BinaryMetadata); ok {
    66  		meta.Matches = append(meta.Matches, m.Matches...)
    67  	}
    68  	target.Metadata = meta
    69  }
    70  
    71  func catalog(resolver file.Resolver, cls classifier) (packages []pkg.Package, err error) {
    72  	locations, err := resolver.FilesByGlob(cls.FileGlob)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	for _, location := range locations {
    77  		pkgs, err := cls.EvidenceMatcher(resolver, cls, location)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  		packages = append(packages, pkgs...)
    82  	}
    83  	return packages, nil
    84  }
    85  
    86  // packagesMatch returns true if the binary packages "match" based on basic criteria
    87  func packagesMatch(p1 *pkg.Package, p2 *pkg.Package) bool {
    88  	if p1.Name != p2.Name ||
    89  		p1.Version != p2.Version ||
    90  		p1.Language != p2.Language ||
    91  		p1.Type != p2.Type {
    92  		return false
    93  	}
    94  
    95  	return true
    96  }