#!/usr/bin/python -t # -*- coding: utf-8; mode: Python; indent-tabs-mode: nil; -*- # # Check repositories for obsolete packages still being available. # Author: Michael Schwendt # Version: 2013-09-22 # # 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, glob import re from optparse import OptionParser #import pdb import codecs, pickle from urllib import FancyURLopener import yum, yum.Errors from yum.misc import getCacheDir import rpmUtils.miscutils, rpmUtils.arch from yum.constants import * from yum.packageSack import ListPackageSack def parseArgs(): usage = "usage: %s [-c ] [-a ] [-r ] [-r ]" % sys.argv[0] parser = OptionParser(usage=usage) parser.add_option("-a", "--arch", default=[], action='append', help='check packages of the given archs, can be specified multiple ' + 'times (default: current arch)') parser.add_option("-c", "--config", default='/etc/yum.conf', help='config file to use (defaults to /etc/yum.conf)') parser.add_option("-d", "--cachedir", default='', help="specify a custom directory for metadata cache") parser.add_option("-r", "--repoid", default=[], action='append', help="specify repo to search within, can be specified multiple times") (opts, args) = parser.parse_args() return (opts, args) class RepoLister(yum.YumBase): def __init__(self, arch = [], config = ''): yum.YumBase.__init__(self) if yum.__version__ < '3.2.24': if arch: self.arch = arch[0] else: self.arch = None else: self._rc_arches = arch if yum.__version__ < '3.0': # TODO: check self.doConfigSetup(fn = config) else: self.doConfigSetup(fn = config, init_plugins = False) if hasattr(self.repos, 'sqlite'): self.repos.sqlite = False self.repos._selectSackType() self.pkglist = [] self.obsolete = {} self.srpmbuilds = {} self.whatobsoletes = {} def readMetadata(self): self.doRepoSetup() if yum.__version__ < '3.2.24': self.doSackSetup(rpmUtils.arch.getArchList(self.arch)) else: archs = [] if not self._rc_arches: archs.extend(self.arch.archlist) else: for arch in self._rc_arches: archs.extend(self.arch.get_arch_list(arch)) self.doSackSetup(archs) #self.doSackFilelistPopulate() # yum >= 2.9 for repo in self.repos.listEnabled(): self.repos.populateSack(which=[repo.id], mdtype='filelists') def returnNewest(self): pkgs = self.pkgSack.returnNewestByName() mypkgSack = ListPackageSack(pkgs) pkgtuplist = mypkgSack.simplePkgList() # Support new checkForObsolete code in Yum (#190116) # so we don't examine old _obsolete_ sub-packages. import rpmUtils.updates self.up = rpmUtils.updates.Updates([],pkgtuplist) self.up.rawobsoletes = mypkgSack.returnObsoletes() self.pkglist = [] for pkg in pkgs: thispkgobsdict = {} try: thispkgobsdict = self.up.checkForObsolete([pkg.pkgtup]) if thispkgobsdict.has_key(pkg.pkgtup): # print 'Obsolete', pkg #pkg.pkgtup srpm = pkg.returnSimple('sourcerpm') # print ' src.rpm:', srpm (sn, sv, sr, se, sa) = rpmUtils.miscutils.splitFilename(srpm) # build a map # key: %name of obsolete src.rpm # value: list of obsolete packages (pkg objects) self.obsolete.setdefault(sn,[]) self.obsolete[sn].append(pkg) # since there may be older srpms with obsolete builds, # only look at %name and not EVR # build a map # key: %name of obsolete src.rpm # value: list of src.rpm %name of the replacements for whatobs in thispkgobsdict[pkg.pkgtup]: (n,a,e,v,r) = whatobs resolve_sack = self.whatProvides(n,'EQ',(e,v,r)) for po in resolve_sack: (pn,pa,pe,pv,pr) = po.pkgtup if pa != a: continue spkgtup = rpmUtils.miscutils.splitFilename( po.returnSimple('sourcerpm') ) self.whatobsoletes.setdefault(sn,[]) if spkgtup[0] not in self.whatobsoletes[sn]: self.whatobsoletes[sn].append(spkgtup[0]) except: pass self.pkglist.append(pkg) def sortbyname(a,b): return cmp(a.__str__(),b.__str__()) self.pkglist.sort(sortbyname) return self.pkglist def findObsoletes(self): for pkg in self.returnNewest(): srpm = pkg.returnSimple('sourcerpm') (sn, sv, sr, se, sa) = rpmUtils.miscutils.splitFilename(srpm) # build a map # key: %name of src.rpm # value: list of builds that refer to this src.rpm self.srpmbuilds.setdefault(sn,[]) self.srpmbuilds[sn].append(pkg) self.deadobs = [] self.allobs = [] self.subobs = [] self.subpkgs = {} self.subpkgsleft = {} for srpmname,pkgs in self.obsolete.iteritems(): if not self.srpmbuilds.has_key(srpmname): print 'ERROR: build without src.rpm:', pkg else: builds = self.srpmbuilds[srpmname] for pkg in pkgs: if pkg not in builds: print 'ERROR: obsolete package not belonging to src.rpm:', pkg, srpm else: builds.remove(pkg) self.subpkgs.setdefault(srpmname,[]) self.subpkgs[srpmname].append(pkg.returnSimple('name')) who = self.whatobsoletes[srpmname] if srpmname in who: who.remove(srpmname) # len(who)==0 means the src.rpm's packages obsolete own (sub)packages only if len(builds)==0: deadurl = 'http://pkgs.fedoraproject.org/cgit/'+srpmname+'.git/plain/dead.package' try: opener = FancyURLopener() f = opener.open(deadurl) l = f.readlines() #print len(l) if len(l)>0: self.deadobs.append(srpmname) elif len(who)>0: # other packages obsolete all from this src.rpm self.allobs.append(srpmname) except IOError, (_err, _strerr): print _strerr elif len(who)>0: # other packages obsolete some (sub)pkgs from this src.rpm self.subobs.append(srpmname) self.subpkgsleft.setdefault(srpmname,len(builds)) print "Dead and all builds obsoleted:" print "------------------------------" self.deadobs.sort() for n in self.deadobs: print n for who in self.whatobsoletes[n]: print ' obsoleted by:', who print print "Undead and all builds obsoleted:" print "--------------------------------" self.allobs.sort() for n in self.allobs: print n for who in self.whatobsoletes[n]: print ' obsoleted by:', who print print "Only obsolete subpackage(s):" print "----------------------------" self.subobs.sort() for n in self.subobs: print n, self.subpkgs[n], self.subpkgsleft[n], 'left' return def log(self, value, msg): # print msg pass def main(): (opts, cruft) = parseArgs() if yum.__version__ < '3.2.24': if len(opts.arch)>1: print 'ERROR: can handle only a single arch with Yum < 3.2.24' sys.exit(errno.EINVAL) # Full set of repositories. global lister lister = RepoLister(arch=opts.arch, config=opts.config) if os.geteuid() != 0 or opts.cachedir != '': if opts.cachedir != '': cachedir = opts.cachedir else: cachedir = getCacheDir() if cachedir is None: print "Error: Could not make cachedir, exiting" sys.exit(50) lister.repos.setCacheDir(cachedir) if len(opts.repoid)>0: for repo in lister.repos.repos.values(): if repo.id not in opts.repoid: repo.disable() else: repo.enable() print 'Target distribution:' for r in lister.repos.listEnabled(): print ' ', r # print r.dump() try: print 'Reading repository metadata...' sys.stdout.flush() lister.readMetadata() except yum.Errors.RepoError, e: print e raise lister.findObsoletes() sys.exit(0) if __name__ == "__main__": main()