package nix import ( "context" "database/sql" "fmt" "io" "os" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/tmpdir" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) var _ dbProcessor = processV10DB func processV10DB(ctx context.Context, config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) { dbContents, err := resolver.FileContentsByLocation(dbLocation) defer internal.CloseAndLogError(dbContents, dbLocation.RealPath) if err != nil { return nil, nil, fmt.Errorf("unable to read Nix database: %w", err) } td := tmpdir.FromContext(ctx) if td == nil { return nil, nil, fmt.Errorf("no temp dir factory in context") } tempDB, cleanupDB, err := createTempDB(td, dbContents) if err != nil { return nil, nil, fmt.Errorf("failed to create temporary database: %w", err) } // defer order is LIFO: cleanupDB (remove file) must run after db.Close and tempDB.Close defer cleanupDB() // close order is LIFO: db.Close() (SQLite conn) → tempDB.Close() (file handle) → cleanupDB (remove file) defer tempDB.Close() db, err := sql.Open("sqlite", tempDB.Name()) if err != nil { return nil, nil, fmt.Errorf("failed to open database: %w", err) } defer db.Close() db.SetConnMaxLifetime(0) packageEntries, err := extractV10DBPackages(config, db, dbLocation, resolver) if err != nil { return nil, nil, err } pkgs, relationships, err := finalizeV10DBResults(db, packageEntries, catalogerName) if err != nil { return nil, nil, err } return pkgs, relationships, nil } func extractV10DBPackages(config Config, db *sql.DB, dbLocation file.Location, resolver file.Resolver) (map[int]*dbPackageEntry, error) { pkgs, err := extractV10DBValidPaths(config, db, dbLocation, resolver) if err != nil { return nil, err } err = extractV10DBDerivationOutputs(db, pkgs) if err != nil { return nil, err } return pkgs, nil } func extractV10DBValidPaths(config Config, db *sql.DB, dbLocation file.Location, resolver file.Resolver) (map[int]*dbPackageEntry, error) { packages := make(map[int]*dbPackageEntry) rows, err := db.Query("SELECT id, path, hash, deriver FROM ValidPaths") if err != nil { return nil, fmt.Errorf("failed to query ValidPaths: %w", err) } defer rows.Close() for rows.Next() { var id int var path, hash, deriver sql.NullString if err := rows.Scan(&id, &path, &hash, &deriver); err != nil { return nil, fmt.Errorf("failed to scan ValidPaths row: %w", err) } if !path.Valid { continue } nsp := parseNixStorePath(path.String) if nsp == nil { nsp = &nixStorePath{} } // always trust the DB values over string parsing nsp.OutputHash = hash.String nsp.StorePath = path.String var files []string if config.CaptureOwnedFiles { files = listOutputPaths(path.String, resolver) } df, err := newDerivationFromPath(deriver.String, resolver) if err != nil { log.WithFields("path", deriver.String, "error", err).Trace("unable to find derivation") df = nil } packages[id] = &dbPackageEntry{ ID: id, nixStorePath: *nsp, derivationFile: df, DeriverPath: deriver.String, Location: &dbLocation, Files: files, } } return packages, nil } func listOutputPaths(storePath string, resolver file.Resolver) []string { if storePath == "" { return nil } searchGlob := storePath + "/**/*" locations, err := resolver.FilesByGlob(searchGlob) if err != nil { log.WithFields("path", storePath, "error", err).Trace("unable to find output paths") return nil } return filePaths(locations) } func extractV10DBDerivationOutputs(db *sql.DB, packages map[int]*dbPackageEntry) error { outputRows, err := db.Query("SELECT drv, id, path FROM DerivationOutputs") if err != nil { return fmt.Errorf("failed to query DerivationOutputs: %w", err) } defer outputRows.Close() pkgsByPath := make(map[string]*dbPackageEntry) for _, p := range packages { pkgsByPath[p.StorePath] = p } for outputRows.Next() { var drvID int var outputID, outputPath string if err := outputRows.Scan(&drvID, &outputID, &outputPath); err != nil { return fmt.Errorf("failed to scan DerivationOutputs row: %w", err) } if _, ok := pkgsByPath[outputPath]; !ok { continue } pkgsByPath[outputPath].Output = outputID pkgsByPath[outputPath].DrvID = drvID } return nil } func finalizeV10DBResults(db *sql.DB, packageEntries map[int]*dbPackageEntry, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) { // make Syft packages for each package entry syftPackages := make(map[int]pkg.Package) for id, entry := range packageEntries { syftPackages[id] = newDBPackage(entry, catalogerName) } var relationships []artifact.Relationship query := ` SELECT r.referrer, r.reference FROM Refs r JOIN ValidPaths v1 ON r.referrer = v1.id JOIN ValidPaths v2 ON r.reference = v2.id ` refRows, err := db.Query(query) if err != nil { return nil, nil, fmt.Errorf("failed to query Refs with ValidPaths JOIN: %w", err) } defer refRows.Close() relExists := make(map[int]map[int]bool) for refRows.Next() { var referrerID, referenceID int if err := refRows.Scan(&referrerID, &referenceID); err != nil { return nil, nil, fmt.Errorf("failed to scan Refs row: %w", err) } if referrerID == referenceID { // skip self-references continue } referrer, refExists := syftPackages[referrerID] reference, refeeExists := syftPackages[referenceID] if !refExists || !refeeExists { // only include relationships for packages we have discovered continue } if _, ok := relExists[referrerID]; !ok { relExists[referrerID] = make(map[int]bool) } if relExists[referrerID][referenceID] { // deduplicate existing relationships continue } relExists[referrerID][referenceID] = true rel := artifact.Relationship{ From: reference, To: referrer, Type: artifact.DependencyOfRelationship, } relationships = append(relationships, rel) } var pkgs []pkg.Package for _, p := range syftPackages { pkgs = append(pkgs, p) } return pkgs, relationships, nil } func createTempDB(td *tmpdir.TempDir, content io.ReadCloser) (*os.File, func(), error) { noop := func() {} tempFile, cleanup, err := td.NewFile("nix-db-*.sqlite") //nolint:gocritic // cleanup is returned to caller, not deferred here if err != nil { return nil, noop, err } _, err = io.Copy(tempFile, content) if err != nil { tempFile.Close() cleanup() return nil, noop, err } return tempFile, cleanup, nil }