rpm

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 17, 2021 License: BSD-3-Clause Imports: 19 Imported by: 17

README

rpm

Go Reference Build Status Go Report Card

Package rpm implements the rpm package file format.

$ go get github.com/cavaliergopher/rpm

See the package documentation or the examples programs in cmd/ to get started.

Extracting rpm packages

The following working example demonstrates how to extract files from an rpm package. In this example, only the cpio format and xz compression are supported which will cover most cases.

Implementations should consider additional formats and compressions algorithms, as well as support for extracting irregular file types and configuring permissions, uids and guids, etc.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"

	"github.com/cavaliergopher/cpio"
	"github.com/cavaliergopher/rpm"
	"github.com/ulikunitz/xz"
)

func ExtractRPM(name string) {
	// Open a package file for reading
	f, err := os.Open(name)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	// Read the package headers
	pkg, err := rpm.Read(f)
	if err != nil {
		log.Fatal(err)
	}

	// Check the compression algorithm of the payload
	if compression := pkg.PayloadCompression(); compression != "xz" {
		log.Fatalf("Unsupported compression: %s", compression)
	}

	// Attach a reader to decompress the payload
	xzReader, err := xz.NewReader(f)
	if err != nil {
		log.Fatal(err)
	}

	// Check the archive format of the payload
	if format := pkg.PayloadFormat(); format != "cpio" {
		log.Fatalf("Unsupported payload format: %s", format)
	}

	// Attach a reader to unarchive each file in the payload
	cpioReader := cpio.NewReader(xzReader)
	for {
		// Move to the next file in the archive
		hdr, err := cpioReader.Next()
		if err == io.EOF {
			break // no more files
		}
		if err != nil {
			log.Fatal(err)
		}

		// Skip directories and other irregular file types in this example
		if !hdr.Mode.IsRegular() {
			continue
		}

		// Create the target directory
		if dirName := filepath.Dir(hdr.Name); dirName != "" {
			if err := os.MkdirAll(dirName, 0o755); err != nil {
				log.Fatal(err)
			}
		}

		// Create and write the file
		outFile, err := os.Create(hdr.Name)
		if err != nil {
			log.Fatal(err)
		}
		if _, err := io.Copy(outFile, cpioReader); err != nil {
			outFile.Close()
			log.Fatal(err)
		}
		outFile.Close()
	}
}

Documentation

Overview

Package rpm implements the rpm package file format.

package main

import (
	"fmt"
	"log"

	"github.com/cavaliergopher/rpm"
)

func main() {
	pkg, err := rpm.Open("golang-1.17.2-1.el7.x86_64.rpm")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Package:", pkg)
	fmt.Println("Summary:", pkg.Summary())

	// Output:
	// Package: golang-1.17.2-1.el7.x86_64
	// Summary: The Go Programming Language
}

For more information about the rpm file format, see:

http://ftp.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html

Packages are composed of two headers: the Signature header and the "Header" header. Each contains key-value pairs called tags. Tags map an integer key to a value whose data type will be one of the TagType types. Tag values can be decoded with the appropriate Tag method for the data type.

Many known tags are available as Package methods. For example, RPMTAG_NAME and RPMTAG_BUILDTIME are available as Package.Name and Package.BuildTime respectively.

fmt.Println(pkg.Name(), pkg.BuildTime())

Tags can be retrieved and decoded from the Signature or Header headers directly using Header.GetTag and their tag identifier.

const (
	RPMTagName      = 1000
	RPMTagBuidlTime = 1006
)

fmt.Println(
	pkg.Header.GetTag(RPMTagName).String()),
	time.Unix(pkg.Header.GetTag(RPMTagBuildTime).Int64(), 0),
)

Header.GetTag and all Tag methods will return a zero value if the header or the tag do not exist, or if the tag has a different data type.

You may enumerate all tags in a header with Header.Tags:

for id, tag := range pkg.Header.Tags {
	fmt.Println(id, tag.Type, tag.Value)
}

Comparing versions

In the rpm ecosystem, package versions are compared using EVR; epoch, version, release. Versions may be compared using the Compare function.

if rpm.Compare(pkgA, pkgB) == 1 {
	fmt.Println("A is more recent than B")
}

Packages may be be sorted using the PackageSlice type which implements sort.Interface. Packages are sorted lexically by name ascending and then by version descending. Version is evaluated first by epoch, then by version string, then by release.

sort.Sort(PackageSlice(pkgs))

