github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/redhat/parse_rpm_db.go (about) 1 package redhat 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 9 rpmdb "github.com/knqyf263/go-rpmdb/pkg" 10 11 "github.com/anchore/syft/internal/log" 12 "github.com/anchore/syft/syft/artifact" 13 "github.com/anchore/syft/syft/file" 14 "github.com/anchore/syft/syft/linux" 15 "github.com/anchore/syft/syft/pkg" 16 "github.com/anchore/syft/syft/pkg/cataloger/generic" 17 ) 18 19 // parseRpmDb parses an "Packages" RPM DB and returns the Packages listed within it. 20 // nolint:funlen 21 func parseRpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 22 f, err := os.CreateTemp("", "rpmdb") 23 if err != nil { 24 return nil, nil, fmt.Errorf("failed to create temp rpmdb file: %w", err) 25 } 26 27 defer func() { 28 err = f.Close() 29 if err != nil { 30 log.Errorf("failed to close temp rpmdb file: %+v", err) 31 } 32 err = os.Remove(f.Name()) 33 if err != nil { 34 log.Errorf("failed to remove temp rpmdb file: %+v", err) 35 } 36 }() 37 38 _, err = io.Copy(f, reader) 39 if err != nil { 40 return nil, nil, fmt.Errorf("failed to copy rpmdb contents to temp file: %w", err) 41 } 42 43 db, err := rpmdb.Open(f.Name()) 44 if err != nil { 45 return nil, nil, err 46 } 47 defer db.Close() 48 49 pkgList, err := db.ListPackages() 50 if err != nil { 51 return nil, nil, err 52 } 53 54 var allPkgs []pkg.Package 55 56 var distro *linux.Release 57 if env != nil { 58 distro = env.LinuxRelease 59 } 60 61 for _, entry := range pkgList { 62 if entry == nil { 63 continue 64 } 65 66 metadata := pkg.RpmDBEntry{ 67 Name: entry.Name, 68 Version: entry.Version, 69 Epoch: entry.Epoch, 70 Arch: entry.Arch, 71 Release: entry.Release, 72 SourceRpm: entry.SourceRpm, 73 Vendor: entry.Vendor, 74 Size: entry.Size, 75 ModularityLabel: &entry.Modularitylabel, 76 Files: extractRpmFileRecords(resolver, *entry), 77 Provides: entry.Provides, 78 Requires: entry.Requires, 79 } 80 81 p := newDBPackage( 82 reader.Location, 83 metadata, 84 distro, 85 []string{entry.License}, 86 ) 87 88 if !pkg.IsValid(&p) { 89 log.WithFields("location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)). 90 Warn("ignoring invalid package found in RPM DB") 91 continue 92 } 93 94 p.SetID() 95 allPkgs = append(allPkgs, p) 96 } 97 98 return allPkgs, nil, nil 99 } 100 101 // The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version]. 102 // RPM version comparison depends on comparing at least the version and release fields together as a subset of the 103 // naming scheme. This toELVersion function takes a RPM DB package information and converts it into a minimally comparable 104 // version string, containing epoch (optional), version, and release information. Epoch is an optional field and can be 105 // assumed to be 0 when not provided for comparison purposes, however, if the underlying RPM DB entry does not have 106 // an epoch specified it would be slightly disingenuous to display a value of 0. 107 func toELVersion(epoch *int, version, release string) string { 108 if epoch != nil { 109 return fmt.Sprintf("%d:%s-%s", *epoch, version, release) 110 } 111 return fmt.Sprintf("%s-%s", version, release) 112 } 113 114 func extractRpmFileRecords(resolver file.PathResolver, entry rpmdb.PackageInfo) []pkg.RpmFileRecord { 115 var records = make([]pkg.RpmFileRecord, 0) 116 117 files, err := entry.InstalledFiles() 118 if err != nil { 119 log.Warnf("unable to parse listing of installed files for RPM DB entry: %s", err.Error()) 120 return records 121 } 122 123 for _, record := range files { 124 // only persist RPMDB file records which exist in the image/directory, otherwise ignore them 125 if resolver.HasPath(record.Path) { 126 records = append(records, pkg.RpmFileRecord{ 127 Path: record.Path, 128 Mode: pkg.RpmFileMode(record.Mode), 129 Size: int(record.Size), 130 Digest: file.Digest{ 131 Value: record.Digest, 132 Algorithm: entry.DigestAlgorithm.String(), 133 }, 134 UserName: record.Username, 135 GroupName: record.Groupname, 136 Flags: record.Flags.String(), 137 }) 138 } 139 } 140 return records 141 }