Skip to content

Commit

Permalink
WIP: Added pacman package parser
Browse files Browse the repository at this point in the history
Fixes anchore#241

Signed-off-by: Morten Linderud <morten@linderud.pw>
  • Loading branch information
Foxboron committed Apr 7, 2022
1 parent e415bb2 commit 982a757
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 0 deletions.
63 changes: 63 additions & 0 deletions syft/pkg/alpm_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package pkg

import (
"sort"

"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/linux"
"github.com/scylladb/go-set/strset"
)

const AlpmDBGlob = "/var/lib/pacman/local/**/desc"

// Package string `mapstructure:"P" json:"package"`
// OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
// Maintainer string `mapstructure:"m" json:"maintainer"`
// Version string `mapstructure:"V" json:"version"`
// License string `mapstructure:"L" json:"license"`
// Architecture string `mapstructure:"A" json:"architecture"`
// URL string `mapstructure:"U" json:"url"`
// Description string `mapstructure:"T" json:"description"`
// Size int `mapstructure:"S" json:"size" cyclonedx:"size"`
// InstalledSize int `mapstructure:"I" json:"installedSize" cyclonedx:"installedSize"`
// PullDependencies string `mapstructure:"D" json:"pullDependencies" cyclonedx:"pullDependencies"`
// PullChecksum string `mapstructure:"C" json:"pullChecksum" cyclonedx:"pullChecksum"`
// GitCommitOfAport string `mapstructure:"c" json:"gitCommitOfApkPort" cyclonedx:"gitCommitOfApkPort"`

type AlpmMetadata struct {
Package string `mapstructure:"name" json:"package"`
Version string `mapstructure:"version" json:"version"`
Epoch *string `mapstructure:"epoch" json:"epoch" cyclonedx:"epoch" jsonschema:"nullable"`
Arch string `mapstructure:"arch" json:"architecture"`
License string `mapstructure:"license" json:"license"`
Files []AlpmFileRecord `json:"files"`
}

// TODO: Implement mtree support
type AlpmFileRecord struct {
Path string `json:"path"`
}

// PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec)
func (m AlpmMetadata) PackageURL(distro *linux.Release) string {
return packageurl.NewPackageURL(
"archlinux",
"",
"",
"",
packageurl.Qualifiers{},
"",
).ToString()
}

func (m AlpmMetadata) OwnedFiles() (result []string) {
s := strset.New()
for _, f := range m.Files {
if f.Path != "" {
s.Add(f.Path)
}
}
result = s.List()
sort.Strings(result)
return result
}
15 changes: 15 additions & 0 deletions syft/pkg/cataloger/alpm/cataloger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package alpm

import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
)

// NewAlpmdbCataloger returns a new Alpine DB cataloger object.
func NewAlpmdbCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
pkg.AlpmDBGlob: parseAlpmDB,
}

return common.NewGenericCataloger(nil, globParsers, "alpmdb-cataloger")
}
129 changes: 129 additions & 0 deletions syft/pkg/cataloger/alpm/parse_alpm_db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package alpm

import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"github.com/mitchellh/mapstructure"
)

// integrity check
var _ common.ParserFn = parseAlpmDB

func newAlpmDBPackage(d *pkg.AlpmMetadata) *pkg.Package {
return &pkg.Package{
Name: d.Package,
Version: d.Version,
Type: "alpm",
Licenses: strings.Split(d.License, " "),
MetadataType: pkg.AlpmMetadataType,
Metadata: *d,
}
}

func newScanner(reader io.Reader) *bufio.Scanner {
// This is taken from the apk parser
const maxScannerCapacity = 1024 * 1024
bufScan := make([]byte, maxScannerCapacity)
scanner := bufio.NewScanner(reader)
scanner.Buffer(bufScan, maxScannerCapacity)
onDoubleLF := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if i > 0 && data[i-1] == '\n' && data[i] == '\n' {
return i + 1, data[:i-1], nil
}
}
if !atEOF {
return 0, nil, nil
}
// deliver the last token (which could be an empty string)
return 0, data, bufio.ErrFinalToken
}

scanner.Split(onDoubleLF)
return scanner
}

