github.com/kastenhq/syft@v0.0.0-20230821225854-0710af25cdbe/syft/pkg/cataloger/rpm/parse_rpm_db.go (about) 1 package rpm 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 8 rpmdb "github.com/knqyf263/go-rpmdb/pkg" 9 10 "github.com/kastenhq/syft/internal" 11 "github.com/kastenhq/syft/internal/log" 12 "github.com/kastenhq/syft/syft/artifact" 13 "github.com/kastenhq/syft/syft/file" 14 "github.com/kastenhq/syft/syft/linux" 15 "github.com/kastenhq/syft/syft/pkg" 16 "github.com/kastenhq/syft/syft/pkg/cataloger/generic" 17 ) 18 19 // parseRpmDb parses an "Packages" RPM DB and returns the Packages listed within it. 20 func parseRpmDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 21 f, err := os.CreateTemp("", internal.ApplicationName+"-rpmdb") 22 if err != nil { 23 return nil, nil, fmt.Errorf("failed to create temp rpmdb file: %w", err) 24 } 25 26 defer func() { 27 err = os.Remove(f.Name()) 28 if err != nil { 29 log.Errorf("failed to remove temp rpmdb file: %+v", err) 30 } 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 50 var distro *linux.Release 51 if env != nil { 52 distro = env.LinuxRelease 53 } 54 55 for _, entry := range pkgList { 56 if entry == nil { 57 continue 58 } 59 60 p := newPackage( 61 reader.Location, 62 newParsedDataFromEntry(reader.Location, *entry, extractRpmdbFileRecords(resolver, *entry)), 63 distro, 64 ) 65 66 if !pkg.IsValid(&p) { 67 log.WithFields("location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)). 68 Warn("ignoring invalid package found in RPM DB") 69 continue 70 } 71 72 p.SetID() 73 allPkgs = append(allPkgs, p) 74 } 75 76 return allPkgs, nil, nil 77 } 78 79 // The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version]. 80 // RPM version comparison depends on comparing at least the version and release fields together as a subset of the 81 // naming scheme. This toELVersion function takes a RPM DB package information and converts it into a minimally comparable 82 // version string, containing epoch (optional), version, and release information. Epoch is an optional field and can be 83 // assumed to be 0 when not provided for comparison purposes, however, if the underlying RPM DB entry does not have 84 // an epoch specified it would be slightly disingenuous to display a value of 0. 85 func toELVersion(metadata pkg.RpmMetadata) string { 86 if metadata.Epoch != nil { 87 return fmt.Sprintf("%d:%s-%s", *metadata.Epoch, metadata.Version, metadata.Release) 88 } 89 return fmt.Sprintf("%s-%s", metadata.Version, metadata.Release) 90 } 91 92 func extractRpmdbFileRecords(resolver file.PathResolver, entry rpmdb.PackageInfo) []pkg.RpmdbFileRecord { 93 var records = make([]pkg.RpmdbFileRecord, 0) 94 95 files, err := entry.InstalledFiles() 96 if err != nil { 97 log.Warnf("unable to parse listing of installed files for RPM DB entry: %s", err.Error()) 98 return records 99 } 100 101 for _, record := range files { 102 // only persist RPMDB file records which exist in the image/directory, otherwise ignore them 103 if resolver.HasPath(record.Path) { 104 records = append(records, pkg.RpmdbFileRecord{ 105 Path: record.Path, 106 Mode: pkg.RpmdbFileMode(record.Mode), 107 Size: int(record.Size), 108 Digest: file.Digest{ 109 Value: record.Digest, 110 Algorithm: entry.DigestAlgorithm.String(), 111 }, 112 UserName: record.Username, 113 GroupName: record.Groupname, 114 Flags: record.Flags.String(), 115 }) 116 } 117 } 118 return records 119 }