github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/nix/store_cataloger.go (about)

     1  /*
     2  Package nix provides a concrete Cataloger implementation for packages within the Nix packaging ecosystem.
     3  */
     4  package nix
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/bmatcuk/doublestar/v4"
    12  
    13  	"github.com/anchore/syft/internal/log"
    14  	"github.com/anchore/syft/internal/unknown"
    15  	"github.com/anchore/syft/syft/artifact"
    16  	"github.com/anchore/syft/syft/file"
    17  	"github.com/anchore/syft/syft/pkg"
    18  	"github.com/anchore/syft/syft/pkg/cataloger/internal/licenses"
    19  )
    20  
    21  // storeCataloger finds package outputs installed in the Nix store location (/nix/store/*).
    22  type storeCataloger struct {
    23  	config Config
    24  	name   string
    25  }
    26  
    27  // NewStoreCataloger returns a new cataloger object initialized for Nix store files.
    28  //
    29  // Deprecated: please use NewCataloger instead
    30  func NewStoreCataloger() pkg.Cataloger {
    31  	return newStoreCataloger(Config{CaptureOwnedFiles: true}, "nix-store-cataloger")
    32  }
    33  
    34  func newStoreCataloger(cfg Config, name string) storeCataloger {
    35  	return storeCataloger{
    36  		config: cfg,
    37  		name:   name,
    38  	}
    39  }
    40  
    41  func (c storeCataloger) Name() string {
    42  	return c.name
    43  }
    44  
    45  func (c storeCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    46  	prototypes, err := c.findPackagesFromStore(ctx, resolver)
    47  	if err != nil {
    48  		return nil, nil, fmt.Errorf("failed to find nix packages: %w", err)
    49  	}
    50  
    51  	drvs, err := c.findDerivationsFromStore(resolver, prototypes)
    52  	if err != nil {
    53  		// preserve unknown errors, but suppress would-be fatal errors
    54  		var cErr *unknown.CoordinateError
    55  		if !errors.As(err, &cErr) {
    56  			// let's ignore fatal errors from this path, since it only enriches packages
    57  			drvs = newDerivations()
    58  			err = nil
    59  			log.WithFields("error", err).Trace("failed to find nix derivations")
    60  		}
    61  	}
    62  
    63  	pkgs, rels := c.finalizeStorePackages(ctx, resolver, prototypes, drvs)
    64  	return pkgs, rels, err
    65  }
    66  
    67  func (c storeCataloger) finalizeStorePackages(ctx context.Context, resolver file.Resolver, pkgPrototypes []nixStorePackage, drvs *derivations) ([]pkg.Package, []artifact.Relationship) {
    68  	var pkgs []pkg.Package
    69  	var pkgByStorePath = make(map[string]pkg.Package)
    70  	for _, pp := range pkgPrototypes {
    71  		if pp.Location == nil {
    72  			continue
    73  		}
    74  
    75  		p := newNixStorePackage(pp, c.name)
    76  		p = licenses.RelativeToPackage(ctx, resolver, p)
    77  		pkgs = append(pkgs, p)
    78  		pkgByStorePath[pp.Location.RealPath] = p
    79  	}
    80  
    81  	var relationships []artifact.Relationship
    82  	for storePath, p := range pkgByStorePath {
    83  		deps := drvs.findDependencies(storePath)
    84  		for _, dep := range deps {
    85  			if depPkg, ok := pkgByStorePath[dep]; ok {
    86  				relationships = append(relationships, artifact.Relationship{
    87  					From: depPkg,
    88  					To:   p,
    89  					Type: artifact.DependencyOfRelationship,
    90  				})
    91  			}
    92  		}
    93  	}
    94  	return pkgs, relationships
    95  }
    96  
    97  func (c storeCataloger) findDerivationsFromStore(resolver file.Resolver, pkgPrototypes []nixStorePackage) (*derivations, error) {
    98  	locs, err := resolver.FilesByGlob("**/nix/store/*.drv")
    99  	if err != nil {
   100  		return nil, fmt.Errorf("failed to find derivations: %w", err)
   101  	}
   102  	var errs error
   103  	dvs := newDerivations()
   104  	for _, loc := range locs {
   105  		d, err := newDerivationFromLocation(loc, resolver)
   106  		if err != nil {
   107  			errs = unknown.Append(errs, loc.Coordinates, err)
   108  			continue
   109  		}
   110  		if d == nil {
   111  			continue
   112  		}
   113  
   114  		dvs.add(*d)
   115  	}
   116  
   117  	// attach derivations to the packages they belong to
   118  	for i := range pkgPrototypes {
   119  		p := &pkgPrototypes[i]
   120  		p.derivationFile = dvs.findDerivationForOutputPath(p.Location.RealPath)
   121  	}
   122  
   123  	return dvs, errs
   124  }
   125  
   126  func (c storeCataloger) findPackagesFromStore(ctx context.Context, resolver file.Resolver) ([]nixStorePackage, error) {
   127  	// we want to search for only directories, which isn't possible via the stereoscope API, so we need to apply the glob manually on all returned paths
   128  	var prototypes []nixStorePackage
   129  	var filesByStorePath = make(map[string]*file.LocationSet)
   130  	ctx, cancel := context.WithCancel(ctx)
   131  	defer cancel()
   132  	for location := range resolver.AllLocations(ctx) {
   133  		matchesStorePath, err := doublestar.Match("**/nix/store/*", location.RealPath)
   134  		if err != nil {
   135  			return nil, fmt.Errorf("failed to match nix store path: %w", err)
   136  		}
   137  
   138  		parentStorePath := findParentNixStorePath(location.RealPath)
   139  		if c.config.CaptureOwnedFiles && parentStorePath != "" {
   140  			fileInfo, err := resolver.FileMetadataByLocation(location)
   141  			if err != nil {
   142  				log.WithFields("path", location.RealPath).Trace("failed to get file metadata")
   143  				continue
   144  			}
   145  
   146  			if fileInfo.IsDir() {
   147  				// we should only add non-directories to the file set
   148  				continue
   149  			}
   150  
   151  			if _, ok := filesByStorePath[parentStorePath]; !ok {
   152  				s := file.NewLocationSet()
   153  				filesByStorePath[parentStorePath] = &s
   154  			}
   155  			filesByStorePath[parentStorePath].Add(location)
   156  		}
   157  
   158  		if !matchesStorePath {
   159  			continue
   160  		}
   161  
   162  		storePath := parseNixStorePath(location.RealPath)
   163  
   164  		if storePath == nil || !storePath.isValidPackage() {
   165  			continue
   166  		}
   167  
   168  		prototypes = append(prototypes, nixStorePackage{
   169  			Location:     &location,
   170  			nixStorePath: *storePath,
   171  		})
   172  	}
   173  
   174  	// add file sets to packages
   175  	for i := range prototypes {
   176  		p := &prototypes[i]
   177  		if p.Location == nil {
   178  			log.WithFields("package", p.nixStorePath.Name).Debug("nix package has no evidence locations associated")
   179  			continue
   180  		}
   181  		parentStorePath := p.Location.RealPath
   182  		files, ok := filesByStorePath[parentStorePath]
   183  		if !ok {
   184  			log.WithFields("path", parentStorePath, "nix-store-path", parentStorePath).Debug("found a nix store file for a non-existent package")
   185  			continue
   186  		}
   187  		p.Files = filePaths(files.ToSlice())
   188  	}
   189  
   190  	return prototypes, nil
   191  }
   192  
   193  func filePaths(files []file.Location) []string {
   194  	var relativePaths []string
   195  	for _, f := range files {
   196  		relativePaths = append(relativePaths, f.RealPath)
   197  	}
   198  	return relativePaths
   199  }