binary rpm package checking
Florian La Roche
laroche at redhat.com
Sat Jan 29 13:49:15 UTC 2005
This is a start to check binary rpm packages for consistency.
Right now mostly the rpm header is checked to get a feeling
how much "strange" binary rpm packages might be out there.
It has two modes of checking, one for the current Fedora Development
tree with more strict checks and a more relaxed one that should
work for all existing rpm packages, also other distributions.
I'd be interested to get feedback on what output is generated
for rpm addon expositories and non - Red Hat distributions
if the script generates warning messages.
At least for Fedora Core only very few rpm tags are actually
used in the rpm header.
Examples usage:
./pyrpm.py --strict /mirror/fedora/development/i386/Fedora/RPMS/*.rpm
Checking all rpms:
locate .rpm | xargs ./pyrpm.py
find /mirror/linux -name "*.rpm" -type f -print0 2>/dev/null |
xargs -0 ./pyrpm.py
greetings,
Florian La Roche
-------------- next part --------------
#!/usr/bin/python
#!/usr/bin/python2.2
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2004 Red Hat, Inc.
#
# Author: Paul Nasrat, Florian La Roche
#
import rpmconstants, cpio, os.path
import sys, getopt, gzip, cStringIO
from types import StringType, IntType, ListType
from struct import unpack
rpmtag = rpmconstants.rpmtag
rpmsigtag = rpmconstants.rpmsigtag
RPM_CHAR = rpmconstants.RPM_CHAR
RPM_INT8 = rpmconstants.RPM_INT8
RPM_INT16 = rpmconstants.RPM_INT16
RPM_INT32 = rpmconstants.RPM_INT32
RPM_INT64 = rpmconstants.RPM_INT64
RPM_STRING = rpmconstants.RPM_STRING
RPM_BIN = rpmconstants.RPM_BIN
RPM_STRING_ARRAY = rpmconstants.RPM_STRING_ARRAY
RPM_I18NSTRING = rpmconstants.RPM_I18NSTRING
RPM_ARGSTRING = rpmconstants.RPM_ARGSTRING
# limit: does not support all RHL5.x and earlier rpms if verify is enabled
class ReadRpm:
def __init__(self, filename, verify=None, fd=None, hdronly=None, legacy=1):
self.filename = filename
self.issrc = 0
if filename[-8:] == ".src.rpm" or filename[-10:] == ".nosrc.rpm":
self.issrc = 1
self.verify = verify # enable/disable more data checking
self.fd = fd # filedescriptor
self.hdronly = hdronly # if only the header is present from a hdlist
# 1 == check if legacy tags are included, 0 allows more old tags
# 1 is good for Fedora Core development trees
self.legacy = legacy
def printErr(self, err):
print "%s: %s" % (self.filename, err)
def raiseErr(self, err):
raise ValueError, "%s: %s" % (self.filename, err)
def openFd(self, offset=None):
if not self.fd:
try:
self.fd = open(self.filename, "ro")
except:
self.printErr("could not open file")
return 1
if offset:
self.fd.seek(offset, 1)
return None
def closeFd(self):
self.fd = None
def __repr__(self):
return self.hdr.__repr__()
def __getitem__(self, key):
try:
if isinstance(key, StringType):
return self.hdr[rpmtag[key][0]]
if isinstance(key, IntType):
return self.hdr[key]
# trick to also look at the sig header
if isinstance(key, ListType):
if isinstance(key[0], StringType):
return self.sig[rpmsigtag[key[0]][0]]
return self.sig[key[0]]
self.raiseErr("wrong arg")
except:
# XXX: try to catch wrong/misspelled keys here?
return None
def parseLead(self, leaddata):
(magic, major, minor, rpmtype, arch, name, osnum, sigtype) = \
unpack("!4scchh66shh16x", leaddata)
failed = None
if self.verify:
if major not in ('\x03', '\x04') or minor != '\x00' or \
sigtype != 5 or rpmtype not in (0, 1):
failed = 1
if osnum not in (1, 255, 256):
failed = 1
name = name.rstrip('\x00')
if self.legacy:
if os.path.basename(self.filename)[:len(name)] != name:
failed = 1
if failed:
print major, minor, rpmtype, arch, name, osnum, sigtype
self.raiseErr("wrong data in rpm lead")
return (magic, major, minor, rpmtype, arch, name, osnum, sigtype)
def verifyTag(self, index, fmt, issig):
(tag, ttype, offset, count) = index
if issig:
if not rpmsigtag.has_key(tag):
self.printErr("rpmsigtag has no tag %d" % tag)
else:
t = rpmsigtag[tag]
if t[1] != None and t[1] != ttype:
self.printErr("sigtag %d has wrong type %d" % (tag, ttype))
if t[2] != None and t[2] != count:
self.printErr("sigtag %d has wrong count %d" % (tag, count))
if (t[3] & 1) and self.legacy:
self.printErr("tag %d is marked legacy" % tag)
if self.issrc:
if (t[3] & 4):
self.printErr("tag %d should be for binary rpms" % tag)
else:
if (t[3] & 2):
self.printErr("tag %d should be for src rpms" % tag)
else:
if not rpmtag.has_key(tag):
self.printErr("rpmtag has no tag %d" % tag)
else:
t = rpmtag[tag]
if t[1] != None and t[1] != ttype:
if t[1] == RPM_ARGSTRING and (ttype == RPM_STRING or \
ttype == RPM_STRING_ARRAY):
pass # special exception case
elif t[0] == rpmconstants.RPMTAG_GROUP and \
ttype == RPM_STRING: # XXX hardcoded exception
pass
else:
self.printErr("tag %d has wrong type %d" % (tag, ttype))
if t[2] != None and t[2] != count:
self.printErr("tag %d has wrong count %d" % (tag, count))
if (t[3] & 1) and self.legacy:
self.printErr("tag %d is marked legacy" % tag)
if self.issrc:
if (t[3] & 4):
self.printErr("tag %d should be for binary rpms" % tag)
else:
if (t[3] & 2):
self.printErr("tag %d should be for src rpms" % tag)
if count == 0:
self.raiseErr("zero length tag")
if ttype < 1 or ttype > 9:
self.raiseErr("unknown rpmtype %d" % ttype)
if ttype == RPM_INT32:
count = count * 4
elif ttype == RPM_STRING_ARRAY or \
ttype == RPM_I18NSTRING:
size = 0
for i in xrange(0, count):
end = fmt.index('\x00', offset) + 1
size += end - offset
offset = end
count = size
elif ttype == RPM_STRING:
if count != 1:
self.raiseErr("tag string count wrong")
count = fmt.index('\x00', offset) - offset + 1
elif ttype == RPM_CHAR or ttype == RPM_INT8:
pass
elif ttype == RPM_INT16:
count = count * 2
elif ttype == RPM_INT64:
count = count * 8
elif ttype == RPM_BIN:
pass
else:
self.raiseErr("unknown tag header")
return count
def verifyIndex(self, fmt, fmt2, indexNo, storeSize, issig):
checkSize = 0
for i in xrange(0, indexNo * 16, 16):
index = unpack("!iiii", fmt[i:i + 16])
ttype = index[1]
# alignment for some types of data
if ttype == RPM_INT16:
checkSize += (2 - (checkSize % 2)) % 2
elif ttype == RPM_INT32:
checkSize += (4 - (checkSize % 4)) % 4
elif ttype == RPM_INT64:
checkSize += (8 - (checkSize % 8)) % 8
checkSize += self.verifyTag(index, fmt2, issig)
if checkSize != storeSize:
# XXX: add a check for very old rpm versions here, seems this
# is triggered for a few RHL5.x rpm packages
self.printErr("storeSize/checkSize is %d/%d" % (storeSize,
checkSize))
def readIndex(self, pad, issig=None):
data = self.fd.read(16)
if not len(data):
return None
(magic, indexNo, storeSize) = unpack("!8sii", data)
if magic != "\x8e\xad\xe8\x01\x00\x00\x00\x00" or indexNo < 1:
self.raiseErr("bad index magic")
fmt = self.fd.read(16 * indexNo)
fmt2 = self.fd.read(storeSize)
padfmt = ""
if pad != 1:
padfmt = self.fd.read((pad - (storeSize % pad)) % pad)
if self.verify:
self.verifyIndex(fmt, fmt2, indexNo, storeSize, issig)
return (indexNo, storeSize, data, fmt, fmt2, 16 + len(fmt) + \
len(fmt2) + len(padfmt))
def parseTag(self, index, fmt):
(tag, ttype, offset, count) = index
if ttype == RPM_INT32:
return unpack("!%dI" % count, fmt[offset:offset + count * 4])
elif ttype == RPM_STRING_ARRAY or ttype == RPM_I18NSTRING:
data = []
for i in xrange(0, count):
end = fmt.index('\x00', offset)
data.append(fmt[offset:end])
offset = end + 1
return data
elif ttype == RPM_STRING:
return fmt[offset:fmt.index('\x00', offset)]
elif ttype == RPM_CHAR:
return unpack("!%dc" % count, fmt[offset:offset + count])
elif ttype == RPM_INT8:
return unpack("!%dB" % count, fmt[offset:offset + count])
elif ttype == RPM_INT16:
return unpack("!%dH" % count, fmt[offset:offset + count * 2])
elif ttype == RPM_INT64:
return unpack("!%dQ" % count, fmt[offset:offset + count * 8])
elif ttype == RPM_BIN:
return fmt[offset:offset + count]
self.raiseErr("unknown tag header")
return None
def parseIndex(self, indexNo, fmt, fmt2, tags=None):
# XXX parseIndex() should be implemented as C function for faster speed
hdr = {}
hdrtype = {}
for i in xrange(0, indexNo * 16, 16):
index = unpack("!4i", fmt[i:i + 16])
tag = index[0]
# support reading only some tags
if tags and tag not in tags:
continue
# ignore duplicate entries as long as they are identical
if hdr.has_key(tag):
if hdr[tag] != self.parseTag(index, fmt2):
self.printErr("tag %d included twice" % tag)
else:
hdr[tag] = self.parseTag(index, fmt2)
hdrtype[tag] = index[1]
return (hdr, hdrtype)
def verifyHeader(self):
if self.hdronly:
return
#self.cpiosize = self[["payloadsize"]][0]
# header + payload size
self.payloadsize = self[["size_in_sig"]][0] - self.hdrdata[5]
identifysig = self[["header_signatures"]]
sha1 = self[["sha1header"]] # header
md5sum = self[["md5"]] # header + payload
dsa = self[["dsaheader"]] # header
gpg = self[["gpg"]] # header + payload
def parseHeader(self, tags=None, parsesig=None):
if (self.verify or parsesig) and not self.hdronly:
(sigindexNo, sigstoreSize, sigdata, sigfmt, sigfmt2, size) = \
self.sigdata
(self.sig, self.sigtype) = self.parseIndex(sigindexNo, sigfmt, \
sigfmt2)
if self.verify:
for i in rpmconstants.rpmsigtagrequired:
if not self.sig.has_key(i):
self.printErr("sig header is missing: %d" % i)
(hdrindexNo, hdrstoreSize, hdrdata, hdrfmt, hdrfmt2, size) = \
self.hdrdata
(self.hdr, self.hdrtype) = self.parseIndex(hdrindexNo, hdrfmt, \
hdrfmt2, tags)
if self.verify:
for i in rpmconstants.rpmtagrequired:
if not self.hdr.has_key(i):
self.printErr("hdr is missing: %d" % i)
self.verifyHeader()
def readHeader(self, parse=1, tags=None, keepdata=None):
if self.openFd():
return 1
leaddata = self.fd.read(96)
if leaddata[:4] != '\xed\xab\xee\xdb':
self.printErr("no rpm magic found")
return 1
if self.verify:
self.parseLead(leaddata)
self.sigdata = self.readIndex(8, 1)
self.hdrdata = self.readIndex(1)
if keepdata:
self.leaddata = leaddata
if parse:
self.parseHeader(tags)
return None
def readHdlist(self, parse=1, tags=None):
self.hdrdata = self.readIndex(1)
if not self.hdrdata:
return None
if parse:
self.parseHeader(tags)
return 1
def readPayload(self, keepdata=None, verbose=None):
self.openFd(96 + self.sigdata[5] + self.hdrdata[5])
if None:
#import zlib
payload = self.fd.read()
if self.verify and self.payloadsize != len(payload):
self.raiseErr("payloadsize")
if payload[:9] != '\037\213\010\000\000\000\000\000\000':
self.raiseErr("not gzipped data")
#cpiodata = zlib.decompress(payload)
return None
else:
gz = gzip.GzipFile(fileobj=self.fd)
cpiodata = gz.read()
#while 1:
# buf = gz.read(4096)
# if not buf:
# break
if self.verify and self.cpiosize != len(cpiodata):
self.raiseErr("cpiosize")
if 1:
c = cpio.CPIOFile(cStringIO.StringIO(cpiodata))
try:
c.read()
except IOError, e:
print "Error reading CPIO payload: %s" % e
if verbose:
print c.namelist()
self.closeFd()
if keepdata:
self.cpiodata = cpiodata
if self.verify:
return self.verifyPayload(c.namelist())
return None
def verifyPayload(self, cpiofiletree=None):
hdrfiletree = self.parseFilelist()
if cpiofiletree == None:
return 0
for filename in cpiofiletree.keys():
cpiostat = cpiofiletree[filename]
if filename not in hdrfiletree.keys():
print "Error "+filename+" not in header tags"
return 1
hdrstat = hdrfiletree[filename]
if cpiostat[1] != hdrstat[1]:
print "Error inode is different for file "+filename
print cpiostat[1]+" != "+hdrstat[1]
return 1
if cpiostat[2] != hdrstat[2]:
print "Error mode is different for file "+filename
print cpiostat[2]+" != "+hdrstat[2]
return 1
# XXX: Need to convert hdr username and groupname to uid and gid
# if cpiostat[3] != hdrstat[3]:
# print "Error uid is different for file "+filename
# print cpiostat[3]+" != "+hdrstat[3]
# return 1
# if cpiostat[4] != hdrstat[4]:
# print "Error gid is different for file "+filename
# print cpiostat[4]+" != "+hdrstat[4]
# return 1
# XXX: Leave that alone. Nlink is for hardlinks, not in rpm headers...
# if hdrstat[5] != "" and cpiostat[5] != hdrstat[5]:
# print "Error nlinks is different for file "+filename
# print str(cpiostat[5])+" != "+hdrstat[5]
# return 1
if cpiostat[6] != hdrstat[6]:
print "Error filesize is different for file "+filename
print cpiostat[6]+" != "+hdrstat[6]
return 1
# XXX: Starting from entry 7 no entries are usable anymore, so leave them...
return 0
def getScript(self, s, p):
script = self[s]
# prog can be a string or an string_array (with args to the app)
prog = self[p]
if script == None and prog == None:
return (None, None)
if self.verify:
if script and prog == None:
self.raiseErr("no prog")
if self.legacy:
if prog not in ("/bin/sh", "/sbin/ldconfig", "/usr/bin/fc-cache",
"/usr/sbin/glibc_post_upgrade", "/usr/sbin/libgcc_post_upgrade",
"/usr/sbin/glibc_post_upgrade.i386",
"/usr/sbin/glibc_post_upgrade.i686",
"/usr/sbin/build-locale-archive",
"/usr/bin/scrollkeeper-update"):
self.raiseErr("unknown prog: %s" % prog)
return (script, prog)
def getNVR(self):
return "%s-%s-%s" % (self["name"], self["version"], self["release"])
def getNA(self):
return "%s.%s" % (self["name"], self["arch"])
def getFilename(self):
return "%s-%s-%s.%s.rpm" % (self["name"], self["version"],
self["release"], self["arch"])
def getDeps(self, name, flags, version):
n = self[name]
if not n:
return None
f = self[flags]
v = self[version]
if f == None or v == None or len(n) != len(f) or len(f) != len(v):
if f != None or v != None:
self.raiseErr("wrong length of deps")
deps = []
for i in xrange(0, len(n)):
if f != None:
deps.append( (n[i], f[i], v[i]) )
else:
deps.append( (n[i], None, None) )
return deps
def getProvides(self):
return self.getDeps("providename", "provideflags", "provideversion")
def getRequires(self):
return self.getDeps("requirename", "requireflags", "requireversion")
def getObsoletes(self):
return self.getDeps("obsoletename", "obsoleteflags", "obsoleteversion")
def getConflicts(self):
return self.getDeps("conflictname", "conflictflags", "conflictversion")
def getTriggers(self):
return self.getDeps("triggername", "triggerflags", "triggerversion")
def buildFileNames(self):
"""Returns (dir, filename, linksto, flags)."""
if self["dirnames"] == None or self["dirindexes"] == None:
return []
dirnames = [ self["dirnames"][index]
for index in self["dirindexes"]
]
return zip (dirnames, self["basenames"], self["fileflags"],
self["fileinodes"], self["filemodes"],
self["fileusername"], self["filegroupname"],
self["filelinktos"], self["filemtimes"],
self["filesizes"], self["filedevices"],
self["filerdevs"], self["filelangs"],
self["filemd5s"]
)
def parseFilelist(self):
fl = {}
for perm in self.buildFileNames():
fl[perm[0] + perm[1]] = perm[2:]
return fl
class RRpm:
def __init__(self, rpm):
self.filename = rpm.filename
self.name = rpm["name"]
self.version = rpm["version"]
self.release = rpm["release"]
self.epoch = rpm["epoch"]
if self.epoch:
self.epoch = self.epoch[0]
evr = str(self.epoch) + ":" + self.version + "-" + self.release
else:
evr = self.version + "-" + self.release
self.dep = (self.name, rpmconstants.RPMSENSE_EQUAL, evr)
self.arch = rpm["arch"]
#self.hdrfiletree = rpm.parseFilelist()
#self.filetree = rpm.buildFileNames()
self.basenames = rpm["basenames"]
self.dirnames = rpm["dirnames"]
self.provides = rpm.getProvides()
self.requires = rpm.getRequires()
self.obsoletes = rpm.getObsoletes()
self.conflicts = rpm.getConflicts()
(self.pre, self.preprog) = rpm.getScript("prein", "preinprog")
(self.post, self.postprog) = rpm.getScript("postin", "postinprog")
(self.preun, self.preunprog) = rpm.getScript("preun", "preunprog")
(self.postun, self.postunprog) = rpm.getScript("postun", "postunprog")
(self.verify, self.verifyprog) = rpm.getScript("verifyscript",
"verifyscriptprog")
self.triggers = rpm.getTriggers()
self.triggerindex = rpm["triggerindex"]
self.trigger = rpm["triggerscripts"]
self.triggerprog = rpm["triggerscriptprog"]
# legacy:
self.triggerin = rpm["triggerin"]
self.triggerun = rpm["triggerun"]
self.triggerpostun = rpm["triggerpostun"]
if rpm.verify:
self.doVerify(rpm)
def doVerify(self, rpm):
if self.trigger != None:
if len(self.trigger) != len(self.triggerprog):
raise ValueError, "wrong trigger lengths"
if "-" in self.version:
self.printErr("version contains wrong char")
if rpm["payloadformat"] not in [None, "cpio"]:
self.printErr("wrong payload format")
if rpm.legacy:
if rpm["payloadcompressor"] not in [None, "gzip"]:
self.printErr("no gzip compressor: %s" % rpm["payloadcompressor"])
else:
if rpm["payloadcompressor"] not in [None, "gzip", "bzip2"]:
self.printErr("no gzip/bzip2 compressor: %s" % rpm["payloadcompressor"])
if rpm.legacy:
if rpm["payloadflags"] not in ["9"]:
self.printErr("no payload flags: %s" % rpm["payloadflags"])
if rpm["os"] not in ["Linux", "linux"]:
self.printErr("bad os: %s" % rpm["os"])
if rpm.legacy:
if rpm["packager"] not in (None, \
"Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>"):
self.printErr("unknown packager: %s" % rpm["packager"])
if rpm["vendor"] not in (None, "Red Hat, Inc."):
self.printErr("unknown vendor: %s" % rpm["vendor"])
if rpm["distribution"] not in (None, "Red Hat Linux", "Red Hat FC-3",
"Red Hat (FC-3)", "Red Hat (RHEL-3)", "Red Hat (FC-4)"):
self.printErr("unknown distribution: %s" % rpm["distribution"])
if rpm["rhnplatform"] not in (None, self.arch):
self.printErr("unknown arch for rhnplatform")
if rpm.legacy:
if rpm["platform"] not in (None, self.arch + "-redhat-linux-gnu",
self.arch + "-redhat-linux", "--target=${target_platform}",
self.arch + "-unknown-linux",
"--target=${TARGET_PLATFORM}", "--target=$TARGET_PLATFORM", ""):
self.printErr("unknown arch %s" % rpm["platform"])
if rpm["exclusiveos"] not in (None, ['Linux'], ['linux']):
self.printErr("unknown os %s" % rpm["exclusiveos"])
if rpm.legacy:
if rpm["buildarchs"] not in (None, ['noarch']):
self.printErr("bad buildarch: %s" % rpm["buildarchs"])
if rpm["excludearch"] != None:
for i in rpm["excludearch"]:
if i not in rpmconstants.possible_archs:
self.printErr("new possible arch %s" % i)
if rpm["exclusivearch"] != None:
for i in rpm["exclusivearch"]:
if i not in rpmconstants.possible_archs:
self.printErr("new possible arch %s" % i)
def printErr(self, err):
print "%s: %s" % (self.filename, err)
def verifyRpm(filename, legacy=1, payload=None):
"""Read in a complete rpm and verify its integrity."""
rpm = ReadRpm(filename, 1, legacy=legacy)
if rpm.readHeader():
return None
if payload:
rpm.readPayload()
rpm.closeFd()
return rpm
def readHdlist(filename, verify=None):
fd = open(filename, "ro")
rpms = []
while 1:
rpm = ReadRpm(filename, verify, fd, 1)
if not rpm.readHdlist():
break
rpms.append(rpm)
return rpms
def showHelp():
print "pyrpm [options] /path/to/foo.rpm"
print
print "options:"
print "--help this message"
print "--queryformat [queryformat] specifying a format to print the query as"
print " see python String Formatting Operations for details"
print
def queryFormatUnescape(s):
import re
# Hack to emulate %{name} but not %%{name} and expand escapes
rpmre = re.compile(r'([^%])%\{(\w+)\}')
s = re.sub(rpmre, r'\1%(\2)s', s)
s = s.replace("\\n","\n")
s = s.replace("\\t","\t")
s = s.replace('\\"', '\"')
s = s.replace('\\v','\v')
s = s.replace('\\r','\r')
return s
def main(args):
queryformat="%(name)s-%(version)s-%(release)s\n"
try:
opts, args = getopt.getopt(args, "hq", ["help", "queryformat="])
except getopt.error, e:
print "Error parsing command list arguments: %s" % e
showHelp()
sys.exit(1)
for (opt, val) in opts:
if opt in ["-h", "--help"]:
showHelp()
sys.exit(1)
if opt in ['-c', "--queryformat"]:
queryformat = val
if not args:
print "Error no packages to query"
showHelp()
sys.exit(1)
queryformat = queryFormatUnescape(queryformat)
for a in args:
rpm = verifyRpm(a)
sys.stdout.write(queryformat % rpm)
def verifyAllRpms():
#import time
#repo = []
args = sys.argv[1:]
legacy = 0
if args[0] == "--strict":
legacy = 1
args = args[1:]
for a in args:
rpm = verifyRpm(a, legacy)
if rpm != None:
#f = rpm["optflags"]
#if f:
# print rpm.getFilename()
# print f
rrpm = RRpm(rpm)
#repo.append(rrpm)
#print "ready"
#time.sleep(30)
if __name__ == "__main__":
if None:
rpms = readHdlist("/home/fedora/i386/Fedora/base/hdlist", 1)
for rpm in rpms:
print rpm.getFilename()
rpms = readHdlist("/home/fedora/i386/Fedora/base/hdlist2", 1)
sys.exit(0)
if 1:
verifyAllRpms()
sys.exit(0)
main(sys.argv[1:])
# vim:ts=4:sw=4:showmatch:expandtab
-------------- next part --------------
#!/usr/bin/python
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2004 Red Hat, Inc.
#
# Author: Florian La Roche
#
# RPM Constants - based from rpmlib.h and elsewhere
# rpm tag types
#RPM_NULL = 0
RPM_CHAR = 1
RPM_INT8 = 2
RPM_INT16 = 3
RPM_INT32 = 4
RPM_INT64 = 5 # currently unused
RPM_STRING = 6
RPM_BIN = 7
RPM_STRING_ARRAY = 8
RPM_I18NSTRING = 9
# new type internal to this tool:
# STRING_ARRAY for app + params or STRING otherwise
RPM_ARGSTRING = 12
# header private tags
HEADER_IMAGE = 61
HEADER_SIGNATURES = 62 # starts a header with signatures
HEADER_IMMUTABLE = 63 # starts a header with other rpm tags
HEADER_REGIONS = 64
HEADER_I18NTABLE = 100
HEADER_SIGBASE = 256 # starting tag for sig information
HEADER_TAGBASE = 1000 # starting tag for other rpm tags
# RPM header tags
RPMTAG_HEADERIMAGE = HEADER_IMAGE
RPMTAG_HEADERSIGNATURES = HEADER_SIGNATURES
RPMTAG_HEADERIMMUTABLE = HEADER_IMMUTABLE
RPMTAG_HEADERREGIONS = HEADER_REGIONS
RPMTAG_HEADERI18NTABLE = HEADER_I18NTABLE
RPMTAG_SIG_BASE = HEADER_SIGBASE
RPMTAG_SIGSIZE = RPMTAG_SIG_BASE+1
RPMTAG_SIGLEMD5_1 = RPMTAG_SIG_BASE+2
RPMTAG_SIGPGP = RPMTAG_SIG_BASE+3
RPMTAG_SIGLEMD5_2 = RPMTAG_SIG_BASE+4
RPMTAG_SIGMD5 = RPMTAG_SIG_BASE+5
RPMTAG_SIGGPG = RPMTAG_SIG_BASE+6
RPMTAG_SIGPGP5 = RPMTAG_SIG_BASE+7
RPMTAG_BADSHA1_1 = RPMTAG_SIG_BASE+8
RPMTAG_BADSHA1_2 = RPMTAG_SIG_BASE+9
RPMTAG_PUBKEYS = RPMTAG_SIG_BASE+10
RPMTAG_DSAHEADER = RPMTAG_SIG_BASE+11
RPMTAG_RSAHEADER = RPMTAG_SIG_BASE+12
RPMTAG_SHA1HEADER = RPMTAG_SIG_BASE+13
RPMSIGTAG_SIZE = 1000
RPMSIGTAG_LEMD5_1 = 1001
RPMSIGTAG_PGP = 1002
RPMSIGTAG_LEMD5_2 = 1002
RPMSIGTAG_MD5 = 1004
RPMSIGTAG_GPG = 1005
RPMSIGTAG_PGP5 = 1006
RPMSIGTAG_PAYLOADSIZE = 1007
RPMTAG_NAME = 1000
RPMTAG_VERSION = 1001
RPMTAG_RELEASE = 1002
RPMTAG_EPOCH = 1003
RPMTAG_SUMMARY = 1004
RPMTAG_DESCRIPTION = 1005
RPMTAG_BUILDTIME = 1006
RPMTAG_BUILDHOST = 1007
RPMTAG_INSTALLTIME = 1008
RPMTAG_SIZE = 1009
RPMTAG_DISTRIBUTION = 1010
RPMTAG_VENDOR = 1011
RPMTAG_GIF = 1012
RPMTAG_XPM = 1013
RPMTAG_LICENSE = 1014
RPMTAG_PACKAGER = 1015
RPMTAG_GROUP = 1016
RPMTAG_CHANGELOG = 1017
RPMTAG_SOURCE = 1018
RPMTAG_PATCH = 1019
RPMTAG_URL = 1020
RPMTAG_OS = 1021
RPMTAG_ARCH = 1022
RPMTAG_PREIN = 1023
RPMTAG_POSTIN = 1024
RPMTAG_PREUN = 1025
RPMTAG_POSTUN = 1026
RPMTAG_OLDFILENAMES = 1027
RPMTAG_FILESIZES = 1028
RPMTAG_FILESTATES = 1029
RPMTAG_FILEMODES = 1030
RPMTAG_FILEUIDS = 1031
RPMTAG_FILEGIDS = 1032
RPMTAG_FILERDEVS = 1033
RPMTAG_FILEMTIMES = 1034
RPMTAG_FILEMD5S = 1035
RPMTAG_FILELINKTOS = 1036
RPMTAG_FILEFLAGS = 1037
RPMTAG_ROOT = 1038
RPMTAG_FILEUSERNAME = 1039
RPMTAG_FILEGROUPNAME = 1040
RPMTAG_EXCLUDE = 1041
RPMTAG_EXCLUSIVE = 1042
RPMTAG_ICON = 1043
RPMTAG_SOURCERPM = 1044
RPMTAG_FILEVERIFYFLAGS = 1045
RPMTAG_ARCHIVESIZE = 1046
RPMTAG_PROVIDENAME = 1047
RPMTAG_REQUIREFLAGS = 1048
RPMTAG_REQUIRENAME = 1049
RPMTAG_REQUIREVERSION = 1050
RPMTAG_NOSOURCE = 1051
RPMTAG_NOPATCH = 1052
RPMTAG_CONFLICTFLAGS = 1053
RPMTAG_CONFLICTNAME = 1054
RPMTAG_CONFLICTVERSION = 1055
RPMTAG_DEFAULTPREFIX = 1056
RPMTAG_BUILDROOT = 1057
RPMTAG_INSTALLPREFIX = 1058
RPMTAG_EXCLUDEARCH = 1059
RPMTAG_EXCLUDEOS = 1060
RPMTAG_EXCLUSIVEARCH = 1061
RPMTAG_EXCLUSIVEOS = 1062
RPMTAG_AUTOREQPROV = 1063
RPMTAG_RPMVERSION = 1064
RPMTAG_TRIGGERSCRIPTS = 1065
RPMTAG_TRIGGERNAME = 1066
RPMTAG_TRIGGERVERSION = 1067
RPMTAG_TRIGGERFLAGS = 1068
RPMTAG_TRIGGERINDEX = 1069
RPMTAG_VERIFYSCRIPT = 1079
RPMTAG_VERIFYSCRIPT2 = 15
RPMTAG_CHANGELOGTIME = 1080
RPMTAG_CHANGELOGNAME = 1081
RPMTAG_CHANGELOGTEXT = 1082
RPMTAG_BROKENMD5 = 1083
RPMTAG_PREREQ = 1084
RPMTAG_PREINPROG = 1085
RPMTAG_POSTINPROG = 1086
RPMTAG_PREUNPROG = 1087
RPMTAG_POSTUNPROG = 1088
RPMTAG_BUILDARCHS = 1089
RPMTAG_OBSOLETENAME = 1090
RPMTAG_VERIFYSCRIPTPROG = 1091
RPMTAG_TRIGGERSCRIPTPROG = 1092
RPMTAG_DOCDIR = 1093
RPMTAG_COOKIE = 1094
RPMTAG_FILEDEVICES = 1095
RPMTAG_FILEINODES = 1096
RPMTAG_FILELANGS = 1097
RPMTAG_PREFIXES = 1098
RPMTAG_INSTPREFIXES = 1099
RPMTAG_TRIGGERIN = 1100
RPMTAG_TRIGGERUN = 1101
RPMTAG_TRIGGERPOSTUN = 1102
RPMTAG_AUTOREQ = 1103
RPMTAG_AUTOPROV = 1104
RPMTAG_CAPABILITY = 1105
RPMTAG_SOURCEPACKAGE = 1106
RPMTAG_OLDORIGFILENAMES = 1107
RPMTAG_BUILDPREREQ = 1108
RPMTAG_BUILDREQUIRES = 1109
RPMTAG_BUILDCONFLICTS = 1110
RPMTAG_BUILDMACROS = 1111
RPMTAG_PROVIDEFLAGS = 1112
RPMTAG_PROVIDEVERSION = 1113
RPMTAG_OBSOLETEFLAGS = 1114
RPMTAG_OBSOLETEVERSION = 1115
RPMTAG_DIRINDEXES = 1116
RPMTAG_BASENAMES = 1117
RPMTAG_DIRNAMES = 1118
RPMTAG_ORIGDIRINDEXES = 1119
RPMTAG_ORIGBASENAMES = 1120
RPMTAG_ORIGDIRNAMES = 1121
RPMTAG_OPTFLAGS = 1122
RPMTAG_DISTURL = 1123
RPMTAG_PAYLOADFORMAT = 1124
RPMTAG_PAYLOADCOMPRESSOR = 1125
RPMTAG_PAYLOADFLAGS = 1126
RPMTAG_INSTALLCOLOR = 1127
RPMTAG_INSTALLTID = 1128
RPMTAG_REMOVETID = 1129
RPMTAG_SHA1RHN = 1130
RPMTAG_RHNPLATFORM = 1131
RPMTAG_PLATFORM = 1132
RPMTAG_PATCHESNAME = 1133
RPMTAG_PATCHESFLAGS = 1134
RPMTAG_PATCHESVERSION = 1135
RPMTAG_CACHECTIME = 1136
RPMTAG_CACHEPKGPATH = 1137
RPMTAG_CACHEPKGSIZE = 1138
RPMTAG_CACHEPKGMTIME = 1139
RPMTAG_FILECOLORS = 1140
RPMTAG_FILECLASS = 1141
RPMTAG_CLASSDICT = 1142
RPMTAG_FILEDEPENDSX = 1143
RPMTAG_FILEDEPENDSN = 1144
RPMTAG_DEPENDSDICT = 1145
RPMTAG_SOURCEPKGID = 1146
RPMTAG_FILECONTEXTS = 1147
RPMSIGTAG_BADSHA1_1 = RPMTAG_BADSHA1_1
RPMSIGTAG_BADSHA1_2 = RPMTAG_BADSHA1_2
RPMSIGTAG_SHA1 = RPMTAG_SHA1HEADER
RPMSIGTAG_DSA = RPMTAG_DSAHEADER
RPMSIGTAG_RSA = RPMTAG_RSAHEADER
RPMTAG_DELTAHOFFSETORDER = 20001 # RPMTAG_NAME array
RPMTAG_DELTAVERSION = 20002 # RPM_PROVIDEVERSION array
RPMTAG_DELTAORIGSIGS = 20003 # BIN
RPMTAG_DELTAHINDEXORDER = 20004 # RPMTAG_NAME array
RPMTAG_DELTARAWPAYLOADXDELTA = 20005 # BIN
RPMTAG_DELTAORIGPAYLOADFORMAT = 20006 # RPMTAG_PAYLOADFORMAT
RPMTAG_DELTAFILEFLAGS = 20007 # INT16 array
# XXX: TODO for possible rpm changes:
# - arch should not be needed for src.rpms
# - deps could be left away from src.rpms
# - cookie could go away
# - rhnplatform could go away
# list of all rpm tags in Fedora Core development
# tagname: (tag, type, how-many, flags:legacy=1,src-only=2,bin-only=4)
rpmtag = {
# basic info
"name": (RPMTAG_NAME, RPM_STRING, None, 0),
"epoch": (RPMTAG_EPOCH, RPM_INT32, 1, 0),
"version": (RPMTAG_VERSION, RPM_STRING, None, 0),
"release": (RPMTAG_RELEASE, RPM_STRING, None, 0),
"arch": (RPMTAG_ARCH, RPM_STRING, None, 0),
# dependencies: provides, requires, obsoletes, conflicts
"providename": (RPMTAG_PROVIDENAME, RPM_STRING_ARRAY, None, 0),
"provideflags": (RPMTAG_PROVIDEFLAGS, RPM_INT32, None, 0),
"provideversion": (RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY, None, 0),
"requirename": (RPMTAG_REQUIRENAME, RPM_STRING_ARRAY, None, 0),
"requireflags": (RPMTAG_REQUIREFLAGS, RPM_INT32, None, 0),
"requireversion": (RPMTAG_REQUIREVERSION, RPM_STRING_ARRAY, None, 0),
"obsoletename": (RPMTAG_OBSOLETENAME, RPM_STRING_ARRAY, None, 4),
"obsoleteflags": (RPMTAG_OBSOLETEFLAGS, RPM_INT32, None, 4),
"obsoleteversion": (RPMTAG_OBSOLETEVERSION, RPM_STRING_ARRAY, None, 4),
"conflictname": (RPMTAG_CONFLICTNAME, RPM_STRING_ARRAY, None, 0),
"conflictflags": (RPMTAG_CONFLICTFLAGS, RPM_INT32, None, 0),
"conflictversion": (RPMTAG_CONFLICTVERSION, RPM_STRING_ARRAY, None, 0),
# triggers
"triggername": (RPMTAG_TRIGGERNAME, RPM_STRING_ARRAY, None, 4),
"triggerflags": (RPMTAG_TRIGGERFLAGS, RPM_INT32, None, 4),
"triggerversion": (RPMTAG_TRIGGERVERSION, RPM_STRING_ARRAY, None, 4),
"triggerscripts": (RPMTAG_TRIGGERSCRIPTS, RPM_STRING_ARRAY, None, 4),
"triggerscriptprog": (RPMTAG_TRIGGERSCRIPTPROG, RPM_STRING_ARRAY, None, 4),
"triggerindex": (RPMTAG_TRIGGERINDEX, RPM_INT32, None, 4),
# scripts
"prein": (RPMTAG_PREIN, RPM_STRING, None, 4),
"preinprog": (RPMTAG_PREINPROG, RPM_ARGSTRING, None, 4),
"postin": (RPMTAG_POSTIN, RPM_STRING, None, 4),
"postinprog": (RPMTAG_POSTINPROG, RPM_ARGSTRING, None, 4),
"preun": (RPMTAG_PREUN, RPM_STRING, None, 4),
"preunprog": (RPMTAG_PREUNPROG, RPM_ARGSTRING, None, 4),
"postun": (RPMTAG_POSTUN, RPM_STRING, None, 4),
"postunprog": (RPMTAG_POSTUNPROG, RPM_ARGSTRING, None, 4),
"verifyscript": (RPMTAG_VERIFYSCRIPT, RPM_STRING, None, 4),
"verifyscriptprog": (RPMTAG_VERIFYSCRIPTPROG, RPM_ARGSTRING, None, 4),
# addon information:
# list of available languages
"i18ntable": (HEADER_I18NTABLE, RPM_STRING_ARRAY, None, 0),
"summary": (RPMTAG_SUMMARY, RPM_I18NSTRING, None, 0),
"description": (RPMTAG_DESCRIPTION, RPM_I18NSTRING, None, 0),
"url": (RPMTAG_URL, RPM_STRING, None, 0),
"license": (RPMTAG_LICENSE, RPM_STRING, None, 0),
"rpmversion": (RPMTAG_RPMVERSION, RPM_STRING, None, 0),
"sourcerpm": (RPMTAG_SOURCERPM, RPM_STRING, None, 4),
"changelogtime": (RPMTAG_CHANGELOGTIME, RPM_INT32, None, 0),
"changelogname": (RPMTAG_CHANGELOGNAME, RPM_STRING_ARRAY, None, 0),
"changelogtext": (RPMTAG_CHANGELOGTEXT, RPM_STRING_ARRAY, None, 0),
# relocatable rpm packages
"prefixes": (RPMTAG_PREFIXES, RPM_STRING_ARRAY, None, 4),
# optimization flags for gcc
"optflags": (RPMTAG_OPTFLAGS, RPM_STRING, None, 4),
# %pubkey in .spec files
"pubkeys": (RPMTAG_PUBKEYS, RPM_STRING_ARRAY, None, 4),
"sourcepkgid": (RPMTAG_SOURCEPKGID, RPM_BIN, 16, 4), # XXX
"immutable": (RPMTAG_HEADERIMMUTABLE, RPM_BIN, 16, 0), # XXX
# less important information:
# time of rpm build
"buildtime": (RPMTAG_BUILDTIME, RPM_INT32, 1, 0),
# hostname where rpm was built
"buildhost": (RPMTAG_BUILDHOST, RPM_STRING, None, 0),
"cookie": (RPMTAG_COOKIE, RPM_STRING, None, 0), # build host and time
# ignored now, succ is comps.xml
# XXX code allows hardcoded exception to also have type RPM_STRING
# for RPMTAG_GROUP
"group": (RPMTAG_GROUP, RPM_I18NSTRING, None, 0),
"size": (RPMTAG_SIZE, RPM_INT32, 1, 0), # sum of all file sizes
"distribution": (RPMTAG_DISTRIBUTION, RPM_STRING, None, 0),
"vendor": (RPMTAG_VENDOR, RPM_STRING, None, 0),
"packager": (RPMTAG_PACKAGER, RPM_STRING, None, 0),
"os": (RPMTAG_OS, RPM_STRING, None, 0), # always "linux"
"payloadformat": (RPMTAG_PAYLOADFORMAT, RPM_STRING, None, 0), # "cpio"
# "gzip" or "bzip2"
"payloadcompressor": (RPMTAG_PAYLOADCOMPRESSOR, RPM_STRING, None, 0),
"payloadflags": (RPMTAG_PAYLOADFLAGS, RPM_STRING, None, 0), # "9"
"rhnplatform": (RPMTAG_RHNPLATFORM, RPM_STRING, None, 4), # == arch
"platform": (RPMTAG_PLATFORM, RPM_STRING, None, 0),
# source rpm packages:
"source": (RPMTAG_SOURCE, RPM_STRING_ARRAY, None, 2),
"patch": (RPMTAG_PATCH, RPM_STRING_ARRAY, None, 2),
"buildarchs": (RPMTAG_BUILDARCHS, RPM_STRING_ARRAY, None, 2),
"excludearch": (RPMTAG_EXCLUDEARCH, RPM_STRING_ARRAY, None, 2),
"exclusivearch": (RPMTAG_EXCLUSIVEARCH, RPM_STRING_ARRAY, None, 2),
# ['Linux'] or ['linux']
"exclusiveos": (RPMTAG_EXCLUSIVEOS, RPM_STRING_ARRAY, None, 2),
# information about files
"filesizes": (RPMTAG_FILESIZES, RPM_INT32, None, 0),
"filemodes": (RPMTAG_FILEMODES, RPM_INT16, None, 0),
"filerdevs": (RPMTAG_FILERDEVS, RPM_INT16, None, 0),
"filemtimes": (RPMTAG_FILEMTIMES, RPM_INT32, None, 0),
"filemd5s": (RPMTAG_FILEMD5S, RPM_STRING_ARRAY, None, 0),
"filelinktos": (RPMTAG_FILELINKTOS, RPM_STRING_ARRAY, None, 0),
"fileflags": (RPMTAG_FILEFLAGS, RPM_INT32, None, 0),
"fileusername": (RPMTAG_FILEUSERNAME, RPM_STRING_ARRAY, None, 0),
"filegroupname": (RPMTAG_FILEGROUPNAME, RPM_STRING_ARRAY, None, 0),
"fileverifyflags": (RPMTAG_FILEVERIFYFLAGS, RPM_INT32, None, 0),
"filedevices": (RPMTAG_FILEDEVICES, RPM_INT32, None, 0),
"fileinodes": (RPMTAG_FILEINODES, RPM_INT32, None, 0),
"filelangs": (RPMTAG_FILELANGS, RPM_STRING_ARRAY, None, 0),
"dirindexes": (RPMTAG_DIRINDEXES, RPM_INT32, None, 0),
"basenames": (RPMTAG_BASENAMES, RPM_STRING_ARRAY, None, 0),
"dirnames": (RPMTAG_DIRNAMES, RPM_STRING_ARRAY, None, 0),
"filecolors": (RPMTAG_FILECOLORS, RPM_INT32, None, 0),
"fileclass": (RPMTAG_FILECLASS, RPM_INT32, None, 0),
"classdict": (RPMTAG_CLASSDICT, RPM_STRING_ARRAY, None, 0),
"filedependsx": (RPMTAG_FILEDEPENDSX, RPM_INT32, None, 0),
"filedependsn": (RPMTAG_FILEDEPENDSN, RPM_INT32, None, 0),
"dependsdict": (RPMTAG_DEPENDSDICT, RPM_INT32, None, 0),
# legacy additions:
# selinux filecontexts
"filecontexts": (RPMTAG_FILECONTEXTS, RPM_STRING_ARRAY, None, 1),
"archivesize": (RPMTAG_ARCHIVESIZE, RPM_INT32, 1, 1),
"capability": (RPMTAG_CAPABILITY, RPM_INT32, None, 1),
"xpm": (RPMTAG_XPM, RPM_BIN, None, 1),
"gif": (RPMTAG_GIF, RPM_BIN, None, 1),
"verifyscript2": (RPMTAG_VERIFYSCRIPT2, RPM_STRING, None, 1),
"nosource": (RPMTAG_NOSOURCE, RPM_INT32, None, 1),
"nopatch": (RPMTAG_NOPATCH, RPM_INT32, None, 1),
"disturl": (RPMTAG_DISTURL, RPM_STRING, None, 1),
"oldfilenames": (RPMTAG_OLDFILENAMES, RPM_STRING_ARRAY, None, 1),
"triggerin": (RPMTAG_TRIGGERIN, RPM_STRING, None, 5),
"triggerun": (RPMTAG_TRIGGERUN, RPM_STRING, None, 5),
"triggerpostun": (RPMTAG_TRIGGERPOSTUN, RPM_STRING, None, 5)
}
rpmtagname = {}
# Add a reverse mapping for all tags and a new tag -> name mapping
for key in rpmtag.keys():
v = rpmtag[key]
rpmtag[v[0]] = v
rpmtagname[v[0]] = key
# Required tags in a header.
rpmtagrequired = []
for key in ["name", "version", "release", "arch"]:
rpmtagrequired.append(rpmtag[key][0])
# Info within the sig header.
rpmsigtag = {
# size of gpg/dsaheader sums differ between 64/65
"dsaheader": (RPMTAG_DSAHEADER, RPM_BIN, None, 0),
"gpg": (RPMSIGTAG_GPG, RPM_BIN, None, 0),
"header_signatures": (HEADER_SIGNATURES, RPM_BIN, 16, 0), # XXX
"payloadsize": (RPMSIGTAG_PAYLOADSIZE, RPM_INT32, 1, 0),
"size_in_sig": (RPMSIGTAG_SIZE, RPM_INT32, 1, 0),
"sha1header": (RPMTAG_SHA1HEADER, RPM_STRING, None, 0),
"md5": (RPMSIGTAG_MD5, RPM_BIN, 16, 0),
# legacy entries:
"pgp": (RPMSIGTAG_PGP, RPM_BIN, None, 1),
"badsha1_1": (RPMTAG_BADSHA1_1, RPM_STRING, None, 1),
"badsha1_2": (RPMTAG_BADSHA1_2, RPM_STRING, None, 1)
}
# Add a reverse mapping for all tags and a new tag -> name mapping
for key in rpmsigtag.keys():
v = rpmsigtag[key]
rpmsigtag[v[0]] = v
rpmtagname[v[0]] = key
# Required tags in a signature header.
rpmsigtagrequired = []
#for key in ["header_signatures", "payloadsize", "size_in_sig", \
# "sha1header", "md5"]:
for key in ["md5"]:
rpmsigtagrequired.append(rpmsigtag[key][0])
# check arch names against this list
possible_archs = ['noarch', 'i386', 'i486', 'i586', 'i686', 'athlon',
'x86_64', 'ia32e', 'alpha', 'sparc', 'sparc64', 's390', 's390x', 'ia64',
'ppc', 'ppc64', 'ppc64iseries', 'ppc64pseries', 'ppcpseries', 'ppciseries',
'ppcmac', 'ppc8260', 'm68k',
'arm', 'armv4l', 'mips', 'mipseb', 'hppa', 'mipsel', 'sh', 'axp',
# these are in old rpms:
'i786', 'i886', 'i986', 's390xc']
RPMSENSE_ANY = 0
RPMSENSE_SERIAL = (1 << 0) # legacy
RPMSENSE_LESS = (1 << 1)
RPMSENSE_GREATER = (1 << 2)
RPMSENSE_EQUAL = (1 << 3)
RPMSENSE_PROVIDES = (1 << 4) # only used internally by builds
RPMSENSE_CONFLICTS = (1 << 5) # only used internally by builds
RPMSENSE_PREREQ = (1 << 6) # legacy
RPMSENSE_OBSOLETES = (1 << 7) # only used internally by builds
RPMSENSE_INTERP = (1 << 8) # Interpreter used by scriptlet.
RPMSENSE_SCRIPT_PRE = ((1 << 9) | RPMSENSE_PREREQ) # %pre dependency
RPMSENSE_SCRIPT_POST = ((1 << 10)|RPMSENSE_PREREQ) # %post dependency
RPMSENSE_SCRIPT_PREUN = ((1 << 11)|RPMSENSE_PREREQ) # %preun dependency
RPMSENSE_SCRIPT_POSTUN = ((1 << 12)|RPMSENSE_PREREQ) # %postun dependency
RPMSENSE_SCRIPT_VERIFY = (1 << 13) # %verify dependency
RPMSENSE_FIND_REQUIRES = (1 << 14) # find-requires generated dependency
RPMSENSE_FIND_PROVIDES = (1 << 15) # find-provides generated dependency
RPMSENSE_TRIGGERIN = (1 << 16) # %triggerin dependency
RPMSENSE_TRIGGERUN = (1 << 17) # %triggerun dependency
RPMSENSE_TRIGGERPOSTUN = (1 << 18) # %triggerpostun dependency
RPMSENSE_MISSINGOK = (1 << 19) # suggests/enhances/recommends hint
RPMSENSE_SCRIPT_PREP = (1 << 20) # %prep build dependency
RPMSENSE_SCRIPT_BUILD = (1 << 21) # %build build dependency
RPMSENSE_SCRIPT_INSTALL = (1 << 22) # %install build dependency
RPMSENSE_SCRIPT_CLEAN = (1 << 23) # %clean build dependency
RPMSENSE_RPMLIB = ((1 << 24) | RPMSENSE_PREREQ) # rpmlib(feature) dependency
RPMSENSE_TRIGGERPREIN = (1 << 25) # @todo Implement %triggerprein
RPMSENSE_KEYRING = (1 << 26)
RPMSENSE_PATCHES = (1 << 27)
RPMSENSE_CONFIG = (1 << 28)
# vim:ts=4:sw=4:showmatch:expandtab
-------------- next part --------------
#!/usr/bin/python
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2004 Red Hat, Inc.
#
# Author: Phil Knirsch, Paul Nasrat, Florian La Roche
#
import os, os.path, commands, shutil
CP_IFMT = 0170000
CP_IFIFO = 0010000
CP_IFCHR = 0020000
CP_IFDIR = 0040000
CP_IFBLK = 0060000
CP_IFREG = 0100000
CP_IFNWK = 0110000
CP_IFLNK = 0120000
CP_IFSOCK = 0140000
class CPIOFile:
""" Read ASCII CPIO files. """
def __init__(self, fd):
self.filelist = {} # hash of CPIO headers and stat data
self.fd = fd # file descript from which we read
self.defered = [] # list of defered files for extract
self.filename = None # current filename
self.filedata = None # current file stat data
self.filerawdata = None # raw data of current file
def getNextHeader(self):
data = self.fd.read(110)
# CPIO ASCII hex, expanded device numbers (070702 with CRC)
if data[0:6] not in ["070701", "070702"]:
raise IOError, "Bad magic reading CPIO headers %s" % data[0:6]
#(magic, inode, mode, uid, gid, nlink, mtime, filesize, devMajor, \
# devMinor, rdevMajor, rdevMinor, namesize, checksum)
filedata = [data[0:6], int(data[6:14], 16), \
int(data[14:22], 16), int(data[22:30], 16), \
int(data[30:38], 16), int(data[38:46], 16), \
int(data[46:54], 16), int(data[54:62], 16), \
int(data[62:70], 16), int(data[70:78], 16), \
int(data[78:86], 16), int(data[86:94], 16), \
int(data[94:102], 16), data[102:110]]
size = filedata[12]
filename = self.fd.read(size)
filename = "/"+os.path.normpath("./"+filename.rstrip("\x00"))
fsize = 110 + size
self.fd.read((4 - (fsize % 4)) % 4)
# Detect if we're at the end of the archive
if filename == "/TRAILER!!!":
return [None, None]
# Contents
filesize = filedata[7]
return [filename, filedata]
def _read_cpio_headers(self):
while 1:
[filename, filedata] = self.getNextHeader()
if filename == None:
break
# Contents
filesize = filedata[7]
self.fd.read(filesize)
self.fd.read((4 - (filesize % 4)) % 4)
self.filelist[filename] = filedata
def createLink(self, src, dst):
try:
# First try to unlink the defered file
os.unlink(dst)
except:
pass
# Behave exactly like cpio: If the hardlink fails (because of different
# partitions), then it has to fail
os.link(src, dst)
def addToDefered(self, filename):
self.defered.append((filename, self.filedata))
def handleCurrentDefered(self, filename):
# Check if we have a defered 0 byte file with the same inode, devmajor
# and devminor and if yes, create a hardlink to the new file.
for i in xrange(len(self.defered)-1, -1, -1):
if self.defered[i][1][1] == self.filedata[1] and \
self.defered[i][1][8] == self.filedata[8] and \
self.defered[i][1][9] == self.filedata[9]:
self.createLink(filename, self.defered[i][0])
self.defered.pop(i)
def postExtract(self):
# In the end we need to process the remaining files in the defered
# list and see if any of them need to be hardlinked, too.
for i in xrange(len(self.defered)-1, -1, -1):
# We mark already processed defered hardlinked files by setting
# the inode of those files to -1. We have to skip those naturally.
if self.defered[i][1][1] < 0:
continue
# Create empty file
fd = open(self.defered[i][0], "w")
fd.write("")
fd.close()
os.chmod(self.defered[i][0], (~CP_IFMT) & self.defered[i][1][2])
os.chown(self.defered[i][0], self.defered[i][1][3], self.defered[i][1][4])
os.utime(self.defered[i][0], (self.defered[i][1][6], self.defered[i][1][6]))
for j in xrange(i-1, -1, -1):
if self.defered[i][1][1] == self.defered[j][1][1] and \
self.defered[i][1][8] == self.defered[j][1][8] and \
self.defered[i][1][9] == self.defered[j][1][9]:
self.createLink(filename, self.defered[i][0])
def makeDirs(self, fullname):
dirname = fullname[:fullname.rfind("/")]
if not os.path.isdir(dirname):
os.makedirs(dirname)
def extractCurrentEntry(self, instroot=None):
if self.filename == None or self.filedata == None:
return 0
if instroot == None:
instroot = "/"
if not os.path.isdir(instroot):
return 0
filetype = self.filedata[2] & CP_IFMT
fullname = instroot + self.filename
if filetype == CP_IFREG:
self.makeDirs(fullname)
# CPIO archives are sick: Hardlinks are stored as 0 byte long
# regular files.
# The last hardlinked file in the archive contains the data, so
# we have to defere creating any 0 byte file until either:
# - We create a file with data and the inode/devmajor/devminor are
# identical
# - We have processed all files and can check the defered list for
# any more identical files (in which case they are hardlinked
# again)
# - For the rest in the end create 0 byte files as they were in
# fact really 0 byte files, not hardlinks.
if self.filedata[7] == 0:
self.addToDefered(fullname)
return 1
fd = open(fullname, "w")
fd.write(self.filerawdata)
fd.close()
os.chown(fullname, self.filedata[3], self.filedata[4])
os.chmod(fullname, (~CP_IFMT) & self.filedata[2])
os.utime(fullname, (self.filedata[6], self.filedata[6]))
self.handleCurrentDefered(fullname)
elif filetype == CP_IFDIR:
if os.path.isdir(fullname):
return 1
os.makedirs(fullname)
os.chown(fullname, self.filedata[3], self.filedata[4])
os.chmod(fullname, (~CP_IFMT) & self.filedata[2])
os.utime(fullname, (self.filedata[6], self.filedata[6]))
elif filetype == CP_IFLNK:
symlinkfile = self.filerawdata.rstrip("\x00")
if os.path.islink(fullname) and os.readlink(fullname) == symlinkfile:
return 1
self.makeDirs(fullname)
os.symlink(symlinkfile, fullname)
elif filetype == CP_IFCHR or filetype == CP_IFBLK or filetype == CP_IFSOCK or filetype == CP_IFIFO:
if filetype == CP_IFCHR:
devtype = "c"
elif filetype == CP_IFBLK:
devtype = "b"
else:
return 0
self.makeDirs(fullname)
ret = commands.getoutput("/bin/mknod "+fullname+" "+devtype+" "+str(self.filedata[10])+" "+str(self.filedata[11]))
if ret != "":
print "Error creating device: "+ret
else:
os.chown(fullname, self.filedata[3], self.filedata[4])
os.chmod(fullname, (~CP_IFMT) & self.filedata[2])
os.utime(fullname, (self.filedata[6], self.filedata[6]))
else:
raise ValueError, "%s: not a valid CPIO filetype" % (oct(filetype))
def getCurrentEntry(self):
return [self.filename, self.filedata, self.filerawdata]
def getNextEntry(self):
[filename, filedata] = self.getNextHeader()
if filename == None:
return [None, None, None]
# Contents
filesize = filedata[7]
filerawdata = self.fd.read(filesize)
self.fd.read((4 - (filesize % 4)) % 4)
self.filename = filename
self.filedata = filedata
self.filerawdata = filerawdata
return self.getCurrentEntry()
def resetEntry(self):
self.fd.seek(0)
self.filename = None
self.filedata = None
self.filerawdata = None
self.defered = []
def namelist(self):
"""Return a list of file names in the archive."""
return self.filelist
def read(self):
"""Read an parse cpio archive."""
self._read_cpio_headers()
if __name__ == "__main__":
import sys
for f in sys.argv[1:]:
if f == "-":
c = CPIOFile(sys.stdin)
else:
c = CPIOFile(f)
try:
c.read()
except IOError, e:
print "error reading cpio: %s" % e
print c.filelist
# vim:ts=4:sw=4:showmatch:expandtab
More information about the fedora-devel-list
mailing list