Skip to content

Instantly share code, notes, and snippets.

@cavaliercoder
Created December 3, 2015 10:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cavaliercoder/7a34bf380248bc2404c4 to your computer and use it in GitHub Desktop.
Save cavaliercoder/7a34bf380248bc2404c4 to your computer and use it in GitHub Desktop.
go-rpm - a naive rpm implementation in go
// see:
// https://github.com/rpm-software-management/rpm/tree/master/lib
// http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html
package main
import (
"bytes"
"fmt"
"math"
"os"
"time"
)
type RPMFile struct {
Metadata RPMFileMetadata
Lead RPMFileLeadSection
Headers Headers
}
type RPMFileMetadata struct {
FilePath string
}
type RPMFileLeadSection struct {
VersionMajor int
VersionMinor int
Name string
Type int
Architecture int
OperatingSystem int
SignatureType int
}
type Header struct {
Version int
IndexCount int
Length int64
Indexes IndexEntries
}
type Headers []Header
type IndexEntry struct {
Tag int64
Type int64
Offset int64
ItemCount int64
Value interface{}
}
type IndexEntries []IndexEntry
const (
IndexDataTypeNull int64 = iota
IndexDataTypeChar
IndexDataTypeInt8
IndexDataTypeInt16
IndexDataTypeInt32
IndexDataTypeInt64
IndexDataTypeString
IndexDataTypeBinary
IndexDataTypeStringArray
IndexDataTypeI8NString
)
var HeaderTagNames = map[int64]string{
1000: "RPMTAG_NAME",
1001: "RPMTAG_VERSION",
1002: "RPMTAG_RELEASE",
1003: "RPMTAG_SERIAL",
1004: "RPMTAG_SUMMARY",
1005: "RPMTAG_DESCRIPTION",
1006: "RPMTAG_BUILDTIME",
1007: "RPMTAG_BUILDHOST",
1008: "RPMTAG_INSTALLTIME",
1009: "RPMTAG_SIZE",
1010: "RPMTAG_DISTRIBUTION",
1011: "RPMTAG_VENDOR",
1012: "RPMTAG_GIF",
1013: "RPMTAG_XPM",
1014: "RPMTAG_COPYRIGHT",
1015: "RPMTAG_PACKAGER",
1016: "RPMTAG_GROUP",
1017: "RPMTAG_CHANGELOG",
1018: "RPMTAG_SOURCE",
1019: "RPMTAG_PATCH",
1020: "RPMTAG_URL",
1021: "RPMTAG_OS",
1022: "RPMTAG_ARCH",
1023: "RPMTAG_PREIN",
1024: "RPMTAG_POSTIN",
1025: "RPMTAG_PREUN",
1026: "RPMTAG_POSTUN",
1027: "RPMTAG_FILENAMES",
1028: "RPMTAG_FILESIZES",
1029: "RPMTAG_FILESTATES",
1030: "RPMTAG_FILEMODES",
1031: "RPMTAG_FILEUIDS",
1032: "RPMTAG_FILEGIDS",
1033: "RPMTAG_FILERDEVS",
1034: "RPMTAG_FILEMTIMES",
1035: "RPMTAG_FILEMD5S",
1036: "RPMTAG_FILELINKTOS",
1037: "RPMTAG_FILEFLAGS",
1038: "RPMTAG_ROOT",
1039: "RPMTAG_FILEUSERNAME",
1040: "RPMTAG_FILEGROUPNAME",
1041: "RPMTAG_EXCLUDE",
1042: "RPMTAG_EXCLUSIVE",
1043: "RPMTAG_ICON",
1044: "RPMTAG_SOURCERPM",
1045: "RPMTAG_FILEVERIFYFLAGS",
1046: "RPMTAG_ARCHIVESIZE",
1047: "RPMTAG_PROVIDES",
1048: "RPMTAG_REQUIREFLAGS",
1049: "RPMTAG_REQUIRENAME",
1050: "RPMTAG_REQUIREVERSION",
1051: "RPMTAG_NOSOURCE",
1052: "RPMTAG_NOPATCH",
1053: "RPMTAG_CONFLICTFLAGS",
1054: "RPMTAG_CONFLICTNAME",
1055: "RPMTAG_CONFLICTVERSION",
1056: "RPMTAG_DEFAULTPREFIX",
1057: "RPMTAG_BUILDROOT",
1058: "RPMTAG_INSTALLPREFIX",
1059: "RPMTAG_EXCLUDEARCH",
1060: "RPMTAG_EXCLUDEOS",
1061: "RPMTAG_EXCLUSIVEARCH",
1062: "RPMTAG_EXCLUSIVEOS",
1063: "RPMTAG_AUTOREQPROV",
1064: "RPMTAG_RPMVERSION",
1065: "RPMTAG_TRIGGERSCRIPTS",
1066: "RPMTAG_TRIGGERNAME",
1067: "RPMTAG_TRIGGERVERSION",
1068: "RPMTAG_TRIGGERFLAGS",
1069: "RPMTAG_TRIGGERINDEX",
1079: "RPMTAG_VERIFYSCRIPT",
}
func ReadRPMFile(path string) (*RPMFile, error) {
rpmfile := &RPMFile{}
// open file
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Error opening RPM file: %s", err)
}
defer f.Close()
// read the deprecated "lead"
lead := make([]byte, 96)
n, err := f.Read(lead)
if err != nil {
return nil, fmt.Errorf("Error reading RPM Lead section: %s", err)
}
if n != 96 {
return nil, fmt.Errorf("RPM Lead section is incorrect length")
}
// check magic number
if 0 != bytes.Compare(lead[:4], []byte{0xED, 0xAB, 0xEE, 0xDB}) {
return nil, fmt.Errorf("RPM file descriptor is invalid")
}
// translate lead
rpmfile.Lead.VersionMajor = int(lead[5])
rpmfile.Lead.VersionMinor = int(lead[6])
rpmfile.Lead.Type = (int(lead[7]) << 8) + int(lead[8])
rpmfile.Lead.Architecture = (int(lead[9]) << 8) + int(lead[10])
rpmfile.Lead.Name = string(lead[10:77])
rpmfile.Lead.OperatingSystem = (int(lead[76]) << 8) + int(lead[77])
rpmfile.Lead.SignatureType = (int(lead[78]) << 8) + int(lead[79])
// TODO: validate lead value ranges
// parse headers
rpmfile.Headers = make(Headers, 0)
// TODO: find last header without using hard limit of 2
for i := 1; i < 3; i++ {
// read the "header structure header"
header := make([]byte, 16)
n, err = f.Read(header)
if err != nil {
return nil, fmt.Errorf("Error reading RPM structure header for header %d: %s", err, i)
}
if n != 16 {
return nil, fmt.Errorf("Error reading RPM structure header for header %d: only %d bytes returned", i, n)
}
// check magic number
if 0 != bytes.Compare(header[:3], []byte{0x8E, 0xAD, 0xE8}) {
return nil, fmt.Errorf("RPM header %d is invalid", i)
}
// translate header
h := Header{}
h.Version = int(header[3])
h.IndexCount = (int(header[8]) << 24) + (int(header[9]) << 16) + (int(header[10]) << 8) + int(header[11])
h.Length = (int64(header[12]) << 24) + (int64(header[13]) << 16) + (int64(header[14]) << 8) + int64(header[15])
h.Indexes = make(IndexEntries, h.IndexCount)
// read indexes
indexLength := 16 * h.IndexCount
indexes := make([]byte, indexLength)
n, err = f.Read(indexes)
if err != nil {
return nil, fmt.Errorf("Error reading index entries for header %d: %s", i, err)
}
if n != indexLength {
return nil, fmt.Errorf("Error reading index entries for header %d: only %d bytes returned", i, n)
}
for x := 0; x < h.IndexCount; x++ {
o := 16 * x
index := IndexEntry{}
index.Tag = (int64(indexes[o]) << 24) + (int64(indexes[o+1]) << 16) + (int64(indexes[o+2]) << 8) + int64(indexes[o+3])
index.Type = (int64(indexes[o+4]) << 24) + (int64(indexes[o+5]) << 16) + (int64(indexes[o+6]) << 8) + int64(indexes[o+7])
index.Offset = (int64(indexes[o+8]) << 24) + (int64(indexes[o+9]) << 16) + (int64(indexes[o+10]) << 8) + int64(indexes[o+11])
index.ItemCount = (int64(indexes[o+12]) << 24) + (int64(indexes[o+13]) << 16) + (int64(indexes[o+14]) << 8) + int64(indexes[o+15])
h.Indexes[x] = index
}
// read the "store"
store := make([]byte, h.Length)
n, err = f.Read(store)
if err != nil {
return nil, fmt.Errorf("Error reading store for header %d: %s", i, err)
}
if int64(n) != h.Length {
return nil, fmt.Errorf("Error reading store for header %d: only %d bytes returned", i, n)
}
for x := 0; x < h.IndexCount; x++ {
index := h.Indexes[x]
switch index.Type {
case IndexDataTypeChar:
index.Value = uint8(store[index.Offset])
break
case IndexDataTypeInt8:
index.Value = int8(store[index.Offset])
break
case IndexDataTypeInt16:
index.Value = (int16(store[index.Offset]) << 8) + int16(store[index.Offset+1])
break
case IndexDataTypeInt32:
index.Value = (int32(store[index.Offset]) << 24) + (int32(store[index.Offset+1]) << 16) + (int32(store[index.Offset+2]) << 8) + int32(store[index.Offset+3])
break
case IndexDataTypeInt64:
index.Value = (int64(store[index.Offset]) << 56) + (int64(store[index.Offset+1]) << 48) + (int64(store[index.Offset+2]) << 40) + (int64(store[index.Offset+3]) << 32) + (int64(store[index.Offset+4]) << 24) + (int64(store[index.Offset+5]) << 16) + (int64(store[index.Offset+6]) << 8) + int64(store[index.Offset+7])
break
case IndexDataTypeString:
// calculate string length
var j int64
for j = 0; store[int64(j)+index.Offset] != 0; j++ {
}
index.Value = string(store[index.Offset : index.Offset+int64(j)])
break
case IndexDataTypeBinary:
b := make([]byte, index.ItemCount)
copy(b, store[index.Offset:index.Offset+index.ItemCount])
index.Value = b
break
case IndexDataTypeStringArray, IndexDataTypeI8NString:
vs := make([]string, index.ItemCount)
o := index.Offset
for s := 0; int64(s) < index.ItemCount; s++ {
// calculate string length
var j int64
for j = 0; store[int64(j)+o] != 0; j++ {
}
vs[s] = string(store[o : o+j])
o += j + 1
}
index.Value = vs
break
}
// save in array
h.Indexes[x] = index
}
// add header
rpmfile.Headers = append(rpmfile.Headers, h)
// calculate location of next header by padding to a multiple of 8
o := 8 - int64(math.Mod(float64(h.Length), 8))
// seek to next header
_, err := f.Seek(o, 1)
if err != nil {
return nil, err
}
}
return rpmfile, nil
}
func (c IndexEntries) Get(tag int64) *IndexEntry {
for _, e := range c {
if e.Tag == tag {
return &e
}
}
return nil
}
func (c *RPMFile) String() string {
return fmt.Sprintf("%v", c.BuildTime())
}
func (c *RPMFile) Name() string {
return c.Headers[1].Indexes.Get(1000).Value.(string)
}
func (c *RPMFile) Version() string {
return c.Headers[1].Indexes.Get(1001).Value.(string)
}
func (c *RPMFile) BuildTime() time.Time {
return time.Unix(int64(c.Headers[1].Indexes.Get(1006).Value.(int32)), 0)
}
package main
import (
"io/ioutil"
"path/filepath"
"testing"
)
func TestReadRPMFile(t *testing.T) {
dir := "/var/www/html/pub/centos/7/updates/x86_64/"
// list RPM files
files, err := ioutil.ReadDir(dir)
if err != nil {
t.Fatalf(err.Error())
}
valid := 0
for _, f := range files {
path := filepath.Join(dir, f.Name())
rpmfile, err := ReadRPMFile(path)
if err != nil {
t.Errorf("Error loading RPM file %s: %s", f.Name(), err)
} else {
valid++
t.Logf("Validated package: %s", rpmfile)
}
}
if valid == 0 {
t.Errorf("No RPM files found for validation in %s", dir)
} else {
t.Logf("Validated %d RPM files", valid)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment