mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
Add relationships for ALPM packages (arch linux) (#2851)
* add alpm relationships Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * tweak reader linter rule to check for reader impl Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update JSON schema with alpm dependency information Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
e7b6284039
commit
ada8f009d2
@ -3,5 +3,5 @@ package internal
|
|||||||
const (
|
const (
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "16.0.7"
|
JSONSchemaVersion = "16.0.8"
|
||||||
)
|
)
|
||||||
|
|||||||
2378
schema/json/schema-16.0.8.json
Normal file
2378
schema/json/schema-16.0.8.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "anchore.io/schema/syft/json/16.0.7/document",
|
"$id": "anchore.io/schema/syft/json/16.0.8/document",
|
||||||
"$ref": "#/$defs/Document",
|
"$ref": "#/$defs/Document",
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"AlpmDbEntry": {
|
"AlpmDbEntry": {
|
||||||
@ -46,6 +46,18 @@
|
|||||||
"$ref": "#/$defs/AlpmFileRecord"
|
"$ref": "#/$defs/AlpmFileRecord"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -27,6 +27,8 @@ type AlpmDBEntry struct {
|
|||||||
Reason int `mapstructure:"reason" json:"reason"`
|
Reason int `mapstructure:"reason" json:"reason"`
|
||||||
Files []AlpmFileRecord `mapstructure:"files" json:"files"`
|
Files []AlpmFileRecord `mapstructure:"files" json:"files"`
|
||||||
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup"`
|
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup"`
|
||||||
|
Provides []string `mapstructure:"provides" json:"provides,omitempty"`
|
||||||
|
Depends []string `mapstructure:"depends" json:"depends,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlpmFileRecord struct {
|
type AlpmFileRecord struct {
|
||||||
|
|||||||
@ -4,12 +4,96 @@ Package arch provides a concrete Cataloger implementations for packages relating
|
|||||||
package arch
|
package arch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type cataloger struct {
|
||||||
|
*generic.Cataloger
|
||||||
|
}
|
||||||
|
|
||||||
// NewDBCataloger returns a new cataloger object initialized for arch linux pacman database flat-file stores.
|
// NewDBCataloger returns a new cataloger object initialized for arch linux pacman database flat-file stores.
|
||||||
func NewDBCataloger() pkg.Cataloger {
|
func NewDBCataloger() pkg.Cataloger {
|
||||||
return generic.NewCataloger("alpm-db-cataloger").
|
return cataloger{
|
||||||
WithParserByGlobs(parseAlpmDB, pkg.AlpmDBGlob)
|
Cataloger: generic.NewCataloger("alpm-db-cataloger").
|
||||||
|
WithParserByGlobs(parseAlpmDB, pkg.AlpmDBGlob),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
pkgs, rels, err := c.Cataloger.Catalog(ctx, resolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rels = append(rels, associateRelationships(pkgs)...)
|
||||||
|
|
||||||
|
return pkgs, rels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// associateRelationships will create relationships between packages based on the "Depends" and "Provides"
|
||||||
|
// fields for installed packages. If there is an installed package that has a dependency that is (somehow) not installed,
|
||||||
|
// then that relationship (between the installed and uninstalled package) will NOT be created.
|
||||||
|
func associateRelationships(pkgs []pkg.Package) (relationships []artifact.Relationship) {
|
||||||
|
// map["provides" + "package"] -> packages that provide that package
|
||||||
|
lookup := make(map[string][]pkg.Package)
|
||||||
|
|
||||||
|
// read providers and add lookup keys as needed
|
||||||
|
for _, p := range pkgs {
|
||||||
|
meta, ok := p.Metadata.(pkg.AlpmDBEntry)
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("cataloger failed to extract alpm 'provides' metadata for package %+v", p.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// allow for lookup by package name
|
||||||
|
lookup[p.Name] = append(lookup[p.Name], p)
|
||||||
|
|
||||||
|
for _, provides := range meta.Provides {
|
||||||
|
// allow for lookup by exact specification
|
||||||
|
lookup[provides] = append(lookup[provides], p)
|
||||||
|
|
||||||
|
// allow for lookup by library name only
|
||||||
|
k := stripVersionSpecifier(provides)
|
||||||
|
lookup[k] = append(lookup[k], p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read "Depends" and match with provider keys
|
||||||
|
for _, p := range pkgs {
|
||||||
|
meta, ok := p.Metadata.(pkg.AlpmDBEntry)
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("cataloger failed to extract alpm 'dependency' metadata for package %+v", p.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dep := range meta.Depends {
|
||||||
|
for _, depPkg := range lookup[dep] {
|
||||||
|
relationships = append(relationships, artifact.Relationship{
|
||||||
|
From: depPkg,
|
||||||
|
To: p,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return relationships
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripVersionSpecifier(s string) string {
|
||||||
|
// examples:
|
||||||
|
// gcc-libs --> gcc-libs
|
||||||
|
// libtree-sitter.so=0-64 --> libtree-sitter.so
|
||||||
|
|
||||||
|
items := strings.Split(s, "=")
|
||||||
|
if len(items) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(items[0])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,20 +12,117 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAlpmCataloger(t *testing.T) {
|
func TestAlpmCataloger(t *testing.T) {
|
||||||
dbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
||||||
expectedPkgs := []pkg.Package{
|
treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc")
|
||||||
|
emacsDbLocation := file.NewLocation("var/lib/pacman/local/emacs-29.3-3/desc")
|
||||||
|
fuzzyDbLocation := file.NewLocation("var/lib/pacman/local/fuzzy-1.2-3/desc")
|
||||||
|
madeupDbLocation := file.NewLocation("var/lib/pacman/local/madeup-20.30-4/desc")
|
||||||
|
|
||||||
|
treeSitterPkg := pkg.Package{
|
||||||
|
Name: "tree-sitter",
|
||||||
|
Version: "0.22.6-1",
|
||||||
|
Type: pkg.AlpmPkg,
|
||||||
|
FoundBy: "alpm-db-cataloger",
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.NewLicenseFromLocations("MIT", treeSitterDbLocation),
|
||||||
|
),
|
||||||
|
Locations: file.NewLocationSet(treeSitterDbLocation),
|
||||||
|
Metadata: pkg.AlpmDBEntry{
|
||||||
|
BasePackage: "tree-sitter",
|
||||||
|
Package: "tree-sitter",
|
||||||
|
Version: "0.22.6-1",
|
||||||
|
Description: "Incremental parsing library",
|
||||||
|
Architecture: "x86_64",
|
||||||
|
Size: 223539,
|
||||||
|
Packager: "Daniel M. Capella <polyzen@archlinux.org>",
|
||||||
|
URL: "https://github.com/tree-sitter/tree-sitter",
|
||||||
|
Validation: "pgp",
|
||||||
|
Reason: 1,
|
||||||
|
Files: []pkg.AlpmFileRecord{},
|
||||||
|
Backup: []pkg.AlpmFileRecord{},
|
||||||
|
Provides: []string{"libtree-sitter.so=0-64"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsPkg := pkg.Package{
|
||||||
|
Name: "emacs",
|
||||||
|
Version: "29.3-3",
|
||||||
|
Type: pkg.AlpmPkg,
|
||||||
|
FoundBy: "alpm-db-cataloger",
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.NewLicenseFromLocations("GPL3", emacsDbLocation),
|
||||||
|
),
|
||||||
|
Locations: file.NewLocationSet(emacsDbLocation),
|
||||||
|
Metadata: pkg.AlpmDBEntry{
|
||||||
|
BasePackage: "emacs",
|
||||||
|
Package: "emacs",
|
||||||
|
Version: "29.3-3",
|
||||||
|
Description: "The extensible, customizable, self-documenting real-time display editor",
|
||||||
|
Architecture: "x86_64",
|
||||||
|
Size: 126427862,
|
||||||
|
Packager: "Frederik Schwan <freswa@archlinux.org>",
|
||||||
|
URL: "https://www.gnu.org/software/emacs/emacs.html",
|
||||||
|
Validation: "pgp",
|
||||||
|
Files: []pkg.AlpmFileRecord{},
|
||||||
|
Backup: []pkg.AlpmFileRecord{},
|
||||||
|
Depends: []string{"libtree-sitter.so=0-64"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzzyPkg := pkg.Package{
|
||||||
|
Name: "fuzzy",
|
||||||
|
Version: "1.2-3",
|
||||||
|
Type: pkg.AlpmPkg,
|
||||||
|
FoundBy: "alpm-db-cataloger",
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
fuzzyDbLocation,
|
||||||
|
file.NewLocation("var/lib/pacman/local/fuzzy-1.2-3/files"),
|
||||||
|
),
|
||||||
|
Metadata: pkg.AlpmDBEntry{
|
||||||
|
Package: "fuzzy",
|
||||||
|
Version: "1.2-3",
|
||||||
|
Files: []pkg.AlpmFileRecord{},
|
||||||
|
Backup: []pkg.AlpmFileRecord{
|
||||||
{
|
{
|
||||||
|
Path: "/etc/fuzzy.conf",
|
||||||
|
Digests: []file.Digest{
|
||||||
|
{Algorithm: "md5", Value: "79fce043df7dfc676ae5ecb903762d8b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Depends: []string{"tree-sitter"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
madeupPkg := pkg.Package{
|
||||||
|
Name: "madeup",
|
||||||
|
Version: "20.30-4",
|
||||||
|
Type: pkg.AlpmPkg,
|
||||||
|
FoundBy: "alpm-db-cataloger",
|
||||||
|
Locations: file.NewLocationSet(madeupDbLocation),
|
||||||
|
Metadata: pkg.AlpmDBEntry{
|
||||||
|
Package: "madeup",
|
||||||
|
Version: "20.30-4",
|
||||||
|
Files: []pkg.AlpmFileRecord{},
|
||||||
|
Backup: []pkg.AlpmFileRecord{},
|
||||||
|
Depends: []string{"libtree-sitter.so"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gmpPkg := pkg.Package{
|
||||||
Name: "gmp",
|
Name: "gmp",
|
||||||
Version: "6.2.1-2",
|
Version: "6.2.1-2",
|
||||||
Type: pkg.AlpmPkg,
|
Type: pkg.AlpmPkg,
|
||||||
FoundBy: "alpm-db-cataloger",
|
FoundBy: "alpm-db-cataloger",
|
||||||
Licenses: pkg.NewLicenseSet(
|
Licenses: pkg.NewLicenseSet(
|
||||||
pkg.NewLicenseFromLocations("LGPL3", dbLocation),
|
pkg.NewLicenseFromLocations("LGPL3", gmpDbLocation),
|
||||||
pkg.NewLicenseFromLocations("GPL", dbLocation),
|
pkg.NewLicenseFromLocations("GPL", gmpDbLocation),
|
||||||
|
),
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
gmpDbLocation,
|
||||||
|
file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/files"),
|
||||||
|
file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/mtree"),
|
||||||
),
|
),
|
||||||
Locations: file.NewLocationSet(dbLocation),
|
|
||||||
CPEs: nil,
|
|
||||||
PURL: "",
|
|
||||||
Metadata: pkg.AlpmDBEntry{
|
Metadata: pkg.AlpmDBEntry{
|
||||||
BasePackage: "gmp",
|
BasePackage: "gmp",
|
||||||
Package: "gmp",
|
Package: "gmp",
|
||||||
@ -37,6 +134,7 @@ func TestAlpmCataloger(t *testing.T) {
|
|||||||
URL: "https://gmplib.org/",
|
URL: "https://gmplib.org/",
|
||||||
Validation: "pgp",
|
Validation: "pgp",
|
||||||
Reason: 1,
|
Reason: 1,
|
||||||
|
Depends: []string{"gcc-libs", "sh", "libtree-sitter.so=1-64"},
|
||||||
Files: []pkg.AlpmFileRecord{
|
Files: []pkg.AlpmFileRecord{
|
||||||
{
|
{
|
||||||
Path: "/usr",
|
Path: "/usr",
|
||||||
@ -167,14 +265,36 @@ func TestAlpmCataloger(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Backup: []pkg.AlpmFileRecord{},
|
Backup: []pkg.AlpmFileRecord{},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
treeSitterPkg,
|
||||||
|
emacsPkg,
|
||||||
|
fuzzyPkg,
|
||||||
|
madeupPkg,
|
||||||
|
gmpPkg,
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedRelationships := []artifact.Relationship{
|
||||||
|
{ // exact spec lookup
|
||||||
|
From: treeSitterPkg,
|
||||||
|
To: emacsPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
},
|
||||||
|
{ // package name lookup
|
||||||
|
From: treeSitterPkg,
|
||||||
|
To: fuzzyPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
},
|
||||||
|
{ // library name lookup
|
||||||
|
From: treeSitterPkg,
|
||||||
|
To: madeupPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: relationships are not under test yet
|
|
||||||
var expectedRelationships []artifact.Relationship
|
|
||||||
|
|
||||||
pkgtest.NewCatalogTester().
|
pkgtest.NewCatalogTester().
|
||||||
FromDirectory(t, "test-fixtures/gmp-fixture").
|
FromDirectory(t, "test-fixtures/installed").
|
||||||
WithCompareOptions(cmpopts.IgnoreFields(pkg.AlpmFileRecord{}, "Time")).
|
WithCompareOptions(cmpopts.IgnoreFields(pkg.AlpmFileRecord{}, "Time")).
|
||||||
Expects(expectedPkgs, expectedRelationships).
|
Expects(expectedPkgs, expectedRelationships).
|
||||||
TestCataloger(t, NewDBCataloger())
|
TestCataloger(t, NewDBCataloger())
|
||||||
|
|||||||
@ -9,13 +9,16 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location) pkg.Package {
|
func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location, otherLocations ...file.Location) pkg.Package {
|
||||||
licenseCandidates := strings.Split(m.Licenses, "\n")
|
licenseCandidates := strings.Split(m.Licenses, "\n")
|
||||||
|
|
||||||
|
locs := file.NewLocationSet(dbLocation)
|
||||||
|
locs.Add(otherLocations...)
|
||||||
|
|
||||||
p := pkg.Package{
|
p := pkg.Package{
|
||||||
Name: m.Package,
|
Name: m.Package,
|
||||||
Version: m.Version,
|
Version: m.Version,
|
||||||
Locations: file.NewLocationSet(dbLocation),
|
Locations: locs,
|
||||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
|
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
|
||||||
Type: pkg.AlpmPkg,
|
Type: pkg.AlpmPkg,
|
||||||
PURL: packageURL(m, release),
|
PURL: packageURL(m, release),
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/vbatts/go-mtree"
|
"github.com/vbatts/go-mtree"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -44,33 +46,26 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
base := filepath.Dir(reader.RealPath)
|
if data == nil {
|
||||||
r, err := getFileReader(filepath.Join(base, "mtree"), resolver)
|
return nil, nil, nil
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgFiles, err := parseMtree(r)
|
base := path.Dir(reader.RealPath)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
||||||
// thus more useful.
|
// thus more useful.
|
||||||
// TODO: probably want to use MTREE and PKGINFO here
|
files, fileLoc := fetchPkgFiles(base, resolver)
|
||||||
data.Files = pkgFiles
|
backups, backupLoc := fetchBackupFiles(base, resolver)
|
||||||
|
|
||||||
// We only really do this to get any backup database entries from the files database
|
var locs []file.Location
|
||||||
files := filepath.Join(base, "files")
|
if fileLoc != nil {
|
||||||
_, err = getFileReader(files, resolver)
|
locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||||
if err != nil {
|
data.Files = files
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
filesMetadata, err := parseAlpmDBEntry(reader)
|
|
||||||
if err != nil {
|
if backupLoc != nil {
|
||||||
return nil, nil, err
|
locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||||
} else if filesMetadata != nil {
|
data.Backup = backups
|
||||||
data.Backup = filesMetadata.Backup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Package == "" {
|
if data.Package == "" {
|
||||||
@ -82,10 +77,67 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
|||||||
data,
|
data,
|
||||||
env.LinuxRelease,
|
env.LinuxRelease,
|
||||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||||
|
locs...,
|
||||||
),
|
),
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
||||||
|
// TODO: probably want to use MTREE and PKGINFO here
|
||||||
|
target := path.Join(base, "mtree")
|
||||||
|
|
||||||
|
loc, err := getLocation(target, resolver)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", target).Trace("failed to find mtree file")
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
if loc == nil {
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := resolver.FileContentsByLocation(*loc)
|
||||||
|
if err != nil {
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
|
pkgFiles, err := parseMtree(reader)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", target).Trace("failed to parse mtree file")
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
return pkgFiles, loc
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
||||||
|
// We only really do this to get any backup database entries from the files database
|
||||||
|
target := filepath.Join(base, "files")
|
||||||
|
|
||||||
|
loc, err := getLocation(target, resolver)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", target).Trace("failed to find alpm files")
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
if loc == nil {
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := resolver.FileContentsByLocation(*loc)
|
||||||
|
if err != nil {
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
|
filesMetadata, err := parseAlpmDBEntry(reader)
|
||||||
|
if err != nil {
|
||||||
|
return []pkg.AlpmFileRecord{}, nil
|
||||||
|
}
|
||||||
|
if filesMetadata != nil {
|
||||||
|
return filesMetadata.Backup, loc
|
||||||
|
}
|
||||||
|
return []pkg.AlpmFileRecord{}, loc
|
||||||
|
}
|
||||||
|
|
||||||
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
||||||
scanner := newScanner(reader)
|
scanner := newScanner(reader)
|
||||||
metadata, err := parseDatabase(scanner)
|
metadata, err := parseDatabase(scanner)
|
||||||
@ -119,7 +171,7 @@ func newScanner(reader io.Reader) *bufio.Scanner {
|
|||||||
return scanner
|
return scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileReader(path string, resolver file.Resolver) (io.Reader, error) {
|
func getLocation(path string, resolver file.Resolver) (*file.Location, error) {
|
||||||
locs, err := resolver.FilesByPath(path)
|
locs, err := resolver.FilesByPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -128,13 +180,11 @@ func getFileReader(path string, resolver file.Resolver) (io.Reader, error) {
|
|||||||
if len(locs) == 0 {
|
if len(locs) == 0 {
|
||||||
return nil, fmt.Errorf("could not find file: %s", path)
|
return nil, fmt.Errorf("could not find file: %s", path)
|
||||||
}
|
}
|
||||||
// TODO: Should we maybe check if we found the file
|
|
||||||
dbContentReader, err := resolver.FileContentsByLocation(locs[0])
|
if len(locs) > 1 {
|
||||||
if err != nil {
|
log.WithFields("path", path).Trace("multiple files found for path, using first path")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogError(dbContentReader, locs[0].RealPath)
|
return &locs[0], nil
|
||||||
return dbContentReader, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
||||||
@ -157,9 +207,9 @@ func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
|||||||
case "files":
|
case "files":
|
||||||
var files []map[string]string
|
var files []map[string]string
|
||||||
for _, f := range strings.Split(value, "\n") {
|
for _, f := range strings.Split(value, "\n") {
|
||||||
path := fmt.Sprintf("/%s", f)
|
p := fmt.Sprintf("/%s", f)
|
||||||
if ok := ignoredFiles[path]; !ok {
|
if ok := ignoredFiles[p]; !ok {
|
||||||
files = append(files, map[string]string{"path": path})
|
files = append(files, map[string]string{"path": p})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pkgFields[key] = files
|
pkgFields[key] = files
|
||||||
@ -167,10 +217,10 @@ func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
|||||||
var backup []map[string]interface{}
|
var backup []map[string]interface{}
|
||||||
for _, f := range strings.Split(value, "\n") {
|
for _, f := range strings.Split(value, "\n") {
|
||||||
fields := strings.SplitN(f, "\t", 2)
|
fields := strings.SplitN(f, "\t", 2)
|
||||||
path := fmt.Sprintf("/%s", fields[0])
|
p := fmt.Sprintf("/%s", fields[0])
|
||||||
if ok := ignoredFiles[path]; !ok {
|
if ok := ignoredFiles[p]; !ok {
|
||||||
backup = append(backup, map[string]interface{}{
|
backup = append(backup, map[string]interface{}{
|
||||||
"path": path,
|
"path": p,
|
||||||
"digests": []file.Digest{{
|
"digests": []file.Digest{{
|
||||||
Algorithm: "md5",
|
Algorithm: "md5",
|
||||||
Value: fields[1],
|
Value: fields[1],
|
||||||
@ -178,6 +228,8 @@ func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pkgFields[key] = backup
|
pkgFields[key] = backup
|
||||||
|
case "depends", "provides":
|
||||||
|
pkgFields[key] = processLibrarySpecs(value)
|
||||||
case "reason":
|
case "reason":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "size":
|
case "size":
|
||||||
@ -193,6 +245,19 @@ func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
|||||||
return parsePkgFiles(pkgFields)
|
return parsePkgFiles(pkgFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processLibrarySpecs(value string) []string {
|
||||||
|
lines := strings.Split(value, "\n")
|
||||||
|
librarySpecs := make([]string, 0)
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
librarySpecs = append(librarySpecs, line)
|
||||||
|
}
|
||||||
|
return librarySpecs
|
||||||
|
}
|
||||||
|
|
||||||
func parsePkgFiles(pkgFields map[string]interface{}) (*parsedData, error) {
|
func parsePkgFiles(pkgFields map[string]interface{}) (*parsedData, error) {
|
||||||
var entry parsedData
|
var entry parsedData
|
||||||
if err := mapstructure.Decode(pkgFields, &entry); err != nil {
|
if err := mapstructure.Decode(pkgFields, &entry); err != nil {
|
||||||
@ -203,6 +268,10 @@ func parsePkgFiles(pkgFields map[string]interface{}) (*parsedData, error) {
|
|||||||
entry.Backup = make([]pkg.AlpmFileRecord, 0)
|
entry.Backup = make([]pkg.AlpmFileRecord, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if entry.Files == nil {
|
||||||
|
entry.Files = make([]pkg.AlpmFileRecord, 0)
|
||||||
|
}
|
||||||
|
|
||||||
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,12 +17,13 @@ func TestDatabaseParser(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
expected pkg.AlpmDBEntry
|
expected *parsedData
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "test alpm database parsing",
|
name: "simple desc parsing",
|
||||||
fixture: "test-fixtures/files",
|
fixture: "test-fixtures/files",
|
||||||
expected: pkg.AlpmDBEntry{
|
expected: &parsedData{
|
||||||
|
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||||
Backup: []pkg.AlpmFileRecord{
|
Backup: []pkg.AlpmFileRecord{
|
||||||
{
|
{
|
||||||
Path: "/etc/pacman.conf",
|
Path: "/etc/pacman.conf",
|
||||||
@ -88,6 +89,51 @@ func TestDatabaseParser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with dependencies",
|
||||||
|
fixture: "test-fixtures/installed/var/lib/pacman/local/gmp-6.2.1-2/desc",
|
||||||
|
expected: &parsedData{
|
||||||
|
Licenses: "LGPL3\nGPL",
|
||||||
|
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||||
|
BasePackage: "gmp",
|
||||||
|
Package: "gmp",
|
||||||
|
Version: "6.2.1-2",
|
||||||
|
Description: "A free library for arbitrary precision arithmetic",
|
||||||
|
Architecture: "x86_64",
|
||||||
|
Size: 1044438,
|
||||||
|
Packager: "Antonio Rojas <arojas@archlinux.org>",
|
||||||
|
URL: "https://gmplib.org/",
|
||||||
|
Validation: "pgp",
|
||||||
|
Reason: 1,
|
||||||
|
Files: []pkg.AlpmFileRecord{},
|
||||||
|
Backup: []pkg.AlpmFileRecord{},
|
||||||
|
Depends: []string{"gcc-libs", "sh", "libtree-sitter.so=1-64"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with provides",
|
||||||
|
fixture: "test-fixtures/installed/var/lib/pacman/local/tree-sitter-0.22.6-1/desc",
|
||||||
|
expected: &parsedData{
|
||||||
|
Licenses: "MIT",
|
||||||
|
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||||
|
BasePackage: "tree-sitter",
|
||||||
|
Package: "tree-sitter",
|
||||||
|
Version: "0.22.6-1",
|
||||||
|
Description: "Incremental parsing library",
|
||||||
|
Architecture: "x86_64",
|
||||||
|
Size: 223539,
|
||||||
|
Packager: "Daniel M. Capella <polyzen@archlinux.org>",
|
||||||
|
URL: "https://github.com/tree-sitter/tree-sitter",
|
||||||
|
Validation: "pgp",
|
||||||
|
Reason: 1,
|
||||||
|
Files: []pkg.AlpmFileRecord{},
|
||||||
|
Backup: []pkg.AlpmFileRecord{},
|
||||||
|
Provides: []string{"libtree-sitter.so=0-64"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -101,13 +147,10 @@ func TestDatabaseParser(t *testing.T) {
|
|||||||
entry, err := parseAlpmDBEntry(reader)
|
entry, err := parseAlpmDBEntry(reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if diff := cmp.Diff(entry.Files, test.expected.Files); diff != "" {
|
if diff := cmp.Diff(test.expected, entry); diff != "" {
|
||||||
t.Errorf("Files mismatch (-want +got):\n%s", diff)
|
t.Errorf("parsed data mismatch (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff := cmp.Diff(entry.Backup, test.expected.Backup); diff != "" {
|
|
||||||
t.Errorf("Backup mismatch (-want +got):\n%s", diff)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
%NAME%
|
||||||
|
emacs
|
||||||
|
|
||||||
|
%VERSION%
|
||||||
|
29.3-3
|
||||||
|
|
||||||
|
%BASE%
|
||||||
|
emacs
|
||||||
|
|
||||||
|
%DESC%
|
||||||
|
The extensible, customizable, self-documenting real-time display editor
|
||||||
|
|
||||||
|
%URL%
|
||||||
|
https://www.gnu.org/software/emacs/emacs.html
|
||||||
|
|
||||||
|
%ARCH%
|
||||||
|
x86_64
|
||||||
|
|
||||||
|
%BUILDDATE%
|
||||||
|
1714249917
|
||||||
|
|
||||||
|
%INSTALLDATE%
|
||||||
|
1715026363
|
||||||
|
|
||||||
|
%PACKAGER%
|
||||||
|
Frederik Schwan <freswa@archlinux.org>
|
||||||
|
|
||||||
|
%SIZE%
|
||||||
|
126427862
|
||||||
|
|
||||||
|
%LICENSE%
|
||||||
|
GPL3
|
||||||
|
|
||||||
|
%VALIDATION%
|
||||||
|
pgp
|
||||||
|
|
||||||
|
%DEPENDS%
|
||||||
|
libtree-sitter.so=0-64
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
%NAME%
|
||||||
|
fuzzy
|
||||||
|
|
||||||
|
%VERSION%
|
||||||
|
1.2-3
|
||||||
|
|
||||||
|
%DEPENDS%
|
||||||
|
tree-sitter
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
%FILES%
|
||||||
|
etc/
|
||||||
|
etc/fuzzy.conf
|
||||||
|
|
||||||
|
%BACKUP%
|
||||||
|
etc/fuzzy.conf 79fce043df7dfc676ae5ecb903762d8b
|
||||||
@ -41,4 +41,4 @@ pgp
|
|||||||
%DEPENDS%
|
%DEPENDS%
|
||||||
gcc-libs
|
gcc-libs
|
||||||
sh
|
sh
|
||||||
|
libtree-sitter.so=1-64
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
%NAME%
|
||||||
|
madeup
|
||||||
|
|
||||||
|
%VERSION%
|
||||||
|
20.30-4
|
||||||
|
|
||||||
|
%DEPENDS%
|
||||||
|
libtree-sitter.so
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
%NAME%
|
||||||
|
tree-sitter
|
||||||
|
|
||||||
|
%VERSION%
|
||||||
|
0.22.6-1
|
||||||
|
|
||||||
|
%BASE%
|
||||||
|
tree-sitter
|
||||||
|
|
||||||
|
%DESC%
|
||||||
|
Incremental parsing library
|
||||||
|
|
||||||
|
%URL%
|
||||||
|
https://github.com/tree-sitter/tree-sitter
|
||||||
|
|
||||||
|
%ARCH%
|
||||||
|
x86_64
|
||||||
|
|
||||||
|
%BUILDDATE%
|
||||||
|
1714945746
|
||||||
|
|
||||||
|
%INSTALLDATE%
|
||||||
|
1715026360
|
||||||
|
|
||||||
|
%PACKAGER%
|
||||||
|
Daniel M. Capella <polyzen@archlinux.org>
|
||||||
|
|
||||||
|
%SIZE%
|
||||||
|
223539
|
||||||
|
|
||||||
|
%REASON%
|
||||||
|
1
|
||||||
|
|
||||||
|
%LICENSE%
|
||||||
|
MIT
|
||||||
|
|
||||||
|
%VALIDATION%
|
||||||
|
pgp
|
||||||
|
|
||||||
|
%PROVIDES%
|
||||||
|
libtree-sitter.so=0-64
|
||||||
@ -8,8 +8,8 @@ import "github.com/quasilyte/go-ruleguard/dsl"
|
|||||||
func resourceCleanup(m dsl.Matcher) {
|
func resourceCleanup(m dsl.Matcher) {
|
||||||
m.Match(`$res, $err := $resolver.FileContentsByLocation($loc); if $*_ { $*_ }; $next`).
|
m.Match(`$res, $err := $resolver.FileContentsByLocation($loc); if $*_ { $*_ }; $next`).
|
||||||
Where(m["res"].Type.Implements(`io.Closer`) &&
|
Where(m["res"].Type.Implements(`io.Closer`) &&
|
||||||
|
m["res"].Type.Implements(`io.Reader`) &&
|
||||||
m["err"].Type.Implements(`error`) &&
|
m["err"].Type.Implements(`error`) &&
|
||||||
m["res"].Type.Implements(`io.Closer`) &&
|
|
||||||
!m["next"].Text.Matches(`defer internal.CloseAndLogError`)).
|
!m["next"].Text.Matches(`defer internal.CloseAndLogError`)).
|
||||||
Report(`please call "defer internal.CloseAndLogError($res, $loc.RealPath)" right after checking the error returned from $resolver.FileContentsByLocation.`)
|
Report(`please call "defer internal.CloseAndLogError($res, $loc.RealPath)" right after checking the error returned from $resolver.FileContentsByLocation.`)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user