The Sort function is provided for your convenience.

rpm.Sort(pkgs)

Checksum validation

Packages may be validated using MD5Check or GPGCheck. See the example for each function.

Extracting files

The payload of an rpm package is typically archived in cpio format and compressed with xz. To decompress and unarchive an rpm payload, the reader that read the rpm package headers will be positioned at the beginning of the payload and can be reused with the appropriate Go packages for the rpm payload format.

You can check the archive format with Package.PayloadFormat and the compression algorithm with Package.PayloadCompression.

For the cpio archive format, the following package is recommended:

https://github.com/cavaliergopher/cpio

For xz compression, the following package is recommended:

https://github.com/ulikunitz/xz

See README.md for a working example of extracting files from a cpio/xz rpm package using these packages.

Example programs

See cmd/rpmdump and cmd/rpminfo for example programs that emulate tools from the rpm ecosystem.

Index

Examples

Constants

View Source
const (
	DepFlagAny            = 0
	DepFlagLesser         = (1 << 1)
	DepFlagGreater        = (1 << 2)
	DepFlagEqual          = (1 << 3)
	DepFlagLesserOrEqual  = (DepFlagEqual | DepFlagLesser)
	DepFlagGreaterOrEqual = (DepFlagEqual | DepFlagGreater)
	DepFlagPrereq         = (1 << 6)
	DepFlagScriptPre      = (1 << 9)
	DepFlagScriptPost     = (1 << 10)
	DepFlagScriptPreUn    = (1 << 11)
	DepFlagScriptPostUn   = (1 << 12)
	DepFlagRpmlib         = (1 << 24)
)

Dependency flags indicate how versions comparisons should be computed when comparing versions of dependent packages.

View Source
const (
	FileFlagNone      = 0
	FileFlagConfig    = (1 << 0)  // %%config
	FileFlagDoc       = (1 << 1)  // %%doc
	FileFlagIcon      = (1 << 2)  // %%donotuse
	FileFlagMissingOk = (1 << 3)  // %%config(missingok)
	FileFlagNoReplace = (1 << 4)  // %%config(noreplace)
	FileFlagGhost     = (1 << 6)  // %%ghost
	FileFlagLicense   = (1 << 7)  // %%license
	FileFlagReadme    = (1 << 8)  // %%readme
	FileFlagPubkey    = (1 << 11) // %%pubkey
	FileFlagArtifact  = (1 << 12) // %%artifact
)

File flags make up some attributes of files depending on how they were specified in the rpmspec

View Source
const TimeFormat = "Mon Jan _2 15:04:05 2006"

TimeFormat is the time format used by the rpm ecosystem. The time being formatted must be in UTC for Format to generate the correct format.

Variables

View Source
var (
	// ErrMD5CheckFailed indicates that an rpm package failed MD5 checksum
	// validation.
	ErrMD5CheckFailed = fmt.Errorf("MD5 checksum validation failed")

	// ErrGPGCheckFailed indicates that an rpm package failed GPG signature
	// validation.
	ErrGPGCheckFailed = fmt.Errorf("GPG signature validation failed")
)
View Source
var ErrNotRPMFile = errorf("invalid file descriptor")

ErrNotRPMFile indicates that the file is not an rpm package.

Functions

func Compare added in v1.1.0

func Compare(a, b Version) int

Compare compares the version details of two packages. Versions are compared by Epoch, Version and Release (EVR) in descending order of precedence.

If a is more recent than b, 1 is returned. If a is less recent than b, -1 is returned. If a and b are equal, 0 is returned.

This function does not consider if the two packages have the same name or if either package has been made obsolete by the other.

func CompareVersions added in v1.2.0

func CompareVersions(a, b string) int

CompareVersion compares version strings. It does not consider package epochs or release numbers like Compare.

If a is more recent than b, 1 is returned. If a is less recent than b, -1 is returned. If a and b are equal, 0 is returned.

func GPGCheck

func GPGCheck(r io.Reader, keyring openpgp.KeyRing) (string, error)

GPGCheck validates the integrity of an rpm package file. Public keys in the given keyring are used to validate the package signature.

If validation fails, ErrGPGCheckFailed is returned.

Example

ExampleGPGCheck reads a public GPG key and uses it to validate the signature of a local rpm package.

// read public key from gpgkey file
keyring, err := OpenKeyRing("testdata/RPM-GPG-KEY-CentOS-7")
if err != nil {
	log.Fatal(err)
}

