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

     1  package nix
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/anchore/syft/internal"
    10  	"github.com/anchore/syft/internal/log"
    11  	"github.com/anchore/syft/syft/artifact"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/pkg"
    14  )
    15  
    16  var _ dbProcessor = processV10DB
    17  
    18  func processV10DB(config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) {
    19  	dbContents, err := resolver.FileContentsByLocation(dbLocation)
    20  	defer internal.CloseAndLogError(dbContents, dbLocation.RealPath)
    21  	if err != nil {
    22  		return nil, nil, fmt.Errorf("unable to read Nix database: %w", err)
    23  	}
    24  
    25  	tempDB, err := createTempDB(dbContents)
    26  	if err != nil {
    27  		return nil, nil, fmt.Errorf("failed to create temporary database: %w", err)
    28  	}
    29  	defer os.RemoveAll(tempDB.Name())
    30  
    31  	db, err := sql.Open("sqlite", tempDB.Name())
    32  	if err != nil {
    33  		return nil, nil, fmt.Errorf("failed to open database: %w", err)
    34  	}
    35  
    36  	db.SetConnMaxLifetime(0)
    37  	defer db.Close()
    38  
    39  	packageEntries, err := extractV10DBPackages(config, db, dbLocation, resolver)
    40  	if err != nil {
    41  		return nil, nil, err
    42  	}
    43  
    44  	pkgs, relationships, err := finalizeV10DBResults(db, packageEntries, catalogerName)
    45  	if err != nil {
    46  		return nil, nil, err
    47  	}
    48  
    49  	return pkgs, relationships, nil
    50  }
    51  
    52  func extractV10DBPackages(config Config, db *sql.DB, dbLocation file.Location, resolver file.Resolver) (map[int]*dbPackageEntry, error) {
    53  	pkgs, err := extractV10DBValidPaths(config, db, dbLocation, resolver)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	err = extractV10DBDerivationOutputs(db, pkgs)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	return pkgs, nil
    64  }
    65  
    66  func extractV10DBValidPaths(config Config, db *sql.DB, dbLocation file.Location, resolver file.Resolver) (map[int]*dbPackageEntry, error) {
    67  	packages := make(map[int]*dbPackageEntry)
    68  
    69  	rows, err := db.Query("SELECT id, path, hash, deriver FROM ValidPaths")
    70  	if err != nil {
    71  		return nil, fmt.Errorf("failed to query ValidPaths: %w", err)
    72  	}
    73  	defer rows.Close()
    74  
    75  	for rows.Next() {
    76  		var id int
    77  		var path, hash, deriver sql.NullString
    78  
    79  		if err := rows.Scan(&id, &path, &hash, &deriver); err != nil {
    80  			return nil, fmt.Errorf("failed to scan ValidPaths row: %w", err)
    81  		}
    82  
    83  		if !path.Valid {
    84  			continue
    85  		}
    86  
    87  		nsp := parseNixStorePath(path.String)
    88  		if nsp == nil {
    89  			nsp = &nixStorePath{}
    90  		}
    91  		// always trust the DB values over string parsing
    92  		nsp.OutputHash = hash.String
    93  		nsp.StorePath = path.String
    94  
    95  		var files []string
    96  		if config.CaptureOwnedFiles {
    97  			files = listOutputPaths(path.String, resolver)
    98  		}
    99  
   100  		df, err := newDerivationFromPath(deriver.String, resolver)
   101  		if err != nil {
   102  			log.WithFields("path", deriver.String, "error", err).Trace("unable to find derivation")
   103  			df = nil
   104  		}
   105  
   106  		packages[id] = &dbPackageEntry{
   107  			ID:             id,
   108  			nixStorePath:   *nsp,
   109  			derivationFile: df,
   110  			DeriverPath:    deriver.String,
   111  			Location:       &dbLocation,
   112  			Files:          files,
   113  		}
   114  	}
   115  
   116  	return packages, nil
   117  }
   118  
   119  func listOutputPaths(storePath string, resolver file.Resolver) []string {
   120  	if storePath == "" {
   121  		return nil
   122  	}
   123  	searchGlob := storePath + "/**/*"
   124  	locations, err := resolver.FilesByGlob(searchGlob)
   125  	if err != nil {
   126  		log.WithFields("path", storePath, "error", err).Trace("unable to find output paths")
   127  		return nil
   128  	}
   129  
   130  	return filePaths(locations)
   131  }
   132  
   133  func extractV10DBDerivationOutputs(db *sql.DB, packages map[int]*dbPackageEntry) error {
   134  	outputRows, err := db.Query("SELECT drv, id, path FROM DerivationOutputs")
   135  	if err != nil {
   136  		return fmt.Errorf("failed to query DerivationOutputs: %w", err)
   137  	}
   138  	defer outputRows.Close()
   139  
   140  	pkgsByPath := make(map[string]*dbPackageEntry)
   141  	for _, p := range packages {
   142  		pkgsByPath[p.StorePath] = p
   143  	}
   144  
   145  	for outputRows.Next() {
   146  		var drvID int
   147  		var outputID, outputPath string
   148  
   149  		if err := outputRows.Scan(&drvID, &outputID, &outputPath); err != nil {
   150  			return fmt.Errorf("failed to scan DerivationOutputs row: %w", err)
   151  		}
   152  
   153  		if _, ok := pkgsByPath[outputPath]; !ok {
   154  			continue
   155  		}
   156  		pkgsByPath[outputPath].Output = outputID
   157  		pkgsByPath[outputPath].DrvID = drvID
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  func finalizeV10DBResults(db *sql.DB, packageEntries map[int]*dbPackageEntry, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) {
   164  	// make Syft packages for each package entry
   165  	syftPackages := make(map[int]pkg.Package)
   166  	for id, entry := range packageEntries {
   167  		syftPackages[id] = newDBPackage(entry, catalogerName)
   168  	}
   169  
   170  	var relationships []artifact.Relationship
   171  
   172  	query := `
   173  	   SELECT r.referrer, r.reference
   174  	   FROM Refs r
   175  	   JOIN ValidPaths v1 ON r.referrer = v1.id
   176  	   JOIN ValidPaths v2 ON r.reference = v2.id
   177  	`
   178  
   179  	refRows, err := db.Query(query)
   180  	if err != nil {
   181  		return nil, nil, fmt.Errorf("failed to query Refs with ValidPaths JOIN: %w", err)
   182  	}
   183  	defer refRows.Close()
   184  
   185  	relExists := make(map[int]map[int]bool)
   186  
   187  	for refRows.Next() {
   188  		var referrerID, referenceID int
   189  
   190  		if err := refRows.Scan(&referrerID, &referenceID); err != nil {
   191  			return nil, nil, fmt.Errorf("failed to scan Refs row: %w", err)
   192  		}
   193  
   194  		if referrerID == referenceID {
   195  			// skip self-references
   196  			continue
   197  		}
   198  
   199  		referrer, refExists := syftPackages[referrerID]
   200  		reference, refeeExists := syftPackages[referenceID]
   201  
   202  		if !refExists || !refeeExists {
   203  			// only include relationships for packages we have discovered
   204  			continue
   205  		}
   206  
   207  		if _, ok := relExists[referrerID]; !ok {
   208  			relExists[referrerID] = make(map[int]bool)
   209  		}
   210  
   211  		if relExists[referrerID][referenceID] {
   212  			// deduplicate existing relationships
   213  			continue
   214  		}
   215  
   216  		relExists[referrerID][referenceID] = true
   217  
   218  		rel := artifact.Relationship{
   219  			From: reference,
   220  			To:   referrer,
   221  			Type: artifact.DependencyOfRelationship,
   222  		}
   223  
   224  		relationships = append(relationships, rel)
   225  	}
   226  
   227  	var pkgs []pkg.Package
   228  	for _, p := range syftPackages {
   229  		pkgs = append(pkgs, p)
   230  	}
   231  
   232  	return pkgs, relationships, nil
   233  }
   234  
   235  func createTempDB(content io.ReadCloser) (*os.File, error) {
   236  	tempFile, err := os.CreateTemp("", "nix-db-*.sqlite")
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	_, err = io.Copy(tempFile, content)
   242  	if err != nil {
   243  		tempFile.Close()
   244  		os.Remove(tempFile.Name())
   245  		return nil, err
   246  	}
   247  
   248  	return tempFile, nil
   249  }