#!/usr/bin/python -t # -*- mode: Python; indent-tabs-mode: nil; -*- # # Unfinished static library devel package checker for repositories. # Author: Michael Schwendt # Version: 2010-01-05 # # 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 yum, yum.Errors from yum.misc import getCacheDir import rpmUtils.miscutils, rpmUtils.arch from yum.constants import * from yum.packageSack import ListPackageSack multicompatmode = False # off by default and just a hidden hack def isWantedName(name,regexplist): for r in regexplist: if re.compile(r).search(name): return True return False # reject by default 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="add a repo to search within, can be specified multiple times (default is all disabled)") (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.filesdict = {} def fixYum(self): # Work around bug in yum <= 2.6.1. Without this, when using # multiple instances of this class, package objects point to # unconfigured repositories, e.g. in downloadHeader(). import yum.packages yum.packages.base = self 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 = [] # for l in self.pkgSack.returnNewestByName(): # traceback with >= 2.9 # for p in l: # pkgs.append(p) pkgs = self.pkgSack.returnNewestByNameArch() mypkgSack = ListPackageSack(pkgs) pkgtuplist = mypkgSack.simplePkgList() # Support new checkForObsolete code in Yum (#190116) # _if available_ # 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): continue except: pass self.pkglist.append(pkg) def sortbyname(a,b): return cmp(a.__str__(),b.__str__()) self.pkglist.sort(sortbyname) return self.pkglist def log(self, value, msg): # print msg pass def isInPrco(name,prco): "check whether name is in a list of prco tuples" for (n,f,(e,v,r)) in prco: if n == name: return True return False def firstObsoletesSecond(pkg1,pkg2): obs = pkg1.returnPrco('obsoletes') prov = pkg2.returnPrco('provides') for (n,f,(e,v,r)) in prov: if isInPrco(n,obs): return True return False def escplusplus(n): while n.find('++')>=0: n= n.replace('++','\+\+') return n class Suspect: def __init__(self, pkg): self.pkg = pkg self.afiles = [] self.sofiles = [] self.name = pkg.returnSimple('name') self.srpm = pkg.returnSimple('sourcerpm') self.isdevel = isWantedName( self.name, ['-devel$'] ) self.isstatic = isWantedName( self.name, ['-static$'] ) self.badname = False self.develreqstatic = False self.virtualstatic = False self.virtualdevel = False self.aso = False self.asomap = [] # examine -devel and -static packages if not self.isdevel and not self.isstatic: return self.prov = self.pkg.returnPrco('provides') self.req = self.pkg.returnPrco('requires') # create lists of .a and .so files for f in self.pkg.returnFileEntries(): if isWantedName( f, ['\\.a$'] ): self.afiles.append(f) elif isWantedName( f, ['\\.so$'] ): self.sofiles.append(f) # packages should be named either -devel or -static # but not -static-devel or -devel-static if self.isdevel and self.name.find('-static')>=0: self.badname = True elif self.isstatic and self.name.find('-devel')>=0: self.badname = True # check if -devel requires -static if self.isdevel: staticname = self.name[:-len('-devel')]+'-static' for (n,f,(e,v,r)) in self.req: staticre = '^%s$' % escplusplus(staticname) if isWantedName( n, [staticre] ): self.develreqstatic = True # TODO: general check for -devel requires *any* -static pkg # check if -devel provides -static if self.isdevel: for (n,f,(e,v,r)) in self.prov: if isWantedName( n, ['-static$'] ): self.virtualstatic = True break # check if -static provides -devel if self.isstatic: for (n,f,(e,v,r)) in self.prov: if isWantedName( n, ['-devel$'] ): self.virtualdevel = True break # check if any .so matches a .a? for so in self.sofiles: sobasename = so[:-3] tocheck = [] tocheck.append( sobasename+'.a' ) tocheck.append( sobasename+'-static.a' ) tocheck.append( sobasename+'_static.a' ) for a in tocheck: if a in self.afiles: self.aso = True self.asomap.append( (so,a) ) def is_suspicious(self): return (len(self.afiles) or len(self.sofiles) or self.badname or self.develreqstatic or self.aso or self.virtualstatic or self.virtualdevel) 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 def printfiles(a): if not a: return for l in a: print ' ', l suspects = [] for pkg in lister.returnNewest(): s = Suspect(pkg) if s.is_suspicious(): suspects.append(s) print "Misnamed -devel/-static packages:" for s in suspects: if s.badname: print ' ', s.name, ' from ', s.srpm print print "Shared library -devel packages that provide a virtual -static package" print "and static libraries:" for s in suspects: if s.isdevel and s.virtualstatic and s.aso: print ' ', s.name, ' from ', s.srpm for (so,a) in s.asomap: print ' ', so, ' <=> ', a print print "Shared library -devel packages that require a -static package:" for s in suspects: if s.isdevel and len(s.sofiles) and s.develreqstatic: print ' ', s.name, ' from ', s.srpm print print "Library-less -devel packages that require a -static package:" for s in suspects: if s.isdevel and len(s.sofiles)==0 and s.develreqstatic: print ' ', s.name, ' from ', s.srpm print print "Mispackaged -static libraries:" for s in suspects: if s.aso: print ' ', s.name, ' from ', s.srpm for (so,a) in s.asomap: print ' ', so, ' <=> ', a print sys.exit(0) if __name__ == "__main__": main()