// open a rpm package for reading
f, err := os.Open("testdata/centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm")
if err != nil {
	log.Fatal(err)
}
defer f.Close()

// validate gpg signature
if signer, err := GPGCheck(f, keyring); err == nil {
	fmt.Printf("Package signed by '%s'\n", signer)
} else if err == ErrGPGCheckFailed {
	fmt.Printf("Package failed GPG signature validation\n")
} else {
	log.Fatal(err)
}
Output:

Package signed by 'CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>'

func MD5Check

func MD5Check(r io.Reader) error

MD5Check validates the integrity of an rpm package file. The MD5 checksum is computed for the package payload and compared with the checksum specified in the package header.

If validation fails, ErrMD5CheckFailed is returned.

Example

ExampleMD5Check validates a local rpm package named using the MD5 checksum value specified in the package header.

// open a rpm package for reading
f, err := os.Open("testdata/centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm")
if err != nil {
	log.Fatal(err)
}

defer f.Close()

// validate md5 checksum
if err := MD5Check(f); err == nil {
	fmt.Printf("Package passed checksum validation\n")
} else if err == ErrMD5CheckFailed {
	fmt.Printf("Package failed checksum validation\n")
} else {
	log.Fatal(err)
}
Output:

Package passed checksum validation

func OpenKeyRing

func OpenKeyRing(name ...string) (openpgp.KeyRing, error)

KeyRingFromFiles reads a openpgp.KeyRing from the given file paths which may then be used to validate GPG keys in rpm packages.

This function might typically be used to read all keys in /etc/pki/rpm-gpg.

func ReadKeyRing

func ReadKeyRing(r io.Reader) (openpgp.KeyRing, error)

ReadKeyRing reads a openpgp.KeyRing from the given io.Reader which may then be used to validate GPG keys in rpm packages.

func Sort added in v1.1.0

func Sort(x []*Package)

Sort sorts a slice of packages lexically by name ascending and then by version descending. Version is evaluated first by epoch, then by version string, then by release.

Types

type Dependency

type Dependency interface {
	Version // Version of the other package

	Name() string // Name of the other package
	Flags() int   // See the DepFlag constants
}

Dependency is an interface which represents a relationship between two packages. It might indicate that one package requires, conflicts with, obsoletes or provides another package.

Dependency implements the Version interface and so may be used when comparing versions.

type FileInfo

type FileInfo struct {
	// contains filtered or unexported fields
}

A FileInfo describes a file in an rpm package.

FileInfo implements the os.FileInfo interface.

func (*FileInfo) Digest

func (f *FileInfo) Digest() string

Digest is the md5sum of a file in an rpm package.

func (*FileInfo) Flags

func (f *FileInfo) Flags() int64

func (*FileInfo) Group

func (f *FileInfo) Group() string

Group is the name of the owner group of a file in an rpm package.

func (*FileInfo) IsDir

func (f *FileInfo) IsDir() bool

IsDir returns true if a file is a directory in an rpm package.

func (*FileInfo) Linkname

func (f *FileInfo) Linkname() string

Linkname is the link target of a link file in an rpm package.

func (*FileInfo) ModTime

func (f *FileInfo) ModTime() time.Time

ModTime is the modification time of a file in an rpm package.

func (*FileInfo) Mode

func (f *FileInfo) Mode() os.FileMode

Mode is the file mode in bits of a file in an rpm package.

func (*FileInfo) Name

func (f *FileInfo) Name() string

Name is the full path of a file in an rpm package.

func (*FileInfo) Owner

func (f *FileInfo) Owner() string

Owner is the name of the owner of a file in an rpm package.

func (*FileInfo) Size

func (f *FileInfo) Size() int64

Size is the size in bytes of a file in an rpm package.

func (*FileInfo) String

func (f *FileInfo) String() string

func (*FileInfo) Sys

func (f *FileInfo) Sys() interface{}

Sys implements os.FileInfo and always returns nil.

type GPGSignature

type GPGSignature []byte

GPGSignature is the raw byte representation of a package's signature.

func (GPGSignature) String

func (b GPGSignature) String() string
type Header struct {
	Version int
	Tags    map[int]*Tag
}

A Header stores metadata about an rpm package.

func (*Header) GetTag

func (c *Header) GetTag(id int) *Tag

GetTag returns the tag with the given identifier.

Nil is returned if the specified tag does not exist or the header is nil.

