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 }