[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

binary rpm package checking



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

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]