iperf3exporter/vendor/github.com/google/rpmpack/rpm.go
Marvin Preuss 2343c9588a
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing
first commit
2021-10-20 10:08:56 +02:00

480 lines
14 KiB
Go

// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package rpmpack packs files to rpm files.
// It is designed to be simple to use and deploy, not requiring any filesystem access
// to create rpm files.
package rpmpack
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"path"
"sort"
"time"
cpio "github.com/cavaliercoder/go-cpio"
"github.com/pkg/errors"
"github.com/ulikunitz/xz"
"github.com/ulikunitz/xz/lzma"
)
var (
// ErrWriteAfterClose is returned when a user calls Write() on a closed rpm.
ErrWriteAfterClose = errors.New("rpm write after close")
// ErrWrongFileOrder is returned when files are not sorted by name.
ErrWrongFileOrder = errors.New("wrong file addition order")
)
// RPMMetaData contains meta info about the whole package.
type RPMMetaData struct {
Name,
Summary,
Description,
Version,
Release,
Arch,
OS,
Vendor,
URL,
Packager,
Group,
Licence,
BuildHost,
Compressor string
Epoch uint32
BuildTime time.Time
Provides,
Obsoletes,
Suggests,
Recommends,
Requires,
Conflicts Relations
}
// RPM holds the state of a particular rpm file. Please use NewRPM to instantiate it.
type RPM struct {
RPMMetaData
di *dirIndex
payload *bytes.Buffer
payloadSize uint
cpio *cpio.Writer
basenames []string
dirindexes []uint32
filesizes []uint32
filemodes []uint16
fileowners []string
filegroups []string
filemtimes []uint32
filedigests []string
filelinktos []string
fileflags []uint32
closed bool
compressedPayload io.WriteCloser
files map[string]RPMFile
prein string
postin string
preun string
postun string
pretrans string
posttrans string
customTags map[int]IndexEntry
customSigs map[int]IndexEntry
pgpSigner func([]byte) ([]byte, error)
}
// NewRPM creates and returns a new RPM struct.
func NewRPM(m RPMMetaData) (*RPM, error) {
var err error
if m.OS == "" {
m.OS = "linux"
}
if m.Arch == "" {
m.Arch = "noarch"
}
p := &bytes.Buffer{}
var z io.WriteCloser
switch m.Compressor {
case "":
m.Compressor = "gzip"
fallthrough
case "gzip":
z, err = gzip.NewWriterLevel(p, 9)
case "lzma":
z, err = lzma.NewWriter(p)
case "xz":
z, err = xz.NewWriter(p)
default:
err = fmt.Errorf("unknown compressor type %s", m.Compressor)
}
if err != nil {
return nil, errors.Wrap(err, "failed to create compression writer")
}
rpm := &RPM{
RPMMetaData: m,
di: newDirIndex(),
payload: p,
compressedPayload: z,
cpio: cpio.NewWriter(z),
files: make(map[string]RPMFile),
customTags: make(map[int]IndexEntry),
customSigs: make(map[int]IndexEntry),
}
// A package must provide itself...
rpm.Provides.addIfMissing(&Relation{
Name: rpm.Name,
Version: rpm.FullVersion(),
Sense: SenseEqual,
})
return rpm, nil
}
// FullVersion properly combines version and release fields to a version string
func (r *RPM) FullVersion() string {
if r.Release != "" {
return fmt.Sprintf("%s-%s", r.Version, r.Release)
}
return r.Version
}
// Write closes the rpm and writes the whole rpm to an io.Writer
func (r *RPM) Write(w io.Writer) error {
if r.closed {
return ErrWriteAfterClose
}
// Add all of the files, sorted alphabetically.
fnames := []string{}
for fn := range r.files {
fnames = append(fnames, fn)
}
sort.Strings(fnames)
for _, fn := range fnames {
if err := r.writeFile(r.files[fn]); err != nil {
return errors.Wrapf(err, "failed to write file %q", fn)
}
}
if err := r.cpio.Close(); err != nil {
return errors.Wrap(err, "failed to close cpio payload")
}
if err := r.compressedPayload.Close(); err != nil {
return errors.Wrap(err, "failed to close gzip payload")
}
if _, err := w.Write(lead(r.Name, r.FullVersion())); err != nil {
return errors.Wrap(err, "failed to write lead")
}
// Write the regular header.
h := newIndex(immutable)
r.writeGenIndexes(h)
// do not write file indexes if there are no files (meta package)
// doing so will result in an invalid package
if (len(r.files)) > 0 {
r.writeFileIndexes(h)
}
if err := r.writeRelationIndexes(h); err != nil {
return err
}
// CustomTags must be the last to be added, because they can overwrite values.
h.AddEntries(r.customTags)
hb, err := h.Bytes()
if err != nil {
return errors.Wrap(err, "failed to retrieve header")
}
// Write the signatures
s := newIndex(signatures)
if err := r.writeSignatures(s, hb); err != nil {
return errors.Wrap(err, "failed to create signatures")
}
s.AddEntries(r.customSigs)
sb, err := s.Bytes()
if err != nil {
return errors.Wrap(err, "failed to retrieve signatures header")
}
if _, err := w.Write(sb); err != nil {
return errors.Wrap(err, "failed to write signature bytes")
}
//Signatures are padded to 8-byte boundaries
if _, err := w.Write(make([]byte, (8-len(sb)%8)%8)); err != nil {
return errors.Wrap(err, "failed to write signature padding")
}
if _, err := w.Write(hb); err != nil {
return errors.Wrap(err, "failed to write header body")
}
_, err = w.Write(r.payload.Bytes())
return errors.Wrap(err, "failed to write payload")
}
// SetPGPSigner registers a function that will accept the header and payload as bytes,
// and return a signature as bytes. The function should simulate what gpg does,
// probably by using golang.org/x/crypto/openpgp or by forking a gpg process.
func (r *RPM) SetPGPSigner(f func([]byte) ([]byte, error)) {
r.pgpSigner = f
}
// Only call this after the payload and header were written.
func (r *RPM) writeSignatures(sigHeader *index, regHeader []byte) error {
sigHeader.Add(sigSize, EntryInt32([]int32{int32(r.payload.Len() + len(regHeader))}))
sigHeader.Add(sigSHA256, EntryString(fmt.Sprintf("%x", sha256.Sum256(regHeader))))
sigHeader.Add(sigPayloadSize, EntryInt32([]int32{int32(r.payloadSize)}))
if r.pgpSigner != nil {
// For sha 256 you need to sign the header and payload separately
header := append([]byte{}, regHeader...)
headerSig, err := r.pgpSigner(header)
if err != nil {
return errors.Wrap(err, "call to signer failed")
}
sigHeader.Add(sigRSA, EntryBytes(headerSig))
body := append(header, r.payload.Bytes()...)
bodySig, err := r.pgpSigner(body)
if err != nil {
return errors.Wrap(err, "call to signer failed")
}
sigHeader.Add(sigPGP, EntryBytes(bodySig))
}
return nil
}
func (r *RPM) writeRelationIndexes(h *index) error {
// add all relation categories
if err := r.Provides.AddToIndex(h, tagProvides, tagProvideVersion, tagProvideFlags); err != nil {
return errors.Wrap(err, "failed to add provides")
}
if err := r.Obsoletes.AddToIndex(h, tagObsoletes, tagObsoleteVersion, tagObsoleteFlags); err != nil {
return errors.Wrap(err, "failed to add obsoletes")
}
if err := r.Suggests.AddToIndex(h, tagSuggests, tagSuggestVersion, tagSuggestFlags); err != nil {
return errors.Wrap(err, "failed to add suggests")
}
if err := r.Recommends.AddToIndex(h, tagRecommends, tagRecommendVersion, tagRecommendFlags); err != nil {
return errors.Wrap(err, "failed to add recommends")
}
if err := r.Requires.AddToIndex(h, tagRequires, tagRequireVersion, tagRequireFlags); err != nil {
return errors.Wrap(err, "failed to add requires")
}
if err := r.Conflicts.AddToIndex(h, tagConflicts, tagConflictVersion, tagConflictFlags); err != nil {
return errors.Wrap(err, "failed to add conflicts")
}
return nil
}
// AddCustomTag adds or overwrites a tag value in the index.
func (r *RPM) AddCustomTag(tag int, e IndexEntry) {
r.customTags[tag] = e
}
// AddCustomSig adds or overwrites a signature tag value.
func (r *RPM) AddCustomSig(tag int, e IndexEntry) {
r.customSigs[tag] = e
}
func (r *RPM) writeGenIndexes(h *index) {
h.Add(tagHeaderI18NTable, EntryString("C"))
h.Add(tagSize, EntryInt32([]int32{int32(r.payloadSize)}))
h.Add(tagName, EntryString(r.Name))
h.Add(tagVersion, EntryString(r.Version))
h.Add(tagEpoch, EntryUint32([]uint32{r.Epoch}))
h.Add(tagSummary, EntryString(r.Summary))
h.Add(tagDescription, EntryString(r.Description))
h.Add(tagBuildHost, EntryString(r.BuildHost))
if !r.BuildTime.IsZero() {
// time.Time zero value is confusing, avoid if not supplied
// see https://github.com/google/rpmpack/issues/43
h.Add(tagBuildTime, EntryInt32([]int32{int32(r.BuildTime.Unix())}))
}
h.Add(tagRelease, EntryString(r.Release))
h.Add(tagPayloadFormat, EntryString("cpio"))
h.Add(tagPayloadCompressor, EntryString(r.Compressor))
h.Add(tagPayloadFlags, EntryString("9"))
h.Add(tagArch, EntryString(r.Arch))
h.Add(tagOS, EntryString(r.OS))
h.Add(tagVendor, EntryString(r.Vendor))
h.Add(tagLicence, EntryString(r.Licence))
h.Add(tagPackager, EntryString(r.Packager))
h.Add(tagGroup, EntryString(r.Group))
h.Add(tagURL, EntryString(r.URL))
h.Add(tagPayloadDigest, EntryStringSlice([]string{fmt.Sprintf("%x", sha256.Sum256(r.payload.Bytes()))}))
h.Add(tagPayloadDigestAlgo, EntryInt32([]int32{hashAlgoSHA256}))
// rpm utilities look for the sourcerpm tag to deduce if this is not a source rpm (if it has a sourcerpm,
// it is NOT a source rpm).
h.Add(tagSourceRPM, EntryString(fmt.Sprintf("%s-%s.src.rpm", r.Name, r.FullVersion())))
if r.pretrans != "" {
h.Add(tagPretrans, EntryString(r.pretrans))
h.Add(tagPretransProg, EntryString("/bin/sh"))
}
if r.prein != "" {
h.Add(tagPrein, EntryString(r.prein))
h.Add(tagPreinProg, EntryString("/bin/sh"))
}
if r.postin != "" {
h.Add(tagPostin, EntryString(r.postin))
h.Add(tagPostinProg, EntryString("/bin/sh"))
}
if r.preun != "" {
h.Add(tagPreun, EntryString(r.preun))
h.Add(tagPreunProg, EntryString("/bin/sh"))
}
if r.postun != "" {
h.Add(tagPostun, EntryString(r.postun))
h.Add(tagPostunProg, EntryString("/bin/sh"))
}
if r.posttrans != "" {
h.Add(tagPosttrans, EntryString(r.posttrans))
h.Add(tagPosttransProg, EntryString("/bin/sh"))
}
}
// WriteFileIndexes writes file related index headers to the header
func (r *RPM) writeFileIndexes(h *index) {
h.Add(tagBasenames, EntryStringSlice(r.basenames))
h.Add(tagDirindexes, EntryUint32(r.dirindexes))
h.Add(tagDirnames, EntryStringSlice(r.di.AllDirs()))
h.Add(tagFileSizes, EntryUint32(r.filesizes))
h.Add(tagFileModes, EntryUint16(r.filemodes))
h.Add(tagFileUserName, EntryStringSlice(r.fileowners))
h.Add(tagFileGroupName, EntryStringSlice(r.filegroups))
h.Add(tagFileMTimes, EntryUint32(r.filemtimes))
h.Add(tagFileDigests, EntryStringSlice(r.filedigests))
h.Add(tagFileLinkTos, EntryStringSlice(r.filelinktos))
h.Add(tagFileFlags, EntryUint32(r.fileflags))
inodes := make([]int32, len(r.dirindexes))
digestAlgo := make([]int32, len(r.dirindexes))
verifyFlags := make([]int32, len(r.dirindexes))
fileRDevs := make([]int16, len(r.dirindexes))
fileLangs := make([]string, len(r.dirindexes))
for ii := range inodes {
// is inodes just a range from 1..len(dirindexes)? maybe different with hard links
inodes[ii] = int32(ii + 1)
digestAlgo[ii] = hashAlgoSHA256
// With regular files, it seems like we can always enable all of the verify flags
verifyFlags[ii] = int32(-1)
fileRDevs[ii] = int16(1)
}
h.Add(tagFileINodes, EntryInt32(inodes))
h.Add(tagFileDigestAlgo, EntryInt32(digestAlgo))
h.Add(tagFileVerifyFlags, EntryInt32(verifyFlags))
h.Add(tagFileRDevs, EntryInt16(fileRDevs))
h.Add(tagFileLangs, EntryStringSlice(fileLangs))
}
// AddPretrans adds a pretrans scriptlet
func (r *RPM) AddPretrans(s string) {
r.pretrans = s
}
// AddPrein adds a prein scriptlet
func (r *RPM) AddPrein(s string) {
r.prein = s
}
// AddPostin adds a postin scriptlet
func (r *RPM) AddPostin(s string) {
r.postin = s
}
// AddPreun adds a preun scriptlet
func (r *RPM) AddPreun(s string) {
r.preun = s
}
// AddPostun adds a postun scriptlet
func (r *RPM) AddPostun(s string) {
r.postun = s
}
// AddPosttrans adds a posttrans scriptlet
func (r *RPM) AddPosttrans(s string) {
r.posttrans = s
}
// AddFile adds an RPMFile to an existing rpm.
func (r *RPM) AddFile(f RPMFile) {
if f.Name == "/" { // rpm does not allow the root dir to be included.
return
}
r.files[f.Name] = f
}
// writeFile writes the file to the indexes and cpio.
func (r *RPM) writeFile(f RPMFile) error {
dir, file := path.Split(f.Name)
r.dirindexes = append(r.dirindexes, r.di.Get(dir))
r.basenames = append(r.basenames, file)
r.fileowners = append(r.fileowners, f.Owner)
r.filegroups = append(r.filegroups, f.Group)
r.filemtimes = append(r.filemtimes, f.MTime)
r.fileflags = append(r.fileflags, uint32(f.Type))
links := 1
switch {
case f.Mode&040000 != 0: // directory
r.filesizes = append(r.filesizes, 4096)
r.filedigests = append(r.filedigests, "")
r.filelinktos = append(r.filelinktos, "")
links = 2
case f.Mode&0120000 == 0120000: // symlink
r.filesizes = append(r.filesizes, uint32(len(f.Body)))
r.filedigests = append(r.filedigests, "")
r.filelinktos = append(r.filelinktos, string(f.Body))
default: // regular file
f.Mode = f.Mode | 0100000
r.filesizes = append(r.filesizes, uint32(len(f.Body)))
r.filedigests = append(r.filedigests, fmt.Sprintf("%x", sha256.Sum256(f.Body)))
r.filelinktos = append(r.filelinktos, "")
}
r.filemodes = append(r.filemodes, uint16(f.Mode))
// Ghost files have no payload
if f.Type == GhostFile {
return nil
}
return r.writePayload(f, links)
}
func (r *RPM) writePayload(f RPMFile, links int) error {
hdr := &cpio.Header{
Name: f.Name,
Mode: cpio.FileMode(f.Mode),
Size: int64(len(f.Body)),
Links: links,
}
if err := r.cpio.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write payload file header")
}
if _, err := r.cpio.Write(f.Body); err != nil {
return errors.Wrap(err, "failed to write payload file content")
}
r.payloadSize += uint(len(f.Body))
return nil
}