func parseDesc(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
var entry pkg.AlpmMetadata
pkgFields := make(map[string]interface{})
for b.Scan() {
fields := strings.SplitN(b.Text(), "\n", 2)

// End of File
if len(fields) == 1 {
break
}

// The alpm database surrounds the keys with %.
key := strings.Replace(fields[0], "%", "", -1)
key = strings.ToLower(key)
value := strings.TrimSpace(fields[1])

switch key {
case "version":
ver := strings.SplitN(value, ":", 2)
if len(ver) == 1 {
pkgFields[key] = value
} else {
pkgFields["epoch"] = ver[0]
pkgFields[key] = ver[1]
}
default:
pkgFields[key] = value
}
}
if err := mapstructure.Decode(pkgFields, &entry); err != nil {
return nil, fmt.Errorf("unable to parse ALPM metadata: %w", err)
}
if entry.Package == "" {
return nil, nil
}
return &entry, nil
}

func parseFiles(b *bufio.Scanner) []pkg.AlpmFileRecord {
var entries []pkg.AlpmFileRecord
for b.Scan() {
if b.Text() == "" {
break
}
fields := strings.SplitN(b.Text(), "\n", 2)
if fields[0] != "%FILES%" {
return nil
}
for _, f := range strings.Split(fields[1], "\n") {
entries = append(entries, pkg.AlpmFileRecord{Path: f})
}
}
return entries
}

func parseAlpmDB(f string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
base := filepath.Dir(f)
scanner := newScanner(reader)
metadata, err := parseDesc(scanner)
if err != nil {
return nil, nil, err
}
if metadata == nil {
return nil, nil, nil
}

filesIndex := filepath.Join(base, "files")
r, err := os.Open(filesIndex)
if err == nil {
// TODO: Figure out why we sometimes don't find the "files" file
scanner = newScanner(r)
metadata.Files = parseFiles(scanner)
}

return []*pkg.Package{newAlpmDBPackage(metadata)}, nil, nil
}
4 changes: 4 additions & 0 deletions syft/pkg/cataloger/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package cataloger
import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/alpm"
"github.com/anchore/syft/syft/pkg/cataloger/apkdb"
"github.com/anchore/syft/syft/pkg/cataloger/dart"
"github.com/anchore/syft/syft/pkg/cataloger/deb"
Expand Down Expand Up @@ -35,6 +36,7 @@ type Cataloger interface {
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
func ImageCatalogers(cfg Config) []Cataloger {
return []Cataloger{
alpm.NewAlpmdbCataloger(),
ruby.NewGemSpecCataloger(),
python.NewPythonPackageCataloger(),
php.NewPHPComposerInstalledCataloger(),
Expand All @@ -50,6 +52,7 @@ func ImageCatalogers(cfg Config) []Cataloger {
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
func DirectoryCatalogers(cfg Config) []Cataloger {
return []Cataloger{
alpm.NewAlpmdbCataloger(),
ruby.NewGemFileLockCataloger(),
python.NewPythonIndexCataloger(),
python.NewPythonPackageCataloger(),
Expand All @@ -69,6 +72,7 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
// AllCatalogers returns all implemented catalogers
func AllCatalogers(cfg Config) []Cataloger {
return []Cataloger{
alpm.NewAlpmdbCataloger(),
ruby.NewGemFileLockCataloger(),
ruby.NewGemSpecCataloger(),
python.NewPythonIndexCataloger(),
Expand Down
3 changes: 3 additions & 0 deletions syft/pkg/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (

UnknownMetadataType MetadataType = "UnknownMetadata"
ApkMetadataType MetadataType = "ApkMetadata"
AlpmMetadataType MetadataType = "AlpmMetadata"
DpkgMetadataType MetadataType = "DpkgMetadata"
GemMetadataType MetadataType = "GemMetadata"
JavaMetadataType MetadataType = "JavaMetadata"
Expand All @@ -27,6 +28,7 @@ const (

var AllMetadataTypes = []MetadataType{
ApkMetadataType,
AlpmMetadataType,
DpkgMetadataType,
GemMetadataType,
JavaMetadataType,
Expand All @@ -42,6 +44,7 @@ var AllMetadataTypes = []MetadataType{

var MetadataTypeByName = map[MetadataType]reflect.Type{
ApkMetadataType: reflect.TypeOf(ApkMetadata{}),
AlpmMetadataType: reflect.TypeOf(AlpmMetadata{}),
DpkgMetadataType: reflect.TypeOf(DpkgMetadata{}),
GemMetadataType: reflect.TypeOf(GemMetadata{}),
JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
Expand Down

0 comments on commit 982a757

Please sign in to comment.