This commit is contained in:
Alan Pope 2025-04-09 14:54:35 +01:00
parent 987ba83674
commit 7c8aad9e1b
5 changed files with 648 additions and 113 deletions

View File

@ -6,6 +6,9 @@ package nix
import (
"context"
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/bmatcuk/doublestar/v4"
@ -28,6 +31,30 @@ func (c *storeCataloger) Name() string {
return catalogerName
}
// Find the parent Nix store path for a given path
func findParentNixStorePath(path string) string {
// Handle standard /nix/store/ paths
parts := strings.Split(path, "/")
for i := 0; i < len(parts)-2; i++ {
if parts[i] == "nix" && parts[i+1] == "store" && i+2 < len(parts) {
// Check if it matches the hash-name pattern
storeItem := parts[i+2]
if matched, _ := regexp.MatchString(`^[a-z0-9]{32}-.*`, storeItem); matched {
return filepath.Join("/", filepath.Join(parts[:i+3]...))
}
}
}
// Handle short-form Nix paths (without the /nix/store/ prefix)
// These would be paths like /[hash]-[package-name]/...
shortPathPattern := regexp.MustCompile(`^/([a-z0-9]{32}-[^/]+)`)
if matches := shortPathPattern.FindStringSubmatch(path); len(matches) == 2 {
return "/" + matches[1]
}
return ""
}
func (c *storeCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
// 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
var pkgs []pkg.Package
@ -37,9 +64,23 @@ func (c *storeCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([
for location := range resolver.AllLocations(ctx) {
matchesStorePath, err := doublestar.Match("**/nix/store/*", location.RealPath)
if err != nil {
log.Debugf("Error matching path %s: %v", location.RealPath, err)
return nil, nil, fmt.Errorf("failed to match nix store path: %w", err)
}
if !matchesStorePath {
// Check if this is a "short-form" Nix store path (without the /nix/store/ prefix)
// This pattern matches paths like /[hash]-[package-name]/...
shortPathPattern := regexp.MustCompile(`^/([a-z0-9]{32})-([^/]+)`)
if shortPathPattern.MatchString(location.RealPath) {
matchesStorePath = true
log.Debugf("Matched short-form Nix path: %s", location.RealPath)
}
}
if !matchesStorePath {
log.Debugf("Not a Nix store path: %s", location.RealPath)
continue
}
parentStorePath := findParentNixStorePath(location.RealPath)
if parentStorePath != "" {
if _, ok := filesByPath[parentStorePath]; !ok {
@ -50,17 +91,24 @@ func (c *storeCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([
}
if !matchesStorePath {
log.Debugf("Not a Nix store path: %s", location.RealPath)
continue
}
storePath := parseNixStorePath(location.RealPath)
if storePath == nil || !storePath.isValidPackage() {
if storePath == nil {
log.Debugf("Failed to parse Nix path: %s", location.RealPath)
continue
}
// Only create packages for non-derivation/source paths
if storePath.shouldIncludeAsPackage() {
p := newNixStorePackage(*storePath, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
pkgs = append(pkgs, p)
} else {
log.Debugf("Skipping non-package path: %s (type: %s)", location.RealPath, storePath.pathType)
}
}
// add file sets to packages
@ -80,7 +128,64 @@ func (c *storeCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([
appendFiles(p, files.ToSlice()...)
}
return pkgs, nil, nil
// After all packages are created, enrich them with derivation data
for i := range pkgs {
p := &pkgs[i]
metadata, ok := p.Metadata.(pkg.NixStoreEntry)
if !ok {
continue
}
// Find and parse the derivation file
derivPath := findDeriverPath(metadata.Path)
if derivPath != "" {
if deriv, err := ParseDerivation(derivPath); err == nil {
// Enrich package metadata with derivation data
if deriv.PName != "" {
p.Name = deriv.PName
}
if deriv.Version != "" {
p.Version = deriv.Version
}
// Add license, homepage, etc. to metadata
metadata.License = deriv.License
metadata.Homepage = deriv.Homepage
metadata.Description = deriv.Description
metadata.DeriverPath = derivPath
// Update the package metadata
p.Metadata = metadata
}
}
}
// Find and add relationships between packages
relationships, err := findDependencies(pkgs)
if err != nil {
log.Warnf("failed to resolve nix dependencies: %v", err)
}
// Deduplicate packages based on name and version
dedupedPkgs := deduplicateNixPackages(pkgs)
pkgs = dedupedPkgs
return pkgs, relationships, nil
}
// ensureNixStorePrefix makes sure the path has the /nix/store/ prefix
func ensureNixStorePrefix(path string) string {
// Check if the path already has the /nix/store/ prefix
if strings.Contains(path, "/nix/store/") {
return path
}
// Check if this is a short-form path starting with /[hash]-[name]
shortPathPattern := regexp.MustCompile(`^/([a-z0-9]{32}-[^/]+)(.*)$`)
if matches := shortPathPattern.FindStringSubmatch(path); len(matches) >= 3 {
// Convert short form path to full path with /nix/store/ prefix
return "/nix/store/" + matches[1] + matches[2]
}
return path
}
func appendFiles(p *pkg.Package, location ...file.Location) {
@ -91,7 +196,9 @@ func appendFiles(p *pkg.Package, location ...file.Location) {
}
for _, l := range location {
metadata.Files = append(metadata.Files, l.RealPath)
// Normalize the path to ensure it has the /nix/store/ prefix
normalizedPath := ensureNixStorePrefix(l.RealPath)
metadata.Files = append(metadata.Files, normalizedPath)
}
if metadata.Files == nil {

View File

@ -0,0 +1,207 @@
package nix
import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
)
// findDependencies attempts to find relationships between Nix packages
// without using external commands
func findDependencies(pkgs []pkg.Package) ([]artifact.Relationship, error) {
var relationships []artifact.Relationship
packageByPath := make(map[string]*pkg.Package)
packageByStorePath := make(map[string]*pkg.Package)
// Index packages by store path and path for quick lookups
for i := range pkgs {
p := &pkgs[i]
metadata, ok := p.Metadata.(pkg.NixStoreEntry)
if !ok {
continue
}
packageByStorePath[metadata.Path] = p
// Also index by the package files
for _, filePath := range metadata.Files {
packageByPath[filePath] = p
}
}
// For each package, try to find references to other packages
for i := range pkgs {
p := &pkgs[i]
metadata, ok := p.Metadata.(pkg.NixStoreEntry)
if !ok {
continue
}
// Scan for references using the derivation file if available
if metadata.DeriverPath != "" {
refs, err := findReferencesInDerivation(metadata.DeriverPath, packageByStorePath)
if err == nil {
relationships = append(relationships, refs...)
}
}
// Scan for references within the package files
if len(metadata.Files) > 0 {
refs, err := findReferencesInFiles(metadata.Files, p, packageByStorePath)
if err == nil {
relationships = append(relationships, refs...)
}
}
}
return relationships, nil
}
// findReferencesInDerivation tries to extract dependencies from a derivation file
func findReferencesInDerivation(drvPath string, packageByPath map[string]*pkg.Package) ([]artifact.Relationship, error) {
var relationships []artifact.Relationship
// Read the derivation file contents
content, err := os.ReadFile(drvPath)
if err != nil {
return nil, err
}
// Look for store paths in the derivation file
storePathPattern := regexp.MustCompile(`/nix/store/[a-z0-9]{32}-[^"'\s]+`)
matches := storePathPattern.FindAllString(string(content), -1)
// Unique matches
uniquePaths := make(map[string]struct{})
for _, match := range matches {
uniquePaths[match] = struct{}{}
}
// Create relationships for found dependencies
for path := range uniquePaths {
depPkg, exists := packageByPath[path]
if !exists {
continue
}
// Skip self-references
fromMeta, ok := depPkg.Metadata.(pkg.NixStoreEntry)
if !ok || fromMeta.Path == drvPath {
continue
}
// Add the dependency relationship
pkg, exists := packageByPath[drvPath]
if !exists {
continue
}
relationships = append(relationships, artifact.Relationship{
From: *pkg,
To: *depPkg,
Type: artifact.DependencyOfRelationship,
Data: map[string]string{"dependencyType": "buildtime"},
})
}
return relationships, nil
}
// findReferencesInFiles looks for references to other packages within file contents
func findReferencesInFiles(files []string, fromPkg *pkg.Package, packageByPath map[string]*pkg.Package) ([]artifact.Relationship, error) {
var relationships []artifact.Relationship
uniqueRefs := make(map[string]struct{})
// Limit search to a reasonable number of files to avoid performance issues
maxFiles := 10
fileCount := 0
for _, file := range files {
if fileCount >= maxFiles {
break
}
// Skip files that are too large
info, err := os.Stat(file)
if err != nil || info.Size() > 1024*1024 {
continue
}
// Skip directories
if info.IsDir() {
continue
}
// Look for binary files that might have references
if isBinary(file) {
fileCount++
refs, err := findReferencesInBinary(file)
if err != nil {
continue
}
for _, ref := range refs {
uniqueRefs[ref] = struct{}{}
}
}
}
// Create relationships for found dependencies
for path := range uniqueRefs {
depPkg, exists := packageByPath[path]
if !exists {
continue
}
// Skip self-references
fromMeta, ok := fromPkg.Metadata.(pkg.NixStoreEntry)
if !ok || fromMeta.Path == path {
continue
}
relationships = append(relationships, artifact.Relationship{
From: *fromPkg,
To: *depPkg,
Type: artifact.DependencyOfRelationship,
Data: map[string]string{"dependencyType": "runtime"},
})
}
return relationships, nil
}
// Helper functions
// isBinary returns true if the file appears to be a binary
func isBinary(path string) bool {
ext := strings.ToLower(filepath.Ext(path))
return ext == ".so" || ext == ".dylib" || ext == ".a" ||
strings.HasSuffix(path, ".so.1") ||
strings.Contains(path, ".so.")
}
// findReferencesInBinary extracts Nix store references from binary files
func findReferencesInBinary(path string) ([]string, error) {
var references []string
// Use string search instead of executing nix-store
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
// Search for store paths in the binary
storePathPattern := regexp.MustCompile(`/nix/store/[a-z0-9]{32}-[^\\:\*\?"<>\|\x00-\x1F]+`)
matches := storePathPattern.FindAll(data, -1)
for _, match := range matches {
references = append(references, string(match))
}
return references, nil
}

View File

@ -0,0 +1,136 @@
package nix
import (
"bufio"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/anchore/syft/internal/log"
)
// NixDerivation represents a parsed Nix derivation file
type NixDerivation struct {
Path string
Name string
PName string
Version string
System string
Builder string
Args []string
EnvVars map[string]string
Outputs map[string]string
InputDrvs map[string][]string
InputSrcs []string
License string
Homepage string
Description string
OutputHash string
}
// ParseDerivation attempts to parse a Nix derivation file without external commands
func ParseDerivation(path string) (*NixDerivation, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
derivation := &NixDerivation{
Path: path,
EnvVars: make(map[string]string),
Outputs: make(map[string]string),
InputDrvs: make(map[string][]string),
}
// Simple parser for .drv files
scanner := bufio.NewScanner(file)
inEnvVars := false
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Process environment variables section
if strings.Contains(line, "envVars = {") {
inEnvVars = true
continue
} else if inEnvVars && strings.Contains(line, "};") {
inEnvVars = false
continue
} else if inEnvVars && strings.Contains(line, " = ") {
// Parse environment variables
parts := strings.SplitN(line, " = ", 2)
if len(parts) == 2 {
key := strings.Trim(parts[0], " \"")
value := strings.Trim(parts[1], " \";")
derivation.EnvVars[key] = value
}
}
}
// Extract package information from environment variables
derivation.Name = derivation.EnvVars["name"]
derivation.PName = derivation.EnvVars["pname"]
derivation.Version = derivation.EnvVars["version"]
derivation.System = derivation.EnvVars["system"]
derivation.License = derivation.EnvVars["license"]
derivation.Homepage = derivation.EnvVars["homepage"]
derivation.Description = derivation.EnvVars["description"]
// If pname is not set but name follows pname-version pattern, extract it
if derivation.PName == "" && derivation.Name != "" {
re := regexp.MustCompile(`^(.*?)-([0-9].*)$`)
if matches := re.FindStringSubmatch(derivation.Name); len(matches) == 3 {
derivation.PName = matches[1]
if derivation.Version == "" {
derivation.Version = matches[2]
}
} else {
derivation.PName = derivation.Name
}
}
// Check if this is a patched package with security fixes
for key := range derivation.EnvVars {
if strings.HasPrefix(key, "patches") {
value := derivation.EnvVars[key]
if strings.Contains(value, "CVE-") {
log.Debugf("Found security patch in %s: %s", derivation.Name, value)
}
}
}
return derivation, nil
}
// findDeriverPath attempts to find the derivation file for a store path
func findDeriverPath(storePath string) string {
// If the path is already a derivation, return it
if strings.HasSuffix(storePath, ".drv") {
return storePath
}
// Look for a .drv file with a similar name
drvPathGuess := storePath + ".drv"
if _, err := os.Stat(drvPathGuess); err == nil {
return drvPathGuess
}
// Fall back strategy using string manipulation
// This is an approximation without running nix-store commands
parts := strings.Split(storePath, "-")
if len(parts) >= 2 {
// Try to find the derivation file using the hash part
hash := strings.Split(filepath.Base(storePath), "-")[0]
if matched, _ := regexp.MatchString(`^[a-z0-9]{32}$`, hash); matched {
drvDir := filepath.Join("/nix/store", hash+"-*.drv")
matches, err := filepath.Glob(drvDir)
if err == nil && len(matches) > 0 {
return matches[0]
}
}
}
return ""
}

View File

@ -56,3 +56,67 @@ func packageURL(storePath nixStorePath) string {
"")
return pURL.ToString()
}
// deduplicateNixPackages combines information from packages with the same name and version
func deduplicateNixPackages(pkgs []pkg.Package) []pkg.Package {
if len(pkgs) == 0 {
return pkgs
}
// Use map to group by name+version
packageMap := make(map[string]*pkg.Package)
for i := range pkgs {
p := &pkgs[i]
key := p.Name + ":" + p.Version
existing, exists := packageMap[key]
if !exists {
// First time seeing this name+version
packageMap[key] = p
continue
}
// Merge information from this package into the existing one
mergePackageInfo(existing, p)
}
// Convert map back to slice
result := make([]pkg.Package, 0, len(packageMap))
for _, p := range packageMap {
result = append(result, *p)
}
return result
}
// mergePackageInfo combines information from src into dest
func mergePackageInfo(dest, src *pkg.Package) {
// Merge locations
for _, loc := range src.Locations.ToSlice() {
dest.Locations.Add(loc)
}
// Merge metadata if both have NixStoreEntry type
destMeta, destOk := dest.Metadata.(pkg.NixStoreEntry)
srcMeta, srcOk := src.Metadata.(pkg.NixStoreEntry)
if destOk && srcOk {
// Combine files lists
destMeta.Files = append(destMeta.Files, srcMeta.Files...)
// Prefer non-empty fields from src
if destMeta.License == "" && srcMeta.License != "" {
destMeta.License = srcMeta.License
}
if destMeta.Homepage == "" && srcMeta.Homepage != "" {
destMeta.Homepage = srcMeta.Homepage
}
if destMeta.Description == "" && srcMeta.Description != "" {
destMeta.Description = srcMeta.Description
}
// Update the metadata
dest.Metadata = destMeta
}
}

View File

@ -1,134 +1,155 @@
package nix
import (
"fmt"
"path"
"regexp"
"strings"
)
var (
numericPattern = regexp.MustCompile(`\d`)
// attempts to find the right-most example of something that appears to be a version (semver or otherwise)
// example input: h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin
// example output:
// version: "2.34-210"
// major: "2"
// minor: "34"
// patch: "210"
// (there are other capture groups, but they can be ignored)
rightMostVersionIshPattern = regexp.MustCompile(`-(?P<version>(?P<major>[0-9][a-zA-Z0-9]*)(\.(?P<minor>[0-9][a-zA-Z0-9]*))?(\.(?P<patch>0|[1-9][a-zA-Z0-9]*)){0,3}(?:-(?P<prerelease>\d*[.a-zA-Z-][.0-9a-zA-Z-]*)*)?(?:\+(?P<metadata>[.0-9a-zA-Z-]+(?:\.[.0-9a-zA-Z-]+)*))?)`)
unstableVersion = regexp.MustCompile(`-(?P<version>unstable-\d{4}-\d{2}-\d{2})$`)
)
// checkout the package naming conventions here: https://nixos.org/manual/nixpkgs/stable/#sec-package-naming
type nixStorePath struct {
path string
hash string
outputHash string
name string
version string
output string
// New field to indicate the type of Nix path
pathType string // "package", "derivation", "source", or "other"
}
func (p nixStorePath) isValidPackage() bool {
return p.name != "" && p.version != ""
}
func findParentNixStorePath(source string) string {
source = strings.TrimRight(source, "/")
indicator := "nix/store/"
start := strings.Index(source, indicator)
if start == -1 {
return ""
}
startOfHash := start + len(indicator)
nextField := strings.Index(source[startOfHash:], "/")
if nextField == -1 {
return ""
}
startOfSubPath := startOfHash + nextField
return source[0:startOfSubPath]
}
func parseNixStorePath(source string) *nixStorePath {
if strings.HasSuffix(source, ".drv") {
// ignore derivations
// parseNixStorePath extracts package information from a Nix store path.
// This is a more permissive implementation that accepts more formats.
func parseNixStorePath(path string) *nixStorePath {
// Extract the store path component - handle both standard and short forms
// Standard form: /nix/store/[hash]-[name]
// Short form: /[hash]-[name]
storePathPattern := regexp.MustCompile(`(^|.*?)(?:/nix/store/|/)([a-z0-9]{32})-(.+?)(/.*)?$`)
matches := storePathPattern.FindStringSubmatch(path)
if len(matches) < 4 {
return nil
}
source = path.Base(source)
// Extract hash and full package name
hash := matches[2]
fullName := matches[3]
versionStartIdx, versionIsh, prerelease := findVersionIsh(source)
if versionStartIdx == -1 {
return nil
// Extract path to first / after the hash-name pattern
basePath := "/nix/store/" + hash + "-" + fullName
if len(matches) > 4 && matches[4] != "" {
// We have a file within a store path
basePath = "/nix/store/" + hash + "-" + fullName
}
hashName := strings.TrimSuffix(source[0:versionStartIdx], "-")
hashNameFields := strings.Split(hashName, "-")
if len(hashNameFields) < 2 {
return nil
}
hash, name := hashNameFields[0], strings.Join(hashNameFields[1:], "-")
prereleaseFields := strings.Split(prerelease, "-")
lastPrereleaseField := prereleaseFields[len(prereleaseFields)-1]
var version = versionIsh
var output string
if !hasNumeric(lastPrereleaseField) {
// this last prerelease field is probably a nix output
version = strings.TrimSuffix(versionIsh, fmt.Sprintf("-%s", lastPrereleaseField))
output = lastPrereleaseField
}
return &nixStorePath{
// Create the basic result
result := &nixStorePath{
path: basePath,
hash: hash,
outputHash: hash,
name: name,
version: version,
output: output,
}
// Determine the path type based on extension or patterns
if strings.HasSuffix(fullName, ".drv") {
result.pathType = "derivation"
// Remove .drv suffix for name parsing
fullName = strings.TrimSuffix(fullName, ".drv")
} else if strings.Contains(fullName, ".tar.") ||
strings.HasSuffix(fullName, ".tgz") ||
strings.HasSuffix(fullName, ".tar.gz") ||
strings.HasSuffix(fullName, ".tar.bz2") ||
strings.HasSuffix(fullName, ".tar.xz") ||
strings.HasSuffix(fullName, ".zip") ||
strings.HasSuffix(fullName, ".patch") {
result.pathType = "source"
} else {
result.pathType = "package"
}
// Try to parse the output from the path (if present)
if outputParts := strings.Split(fullName, "-"); len(outputParts) > 1 {
lastPart := outputParts[len(outputParts)-1]
// Common Nix output names
outputs := map[string]bool{
"bin": true, "dev": true, "lib": true, "man": true,
"doc": true, "info": true, "out": true,
}
if outputs[lastPart] {
result.output = lastPart
fullName = strings.Join(outputParts[:len(outputParts)-1], "-")
}
}
func hasNumeric(s string) bool {
return numericPattern.MatchString(s)
// For non-drv files, try different version extraction patterns
versionPatterns := []*regexp.Regexp{
// Standard version: name-1.2.3
regexp.MustCompile(`^(.+)-([0-9][0-9.]+(?:-[0-9]+)?)$`),
// Unstable version: name-unstable-2022-05-15
regexp.MustCompile(`^(.+)-unstable-([0-9]{4}-[0-9]{2}-[0-9]{2})$`),
// Version with prefix: name-v1.2.3
regexp.MustCompile(`^(.+)-(v[0-9][0-9.]+)$`),
// Year-based versions: name-2022.05
regexp.MustCompile(`^(.+)-([0-9]{4}(?:\.[0-9]+)*)$`),
// Version with suffix: name-1.2.3-suffix
regexp.MustCompile(`^(.+)-([0-9][0-9.]+(?:-[a-z0-9]+)*)$`),
}
func findVersionIsh(input string) (int, string, string) {
// we want to return the index of the start of the "version" group (the first capture group).
// note that the match indices are in the form of [start, end, start, end, ...]. Also note that the
// capture group for version in both regexes are the same index, but if the regexes are changed
// this code will start to fail.
versionGroup := 1
match := unstableVersion.FindAllStringSubmatchIndex(input, -1)
if len(match) > 0 && len(match[0]) > 0 {
return match[0][versionGroup*2], input[match[0][versionGroup*2]:match[0][(versionGroup*2)+1]], ""
// Try each pattern to extract name and version
for _, pattern := range versionPatterns {
if nameVerMatches := pattern.FindStringSubmatch(fullName); len(nameVerMatches) == 3 {
result.name = nameVerMatches[1]
result.version = nameVerMatches[2]
return result
}
}
match = rightMostVersionIshPattern.FindAllStringSubmatchIndex(input, -1)
if len(match) == 0 || len(match[0]) == 0 {
return -1, "", ""
// If no version pattern matched, consider the entire string the package name
result.name = fullName
result.version = ""
return result
}
var version string
versionStart, versionStop := match[0][versionGroup*2], match[0][(versionGroup*2)+1]
if versionStart != -1 || versionStop != -1 {
version = input[versionStart:versionStop]
// isLikelyOutput checks if a string is likely to be a Nix output name
func isLikelyOutput(s string) bool {
commonOutputs := map[string]bool{
"bin": true, "lib": true, "dev": true, "out": true,
"doc": true, "man": true, "info": true, "devdoc": true,
}
return commonOutputs[s]
}
prereleaseGroup := 7
var prerelease string
prereleaseStart, prereleaseStop := match[0][prereleaseGroup*2], match[0][(prereleaseGroup*2)+1]
if prereleaseStart != -1 && prereleaseStop != -1 {
prerelease = input[prereleaseStart:prereleaseStop]
// isLikelyVersion checks if a string looks like a version number
func isLikelyVersion(s string) bool {
// Simple check for common version patterns
versionPattern := regexp.MustCompile(`^v?\d+(\.\d+)*(-\w+)*$`)
return versionPattern.MatchString(s)
}
return versionStart,
version,
prerelease
// shouldIncludeAsPackage determines if this store path should be included as a package
func (n *nixStorePath) shouldIncludeAsPackage() bool {
// Only include actual packages, not derivations or source files
if n.pathType == "derivation" || n.pathType == "source" {
return false
}
// Skip paths that look like source archives even if not explicitly marked
if strings.HasSuffix(n.path, ".tar.gz") ||
strings.HasSuffix(n.path, ".tgz") ||
strings.HasSuffix(n.path, ".tar.bz2") ||
strings.HasSuffix(n.path, ".tar.xz") ||
strings.HasSuffix(n.path, ".zip") ||
strings.HasSuffix(n.path, ".drv") {
return false
}
// Make sure it has a name (at minimum)
return n.name != ""
}
// isValidPackage should be much more permissive
func (n *nixStorePath) isValidPackage() bool {
// Any path with a hash and name is considered valid
return n.hash != "" && n.name != ""
}