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  }