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