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
#!/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
#!/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
#!/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