type Lead

type Lead struct {
	VersionMajor    int
	VersionMinor    int
	Name            string
	Type            int
	Architecture    int
	OperatingSystem int
	SignatureType   int
}

Lead is the deprecated lead section of an rpm file which is used in legacy rpm versions to store package metadata.

type Package

type Package struct {
	Lead      Lead
	Signature Header
	Header    Header
}

A Package is an rpm package file.

func Open

func Open(name string) (*Package, error)

Open opens an rpm package from the file system.

Once the package headers are read, the underlying reader is closed and cannot be used to read the package payload. To read the package payload, open the package with os.Open and read the headers with Read. You may then use the same reader to read the payload.

func Read

func Read(r io.Reader) (*Package, error)

Read reads an rpm package from r.

When this function returns, the reader will be positioned at the start of the package payload. Use Package.PayloadFormat and Package.PayloadCompression to determine how to decompress and unarchive the payload.

func (*Package) Architecture

func (c *Package) Architecture() string

func (*Package) ArchiveSize

func (c *Package) ArchiveSize() uint64

ArchiveSize specifies the size of the archived payload of the package in bytes.

func (*Package) BuildHost

func (c *Package) BuildHost() string

func (*Package) BuildTime

func (c *Package) BuildTime() time.Time

func (*Package) ChangeLog

func (c *Package) ChangeLog() []string

func (*Package) Conflicts

func (c *Package) Conflicts() []Dependency

func (*Package) Description

func (c *Package) Description() string

func (*Package) Distribution

func (c *Package) Distribution() string

func (*Package) Enhances

func (c *Package) Enhances() []Dependency

func (*Package) Epoch

func (c *Package) Epoch() int

func (*Package) Files

func (c *Package) Files() []FileInfo

Files returns file information for each file that is installed by this RPM package.

Example

Lists all the files in an rpm package.

// open a package file
pkg, err := Open("./testdata/epel-release-7-5.noarch.rpm")
if err != nil {
	log.Fatal(err)
}

// list each file
files := pkg.Files()
fmt.Printf("total %v\n", len(files))
for _, fi := range files {
	fmt.Printf("%v %v %v %5v %v %v\n",
		fi.Mode().Perm(),
		fi.Owner(),
		fi.Group(),
		fi.Size(),
		fi.ModTime().UTC().Format("Jan 02 15:04"),
		fi.Name())
}
Output:

total 7
-rw-r--r-- root root  1662 Nov 25 16:23 /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
-rw-r--r-- root root  1056 Nov 25 16:23 /etc/yum.repos.d/epel-testing.repo
-rw-r--r-- root root   957 Nov 25 16:23 /etc/yum.repos.d/epel.repo
-rw-r--r-- root root    41 Nov 25 16:23 /usr/lib/rpm/macros.d/macros.epel
-rw-r--r-- root root  2813 Nov 25 16:23 /usr/lib/systemd/system-preset/90-epel.preset
-rwxr-xr-x root root  4096 Nov 25 16:26 /usr/share/doc/epel-release-7
-rw-r--r-- root root 18385 Nov 25 16:23 /usr/share/doc/epel-release-7/GPL

func (*Package) GIFImage

func (c *Package) GIFImage() []byte

func (*Package) GPGSignature

func (c *Package) GPGSignature() GPGSignature

func (*Package) Groups

func (c *Package) Groups() []string

func (*Package) Icon

func (c *Package) Icon() []byte

func (*Package) InstallTime

func (c *Package) InstallTime() time.Time

func (*Package) License

func (c *Package) License() string

func (*Package) Name

func (c *Package) Name() string

func (*Package) Obsoletes

func (c *Package) Obsoletes() []Dependency

func (*Package) OldFilenames

func (c *Package) OldFilenames() []string

func (*Package) OperatingSystem

func (c *Package) OperatingSystem() string

func (*Package) Packager

func (c *Package) Packager() string

func (*Package) Patch

func (c *Package) Patch() []string

func (*Package) PayloadCompression

func (c *Package) PayloadCompression() string

PayloadCompression returns the name of the compression used for the package payload. Typically xz.

func (*Package) PayloadFormat

func (c *Package) PayloadFormat() string

PayloadFormat returns the name of the format used for the package payload. Typically cpio.

func (*Package) Platform

func (c *Package) Platform() string

func (*Package) PostInstallScript

func (c *Package) PostInstallScript() string

func (*Package) PostUninstallScript

