github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/nix/db_cataloger.go (about) 1 package nix 2 3 import ( 4 "fmt" 5 "io" 6 "path" 7 "strconv" 8 "strings" 9 10 "github.com/anchore/syft/internal" 11 "github.com/anchore/syft/internal/log" 12 "github.com/anchore/syft/internal/unknown" 13 "github.com/anchore/syft/syft/artifact" 14 "github.com/anchore/syft/syft/file" 15 "github.com/anchore/syft/syft/pkg" 16 ) 17 18 const defaultSchema = 10 19 20 type dbProcessor func(config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) 21 22 type dbCataloger struct { 23 config Config 24 schemaProcessor map[int]dbProcessor 25 catalogerName string 26 } 27 28 func newDBCataloger(cfg Config, catalogerName string) dbCataloger { 29 return dbCataloger{ 30 config: cfg, 31 catalogerName: catalogerName, 32 schemaProcessor: map[int]dbProcessor{ 33 10: processV10DB, 34 }, 35 } 36 } 37 38 type dbPackageEntry struct { 39 ID int 40 DrvID int 41 nixStorePath 42 DeriverPath string 43 *derivationFile 44 Location *file.Location 45 Files []string 46 } 47 48 func (c dbCataloger) catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { 49 dbLocs, err := resolver.FilesByGlob("**/nix/var/nix/db/db.sqlite") 50 if err != nil { 51 return nil, nil, fmt.Errorf("failed to find Nix database: %w", err) 52 } 53 54 if len(dbLocs) == 0 { 55 return nil, nil, nil 56 } 57 var pkgs []pkg.Package 58 var relationships []artifact.Relationship 59 var errs error 60 61 for _, dbLoc := range dbLocs { 62 parser, schema := c.selectDBParser(dbLoc, resolver) 63 if parser == nil { 64 errs = unknown.Appendf(errs, dbLoc.Coordinates, "unsupported Nix database schema for schema=%d at %q", schema, dbLoc.RealPath) 65 continue 66 } 67 68 newPkgs, newRelationships, err := parser(c.config, dbLoc, resolver, c.catalogerName) 69 if err != nil { 70 errs = unknown.Append(errs, dbLoc.Coordinates, err) 71 continue 72 } 73 74 pkgs = append(pkgs, newPkgs...) 75 relationships = append(relationships, newRelationships...) 76 } 77 78 return pkgs, relationships, errs 79 } 80 81 func (c dbCataloger) selectDBParser(dbLocation file.Location, resolver file.Resolver) (dbProcessor, int) { 82 loc := resolver.RelativeFileByPath(dbLocation, path.Join(path.Dir(dbLocation.RealPath), "schema")) 83 if loc == nil { 84 log.WithFields("path", dbLocation.RealPath).Tracef("failed to detect Nix database schema, assuming %d", defaultSchema) 85 return c.schemaProcessor[defaultSchema], 0 86 } 87 88 schemaContents, err := resolver.FileContentsByLocation(*loc) 89 defer internal.CloseAndLogError(schemaContents, loc.RealPath) 90 if err != nil { 91 log.WithFields("path", loc.RealPath).Tracef("failed to open Nix database schema file, assuming %d", defaultSchema) 92 return c.schemaProcessor[defaultSchema], 0 93 } 94 95 contents, err := io.ReadAll(schemaContents) 96 if err != nil { 97 log.WithFields("path", loc.RealPath).Tracef("failed to read Nix database schema file, assuming %d", defaultSchema) 98 return c.schemaProcessor[defaultSchema], 0 99 } 100 101 schema, err := strconv.Atoi(strings.TrimSpace(string(contents))) 102 if err != nil { 103 log.WithFields("path", loc.RealPath).Tracef("failed to parse Nix database schema file, assuming %d", defaultSchema) 104 return c.schemaProcessor[defaultSchema], 0 105 } 106 107 processor := c.schemaProcessor[schema] 108 109 if processor == nil { 110 closestSchema := c.findClosestSchema(schema) 111 if closestSchema == 0 { 112 schema = defaultSchema 113 } 114 processor = c.schemaProcessor[closestSchema] 115 log.WithFields("path", loc.RealPath).Tracef("unsupported Nix database schema (%d), treating as closest available schema (%d)", schema, closestSchema) 116 } 117 118 return processor, schema 119 } 120 121 func (c dbCataloger) findClosestSchema(got int) int { 122 var closest int 123 var closestDiff int 124 for schema := range c.schemaProcessor { 125 if schema == got { 126 return schema 127 } 128 diff := schema - got 129 if diff < 0 { 130 diff = -diff 131 } 132 if diff < closestDiff { 133 closestDiff = diff 134 closest = schema 135 } 136 } 137 return closest 138 }