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