func (c *Package) PostUninstallScript() string

func (*Package) PreInstallScript

func (c *Package) PreInstallScript() string

func (*Package) PreUninstallScript

func (c *Package) PreUninstallScript() string

func (*Package) Provides

func (c *Package) Provides() []Dependency

func (*Package) RPMVersion

func (c *Package) RPMVersion() string

func (*Package) Recommends

func (c *Package) Recommends() []Dependency

func (*Package) Release

func (c *Package) Release() string

func (*Package) Requires

func (c *Package) Requires() []Dependency

func (*Package) Size

func (c *Package) Size() uint64

Size specifies the disk space consumed by installation of the package.

func (*Package) Source

func (c *Package) Source() []string

func (*Package) SourceRPM

func (c *Package) SourceRPM() string

func (*Package) String

func (c *Package) String() string

String returns the package identifier in the form '[name]-[version]-[release].[architecture]'.

func (*Package) Suggests

func (c *Package) Suggests() []Dependency

func (*Package) Summary

func (c *Package) Summary() string

func (*Package) Supplements

func (c *Package) Supplements() []Dependency

func (*Package) URL

func (c *Package) URL() string

func (*Package) Vendor

func (c *Package) Vendor() string

func (*Package) Version

func (c *Package) Version() string

func (*Package) XPMImage

func (c *Package) XPMImage() []byte

type PackageSlice added in v1.1.0

type PackageSlice []*Package

PackageSlice implements sort.Interface for a slice of packages. Packages are sorted lexically by name ascending and then by version descending. Version is evaluated first by epoch, then by version string, then by release.

func (PackageSlice) Len added in v1.1.0

func (x PackageSlice) Len() int

func (PackageSlice) Less added in v1.1.0

func (x PackageSlice) Less(i, j int) bool

func (PackageSlice) Sort added in v1.1.0

func (x PackageSlice) Sort()

Sort is a convenience method: x.Sort() calls sort.Sort(x).

func (PackageSlice) Swap added in v1.1.0

func (x PackageSlice) Swap(i, j int)

type Tag

type Tag struct {
	ID    int
	Type  TagType
	Value interface{}
}

Tag is an rpm header entry and its associated data value. Once the data type is known, use the associated value method to retrieve the tag value.

All Tag methods will return their zero value if the underlying data type is a different type or if the tag is nil.

func (*Tag) Bytes

func (c *Tag) Bytes() []byte

Bytes returns a slice of bytes or nil if the index is not a byte slice value.

Use Bytes for all CHAR, INT8 and BIN data types.

func (*Tag) Int64

func (c *Tag) Int64() int64

Int64 returns an int64 if the index is not a numerical value. All integer types are cast to int64.

Use Int64 for all INT16, INT32 and INT64 data types.

func (*Tag) Int64Slice

func (c *Tag) Int64Slice() []int64

Int64Slice returns a slice of int64s or nil if the index is not a numerical slice value. All integer types are cast to int64.

Use Int64Slice for all INT16, INT32 and INT64 data types.

func (*Tag) String

func (c *Tag) String() string

String returns a string or an empty string if the index is not a string value.

Use String for all STRING, STRING_ARRAY and I18NSTRING data types.

This is not intended to implement fmt.Stringer. To format the tag using its identifier, use Tag.ID. To format the tag's value, use Tag.Value.

func (*Tag) StringSlice

func (c *Tag) StringSlice() []string

StringSlice returns a slice of strings or nil if the index is not a string slice value.

Use StringSlice for all STRING, STRING_ARRAY and I18NSTRING data types.

type TagType

type TagType int

TagType describes the data type of a tag's value.

const (
	TagTypeNull TagType = iota
	TagTypeChar
	TagTypeInt8
	TagTypeInt16
	TagTypeInt32
	TagTypeInt64
	TagTypeString
	TagTypeBinary
	TagTypeStringArray
	TagTypeI18NString
)

func (TagType) String

func (i TagType) String() string

type Version added in v1.1.0

type Version interface {
	Epoch() int
	Version() string
	Release() string
}

Version is an interface which holds version information for a package in EVR form.

Directories

Path Synopsis
cmd
rpmdump
rpmdump displays all headers and tags in rpm packages as a YAML document.
rpmdump displays all headers and tags in rpm packages as a YAML document.
rpminfo
rpminfo displays package information, akin to rpm --info.
rpminfo displays package information, akin to rpm --info.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL