#!/usr/bin/python -t # -*- mode: Python; indent-tabs-mode: nil; -*- # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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 General Public License for more details. # # You should have received a copy of the GNU 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 import errno, os, sys import fnmatch, re import rpmUtils.transaction, rpmUtils.miscutils import commands import string import getopt from dumpMetadata import byteranges from packagelist import RpmItem, DrpmItem, PackageList import gzip from zlib import error as zlibError from gzip import write32u, FNAME DRPMWORTHKEEPINGTHRESH=0.5 DEBUG=0 __version__ = "0.2.0" def errorprint(stuff): print >> sys.stderr, stuff def _(args): """Stub function for translation""" return args def usage(retval=1): print _(""" createdeltarpms [options] directory-of-packages directory-of-deltarpms Options: -d, --dist-update = optional directory containing old distribution to create an update from -c, --count = optional maximum number of deltarpms to create per package. Default is maximum available -n, --no-first = Don't create deltarpm against first rpm if number exceeds count. Useful if you are running a continually-updated distribution rather than one with set release cycles. -q, --quiet = run quietly -v, --verbose = run verbosely -h, --help = show this help -V, --version = output version """) sys.exit(retval) def getDeltaNevr(filename): """Get nevr for src rpm of a deltarpm. Return a tuple of (n, e, v, r)""" def _getLength(in_data): length = 0 for val in in_data: length = length * 256 length += ord(val) return length def _stringToVersion(strng): i = strng.find(':') if i != -1: epoch = strng[:i] else: epoch = '0' j = strng.find('-') if j != -1: if strng[i + 1:j] == '': version = None else: version = strng[i + 1:j] release = strng[j + 1:] else: if strng[i + 1:] == '': version = None else: version = strng[i + 1:] release = None return (epoch, version, release) def _stringToNEVR(string): i = string.rfind("-", 0, string.rfind("-")-1) name = string[:i] (epoch, ver, rel) = _stringToVersion(string[i+1:]) return (name, epoch, ver, rel) (range_start, range_end) = byteranges(filename) fd = open(filename, "r") fd.seek(range_end) try: compobj = gzip.GzipFile("", "rb", 9, fd) except: raise zlibError("Data not stored in gzip format") if compobj.read(4)[:3] != "DLT": raise Exception("Not a deltarpm") nevr_length = _getLength(compobj.read(4)) nevr = compobj.read(nevr_length).strip("\x00") oldnevr = _stringToNEVR(nevr) compobj.close() return oldnevr def getFileList(path, ext, filelist): """Return all files in path matching ext, store them in filelist, recurse dirs. Returns a list object""" extlen = len(ext) totalpath = os.path.normpath(path) dir_list = os.listdir(totalpath) for d in dir_list: if os.path.isdir(totalpath + '/' + d): filelist = getFileList(os.path.join(totalpath, d), ext, filelist) elif string.lower(d[-extlen:]) == '%s' % (ext): filelist.append(os.path.join(totalpath, d)) return filelist def createPrestoRepo(base_dir, drpm_dir, dst_dir=None, DrpmsPerPackage=0, DoFirst=True): ts = rpmUtils.transaction.initReadOnlyTransaction() changed = False # Create list of .rpm files. # We don't use "glob", so sub-directories are supported. print 'Using base dir: %s' % base_dir print 'Using destination dir: %s' % drpm_dir try: rpm_files = getFileList(base_dir, ".rpm", []) except OSError, e: errorprint(_("Error: Unable to find directory %s: %s" % (base_dir, e))) return False if dst_dir != None: try: dst_rpm_files = getFileList(dst_dir, ".rpm", []) except OSError, e: errorprint(_("Error: Unable to find directory %s: %s" % (dst_dir, e))) return False try: drpm_files = getFileList(drpm_dir, ".drpm", []) except: drpm_files = [] if not len(rpm_files): print ' Nothing found.' return changed if dst_dir != None and not len(dst_rpm_files): print ' No new rpms found.' return changed # Check whether rel_dir exists, and if it doesn't, create it if not os.access(drpm_dir, os.F_OK): os.makedirs(drpm_dir, 0755) elif not os.access(drpm_dir, os.W_OK): print 'ERROR: Unable to write to %s' % drpm_dir sys.exit(1) # Add all rpms to PackageList rpm_list = PackageList(base_dir, drpm_dir, DRPMWORTHKEEPINGTHRESH, DEBUG) for f in rpm_files: try: hdr = rpmUtils.miscutils.hdrFromPackage(ts, f) except: print "Unable to open %s" % f else: nm = hdr['name'] + "." + hdr['arch'] e = hdr['epoch'] if e is None: e = "0" nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch']) rpm = RpmItem(nevra, f) rpm_list.addRpm(rpm) # If we have a new distro, add its rpms with epoch bumped +5 if dst_dir != None: for f in dst_rpm_files: try: hdr = rpmUtils.miscutils.hdrFromPackage(ts, f) except: print "Unable to open %s" % f else: nm = hdr['name'] + "." + hdr['arch'] e = hdr['epoch'] if e is None: e = "0" e = str(int(e) + 5) nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch']) rpm = RpmItem(nevra, f) rpm_list.addRpm(rpm) # Add all deltarpms to PackageList for f in drpm_files: try: hdr = rpmUtils.miscutils.hdrFromPackage(ts, f) except: print "Unable to open %s" % f else: (sn, se, sv, sr) = getDeltaNevr(f) nm = hdr['name'] + "." + hdr['arch'] e = hdr['epoch'] if e is None: e = "0" dst_nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch']) src_nevra = (sn, se, sv, sr, hdr['arch']) drpm = DrpmItem(src_nevra, dst_nevra, f) rpm_list.addDrpm(drpm) if DEBUG: rpm_list.dump() # Build deltarpms rpm_list.makeDeltaRpms(DrpmsPerPackage, DoFirst) return True def parseArgs(args): """ Parse the command line args return a commands dict and directory. Sanity check all the things being passed in. """ cmds = {} cmds['quiet'] = 0 cmds['verbose'] = 0 cmds['dist-update'] = None cmds['count'] = None cmds['do-first'] = 1 try: gopts, argsleft = getopt.getopt(args, 'hqvVd:c:n', ['help', 'quiet', 'verbose', 'version', 'dist-update=', 'count=', 'no-first']) except getopt.error, e: errorprint(_('Options Error: %s.') % e) usage() try: for arg,a in gopts: if arg in ['-h','--help']: usage(retval=0) elif arg in ['-V', '--version']: print '%s' % __version__ sys.exit(0) except ValueError, e: errorprint(_('Options Error: %s') % e) usage() # make sure our dir makes sense before we continue if len(argsleft) > 2: errorprint(_('Error: You may only specify one rpm directory and one deltarpm directory.')) usage() elif len(argsleft) == 0: errorprint(_('Error: Must specify an rpm directory to index and a destination deltarpm directory.')) usage() else: directories = argsleft try: for arg,a in gopts: if arg in ['-v', '--verbose']: cmds['verbose'] = 1 elif arg in ["-q", '--quiet']: cmds['quiet'] = 1 elif arg in ["-n", '--no-first']: cmds['do-first'] = 0 elif arg in ['-d', '--dist-update']: if cmds['dist-update'] is not None: errorprint(_('Error: Only one directory for distribution updates allowed.')) usage() else: cmds['dist-update'] = a elif arg in ['-c', '--count']: if cmds['count'] is not None: errorprint(_('Error: Only one count allowed.')) usage() else: cmds['count'] = int(a) except ValueError, e: errorprint(_('Options Error: %s') % e) usage() rpm_directory = directories[0] drpm_directory = directories[1] rpm_directory = os.path.normpath(rpm_directory) drpm_directory = os.path.normpath(drpm_directory) # Fixup first directory directories[0] = rpm_directory directories[1] = drpm_directory # Set count if cmds['count'] == None: cmds['count'] = 0 return cmds, directories if __name__ == '__main__': cmds, directories = parseArgs(sys.argv[1:]) if cmds['dist-update'] != None: if createPrestoRepo(cmds['dist-update'], directories[1], directories[0], cmds['count'], cmds['do-first']): sys.exit(0) else: sys.exit(1) else: if createPrestoRepo(directories[0], directories[1], None, cmds['count'], cmds['do-first']): sys.exit(0) else: sys.exit(1)