diff options
author | fuzzyray <fuzzyray@gentoo.org> | 2009-05-05 17:39:24 +0000 |
---|---|---|
committer | fuzzyray <fuzzyray@gentoo.org> | 2009-05-05 17:39:24 +0000 |
commit | c819d146be6bce86d97019494173253e71b85d2f (patch) | |
tree | 200d00c2b9a420540ff9c4e0d8b3080b762fb562 /src | |
parent | Add some useful informations when using $EDITOR. (diff) | |
download | gentoolkit-c819d146be6bce86d97019494173253e71b85d2f.tar.gz gentoolkit-c819d146be6bce86d97019494173253e71b85d2f.tar.bz2 gentoolkit-c819d146be6bce86d97019494173253e71b85d2f.zip |
Rearrange trunk to support gentoolkit version 0.3. Split into gentoolkit, gentoolkit-dev, and deprecated. Import djanderson's work on the gentoolkit library and equery
svn path=/trunk/gentoolkit/; revision=589
Diffstat (limited to 'src')
77 files changed, 9095 insertions, 0 deletions
diff --git a/src/eclean/AUTHORS b/src/eclean/AUTHORS new file mode 100644 index 0000000..9263cbb --- /dev/null +++ b/src/eclean/AUTHORS @@ -0,0 +1 @@ +Thomas de Grenier de Latour (tgl) <degrenier@easyconnect.fr> diff --git a/src/eclean/ChangeLog b/src/eclean/ChangeLog new file mode 100644 index 0000000..36d9a28 --- /dev/null +++ b/src/eclean/ChangeLog @@ -0,0 +1,27 @@ +2005-12-19 Paul Varner <fuzzyray@gentoo.org> + * Add support for reqular expression matching for file names in the + exclude files. + +2005-08-28 Thomas de Grenier de Latour (tgl) <degrenier@easyconnect.fr> + * Version 0.4.1 + * added support for some "eclean-dist" and "eclean-pkg" symlinks on eclean + (and thus refactored command-line parsing and help screen code) + * accept file names in exclude files for specific distfiles protection + (useful to protect the OOo i18n files for instance, which are not in + $SRC_URI but put there manually) + * minor rewrite of some findDistfiles() code + * added /usr/lib/portage/pym python path, just to be sure it comes first + (after all, "ouput" is a pretty generic name for a python module...) + * updated manpage + +2005-08-27 Thomas de Grenier de Latour (tgl) <degrenier@easyconnect.fr> + * Version 0.4 + * added exclusion files support + * added time limit option + * added size limit option (for distfiles only) + * added fetch-restricted distfile optionnal protection + * added --package-names option for protection of all versions of installed + packages. + * removed support of multiple actions on command-line. That would have been + hell with action-specific options. + * updated manpage diff --git a/src/eclean/Makefile b/src/eclean/Makefile new file mode 100644 index 0000000..79c5895 --- /dev/null +++ b/src/eclean/Makefile @@ -0,0 +1,24 @@ +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + +dist: + mkdir -p ../../$(distdir)/src/eclean + cp eclean eclean.1 Makefile *.exclude ../../$(distdir)/src/eclean + cp AUTHORS THANKS TODO ChangeLog ../../$(distdir)/src/eclean + +install: + install -m 0755 eclean $(bindir)/ + ln -sf eclean $(bindir)/eclean-pkg + ln -sf eclean $(bindir)/eclean-dist + install -d $(sysconfdir)/eclean + install -m 0644 distfiles.exclude packages.exclude $(sysconfdir)/eclean/ + install -d $(docdir)/eclean + install -m 0644 AUTHORS THANKS TODO ChangeLog $(docdir)/eclean/ + install -m 0644 eclean.1 $(mandir)/ diff --git a/src/eclean/THANKS b/src/eclean/THANKS new file mode 100644 index 0000000..6b8dc2e --- /dev/null +++ b/src/eclean/THANKS @@ -0,0 +1,7 @@ +The starting point ideas were found here: +http://forums.gentoo.org/viewtopic.php?t=3011 + +Thanks to eswanson and far for their contributions, and to wolf31o2 for his +support. Thanks also to karltk, some of this code was at some point inspired +by his "equery" tool. And thanks to people who had a look on bug #33877: +Benjamin Braatz, fuzzyray, genone, etc. diff --git a/src/eclean/TODO b/src/eclean/TODO new file mode 100644 index 0000000..04e64ca --- /dev/null +++ b/src/eclean/TODO @@ -0,0 +1,16 @@ +- exclusion file syntax could be improved (maybe it should support real + dep-atoms, or wildcards, etc.) + +- some policy to keep the X latest versions of a package (in each of its + SLOT maybe) would be really cool... + +- add an option to protect system binary packages + => yup, but later... (needs some portage modifications to be done right) + +- add actions for PORT_LOGDIR and/or /var/tmp/portage cleaning? + => bah, don't know... imho tmpreaper or find+rm onliners are enough here + +- cleanup of DISTDIR/cvs-src when action=distfiles + => i never use cvs ebuilds, i should check what it does exactly + +- rewrite for a decent Portage API if there ever is one diff --git a/src/eclean/distfiles.exclude b/src/eclean/distfiles.exclude new file mode 100644 index 0000000..a31be55 --- /dev/null +++ b/src/eclean/distfiles.exclude @@ -0,0 +1,5 @@ +# /etc/eclean/distfiles.exclude +# In this file you can list some categories or cat/pkg-name for which you want +# to protect distfiles from "ecleaning". You can also name some specific files. +# See `man eclean` for syntax details. +metadata.dtd diff --git a/src/eclean/eclean b/src/eclean/eclean new file mode 100644 index 0000000..55cc2a7 --- /dev/null +++ b/src/eclean/eclean @@ -0,0 +1,838 @@ +#!/usr/bin/python +# Copyright 2003-2005 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +from __future__ import with_statement + +############################################################################### +# Meta: +__author__ = "Thomas de Grenier de Latour (tgl)" +__email__ = "degrenier@easyconnect.fr" +__version__ = "0.4.1" +__productname__ = "eclean" +__description__ = "A cleaning tool for Gentoo distfiles and binaries." + + +############################################################################### +# Python imports: + +import sys +import os, stat +import re +import time +import getopt +import fpformat +import signal +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage +try: + from portage.output import * +except ImportError: + from output import * + +listdir = portage.listdir + +############################################################################### +# Misc. shortcuts to some portage stuff: +port_settings = portage.settings +distdir = port_settings["DISTDIR"] +pkgdir = port_settings["PKGDIR"] + +############################################################################### +# printVersion: +def printVersion(): + print "%s (version %s) - %s" \ + % (__productname__, __version__, __description__) + print "Author: %s <%s>" % (__author__,__email__) + print "Copyright 2003-2005 Gentoo Foundation" + print "Distributed under the terms of the GNU General Public License v2" + + +############################################################################### +# printUsage: print help message. May also print partial help to stderr if an +# error from {'options','actions'} is specified. +def printUsage(error=None,help=None): + out = sys.stdout + if error: out = sys.stderr + if not error in ('actions', 'global-options', \ + 'packages-options', 'distfiles-options', \ + 'merged-packages-options', 'merged-distfiles-options', \ + 'time', 'size'): + error = None + if not error and not help: help = 'all' + if error == 'time': + eerror("Wrong time specification") + print >>out, "Time specification should be an integer followed by a"+ \ + " single letter unit." + print >>out, "Available units are: y (years), m (months), w (weeks), "+ \ + "d (days) and h (hours)." + print >>out, "For instance: \"1y\" is \"one year\", \"2w\" is \"two"+ \ + " weeks\", etc. " + return + if error == 'size': + eerror("Wrong size specification") + print >>out, "Size specification should be an integer followed by a"+ \ + " single letter unit." + print >>out, "Available units are: G, M, K and B." + print >>out, "For instance: \"10M\" is \"ten megabytes\", \"200K\" "+ \ + "is \"two hundreds kilobytes\", etc." + return + if error in ('global-options', 'packages-options', 'distfiles-options', \ + 'merged-packages-options', 'merged-distfiles-options',): + eerror("Wrong option on command line.") + print >>out + elif error == 'actions': + eerror("Wrong or missing action name on command line.") + print >>out + print >>out, white("Usage:") + if error in ('actions','global-options', 'packages-options', \ + 'distfiles-options') or help == 'all': + print >>out, " "+turquoise(__productname__), \ + yellow("[global-option] ..."), \ + green("<action>"), \ + yellow("[action-option] ...") + if error == 'merged-distfiles-options' or help in ('all','distfiles'): + print >>out, " "+turquoise(__productname__+'-dist'), \ + yellow("[global-option, distfiles-option] ...") + if error == 'merged-packages-options' or help in ('all','packages'): + print >>out, " "+turquoise(__productname__+'-pkg'), \ + yellow("[global-option, packages-option] ...") + if error in ('global-options', 'actions'): + print >>out, " "+turquoise(__productname__), \ + yellow("[--help, --version]") + if help == 'all': + print >>out, " "+turquoise(__productname__+"(-dist,-pkg)"), \ + yellow("[--help, --version]") + if error == 'merged-packages-options' or help == 'packages': + print >>out, " "+turquoise(__productname__+'-pkg'), \ + yellow("[--help, --version]") + if error == 'merged-distfiles-options' or help == 'distfiles': + print >>out, " "+turquoise(__productname__+'-dist'), \ + yellow("[--help, --version]") + print >>out + if error in ('global-options', 'merged-packages-options', \ + 'merged-distfiles-options') or help: + print >>out, "Available global", yellow("options")+":" + print >>out, yellow(" -C, --nocolor")+ \ + " - turn off colors on output" + print >>out, yellow(" -d, --destructive")+ \ + " - only keep the minimum for a reinstallation" + print >>out, yellow(" -e, --exclude-file=<path>")+ \ + " - path to the exclusion file" + print >>out, yellow(" -i, --interactive")+ \ + " - ask confirmation before deletions" + print >>out, yellow(" -n, --package-names")+ \ + " - protect all versions (when --destructive)" + print >>out, yellow(" -p, --pretend")+ \ + " - only display what would be cleaned" + print >>out, yellow(" -q, --quiet")+ \ + " - be as quiet as possible" + print >>out, yellow(" -t, --time-limit=<time>")+ \ + " - don't delete files modified since "+yellow("<time>") + print >>out, " "+yellow("<time>"), "is a duration: \"1y\" is"+ \ + " \"one year\", \"2w\" is \"two weeks\", etc. " + print >>out, " "+"Units are: y (years), m (months), w (weeks), "+ \ + "d (days) and h (hours)." + print >>out, yellow(" -h, --help")+ \ + " - display the help screen" + print >>out, yellow(" -V, --version")+ \ + " - display version info" + print >>out + if error == 'actions' or help == 'all': + print >>out, "Available", green("actions")+":" + print >>out, green(" packages")+ \ + " - clean outdated binary packages from:" + print >>out, " ",teal(pkgdir) + print >>out, green(" distfiles")+ \ + " - clean outdated packages sources files from:" + print >>out, " ",teal(distdir) + print >>out + if error in ('packages-options','merged-packages-options') \ + or help in ('all','packages'): + print >>out, "Available", yellow("options"),"for the", \ + green("packages"),"action:" + print >>out, yellow(" NONE :)") + print >>out + if error in ('distfiles-options', 'merged-distfiles-options') \ + or help in ('all','distfiles'): + print >>out, "Available", yellow("options"),"for the", \ + green("distfiles"),"action:" + print >>out, yellow(" -f, --fetch-restricted")+ \ + " - protect fetch-restricted files (when --destructive)" + print >>out, yellow(" -s, --size-limit=<size>")+ \ + " - don't delete distfiles bigger than "+yellow("<size>") + print >>out, " "+yellow("<size>"), "is a size specification: "+ \ + "\"10M\" is \"ten megabytes\", \"200K\" is" + print >>out, " "+"\"two hundreds kilobytes\", etc. Units are: "+ \ + "G, M, K and B." + print >>out + print >>out, "More detailed instruction can be found in", \ + turquoise("`man %s`" % __productname__) + + +############################################################################### +# einfo: display an info message depending on a color mode +def einfo(message="", nocolor=False): + if not nocolor: prefix = " "+green('*') + else: prefix = ">>>" + print prefix,message + + +############################################################################### +# eerror: display an error depending on a color mode +def eerror(message="", nocolor=False): + if not nocolor: prefix = " "+red('*') + else: prefix = "!!!" + print >>sys.stderr,prefix,message + + +############################################################################### +# eprompt: display a user question depending on a color mode. +def eprompt(message, nocolor=False): + if not nocolor: prefix = " "+red('>')+" " + else: prefix = "??? " + sys.stdout.write(prefix+message) + sys.stdout.flush() + + +############################################################################### +# prettySize: integer -> byte/kilo/mega/giga converter. Optionnally justify the +# result. Output is a string. +def prettySize(size,justify=False): + units = [" G"," M"," K"," B"] + approx = 0 + while len(units) and size >= 1000: + approx = 1 + size = size / 1024. + units.pop() + sizestr = fpformat.fix(size,approx)+units[-1] + if justify: + sizestr = " " + blue("[ ") + " "*(7-len(sizestr)) \ + + green(sizestr) + blue(" ]") + return sizestr + + +############################################################################### +# yesNoAllPrompt: print a prompt until user answer in yes/no/all. Return a +# boolean for answer, and also may affect the 'accept_all' option. +# Note: i gave up with getch-like functions, to much bugs in case of escape +# sequences. Back to raw_input. +def yesNoAllPrompt(myoptions,message="Do you want to proceed?"): + user_string="xxx" + while not user_string.lower() in ["","y","n","a","yes","no","all"]: + eprompt(message+" [Y/n/a]: ", myoptions['nocolor']) + user_string = raw_input() + if user_string.lower() in ["a","all"]: + myoptions['accept_all'] = True + myanswer = user_string.lower() in ["","y","a","yes","all"] + return myanswer + + +############################################################################### +# ParseArgsException: for parseArgs() -> main() communication +class ParseArgsException(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +############################################################################### +# parseSize: convert a file size "Xu" ("X" is an integer, and "u" in [G,M,K,B]) +# into an integer (file size in Bytes). Raises ParseArgsException('size') in +# case of failure. +def parseSize(size): + myunits = { \ + 'G': (1024**3), \ + 'M': (1024**2), \ + 'K': 1024, \ + 'B': 1 \ + } + try: + mymatch = re.match(r"^(?P<value>\d+)(?P<unit>[GMKBgmkb])?$",size) + mysize = int(mymatch.group('value')) + if mymatch.group('unit'): + mysize *= myunits[mymatch.group('unit').capitalize()] + except: + raise ParseArgsException('size') + return mysize + + +############################################################################### +# parseTime: convert a duration "Xu" ("X" is an int, and "u" a time unit in +# [Y,M,W,D,H]) into an integer which is a past EPOCH date. +# Raises ParseArgsException('time') in case of failure. +# (yep, big approximations inside... who cares?) +def parseTime(timespec): + myunits = {'H' : (60 * 60)} + myunits['D'] = myunits['H'] * 24 + myunits['W'] = myunits['D'] * 7 + myunits['M'] = myunits['D'] * 30 + myunits['Y'] = myunits['D'] * 365 + try: + # parse the time specification + mymatch = re.match(r"^(?P<value>\d+)(?P<unit>[YMWDHymwdh])?$",timespec) + myvalue = int(mymatch.group('value')) + if not mymatch.group('unit'): myunit = 'D' + else: myunit = mymatch.group('unit').capitalize() + except: raise ParseArgsException('time') + # calculate the limit EPOCH date + mytime = time.time() - (myvalue * myunits[myunit]) + return mytime + + +############################################################################### +# parseCmdLine: parse the command line arguments. Raise exceptions on errors or +# non-action modes (help/version). Returns an action, and affect the options +# dict. +def parseArgs(myoptions={}): + + # local function for interpreting command line options + # and setting myoptions accordingly + def optionSwitch(myoption,opts,action=None): + return_code = True + for o, a in opts: + if o in ("-h", "--help"): + if action: raise ParseArgsException('help-'+action) + else: raise ParseArgsException('help') + elif o in ("-V", "--version"): + raise ParseArgsException('version') + elif o in ("-C", "--nocolor"): + myoptions['nocolor'] = True + nocolor() + elif o in ("-d", "--destructive"): + myoptions['destructive'] = True + elif o in ("-i", "--interactive") and not myoptions['pretend']: + myoptions['interactive'] = True + elif o in ("-p", "--pretend"): + myoptions['pretend'] = True + myoptions['interactive'] = False + elif o in ("-q", "--quiet"): + myoptions['quiet'] = True + elif o in ("-t", "--time-limit"): + myoptions['time-limit'] = parseTime(a) + elif o in ("-e", "--exclude-file"): + myoptions['exclude-file'] = a + elif o in ("-n", "--package-names"): + myoptions['package-names'] = True + elif o in ("-f", "--fetch-restricted"): + myoptions['fetch-restricted'] = True + elif o in ("-s", "--size-limit"): + myoptions['size-limit'] = parseSize(a) + else: return_code = False + # sanity check of --destructive only options: + for myopt in ('fetch-restricted', 'package-names'): + if (not myoptions['destructive']) and myoptions[myopt]: + if not myoptions['quiet']: + eerror("--%s only makes sense in --destructive mode." \ + % myopt, myoptions['nocolor']) + myoptions[myopt] = False + return return_code + + # here are the different allowed command line options (getopt args) + getopt_options = {'short':{}, 'long':{}} + getopt_options['short']['global'] = "Cdipqe:t:nhV" + getopt_options['long']['global'] = ["nocolor", "destructive", \ + "interactive", "pretend", "quiet", "exclude-file=", "time-limit=", \ + "package-names", "help", "version"] + getopt_options['short']['distfiles'] = "fs:" + getopt_options['long']['distfiles'] = ["fetch-restricted", "size-limit="] + getopt_options['short']['packages'] = "" + getopt_options['long']['packages'] = [""] + # set default options, except 'nocolor', which is set in main() + myoptions['interactive'] = False + myoptions['pretend'] = False + myoptions['quiet'] = False + myoptions['accept_all'] = False + myoptions['destructive'] = False + myoptions['time-limit'] = 0 + myoptions['package-names'] = False + myoptions['fetch-restricted'] = False + myoptions['size-limit'] = 0 + # if called by a well-named symlink, set the acction accordingly: + myaction = None + if os.path.basename(sys.argv[0]) in \ + (__productname__+'-pkg', __productname__+'-packages'): + myaction = 'packages' + elif os.path.basename(sys.argv[0]) in \ + (__productname__+'-dist', __productname__+'-distfiles'): + myaction = 'distfiles' + # prepare for the first getopt + if myaction: + short_opts = getopt_options['short']['global'] \ + + getopt_options['short'][myaction] + long_opts = getopt_options['long']['global'] \ + + getopt_options['long'][myaction] + opts_mode = 'merged-'+myaction + else: + short_opts = getopt_options['short']['global'] + long_opts = getopt_options['long']['global'] + opts_mode = 'global' + # apply getopts to command line, show partial help on failure + try: opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) + except: raise ParseArgsException(opts_mode+'-options') + # set myoptions accordingly + optionSwitch(myoptions,opts,action=myaction) + # if action was already set, there should be no more args + if myaction and len(args): raise ParseArgsException(opts_mode+'-options') + # if action was set, there is nothing left to do + if myaction: return myaction + # So, we are in "eclean --foo action --bar" mode. Parse remaining args... + # Only two actions are allowed: 'packages' and 'distfiles'. + if not len(args) or not args[0] in ('packages','distfiles'): + raise ParseArgsException('actions') + myaction = args.pop(0) + # parse the action specific options + try: opts, args = getopt.getopt(args, \ + getopt_options['short'][myaction], \ + getopt_options['long'][myaction]) + except: raise ParseArgsException(myaction+'-options') + # set myoptions again, for action-specific options + optionSwitch(myoptions,opts,action=myaction) + # any remaning args? Then die! + if len(args): raise ParseArgsException(myaction+'-options') + # returns the action. Options dictionary is modified by side-effect. + return myaction + +############################################################################### +# isValidCP: check wether a string is a valid cat/pkg-name +# This is for 2.0.51 vs. CVS HEAD compatibility, i've not found any function +# for that which would exists in both. Weird... +def isValidCP(cp): + if not '/' in cp: return False + try: portage.cpv_getkey(cp+"-0") + except: return False + else: return True + + +############################################################################### +# ParseExcludeFileException: for parseExcludeFile() -> main() communication +class ParseExcludeFileException(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +############################################################################### +# parseExcludeFile: parses an exclusion file, returns an exclusion dictionnary +# Raises ParseExcludeFileException in case of fatal error. +def parseExcludeFile(filepath): + excl_dict = { \ + 'categories':{}, \ + 'packages':{}, \ + 'anti-packages':{}, \ + 'garbage':{} } + try: file = open(filepath,"r") + except IOError: + raise ParseExcludeFileException("Could not open exclusion file.") + filecontents = file.readlines() + file.close() + cat_re = re.compile('^(?P<cat>[a-zA-Z0-9]+-[a-zA-Z0-9]+)(/\*)?$') + cp_re = re.compile('^(?P<cp>[-a-zA-Z0-9_]+/[-a-zA-Z0-9_]+)$') + for line in filecontents: + line = line.strip() + if not len(line): continue + if line[0] == '#': continue + try: mycat = cat_re.match(line).group('cat') + except: pass + else: + if not mycat in portage.settings.categories: + raise ParseExcludeFileException("Invalid category: "+mycat) + excl_dict['categories'][mycat] = None + continue + dict_key = 'packages' + if line[0] == '!': + dict_key = 'anti-packages' + line = line[1:] + try: + mycp = cp_re.match(line).group('cp') + if isValidCP(mycp): + excl_dict[dict_key][mycp] = None + continue + else: raise ParseExcludeFileException("Invalid cat/pkg: "+mycp) + except: pass + #raise ParseExcludeFileException("Invalid line: "+line) + try: + excl_dict['garbage'][line] = re.compile(line) + except: + try: + excl_dict['garbage'][line] = re.compile(re.escape(line)) + except: + raise ParseExcludeFileException("Invalid file name/regular expression: "+line) + return excl_dict + + +############################################################################### +# exclDictExpand: returns a dictionary of all CP from porttree which match +# the exclusion dictionary +def exclDictExpand(excl_dict): + mydict = {} + if 'categories' in excl_dict: + # XXX: i smell an access to something which is really out of API... + for mytree in portage.portdb.porttrees: + for mycat in excl_dict['categories']: + for mypkg in listdir(os.path.join(mytree,mycat),ignorecvs=1): + mydict[mycat+'/'+mypkg] = None + if 'packages' in excl_dict: + for mycp in excl_dict['packages']: + mydict[mycp] = None + if 'anti-packages' in excl_dict: + for mycp in excl_dict['anti-packages']: + if mycp in mydict: + del mydict[mycp] + return mydict + + +############################################################################### +# exclDictMatch: checks whether a CP matches the exclusion rules +def exclDictMatch(excl_dict,pkg): + if 'anti-packages' in excl_dict \ + and pkg in excl_dict['anti-packages']: + return False + if 'packages' in excl_dict \ + and pkg in excl_dict['packages']: + return True + mycat = pkg.split('/')[0] + if 'categories' in excl_dict \ + and mycat in excl_dict['categories']: + return True + return False + + +############################################################################### +# findDistfiles: find all obsolete distfiles. +# XXX: what about cvs ebuilds? i should install some to see where it goes... +def findDistfiles( \ + myoptions, \ + exclude_dict={}, \ + destructive=False,\ + fetch_restricted=False, \ + package_names=False, \ + time_limit=0, \ + size_limit=0,): + # this regexp extracts files names from SRC_URI. It is not very precise, + # but we don't care (may return empty strings, etc.), since it is fast. + file_regexp = re.compile('([a-zA-Z0-9_,\.\-\+\~]*)[\s\)]') + clean_dict = {} + keep = [] + pkg_dict = {} + + # create a big CPV->SRC_URI dict of packages whose distfiles should be kept + if (not destructive) or fetch_restricted: + # list all CPV from portree (yeah, that takes time...) + for package in portage.portdb.cp_all(): + for my_cpv in portage.portdb.cp_list(package): + # get SRC_URI and RESTRICT from aux_get + try: (src_uri,restrict) = \ + portage.portdb.aux_get(my_cpv,["SRC_URI","RESTRICT"]) + except KeyError: continue + # keep either all or fetch-restricted only + if (not destructive) or ('fetch' in restrict): + pkg_dict[my_cpv] = src_uri + if destructive: + if not package_names: + # list all CPV from vartree + pkg_list = portage.db[portage.root]["vartree"].dbapi.cpv_all() + else: + # list all CPV from portree for CP in vartree + pkg_list = [] + for package in portage.db[portage.root]["vartree"].dbapi.cp_all(): + pkg_list += portage.portdb.cp_list(package) + for my_cp in exclDictExpand(exclude_dict): + # add packages from the exclude file + pkg_list += portage.portdb.cp_list(my_cp) + for my_cpv in pkg_list: + # skip non-existing CPV (avoids ugly aux_get messages) + if not portage.portdb.cpv_exists(my_cpv): continue + # get SRC_URI from aux_get + try: pkg_dict[my_cpv] = \ + portage.portdb.aux_get(my_cpv,["SRC_URI"])[0] + except KeyError: continue + del pkg_list + + # create a dictionary of files which should be deleted + if not (os.path.isdir(distdir)): + eerror("%s does not appear to be a directory." % distdir, myoptions['nocolor']) + eerror("Please set DISTDIR to a sane value.", myoptions['nocolor']) + eerror("(Check your /etc/make.conf and environment).", myoptions['nocolor']) + exit(1) + for file in os.listdir(distdir): + filepath = os.path.join(distdir, file) + try: file_stat = os.stat(filepath) + except: continue + if not stat.S_ISREG(file_stat[stat.ST_MODE]): continue + if size_limit and (file_stat[stat.ST_SIZE] >= size_limit): + continue + if time_limit and (file_stat[stat.ST_MTIME] >= time_limit): + continue + if 'garbage' in exclude_dict: + # Try to match file name directly + if file in exclude_dict['garbage']: + file_match = True + # See if file matches via regular expression matching + else: + file_match = False + for file_entry in exclude_dict['garbage']: + if exclude_dict['garbage'][file_entry].match(file): + file_match = True + break + + if file_match: + continue + # this is a candidate for cleaning + clean_dict[file]=[filepath] + # remove files owned by some protected packages + for my_cpv in pkg_dict: + for file in file_regexp.findall(pkg_dict[my_cpv]+"\n"): + if file in clean_dict: + del clean_dict[file] + # no need to waste IO time if there is nothing left to clean + if not len(clean_dict): return clean_dict + return clean_dict + + +############################################################################### +# findPackages: find all obsolete binary packages. +# XXX: packages are found only by symlinks. Maybe i should also return .tbz2 +# files from All/ that have no corresponding symlinks. +def findPackages( \ + myoptions, \ + exclude_dict={}, \ + destructive=False, \ + time_limit=0, \ + package_names=False): + clean_dict = {} + # create a full package dictionary + + if not (os.path.isdir(pkgdir)): + eerror("%s does not appear to be a directory." % pkgdir, myoptions['nocolor']) + eerror("Please set PKGDIR to a sane value.", myoptions['nocolor']) + eerror("(Check your /etc/make.conf and environment).", myoptions['nocolor']) + exit(1) + for root, dirs, files in os.walk(pkgdir): + if root[-3:] == 'All': continue + for file in files: + if not file[-5:] == ".tbz2": + # ignore non-tbz2 files + continue + path = os.path.join(root, file) + category = os.path.split(root)[-1] + cpv = category+"/"+file[:-5] + mystat = os.lstat(path) + if time_limit and (mystat[stat.ST_MTIME] >= time_limit): + # time-limit exclusion + continue + # dict is cpv->[files] (2 files in general, because of symlink) + clean_dict[cpv] = [path] + #if os.path.islink(path): + if stat.S_ISLNK(mystat[stat.ST_MODE]): + clean_dict[cpv].append(os.path.realpath(path)) + # keep only obsolete ones + if destructive: + mydbapi = portage.db[portage.root]["vartree"].dbapi + if package_names: cp_all = dict.fromkeys(mydbapi.cp_all()) + else: cp_all = {} + else: + mydbapi = portage.db[portage.root]["porttree"].dbapi + cp_all = {} + for mycpv in clean_dict.keys(): + if exclDictMatch(exclude_dict,portage.cpv_getkey(mycpv)): + # exclusion because of the exclude file + del clean_dict[mycpv] + continue + if mydbapi.cpv_exists(mycpv): + # exclusion because pkg still exists (in porttree or vartree) + del clean_dict[mycpv] + continue + if portage.cpv_getkey(mycpv) in cp_all: + # exlusion because of --package-names + del clean_dict[mycpv] + + return clean_dict + + +############################################################################### +# doCleanup: takes a dictionnary {'display name':[list of files]}. Calculate +# size of each entry for display, prompt user if needed, delete files if needed +# and return the total size of files that [have been / would be] deleted. +def doCleanup(clean_dict,action,myoptions): + # define vocabulary of this action + if action == 'distfiles': file_type = 'file' + else: file_type = 'binary package' + # sorting helps reading + clean_keys = clean_dict.keys() + clean_keys.sort() + clean_size = 0 + # clean all entries one by one + for mykey in clean_keys: + key_size = 0 + for file in clean_dict[mykey]: + # get total size for an entry (may be several files, and + # symlinks count zero) + if os.path.islink(file): continue + try: key_size += os.path.getsize(file) + except: eerror("Could not read size of "+file, \ + myoptions['nocolor']) + if not myoptions['quiet']: + # pretty print mode + print prettySize(key_size,True),teal(mykey) + elif myoptions['pretend'] or myoptions['interactive']: + # file list mode + for file in clean_dict[mykey]: print file + #else: actually delete stuff, but don't print anything + if myoptions['pretend']: clean_size += key_size + elif not myoptions['interactive'] \ + or myoptions['accept_all'] \ + or yesNoAllPrompt(myoptions, \ + "Do you want to delete this " \ + + file_type+"?"): + # non-interactive mode or positive answer. + # For each file, try to delete the file and clean it out + # of Packages metadata file + if action == 'packages': + metadata = portage.getbinpkg.PackageIndex() + with open(os.path.join(pkgdir, 'Packages')) as metadata_file: + metadata.read(metadata_file) + for file in clean_dict[mykey]: + # ...get its size... + filesize = 0 + if not os.path.exists(file): continue + if not os.path.islink(file): + try: filesize = os.path.getsize(file) + except: eerror("Could not read size of "\ + +file, myoptions['nocolor']) + # ...and try to delete it. + try: + os.unlink(file) + except: + eerror("Could not delete "+file, \ + myoptions['nocolor']) + # only count size if successfully deleted + else: + clean_size += filesize + if action == 'packages': + metadata.packages[:] = [p for p in metadata.packages if 'CPV' in p and p['CPV'] != file] + + if action == 'packages': + with open(os.path.join(pkgdir, 'Packages'), 'w') as metadata_file: + metadata.write(metadata_file) + + # return total size of deleted or to delete files + return clean_size + + +############################################################################### +# doAction: execute one action, ie display a few message, call the right find* +# function, and then call doCleanup with its result. +def doAction(action,myoptions,exclude_dict={}): + # define vocabulary for the output + if action == 'packages': files_type = "binary packages" + else: files_type = "distfiles" + # find files to delete, depending on the action + if not myoptions['quiet']: + einfo("Building file list for "+action+" cleaning...", \ + myoptions['nocolor']) + if action == 'packages': + clean_dict = findPackages( + myoptions, \ + exclude_dict=exclude_dict, \ + destructive=myoptions['destructive'], \ + package_names=myoptions['package-names'], \ + time_limit=myoptions['time-limit']) + else: + clean_dict = findDistfiles( \ + myoptions, \ + exclude_dict=exclude_dict, \ + destructive=myoptions['destructive'], \ + fetch_restricted=myoptions['fetch-restricted'], \ + package_names=myoptions['package-names'], \ + time_limit=myoptions['time-limit'], \ + size_limit=myoptions['size-limit']) + # actually clean files if something was found + if len(clean_dict.keys()): + # verbose pretend message + if myoptions['pretend'] and not myoptions['quiet']: + einfo("Here are "+files_type+" that would be deleted:", \ + myoptions['nocolor']) + # verbose non-pretend message + elif not myoptions['quiet']: + einfo("Cleaning "+files_type+"...",myoptions['nocolor']) + # do the cleanup, and get size of deleted files + clean_size = doCleanup(clean_dict,action,myoptions) + # vocabulary for final message + if myoptions['pretend']: verb = "would be" + else: verb = "has been" + # display freed space + if not myoptions['quiet']: + einfo("Total space that "+verb+" freed in " \ + + action + " directory: " \ + + red(prettySize(clean_size)), \ + myoptions['nocolor']) + # nothing was found, return + elif not myoptions['quiet']: + einfo("Your "+action+" directory was already clean.", \ + myoptions['nocolor']) + + +############################################################################### +# main: parse command line and execute all actions +def main(): + # set default options + myoptions = {} + myoptions['nocolor'] = port_settings["NOCOLOR"] in ('yes','true') \ + and sys.stdout.isatty() + if myoptions['nocolor']: nocolor() + # parse command line options and actions + try: myaction = parseArgs(myoptions) + # filter exception to know what message to display + except ParseArgsException, e: + if e.value == 'help': + printUsage(help='all') + sys.exit(0) + elif e.value[:5] == 'help-': + printUsage(help=e.value[5:]) + sys.exit(0) + elif e.value == 'version': + printVersion() + sys.exit(0) + else: + printUsage(e.value) + sys.exit(2) + # parse the exclusion file + if not 'exclude-file' in myoptions: + my_exclude_file = "/etc/%s/%s.exclude" % (__productname__ , myaction) + if os.path.isfile(my_exclude_file): + myoptions['exclude-file'] = my_exclude_file + if 'exclude-file' in myoptions: + try: exclude_dict = parseExcludeFile(myoptions['exclude-file']) + except ParseExcludeFileException, e: + eerror(e, myoptions['nocolor']) + eerror("Invalid exclusion file: %s" % myoptions['exclude-file'], \ + myoptions['nocolor']) + eerror("See format of this file in `man %s`" % __productname__, \ + myoptions['nocolor']) + sys.exit(1) + else: exclude_dict={} + # security check for non-pretend mode + if not myoptions['pretend'] and portage.secpass == 0: + eerror("Permission denied: you must be root or belong to the portage group.", \ + myoptions['nocolor']) + sys.exit(1) + # execute action + doAction(myaction, myoptions, exclude_dict=exclude_dict) + + +############################################################################### +# actually call main() if launched as a script +if __name__ == "__main__": + try: main() + except KeyboardInterrupt: + print "Aborted." + sys.exit(130) + sys.exit(0) + diff --git a/src/eclean/eclean.1 b/src/eclean/eclean.1 new file mode 100644 index 0000000..7d785af --- /dev/null +++ b/src/eclean/eclean.1 @@ -0,0 +1,176 @@ +.TH "eclean" "1" "0.4.1" "gentoolkit" +.SH "NAME" +eclean \- A cleaning tool for Gentoo distfiles and binary packages. +.SH "SYNOPSIS" +.LP +.B eclean \fR[\fIglobal\-options\fR] ... <\fIactions\fR> \fR[\fIaction\-options\fR] ... +.LP +.B eclean\-dist \fR[\fIglobal\-options, distfiles\-options\fR] ... +.LP +.B eclean\-pkg \fR[\fIglobal\-options, packages\-options\fR] ... +.LP +.B eclean(-dist,-pkg) \fR[\fI\-\-help, \-\-version\fR] +.SH "DESCRIPTION" +\fBeclean\fP is small tool to remove obsolete portage sources files and binary packages. +Used on a regular basis, it prevents your DISTDIR and PKGDIR directories to +infinitely grow, while not deleting files which may still be useful. +.PP +By default, eclean will protect all distfiles or binary packages corresponding to some +ebuilds available in the Portage tree. This is the safest mode, since it will protect +whatever may still be useful, for instance to downgrade a package without downloading +its sources for the second time, or to reinstall a package you unmerge by mistake +without recompiling it. Sure, it's also a mode in which your DISTDIR and PKGDIR will +stay rather big (although still not growing infinitely). For the 'distfiles', this +mode is also quit slow mode because it requiries some access to the whole Portage tree. +.PP +If you use the \-\-destructive option, eclean will only protect files corresponding to +some currently installed package (taking their exact version into account). It will +save much more space, while still preserving sources files around for minor revision +bumps, and binaries for reinstallation of corrupted packages. But it won't keep files +for less usual operations like downgrading or reinstalling an unmerged package. This +is also the fastest execution mode (big difference for distfiles), and the one used by +most other cleaning scripts around like yacleaner (at least in its version 0.3). +.PP +Somewhere in the middle, adding the \-\-package\-names option when using \-\-destructive +will protect files corresponding to all existing versions of installed packages. It will +allow easy downgrading without recompilation or redownloading in case of trouble, but +won't protect you against package uninstallation. +.PP +In addition to this main modes, some options allow to declare a few special cases file +protection rules: +.IP o +\-\-time-limit is useful to protect files which are more recent than a given amount of time. +.IP o +\-\-size-limit (for distfiles only) is useful if you want to protect files bigger than a given size. +.IP o +\-\-fetch-restricted (for distfiles only) is useful to protect manually downloaded files. +But it's also very slow (again, it's a reading of the whole Portage tree data)... +.IP o +Finally, you can list some categories or package names to protect in exclusion files (see +\fBEXCLUSION FILES\fP below). +.SH "PARAMETERS" +.SS "Global options" +.TP +\fB\-C, \-\-nocolor\fP turn off colors on output +.TP +\fB\-d, \-\-destructive\fP only keep the minimum for a reinstallation +.TP +\fB\-e, \-\-exclude\-file=<path>\fP path to the exclusion file +\fB<path>\fP is the absolute path to the exclusion file you want to use. +When this option is not used, default paths are /etc/eclean/{packages,distfiles}.exclude +(if they exist). Use /dev/null if you have such a file at it standard location and +you want to temporary ignore it. +.TP +\fB\-i, \-\-interactive\fP ask confirmation before deleting +.TP +\fB\-n, \-\-package\-names\fP protect all versions (\-\-destructive only) +.TP +\fB\-p, \-\-pretend\fP only display what would be cleaned +.TP +\fB\-q, \-\-quiet\fP be as quiet as possible, only display errors +.TP +\fB\-t, \-\-time-limit=<time>\fP don't delete files modified since <time> +\fB<time>\fP is an amount of time: "1y" is "one year", "2w" is "two weeks", etc. +.br +Units are: y (years), m (months), w (weeks), d (days) and h (hours). +.TP +\fB\-h, \-\-help\fP display the help screen +.TP +\fB\-V, \-\-version\fP display version informations +.SS "Actions" +.TP +\fBdistfiles\fR +Clean files from /usr/portage/distfiles (or whatever else is your DISTDIR in /etc/make.conf). +This action should be useful to almost any Gentoo user, we all have to big DISTDIRs sometime... +.br +\fBeclean\-dist\fP is a shortcut to call eclean with the "distfiles" action, for simplified +command\-line. +.TP +\fBpackages\fR +Clean files from /usr/portage/packages (or whatever else is your PKGDIR in /etc/make.conf). +This action is in particular useful for people who use the "buildpkg" or "buildsyspkg" +FEATURES flags. +.br +\fBeclean\-pkg\fP is a shortcut to call eclean with the "packages" action, for simplified +command\-line. +.SS "Options for the 'distfiles' action" +.TP +\fB\-f, \-\-fetch-restricted\fP protect fetch-restricted files (\-\-destructive only) +.TP +\fB\-s, \-\-size-limit=<size>\fP don't delete distfiles bigger than <size> +<size> is a size specification: "10M" is "ten megabytes", "200K" is "two hundreds kilobytes", +etc. +.br +Units are: G, M, K and B. +.SS "Options for the 'packages' action" +.TP +There is no specific option for this action. +.SH "EXCLUSION FILES" +Exclusions files are lists of packages names or categories you want to protect +in particular. This may be useful to protect more binary packages for some system +related packages for instance. Syntax is the following: +.IP o +blank lines and lines starting with a "#" (comments) are ignored. +.IP o +only one entry per line is allowed. +.IP o +if a line contains a category name, like "sys\-apps", then all packages from this +category will be protected. "sys\-apps/*" is also allowed for aesthetic reasons, but +that does NOT mean that wildcard are supported in any way for any other usage. +.IP o +if a line contains a package name ("app\-shells/bash"), then this package will be +protected. Versioned atoms like ">=app\-shells/bash\-3" are NOT supported. Also, the +full package name (with category) is mandatory. +.IP o +if a line contains a package name with an exclamation mark in front ("!sys\-apps/portage"), +then this package will be excluded from protection. This is only useful if the category +itself was protected. +.IP o +for distfiles protection, a line can also a filename to protect. This is useful if you have +there some files which are not registered by the ebuilds, like OpenOffice.org i18n files +("helpcontent_33_unix.tgz" for instance). +.LP +By default, if it exists, /etc/eclean/packages.exclude (resp. distfiles.exclude) will be use +when action is "packages" (resp. "distfiles"). This can be overide with the \-\-exclude\-file +option. +.SH "EXAMPLES" +.LP +Clean distfiles only, with per file confirmation prompt: +.br +.B # eclean \-i distfiles +.LP +Check which binary packages could be removed, with a no-color display: +.br +.B # eclean \-Cp packages +.LP +Clean binary packages of uninstalled packages, but keep all versions of installed ones: +.br +.B # eclean-pkg \-d \-n +.LP +Clean all distfiles except for installed packages (exact version), those which +are less than one month old, bigger than 50MB, or fetch-restricted: +.br +.B # eclean-dist \-d \-t1m -s50M -f +.LP +From a crontab, silently clean packages in the safest mode, and then distfiles in destructive +mode but protecting files less than a week old, every sunday at 1am: +.br +.B 0 1 * * sun \ \ eclean \-C \-q packages ; eclean \-C \-q \-d \-t1w distfiles +.".SH "BUGS" +.".TP +."The policy used to decide wether a distfile can be removed or not relies on the SRC_URI variables ."of ebuilds. It means that if an ebuild uses files that are not part of its SRC_URI, eclean will ."probably remove them. This are ebuilds bugs, please report them as such on ."http://bugs.gentoo.org. +.".TP +."In safest mode (default, without the \-\-destructive option), this script can be very slow. There +."is not much to do about it without hacking outside of the portage API. +.SH "SEE ALSO" +.TP +The Gentoo forum thread that gave birth to eclean: +.B http://forums.gentoo.org/viewtopic.php?t=3011 +.TP +The bug report requesting eclean inclusion in gentoolkit: +.B http://bugs.gentoo.org/show_bug.cgi?id=33877 +.TP +Yacleaner, one of the other similar tools: +.B http://blog.tacvbo.net/data/files/yacleaner/ +.SH "AUTHORS" +Thomas de Grenier de Latour (tgl) <degrenier@easyconnect.fr> diff --git a/src/eclean/packages.exclude b/src/eclean/packages.exclude new file mode 100644 index 0000000..8277155 --- /dev/null +++ b/src/eclean/packages.exclude @@ -0,0 +1,4 @@ +# /etc/eclean/packages.exclude +# In this file you can list some categories or cat/pkg-name for which you want +# to protect binary packages from "ecleaning". +# See `man eclean` for syntax details. diff --git a/src/epkginfo/Makefile b/src/epkginfo/Makefile new file mode 100644 index 0000000..6a8de9a --- /dev/null +++ b/src/epkginfo/Makefile @@ -0,0 +1,17 @@ +# Copyright 2007 Gentoo Foundation. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "CLIXBY (adj.) Politely rude. Bliskly vague. Firmly uninformative." + +dist: + mkdir -p ../../$(distdir)/src/epkginfo + cp Makefile epkginfo epkginfo.1 ../../$(distdir)/src/epkginfo/ + +install: + install -m 0755 epkginfo $(bindir)/ + install -m 0644 epkginfo.1 $(mandir)/ diff --git a/src/epkginfo/epkginfo b/src/epkginfo/epkginfo new file mode 100755 index 0000000..637deff --- /dev/null +++ b/src/epkginfo/epkginfo @@ -0,0 +1,210 @@ +#!/usr/bin/python +############################################################################## +# $Header: $ +############################################################################## +# Distributed under the terms of the GNU General Public License, v2 or later +# Author: Ned Ludd <solar@gentoo.org> (glue all the parts together) +# Author: Eldad Zack <eldad@gentoo.org> (earch) +# Author : Eric Olinger <EvvL AT RustedHalo DOT net> (metadata) + +# Gentoo metadata xml and arch keyword checking tool. + +import os +import sys +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage +import re +from stat import * +try: + from portage.output import * +except ImportError: + from output import * +from xml.sax import saxutils, make_parser, handler +from xml.sax.handler import feature_namespaces + +version="0.4.1" + +def getvar(pkg, var): + file = open(pkg + ".ebuild") + for line in file.readlines(): + line = line.rstrip() + if re.match("^"+var+"=",line): + vars = re.split("\"",line)[1] + file.close + return re.split(" ",vars) + file.close + +def earch(workdir): + """Prints arch keywords for a given dir""" + portdir = portage.settings["PORTDIR"] + #workdir = "." + os.chdir(workdir) + + archdict = {} + ebuildlist = [] + for file in os.listdir(workdir): + if re.search("\.ebuild$",file): + ebuildlist.append(re.split("\.ebuild$",file)[0]) + + ebuildlist.sort(lambda x,y: portage.pkgcmp(portage.pkgsplit(x),portage.pkgsplit(y))) + + for pkg in ebuildlist: + keywords = getvar(pkg, "KEYWORDS") + for arch in keywords: + if arch == "": + arch = None + archdict[arch] = pkg + + archlist = archdict.keys(); + archlist.sort() + + for pkg in ebuildlist: + print darkgreen("Keywords: ") + pkg + ":", + for value in archlist: + if (value and archdict[value] == pkg): + if value[0] == "-": + print red(value), + elif "~" == value[0]: + print blue(value), + else: + print green(value), + print "" + + +class Metadata_XML(handler.ContentHandler): + _inside_herd="No" + _inside_maintainer="No" + _inside_email="No" + _inside_longdescription="No" + + _herd = "" + _maintainers = [] + _longdescription = "" + + def startElement(self, tag, attr): + if tag == "herd": + self._inside_herd="Yes" + if tag == "longdescription": + self._inside_longdescription="Yes" + if tag == "maintainer": + self._inside_maintainer="Yes" + if tag == "email": + self._inside_email="Yes" + + def endElement(self, tag): + if tag == "herd": + self._inside_herd="No" + if tag == "longdescription": + self._inside_longdescription="No" + if tag == "maintainer": + self._inside_maintainer="No" + if tag == "email": + self._inside_email="No" + + def characters(self, contents): + if self._inside_herd == "Yes": + self._herd = contents + + if self._inside_longdescription == "Yes": + self._longdescription = contents + + if self._inside_maintainer=="Yes" and self._inside_email=="Yes": + self._maintainers.append(contents) + + +def check_metadata(full_package): + """Checks that the primary maintainer is still an active dev and list the hed the package belongs to""" + metadata_file=portage.settings["PORTDIR"] + "/" + portage.pkgsplit(full_package)[0] + "/metadata.xml" + if not os.path.exists(metadata_file): + print darkgreen("Maintainer: ") + red("Error (Missing metadata.xml)") + return 1 + + parser = make_parser() + handler = Metadata_XML() + handler._maintainers = [] + parser.setContentHandler(handler) + parser.parse( metadata_file ) + + if len(handler._herd) < 1: + print darkgreen("Herd: ") + red("Error (No Herd)") + return 1 + else: + print darkgreen("Herd: ") + handler._herd + + if len(handler._maintainers) < 1: + print darkgreen("Maintainer: ") + handler._herd + else: + print darkgreen("Maintainer: ") + ", ".join(handler._maintainers) + + if len(handler._longdescription) > 1: + print darkgreen("Description: ") + handler._longdescription + print darkgreen("Location: ") + os.path.normpath(portage.settings["PORTDIR"] + "/" + portage.pkgsplit(full_package)[0]) + + +def usage(code): + """Prints the uage information for this script""" + print green("epkginfo v" + version + "\n") + print "Usage: epkginfo [package-cat/]package" + sys.exit(code) + + +# default color setup +if ( not sys.stdout.isatty() ) or ( portage.settings["NOCOLOR"] in ["yes","true"] ): + nocolor() + +def fc(x,y): + return cmp(y[0], x[0]) + + +def grab_changelog_devs(catpkg): + try: + os.chdir(portage.settings["PORTDIR"] + "/" + catpkg) + foo="" + r=re.compile("<[^@]+@gentoo.org>", re.I) + s="\n".join(portage.grabfile("ChangeLog")) + d={} + for x in r.findall(s): + if x not in d: + d[x] = 0 + d[x] += 1 + + l=[(d[x], x) for x in d.keys()] + #l.sort(lambda x,y: cmp(y[0], x[0])) + l.sort(fc) + for x in l: + p = str(x[0]) +" "+ x[1].lstrip("<").rstrip(">") + foo += p[:p.find("@")]+", " + return foo + except: + raise + +def main (): + if len( sys.argv ) < 2: + usage(1) + + for pkg in sys.argv[1:]: + + if sys.argv[1:][:1] == "-": + print "NOT WORKING?=="+sys.argv[1:] + continue + + try: + package_list = portage.portdb.xmatch("match-all", pkg) + if package_list: + + catpkg = portage.pkgsplit(package_list[0])[0] + + print darkgreen("Package: ") + catpkg + check_metadata(package_list[0]) + earch(portage.settings["PORTDIR"] + "/" + catpkg) + #print darkgreen("ChangeLog: ") + grab_changelog_devs(catpkg) + print "" + except: + print red("Error: "+pkg+"\n") + + +if __name__ == '__main__': + main() diff --git a/src/epkginfo/epkginfo.1 b/src/epkginfo/epkginfo.1 new file mode 100644 index 0000000..cefe602 --- /dev/null +++ b/src/epkginfo/epkginfo.1 @@ -0,0 +1,34 @@ +.TH "epkginfo" "1" "0.4.1" "Ned Ludd" "gentoolkit" +.SH "NAME" +.LP +epkginfo \- Displays metadata information from packages in portage +.SH "SYNTAX" +.LP +epkginfo [\fIpackage\-cat/\fP]package +.SH "EXAMPLES" +$ epkginfo app\-portage/gentoolkit +.br +\fBPackage:\fR app\-portage/gentoolkit +.br +\fBHerd:\fR tools\-portage +.br +\fBMaintainer:\fR tools\-portage +.br +\fBLocation:\fR /usr/portage/app\-portage/gentoolkit +.br +\fBKeywords:\fR gentoolkit\-0.2.2: +.br +\fBKeywords:\fR gentoolkit\-0.2.3: mips +.br +\fBKeywords:\fR gentoolkit\-0.2.3\-r1: ppc ppc64 alpha arm s390 amd64 hppa x86 sparc ia64 m68k sh +.br +\fBKeywords:\fR gentoolkit\-0.2.4_pre3: +.br +\fBKeywords:\fR gentoolkit\-0.2.4_pre4: +.br +\fBKeywords:\fR gentoolkit\-0.2.4_pre5: ~arm ~hppa ~x86 ~m68k ~amd64 ~ppc ~sh ~x86\-fbsd ~ia64 ~alpha ~sparc ~ppc64 ~sparc\-fbsd ~mips ~s390 +.SH "AUTHORS" +.LP +Ned Ludd <solar@gentoo.org> +.SH "BUGS" +Please report any bugs to http://bugs.gentoo.org diff --git a/src/equery/AUTHORS b/src/equery/AUTHORS new file mode 100644 index 0000000..9935ef7 --- /dev/null +++ b/src/equery/AUTHORS @@ -0,0 +1,3 @@ +Karl Trygve Kalleberg <karltk@gentoo.org> + * Initial version + diff --git a/src/equery/Makefile b/src/equery/Makefile new file mode 100644 index 0000000..177427d --- /dev/null +++ b/src/equery/Makefile @@ -0,0 +1,20 @@ +# Copyright 2003 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2003 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "YADDLETHORPE (vb.) (Of offended pooves.) To exit huffily from a boutique." + +dist: + mkdir -p ../../$(distdir)/src/equery/ + cp Makefile AUTHORS README TODO equery equery.1 ../../$(distdir)/src/equery/ + +install: + install -m 0755 equery $(bindir)/ + install -d $(docdir)/equery + install -m 0644 README AUTHORS $(docdir)/equery/ + install -m 0644 equery.1 $(mandir)/ diff --git a/src/equery/README b/src/equery/README new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/equery/README diff --git a/src/equery/TODO b/src/equery/TODO new file mode 100644 index 0000000..5f38e60 --- /dev/null +++ b/src/equery/TODO @@ -0,0 +1,63 @@ +- sqlite + - regexp comparisons + - check /var/log/emerge.log for database up-to-dateness + + +------------------------- +- pkgquery language: + Query ::= NewStyle | OldStyle | with OldStyle NewStyle + NewStyle ::= NameVar in /RegExp/ + | VerVar in [ VerExpr ] + | SetVar in [ SetExpr ] + NameVar ::= PC | PN | DESCRIPTION | SRC_URI | HOMEPAGE + + SetVar ::= LICENSE | KEYWORDS | IUSE + VerVar ::= SLOT | PV | DEPEND | RDEPEND + + BinaryOp ::= and | or + UnaryOp ::= not + + VerExpr ::= SingleVer + | VerExpr BinOp VerExpr + | UnaryOp UnaryOp + + SetExpr ::= Element + | Element BinOp Element + | UnaryOp Element + + SingleVer ::= PrefixOp VersionBody ( VersionSuffix )? ( - Revision )? + PrefixOp ::= ! | < | > | <= | >= | = | ~ + VersionBody ::= Number ( . Number )+ ( . * )? + VersionSuffix ::= _ ( pre | beta | alpha | rc ) Number? + | [a-z] + Revision ::= r Number + +------ + + PC in /dev-java/ and + PN in /ant/ and + PV in [ >=1.0 or <=2.3 and =2.0.* ] and + IUSE in [ java or junit ] + + +-- + with >=dev-java/ant-1.0* + IUSE in [ java or junit ] and + SLOT in [ >=1.0 ] + + +---------- + +old cruft: + + SingleVer ::= ( Operator )? ( Category / ) PackageName ( - Version )? + Operator ::= = | > | >= | < | <= | ~ | ! + Category ::= PackageName + PackageName ::= NamePart ( - NamePart )+ + NamePart ::= [a-zA-Z+]+ + Version ::= VersionPart ( - VersionPart )+ ( _ VersionSuffix )? ( - Revision )? + VersionSuffix ::= ( pre | rc | beta | alpha ) ( Number ) ? + + old style: >=dev-java/ant-1.0* + + diff --git a/src/equery/equery b/src/equery/equery new file mode 100755 index 0000000..fd8fa4f --- /dev/null +++ b/src/equery/equery @@ -0,0 +1,1865 @@ +#!/usr/bin/python +# +# Copyright 2003-2004 Karl Trygve Kalleberg +# Copyright 2003-2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ +# Author: Karl Trygve Kalleberg <karltk@gentoo.org> + +__author__ = "Karl Trygve Kalleberg" +__email__ = "karltk@gentoo.org" +__version__ = "0.1.4" +__productname__ = "equery" +__description__ = "Gentoo Package Query Tool" + +import os +import re +import sys +import time +from glob import glob + +# portage (output module) and gentoolkit need special path modifications +sys.path.insert(0, "/usr/lib/gentoolkit/pym") + +import gentoolkit +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage + +try: + import portage.checksum as checksum + from portage.util import unique_array +except ImportError: + import portage_checksum as checksum + from portage_util import unique_array + +import gentoolkit.pprinter as pp +from gentoolkit.pprinter import print_info, print_error, print_warn, die + +# Auxiliary functions + +def fileAsStr(name, fdesc, showType=0, showMD5=0, showTimestamp=0): + """ + Return file in fdesc as a filename + @param name: + @param fdesc: + @param showType: + @param showMD5: + @param showTimestamp: + @rtype: string + """ + type = ""; fname = ""; stamp = ""; md5sum = "" + + if fdesc[0] == 'obj': + type = "file" + fname = name + stamp = timestampAsStr(int(fdesc[1])) + md5sum = fdesc[2] + elif fdesc[0] == "dir": + type = "dir" + fname = pp.path(name) + elif fdesc[0] == "sym": + type = "symlink" + stamp = timestampAsStr(int(fdesc[1].replace(")",""))) + tgt = fdesc[2].split()[0] + if Config["piping"]: + fname = name + else: + fname = pp.path_symlink(name + " -> " + tgt) + elif fdesc[0] == "fif": + type = "fifo" + fname = name + elif fdesc[0] == "dev": + type = "device" + fname = name + else: + raise Exception(name + " has unknown type: " + fdesc[0]) + + s = "" + if showType: + s += "%6s " % type + s += fname + if showTimestamp: + s += " " + stamp + " " + if showMD5: + s += " " + md5sum + " " + return s + +def timestampAsStr(timestamp): + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + + +class Command: + """ + Abstract root class for all equery commands + """ + def __init__(self): + pass + def shortHelp(self): + """Return a help formatted to fit a single line, approx 70 characters. + Must be overridden in the subclass.""" + return " - not implemented yet" + def longHelp(self): + """Return full, multiline, color-formatted help. + Must be overridden in the subclass.""" + return "help for syntax and options" + def perform(self, args): + """Stub code for performing the command. + Must be overridden in the subclass""" + pass + def parseArgs(self, args): + """Stub code for parsing command line arguments for this command. + Must be overridden in the subclass.""" + pass + + +class CmdListFiles(Command): + """List files owned by a particular package""" + def __init__(self): + self.default_options = { + "showType": 0, + "showTimestamp": 0, + "showMD5": 0, + "tree": 0, + "filter": None + } + + def parseArgs(self,args): + query = "" + need_help = 0 + opts = self.default_options + for x in args: + if x in ["-h", "--help"]: + need_help = 1 + elif x in ["--md5sum"]: + opts["showMD5"] = 1 + elif x in ["--timestamp"]: + opts["showTimestamp"] = 1 + elif x in ["--type"]: + opts["showType"] = 1 + elif x in ["--tree"]: + opts["tree"] = 1 + elif x[:9] == "--filter=": + opts["filter"] = x[9:].split(',') + elif x[0] == "/": + die(2, "The query '" + pp.pkgquery(x) + "' does not appear to be a valid package specification") + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help or query == "": + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def filterContents(self, cnt, filter): + if filter in [None,[]]: + return cnt + + mycnt = {} + + for mytype in filter: + # Filter elements by type (as recorded in CONTENTS). + if mytype in ["dir","obj","sym","dev","fif"]: + for mykey in cnt.keys(): + if cnt[mykey][0] == mytype: + mycnt[mykey] = cnt[mykey] + + if "cmd" in filter: + # List files that are in $PATH. + userpath = map(os.path.normpath,os.environ["PATH"].split(os.pathsep)) + for mykey in cnt.keys(): + if cnt[mykey][0] in ['obj','sym'] \ + and os.path.dirname(mykey) in userpath: + mycnt[mykey] = cnt[mykey] + + if "path" in filter: + # List only dirs where some files where actually installed, + # and also skip their subdirs. + mykeys = cnt.keys() + mykeys.sort() + while len(mykeys): + mykey = mykeys.pop(0) + if cnt[mykey][0] == 'dir': + i = 0 + while i < len(mykeys) : + if cnt[mykeys[i]][0] != "dir" \ + and os.path.dirname(mykeys[i]) == mykey: + mycnt[mykey] = cnt[mykey] + break + i += 1 + if i < len(mykeys): + while len(mykeys) \ + and len(mykey+"/") < len(mykeys[0]) \ + and mykey+"/" == mykeys[0][:len(mykey)+1]: + mykeys.pop(0) + + if "conf" in filter: + # List configuration files. + conf_path = gentoolkit.settings["CONFIG_PROTECT"].split() + conf_mask_path = gentoolkit.settings["CONFIG_PROTECT_MASK"].split() + conf_path = map(os.path.normpath, conf_path) + conf_mask_path = map(os.path.normpath, conf_mask_path) + for mykey in cnt.keys(): + is_conffile = False + if cnt[mykey][0] == 'obj': + for conf_dir in conf_path: + if conf_dir+"/" == mykey[:len(conf_dir)+1]: + is_conffile = True + for conf_mask_dir in conf_mask_path: + if conf_mask_dir+"/" == mykey[:len(conf_mask_dir)+1]: + is_conffile = False + break + break + if is_conffile: + mycnt[mykey] = cnt[mykey] + + + for mydoctype in ["doc","man","info"]: + # List only files from /usr/share/{doc,man,info} + mydocpath = "/usr/share/"+mydoctype+"/" + if mydoctype in filter: + for mykey in cnt.keys(): + if cnt[mykey][0] == 'obj' \ + and mykey[:len(mydocpath)] == mydocpath : + mycnt[mykey] = cnt[mykey] + + return mycnt + + def perform(self, args): + + (query, opts) = self.parseArgs(args) + + # Turn off filtering for tree output + if opts["tree"]: + opts["filter"] = None + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]") + + pkgs = gentoolkit.find_installed_packages(query, True) + for x in pkgs: + if not x.is_installed(): + continue + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(1, pp.section("* ") + "Contents of " + pp.cpv(x.get_cpv()) + ":") + + cnt = self.filterContents(x.get_contents(),opts["filter"]) + + filenames = cnt.keys() + filenames.sort() + + last=[] + for name in filenames: + if not opts["tree"]: + print_info(0, fileAsStr(name, + cnt[name], + showType=opts["showType"], + showTimestamp=opts["showTimestamp"], + showMD5=opts["showMD5"])) + else: + c = name.split( "/" )[1:] + if cnt[name][0] == "dir": + if len(last) == 0: + last = c + print pp.path(" /" + c[0]) + continue + numol = 0 + for d in c: + if d in last: + numol = last.index(d) + 1 + continue + last = c + if len(last) == 1: + print pp.path(" " + last[0]) + continue + print pp.path(" " * ( numol * 3 ) + "> " + "/" + last[-1]) + elif cnt[name][0] == "sym": + print pp.path(" " * ( bl * 3 ) + "+ ") + pp.path_symlink(c[-1] + " -> " + cnt[name][2]) + else: + bl = len(last) + print pp.path(" " * ( bl * 3 ) + "+ ") + c[-1] + + def longHelp(self): + return "List files owned by a particular package\n" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("files") + pp.localoption(" <local-opts> ") + pp.pkgquery("<cat/>packagename<-version>") + "\n" + \ + "\n" + \ + "Note: category and version parts are optional. \n" + \ + "\n" + \ + pp.localoption("<local-opts>") + " is either of: \n" + \ + " " + pp.localoption("--timestamp") + " - append timestamp\n" + \ + " " + pp.localoption("--md5sum") + " - append md5sum\n" + \ + " " + pp.localoption("--type") + " - prepend file type\n" + \ + " " + pp.localoption("--tree") + " - display results in a tree (turns off other options)\n" + \ + " " + pp.localoption("--filter=<rules>") + " - filter output\n" + \ + " " + pp.localoption("<rules>") + " is a comma separated list of elements you want to see:\n" + \ + " " + " " + pp.localoption("dir") + \ + ", " + pp.localoption("obj") + \ + ", " + pp.localoption("sym") + \ + ", " + pp.localoption("dev") + \ + ", " + pp.localoption("fifo") + \ + ", " + pp.localoption("path") + \ + ", " + pp.localoption("conf") + \ + ", " + pp.localoption("cmd") + \ + ", " + pp.localoption("doc") + \ + ", " + pp.localoption("man") + \ + ", " + pp.localoption("info") + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - list files owned by " + pp.pkgquery("pkgspec") + + +class CmdListBelongs(Command): + """List all packages owning a particular file""" + def __init__(self): + self.default_opts = { + "category": "*", + "fullRegex": 0, + "earlyOut": 0, + "nameOnly": 0 + } + + def parseArgs(self, args): + + query = [] + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-c", "--category"]: + opts["category"] = args[i+1] + skip = 1 + elif x in ["-e", "--earlyout"]: + opts["earlyOut"] = 1 + elif x in ["-f", "--full-regex"]: + opts["fullRegex"] = 1 + elif x in ["-n", "--name-only"]: + opts["nameOnly"] = 1 + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query.append(x) + + if need_help or query == []: + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + if opts["fullRegex"]: + q = query + else: + # Trim trailing and multiple slashes from query + for i in range(0, len(query)): + query[i] = re.compile('/+').sub('/', query[i]) + query[i] = query[i].rstrip('/') + q = map(lambda x: ((len(x) and x[0] == "/") and "^" or "/") + + re.escape(x) + "$", query) + try: + q = "|".join(q) + rx = re.compile(q) + except: + die(2, "The query '" + pp.regexpquery(q) + "' does not appear to be a valid regular expression") + + # Pick out only selected categories + cat = opts["category"] + filter_fn = None + if cat != "*": + filter_fn = lambda x: x.find(cat+"/")==0 + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, "[ Searching for file(s) " + pp.regexpquery(",".join(query)) + " in " + pp.cpv(cat) + "... ]") + + matches = portage.db["/"]["vartree"].dbapi.cpv_all() + #matches = gentoolkit.find_all_installed_packages(filter_fn) + + found = 0 + + def dumpToPipe(pkg): + mysplit = pkg.split("/") + cnt = portage.dblink(mysplit[0], mysplit[1], "/", gentoolkit.settings).getcontents() + #cnt = pkg.get_contents() + if not cnt: return + for file in cnt.keys(): + if rx.search(file) and (opts["category"] == "*" or portage.catpkgsplit(pkg)[0] == opts["category"]): + if opts["nameOnly"]: + x = portage.catpkgsplit(pkg) + print x[0]+"/"+x[1] + else: + print pkg + return + + class DummyExp: + pass + + def dumpToScreen(pkg): + mysplit = pkg.split("/") + cnt = portage.dblink(mysplit[0], mysplit[1], "/", gentoolkit.settings).getcontents() + #cnt = pkg.get_contents() + if not cnt: return + for file in cnt.keys(): + if rx.search(file) and (opts["category"] == "*" or portage.catpkgsplit(pkg)[0] == opts["category"]): + if opts["nameOnly"]: + x = portage.catpkgsplit(pkg) + s = x[0]+"/"+x[1] + else: + s = pkg + s += " (" + pp.path(fileAsStr(file, cnt[file])) + ")" + print_info(0, s) + if opts["earlyOut"]: + raise DummyExp + + try: + if Config["piping"]: + map(dumpToPipe, matches) + else: + map(dumpToScreen, matches) + except DummyExp: + pass + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.path("files...") + " - list all packages owning " + pp.path("files...") + def longHelp(self): + return "List all packages owning a particular set of files" + \ + "\n" + \ + "\n" + \ + pp.emph("Note: ") + "Normally, only one package will own a file. If multiple packages own the same file, it usually consitutes a problem, and should be reported.\n" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("belongs") + pp.localoption(" <local-opts> ") + pp.path("filename") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is either of: \n" + \ + " " + pp.localoption("-c, --category cat") + " - only search in category " + \ + pp.pkgquery("cat") + "\n" + \ + " " + pp.localoption("-f, --full-regex") + " - supplied query is a regex\n" + \ + " " + pp.localoption("-e, --earlyout") + " - stop when first match is found\n" + \ + " " + pp.localoption("-n, --name-only") + " - don't print the version." + +class CmdDisplayUSEs(Command): + """Advanced report of a package's USE flags""" + def __init__(self): + self.default_opts = { + "allPackages" : False + } + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-a", "--all"]: + opts["allPackages"] = True + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help or query == "": + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + + (query, opts) = self.parseArgs(args) + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]") + + if not opts["allPackages"]: + matches = gentoolkit.find_installed_packages(query, True) + if not matches: + matches = gentoolkit.find_packages(query, False) + if matches: + matches = gentoolkit.sort_package_list(matches) + matches = matches[-1:] + else: + matches = gentoolkit.find_packages(query, True) + + if not matches: + die(3, "No matching packages found for \"" + pp.pkgquery(query) + "\"") + + + useflags = gentoolkit.settings["USE"].split() + usedesc = {} + uselocaldesc = {} + + # Load global USE flag descriptions + try: + fd = open(gentoolkit.settings["PORTDIR"]+"/profiles/use.desc") + usedesc = {} + for line in fd.readlines(): + if line[0] == "#": + continue + fields = line.split(" - ", 1) + if len(fields) == 2: + usedesc[fields[0].strip()] = fields[1].strip() + except IOError: + print_warn(5, "Could not load USE flag descriptions from " + ppath(gentoolkit.settings["PORTDIR"] + "/profiles/use.desc")) + + # TODO: Add USE_EXPANDED variables to usedesc hash -- Bug #238005 + # Pseudo-code: + # for all files in gentoolkit.settings["PORTDIR"]+"/desc/*.desc + # variable name = <filename>_<field1> + # description = <field 2> + for descfile in glob(gentoolkit.settings["PORTDIR"]+"/profiles/desc/*.desc"): + try: + fd = open(descfile) + for line in fd.readlines(): + if line[0] == "#": + continue + fields = [field.strip() for field in line.split(" - ", 1)] + if len(fields) == 2: + usedesc["%s_%s" % (descfile.split("/")[-1][0:-5], fields[0],)] = fields[1] + except IOError: + print_warn(5, "Could not load USE flag descriptions from " + descfile) + + # Load local USE flag descriptions + try: + fd = open(gentoolkit.settings["PORTDIR"]+"/profiles/use.local.desc") + for line in fd.readlines(): + if line[0] == "#": + continue + fields = line.split(" - ", 1) + if len(fields) == 2: + catpkguse = re.search("(.*):(.*)", fields[0]) + if catpkguse: + if not uselocaldesc.has_key(catpkguse.group(1).strip()): + uselocaldesc[catpkguse.group(1).strip()] = {catpkguse.group(2).strip() : fields[1].strip()} + else: + uselocaldesc[catpkguse.group(1).strip()][catpkguse.group(2).strip()] = fields[1].strip() + except IOError: + print_warn(5, "Could not load USE flag descriptions from " + path(gentoolkit.settings["PORTDIR"] + "/profiles/use.local.desc")) + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, "[ Colour Code : " + pp.useflagon("set") + " " + pp.useflagoff("unset") + " ]") + print_info(3, "[ Legend : Left column (U) - USE flags from make.conf ]") + print_info(3, "[ : Right column (I) - USE flags packages was installed with ]") + + # Iterate through matches, printing a report for each package + matches = gentoolkit.sort_package_list(matches) + matches_found = 0 + for p in matches: + + matches_found += 1 + + bestver = p.get_cpv() + iuse = p.get_env_var("IUSE") + + if iuse: + # Fix Bug #91623 by making sure the list of USE flags is unique + # Added sort to make output prettier + usevar = unique_array(iuse.split()) + + # Remove prefixed +/- from flags in IUSE, Bug #232019 + for i in range(len(usevar)): + if usevar[i][0] == "+" or usevar[i][0] == "-": + usevar[i] = usevar[i][1:] + + usevar.sort() + else: + usevar = [] + + inuse = [] + if p.is_installed(): + used = p.get_use_flags().split() + else: + # cosmetic issue here as noninstalled packages don't have "used" flags + used = useflags + + # store (inuse, inused, flag, desc) + output = [] + + for u in usevar: + inuse = 0 + inused = 0 + try: + desc = usedesc[u] + except KeyError: + try: + desc = uselocaldesc[p.get_category() + "/" + p.get_name()][u] + except KeyError: + desc = "" + + if u in p.get_settings("USE").split(): + inuse = 1 + if u in used: + inused = 1 + + output.append((inuse, inused, u, desc)) + + # pretty print + if output: + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(0, "[ Found these USE variables for " + pp.cpv(bestver) + " ]") + print_info(3, pp.emph(" U I")) + maxflag_len = 0 + for inuse, inused, u, desc in output: + if len(u) > maxflag_len: + maxflag_len = len(u) + + for in_makeconf, in_installed, flag, desc in output: + markers = ["-","+"] + colour = [pp.useflagoff, pp.useflagon] + if Config["piping"]: + print_info(0, markers[in_makeconf] + flag) + else: + if in_makeconf != in_installed: + print_info(0, pp.emph(" %s %s" % (markers[in_makeconf], markers[in_installed])), False) + else: + print_info(0, " %s %s" % (markers[in_makeconf], markers[in_installed]), False) + + print_info(0, " " + colour[in_makeconf](flag.ljust(maxflag_len)), False) + + # print description + if desc: + print_info(0, " : " + desc) + else: + print_info(0, " : <unknown>") + else: + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(1, "[ No USE flags found for " + pp.cpv(p.get_cpv()) + "]") + + if Config["verbosityLevel"] >= 2: + if matches_found == 0: + s = "" + die(3, "No " + s + "packages found for " + pp.pkgquery(query)) + + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - display USE flags for " + pp.pkgquery("pkgspec") + def longHelp(self): + return "Display USE flags for a given package\n" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("uses") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is: \n" + \ + " " + pp.localoption("-a, --all") + " - include all package versions\n" + + +class CmdDisplayDepGraph(Command): + """Display tree graph of dependencies for a query""" + + def __init__(self): + self.default_opts = { + "displayUSEFlags": 1, + "fancyFormatting": 1, + "depth": 0 + } + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-U","--no-useflags"]: + opts["displayUSEFlags"] = 0 + elif x in ["-l","--linear"]: + opts["fancyFormatting"] = 0 + elif x[:8] == "--depth=": + opts["depth"] = int(x[8:]) + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help or query == "": + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]") + + matches = gentoolkit.find_packages(query, True) + + for pkg in matches: + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, pp.section("* ") + "dependency graph for " + pp.cpv(pkg.get_cpv())) + else: + print_info(0, pkg.get_cpv() + ":") + + stats = { "maxdepth": 0, "packages": 0 } + self._graph(pkg, opts, stats, 0, [], "") + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(0, "[ " + pp.cpv(pkg.get_cpv()) + " stats: packages (" + pp.number(str(stats["packages"])) + \ + "), max depth (" + pp.number(str(stats["maxdepth"])) + ") ]") + + def _graph(self, pkg, opts, stats, level=0, pkgtbl=[], suffix=""): + + stats["packages"] += 1 + stats["maxdepth"] = max(stats["maxdepth"], level) + + cpv = pkg.get_cpv() + + pfx = "" + if opts["fancyFormatting"]: + pfx = level * " " + "`-- " + print_info(0, pfx + cpv + suffix) + + pkgtbl.append(cpv) + + pkgdeps = pkg.get_runtime_deps() + pkg.get_compiletime_deps() + pkg.get_postmerge_deps() + for x in pkgdeps: + suffix = "" + cpv = x[2] + pkg = gentoolkit.find_best_match(x[0] + cpv) + if not pkg: + print pfx + x[0] + cpv + " (unable to resolve to a package / package masked or removed)" + continue + if pkg.get_cpv() in pkgtbl: + continue + if cpv.find("virtual") == 0: + suffix += " (" + pp.cpv(cpv) + ")" + if len(x[1]) and opts["displayUSEFlags"]: + suffix += " [ " + pp.useflagon(' '.join(x[1])) + " ]" + if (level < opts["depth"] or opts["depth"] <= 0): + pkgtbl = self._graph(pkg, opts, stats, level+1, pkgtbl, suffix) + return pkgtbl + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - display a dependency tree for " + pp.pkgquery("pkgspec") + def longHelp(self): + return "Display a dependency tree for a given package\n" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("depgraph") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is either of: \n" + \ + " " + pp.localoption("-U, --no-useflags") + " - do not show USE flags\n" + \ + " " + pp.localoption("-l, --linear") + " - do not use fancy formatting\n" + \ + " " + pp.localoption(" --depth=n") + " - limit dependency graph to specified depth" + + +class CmdDisplaySize(Command): + """Display disk size consumed by a package""" + def __init__(self): + self.default_opts = { + "regex": 0, + "exact": 0, + "reportSizeInBytes": 0 + } + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-b","--bytes"]: + opts["reportSizeInBytes"] = 1 + elif x in ["-f", "--full-regex"]: + opts["regex"] = 1 + elif x in ["-e", "--exact-name"]: + opts["exact"] = 1 + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + +# if need_help or query == "": + if need_help: + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + rev = "" + name = "" + ver = "" + cat = "" + + if query != "": + (cat, name, ver, rev) = gentoolkit.split_package_name(query) + if rev == "r0": rev = "" + + # replace empty strings with .* and escape regular expression syntax + if query != "": + if not opts["regex"]: + cat, name, ver, rev = [re.sub('^$', ".*", re.escape(x)) for x in cat, name, ver, rev] + else: + cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev] + + try: + if opts["exact"]: + filter_fn = lambda x: re.match(cat+"/"+name, x) + else: + filter_fn = lambda x: re.match(cat+"/.*"+name, x) + matches = gentoolkit.find_all_installed_packages(filter_fn) + except: + die(2, "The query '" + pp.regexpquery(query) + "' does not appear to be a valid regular expression") + else: + cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev] + matches = gentoolkit.find_all_installed_packages() + + matches = gentoolkit.sort_package_list(matches) + + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]") + + # If no version supplied, fix regular expression + if ver == ".*": ver = "[0-9]+[^-]*" + + if rev != ".*": # revision supplied + ver = ver + "-" + rev + + if opts["exact"]: + rx = re.compile(cat + "/" + name + "-" + ver) + else: + rx = re.compile(cat + "/.*" + name + ".*-" + ver) + + for pkg in matches: + if rx.search(pkg.get_cpv()): + (size, files, uncounted) = pkg.size() + + if Config["piping"]: + print_info(0, pkg.get_cpv() + ": total(" + str(files) + "), inaccessible(" + str(uncounted) + \ + "), size(" + str(size) + ")") + else: + print_info(0, pp.section("* ") + "size of " + pp.cpv(pkg.get_cpv())) + print_info(0, " Total files : ".rjust(25) + pp.number(str(files))) + + if uncounted: + print_info(0, " Inaccessible files : ".rjust(25) + pp.number(str(uncounted))) + + sz = "%.2f KiB" % (size/1024.0) + if opts["reportSizeInBytes"]: + sz = pp.number(str(size)) + " bytes" + + print_info(0, "Total size : ".rjust(25) + pp.number(sz)) + + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - print size of files contained in package " + pp.pkgquery("pkgspec") + def longHelp(self): + return "Print size total size of files contained in a given package" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("size") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is: \n" + \ + " " + pp.localoption("-b, --bytes") + " - report size in bytes\n" \ + " " + pp.localoption("-f, --full-regex") + " - query is a regular expression\n" + \ + " " + pp.localoption("-e, --exact-name") + " - list only those packages that exactly match\n" + +class CmdDisplayChanges(Command): + """Display changes for pkgQuery""" + pass + +class CheckException: + def __init__(self, s): + self.s = s + +class CmdCheckIntegrity(Command): + """Check timestamps and md5sums for files owned by pkgspec""" + def __init__(self): + self.default_opts = { + "showSummary" : 1, + "showGoodFiles" : 0, + "showBadFiles" : 1, + "checkTimestamp" : 1, + "checkMD5sum": 1 + } + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help: + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def getMD5sum(self, file): + return checksum.perform_md5(file, calc_prelink=1) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + if query == "": + matches=gentoolkit.find_all_installed_packages() + else: + matches = gentoolkit.find_packages(query, True) + + matches = gentoolkit.sort_package_list(matches) + + for pkg in matches: + if not pkg.is_installed(): + continue + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(1, "[ Checking " + pp.cpv(pkg.get_cpv()) + " ]") + else: + print_info(0, pkg.get_cpv() + ":") + + files = pkg.get_contents() + checked_files = 0 + good_files = 0 + for file in files.keys(): + type = files[file][0] + try: + st = os.lstat(file) + if type == "dir": + if not os.path.isdir(file): + raise CheckException(file + " exists, but is not a directory") + elif type == "obj": + mtime = files[file][1] + md5sum = files[file][2] + if opts["checkMD5sum"]: + try: + actual_checksum = self.getMD5sum(file) + except: + raise CheckException("Failed to calculate MD5 sum for " + file) + + if self.getMD5sum(file) != md5sum: + raise CheckException(file + " has incorrect md5sum") + if opts["checkTimestamp"]: + if int(st.st_mtime) != int(mtime): + raise CheckException(file + (" has wrong mtime (is %d, should be %s)" % (st.st_mtime, mtime))) + elif type == "sym": + # FIXME: nastry strippery; portage should have this fixed! + t = files[file][2] + # target = os.path.normpath(t.strip()) + target = t.strip() + if not os.path.islink(file): + raise CheckException(file + " exists, but is not a symlink") + tgt = os.readlink(file) + if tgt != target: + raise CheckException(file + " does not point to " + target) + elif type == "fif": + pass + else: + pp.print_error(file) + pp.print_error(files[file]) + pp.print_error(type) + raise CheckException(file + " has unknown type " + type) + good_files += 1 + except CheckException, (e): + print_error(e.s) + except OSError: + print_error(file + " does not exist") + checked_files += 1 + print_info(0, pp.section(" * ") + pp.number(str(good_files)) + " out of " + pp.number(str(checked_files)) + " files good") + + def shortHelp(self): + return pp.pkgquery("pkgspec") + " - check MD5sums and timestamps of " + pp.pkgquery("pkgspec") + "'s files" + def longHelp(self): + return "Check package's files against recorded MD5 sums and timestamps" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("check") + pp.pkgquery(" pkgspec") + +class CmdDisplayStatistics(Command): + """Display statistics about installed and uninstalled packages""" + pass + +class CmdWhich(Command): + """Display the filename of the ebuild for a given package + that would be used by Portage with the current configuration.""" + def __init__(self): + self.default_opts = { + "includeMasked": False + } + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-m", "--include-masked"]: + opts["includeMasked"] = True + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help or query == "": + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + matches = gentoolkit.find_packages(query, opts["includeMasked"]) + matches = gentoolkit.sort_package_list(matches) + + if matches: + pkg = matches[-1] + ebuild_path = pkg.get_ebuild_path() + if ebuild_path: + print_info(0, os.path.normpath(ebuild_path)) + else: + print_warn("There are no ebuilds to satisfy %s" % pkg.get_name()) + else: + print_error("No masked or unmasked packages found for " + pp.pkgquery(query)) + + def shortHelp(self): + return pp.pkgquery("pkgspec") + " - print full path to ebuild for package " + pp.pkgquery("pkgspec") + def longHelp(self): + # Not documenting --include-masked at this time, since I'm not sure that it is needed. - FuzzyRay + return "Print full path to ebuild for a given package" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("which ") + pp.pkgquery("pkgspec") + +class CmdListGLSAs(Command): + """List outstanding GLSAs.""" + pass + +class CmdListDepends(Command): + """List all packages directly or indirectly depending on pkgQuery""" + def __init__(self): + self.default_opts = { + "onlyDirect": 1, + "onlyInstalled": 1, + "spacing": 0, + "depth": -1 + } + # Used to cache and detect looping + self.pkgseen = [] + self.pkglist = [] + self.pkgdeps = {} + self.deppkgs = {} + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-d", "--direct"]: + opts["onlyDirect"] = 1 + elif x in ["-D", "--indirect"]: + opts["onlyDirect"] = 0 + elif x in ["-a", "--all-packages"]: + opts["onlyInstalled"] = 0 + elif x[:10] == "--spacing=": + opts["spacing"] = int(x[10:]) + elif x[:8] == "--depth=": + opts["depth"] = int(x[8:]) + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help or query == "": + print self.longHelp() + sys.exit(-1) + return (query, opts) + + def perform(self, args): + + (query, opts) = self.parseArgs(args) + + # We call ourself recursively if --indirect specified. spacing is used to control printing the tree. + spacing = opts["spacing"] + + if not Config["piping"] and Config["verbosityLevel"] >= 3 and not spacing: + print_info(3, "[ Searching for packages depending on " + pp.pkgquery(query) + "... ]") + + isdepend = gentoolkit.split_package_name(query) + isdepends = map((lambda x: x.get_cpv()), gentoolkit.find_packages(query)) + if not isdepends: + print_warn("Warning: No packages found matching %s" % query) + + # Cache the list of packages + if not self.pkglist: + if opts["onlyInstalled"]: + packages = gentoolkit.find_all_installed_packages() + else: + packages = gentoolkit.find_all_packages() + + packages = gentoolkit.sort_package_list(packages) + self.pkglist = packages + else: + packages = self.pkglist + + for pkg in packages: + pkgcpv = pkg.get_cpv() + if not pkgcpv in self.pkgdeps: + try: + deps = pkg.get_runtime_deps() + pkg.get_compiletime_deps() + pkg.get_postmerge_deps() + except KeyError, e: + # If the ebuild is not found... + continue + # Remove duplicate deps + deps = unique_array(deps) + self.pkgdeps[pkgcpv] = deps + else: + deps = self.pkgdeps[pkgcpv] + isdep = 0 + for dependency in deps: + # TODO determine if dependency is enabled by USE flag + # Find all packages matching the dependency + depstr = dependency[0]+dependency[2] + if not depstr in self.deppkgs: + try: + depcpvs = map((lambda x: x.get_cpv()), gentoolkit.find_packages(depstr)) + self.deppkgs[depstr] = depcpvs + except KeyError, e: + print_warn("") + print_warn("Package: " + pkgcpv + " contains invalid dependency specification.") + print_warn("Portage error: " + str(e)) + print_warn("") + continue + else: + depcpvs = self.deppkgs[depstr] + for x in depcpvs: + cpvs=gentoolkit.split_package_name(x) + if x in isdepends: + cat_match=1 + name_match=1 + ver_match=1 + else: + cat_match=0 + name_match=0 + ver_match=0 + # Match Category + if not isdepend[0] or cpvs[0] == isdepend[0]: + cat_match=1 + # Match Name + if cpvs[1] == isdepend[1]: + name_match=1 + # Match Version + if not isdepend[2] or ( cpvs[2] == isdepend[2] and (isdepend[3] \ + or isdepend[3] == "r0" or cpvs[3] == isdepend[3])): + ver_match=1 + + if cat_match and ver_match and name_match: + if not isdep: + if dependency[1]: + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print " " * (spacing * 2) + pp.cpv(pkg.get_cpv()), + print "(" + \ + pp.useflag(" & ".join(dependency[1]) + "? ") + \ + pp.pkgquery(dependency[0]+dependency[2]) + ")" + else: + print " " * (spacing * 2) + pkg.get_cpv() + else: + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print " " * (spacing * 2) + pp.cpv(pkg.get_cpv()), + print "(" + pp.pkgquery(dependency[0]+dependency[2]) + ")" + else: + print " " * (spacing * 2) + pkg.get_cpv() + isdep = 1 + elif not Config["piping"] and Config["verbosityLevel"] >= 3: + if dependency[1]: + print " "*len(pkg.get_cpv()) + " " * (spacing * 2) + \ + " (" + pp.useflag("&".join(dependency[1]) + "? ") + \ + pp.pkgquery(dependency[0]+dependency[2]) + ")" + else: + print " "*len(pkg.get_cpv()) + " " * (spacing * 2) + " (" + \ + pp.pkgquery(dependency[0]+dependency[2]) + ")" + + break + + # if --indirect specified, call ourselves again with the dependency + # Do not call, if we have already called ourselves. + if isdep and not opts["onlyDirect"] and pkg.get_cpv() not in self.pkgseen \ + and (spacing < opts["depth"] or opts["depth"] == -1): + self.pkgseen.append(pkg.get_cpv()) + self.perform(['=' + pkg.get_cpv(), '--indirect', '--spacing=' + str(int(opts["spacing"]+1))]) + opts["spacing"] = spacing; + + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - list all direct dependencies matching " + \ + pp.pkgquery("pkgspec") + + def longHelp(self): + return "List all direct dependencies matching a query pattern" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("depends") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is either of: \n" + \ + " " + pp.localoption("-a, --all-packages") + " - search in all available packages (slow)\n" + \ + " " + pp.localoption("-d, --direct") + " - search direct dependencies only (default)\n" + \ + " " + pp.localoption("-D, --indirect") + " - search indirect dependencies (VERY slow)\n" + \ + " " + pp.localoption(" --depth=n") + " - limit indirect dependency tree to specified depth" + + +class CmdListPackages(Command): + """List packages satisfying pkgQuery""" + def __init__(self): + self.default_opts = { + "category": "*", + "includeInstalled": 1, + "includePortTree": 0, + "includeOverlayTree": 0, + "includeMasked": 1, + "regex": 0, + "exact": 0, + "duplicates": 0 + } + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-i", "--installed"]: + opts["includeInstalled"] = 1 + elif x in ["-I", "--exclude-installed"]: + # If -I is the only option, warn + # (warning located in perform()) + opts["includeInstalled"] = 0 + elif x in ["-p", "--portage-tree"]: + opts["includePortTree"] = 1 + elif x in ["-o", "--overlay-tree"]: + opts["includeOverlayTree"] = 1 + elif x in ["-m", "--exclude-masked"]: + opts["includeMasked"] = 0 + elif x in ["-f", "--full-regex"]: + opts["regex"] = 1 + elif x in ["-e", "--exact-name"]: + opts["exact"] = 1 + elif x in ["-d", "--duplicates"]: + opts["duplicates"] = 1 + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + # Only search installed packages when listing duplicated packages + if opts["duplicates"]: + opts["includeInstalled"] = 1 + opts["includePortTree"] = 0 + opts["includeOverlayTree"] = 0 + + if need_help: + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + rev = "" + name = "" + ver = "" + cat = "" + + if query != "": + try: (cat, name, ver, rev) = gentoolkit.split_package_name(query) + except ValueError, e: + if str(e) == 'too many values to unpack': + print_error("A pattern to match against package names was expected, ") + warn_msg = "but %s has too many slashes ('/') to match any package." + die (1, warn_msg % query) + else: raise ValueError(e) + if rev == "r0": rev = "" + + package_finder = None + + if opts["includeInstalled"]: + if opts["includePortTree"] or opts["includeOverlayTree"]: + package_finder = gentoolkit.find_all_packages + else: + package_finder = gentoolkit.find_all_installed_packages + elif opts["includePortTree"] or opts["includeOverlayTree"]: + package_finder = gentoolkit.find_all_uninstalled_packages + else: + # -I was specified, and no selection of what packages to list was made + print_warn("With -I you must specify one of -i, -p or -o. Assuming -p") + opts["includePortTree"] = 1 + package_finder = gentoolkit.find_all_uninstalled_packages + + filter_fn = None + + if Config["verbosityLevel"] >= 3: + scat = "'" + cat + "'" + if not cat: + scat = "all categories" + sname = "package '" + name + "'" + if not name: + sname = "all packages" + if not Config["piping"] and Config["verbosityLevel"] >= 3: + print_info(1, "[ Searching for " + pp.cpv(sname) + " in " + pp.cpv(scat) + " among: ]") + + # replace empty strings with .* and escape regular expression syntax + if query != "": + if not opts["regex"]: + cat, name, ver, rev = [re.sub('^$', ".*", re.escape(x)) for x in cat, name, ver, rev] + else: + cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev] + + try: + if opts["exact"]: + filter_fn = lambda x: re.match(cat+"/"+name, x) + else: + filter_fn = lambda x: re.match(cat+"/.*"+name, x) + matches = package_finder(filter_fn) + except: + die(2, "The query '" + pp.regexpquery(query) + "' does not appear to be a valid regular expression") + else: + cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev] + filter_fn = lambda x: True + matches = package_finder(filter_fn) + + # Find duplicate packages + if opts["duplicates"]: + dups = {} + newmatches = [] + for pkg in matches: + mykey = pkg.get_category() + "/" + pkg.get_name() + if dups.has_key(mykey): + dups[mykey].append(pkg) + else: + dups[mykey] = [pkg] + + for mykey in dups.keys(): + if len(dups[mykey]) > 1: + newmatches += dups[mykey] + + matches = newmatches + + matches = gentoolkit.sort_package_list(matches) + + # If no version supplied, fix regular expression + if ver == ".*": ver = "[0-9]+[^-]*" + + if rev != ".*": # revision supplied + ver = ver + "-" + rev + + if opts["exact"]: + rx = re.compile(cat + "/" + name + "-" + ver) + else: + rx = re.compile(cat + "/.*" + name + ".*-" + ver) + + if opts["includeInstalled"]: + self._print_installed(matches, rx) + + if opts["includePortTree"]: + self._print_porttree(matches, rx) + + if opts["includeOverlayTree"]: + self._print_overlay(matches, rx) + + def _get_mask_status(self, pkg): + pkgmask = 0 + if pkg.is_masked(): + # Uncomment to only have package masked files show an 'M' + # maskreasons = portage.getmaskingstatus(pkg.get_cpv()) + # if "package.mask" in maskreasons: + pkgmask = pkgmask + 3 + keywords = pkg.get_env_var("KEYWORDS").split() + if gentoolkit.settings["ARCH"] not in keywords: + if "~" + gentoolkit.settings["ARCH"] in keywords: + pkgmask = pkgmask + 1 + elif "-" + gentoolkit.settings["ARCH"] in keywords or "-*" in keywords: + pkgmask = pkgmask + 2 + return pkgmask + + def _generic_print(self, header, exclude, matches, rx, status): + if Config["verbosityLevel"] >= 3: + print_info(1, header) + + pfxmodes = [ "---", "I--", "-P-", "--O" ] + maskmodes = [ " ", " ~", " -", "M ", "M~", "M-" ] + + for pkg in matches: + if exclude(pkg): + continue + + pkgmask = self._get_mask_status(pkg) + + slot = pkg.get_env_var("SLOT") + + if rx.search(pkg.get_cpv()): + if Config["piping"]: + print_info(0, pkg.get_cpv()) + else: + print_info(0, "[" + pp.installedflag(pfxmodes[status]) + "] [" + pp.maskflag(maskmodes[pkgmask]) + "] " + pp.cpv(pkg.get_cpv()) + " (" + pp.slot(slot) + ")") + + def _print_overlay(self, matches, rx): + self._generic_print( + pp.section(" *") + " overlay tree (" + pp.path(gentoolkit.settings["PORTDIR_OVERLAY"]) + ")", + lambda x: not x.is_overlay(), + matches, rx, 3) + + def _print_porttree(self, matches, rx): + self._generic_print( + pp.section(" *") + " Portage tree (" + pp.path(gentoolkit.settings["PORTDIR"]) + ")", + lambda x: x.is_overlay() or x.is_installed(), + matches, rx, 2) + + def _print_installed(self, matches, rx): + self._generic_print( + pp.section(" *") + " installed packages", + lambda x: not x.is_installed(), + matches, rx, 1) + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - list all packages matching " + pp.pkgquery("pkgspec") + def longHelp(self): + return "List all packages matching a query pattern" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("list") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is either of: \n" + \ + " " + pp.localoption("-i, --installed") + " - search installed packages (default)\n" + \ + " " + pp.localoption("-I, --exclude-installed") + " - do not search installed packages\n" + \ + " " + pp.localoption("-p, --portage-tree") + " - also search in portage tree (" + gentoolkit.settings["PORTDIR"] + ")\n" + \ + " " + pp.localoption("-o, --overlay-tree") + " - also search in overlay tree (" + gentoolkit.settings["PORTDIR_OVERLAY"] + ")\n" + \ + " " + pp.localoption("-f, --full-regex") + " - query is a regular expression\n" + \ + " " + pp.localoption("-e, --exact-name") + " - list only those packages that exactly match\n" + \ + " " + pp.localoption("-d, --duplicates") + " - list only installed duplicate packages\n" + +class CmdFindUSEs(Command): + """Find all packages with a particular USE flag.""" + def __init__(self): + self.default_opts = { + "category": "*", + "includeInstalled": 1, + "includePortTree": 0, + "includeOverlayTree": 0, + "includeMasked": 1, + "regex": 0 + } + + def parseArgs(self, args): + + query = "" + need_help = 0 + opts = self.default_opts + skip = 0 + + for i in xrange(len(args)): + + if skip: + skip -= 1 + continue + x = args[i] + + if x in ["-h","--help"]: + need_help = 1 + break + elif x in ["-i", "--installed"]: + opts["includeInstalled"] = 1 + elif x in ["-I", "--exclude-installed"]: + opts["includeInstalled"] = 0 + elif x in ["-p", "--portage-tree"]: + opts["includePortTree"] = 1 + elif x in ["-o", "--overlay-tree"]: + opts["includeOverlayTree"] = 1 + elif x in ["-m", "--exclude-masked"]: + opts["includeMasked"] = 0 + elif x[0] == "-": + print_warn("unknown local option %s, ignoring" % x) + else: + query = x + + if need_help: + print_info(0, self.longHelp()) + sys.exit(-1) + + return (query, opts) + + def perform(self, args): + (query, opts) = self.parseArgs(args) + + rev = ".*" + name = ".*" + ver = ".*" + cat = ".*" + + package_finder = None + + if opts["includeInstalled"] and (opts["includePortTree"] or opts["includeOverlayTree"]): + package_finder = gentoolkit.find_all_packages + elif opts["includeInstalled"]: + package_finder = gentoolkit.find_all_installed_packages + elif opts["includePortTree"] or opts["includeOverlayTree"]: + package_finder = gentoolkit.find_all_uninstalled_packages + + if not package_finder: + die(2,"You must specify one of -i, -p or -o") + + filter_fn = lambda x: True + + if Config["verbosityLevel"] >= 3: + scat = "'" + cat + "'" + if cat == ".*": + scat = "all categories" + if not Config["piping"]: + print_info(2, "[ Searching for USE flag " + pp.useflag(query) + " in " + pp.cpv(scat) + " among: ]") + if opts["includeInstalled"]: + print_info(1, pp.section(" *") + " installed packages") + if opts["includePortTree"]: + print_info(1, pp.section(" *") + " Portage tree (" + pp.path(gentoolkit.settings["PORTDIR"]) + ")") + if opts["includeOverlayTree"]: + print_info(1, pp.section(" *") + " overlay tree (" + pp.path(gentoolkit.settings["PORTDIR_OVERLAY"]) + ")") + + matches = package_finder(filter_fn) + + rx = re.compile(cat + "/" + name + "-" + ver + "(-" + rev + ")?") + pfxmodes = [ "---", "I--", "-P-", "--O" ] + maskmodes = [ " ", " ~", " -", "M ", "M~", "M-" ] + for pkg in matches: + status = 0 + if pkg.is_installed(): + status = 1 + elif pkg.is_overlay(): + status = 3 + else: + status = 2 + + useflags = pkg.get_env_var("IUSE").split() + useflags = [f.lstrip("+-") for f in useflags] + + if query not in useflags: + continue + + # Determining mask status + pkgmask = 0 + if pkg.is_masked(): + pkgmask = pkgmask + 3 + keywords = pkg.get_env_var("KEYWORDS").split() + if "~"+gentoolkit.settings["ARCH"] in keywords: + pkgmask = pkgmask + 1 + elif "-*" in keywords or "-"+gentoolkit.settings["ARCH"] in keywords: + pkgmask = pkgmask + 2 + + # Determining SLOT value + slot = pkg.get_env_var("SLOT") + + if (status == 1 and opts["includeInstalled"]) or \ + (status == 2 and opts["includePortTree"]) or \ + (status == 3 and opts["includeOverlayTree"]): + if Config["piping"]: + print_info(0, pkg.get_cpv()) + else: + print_info(0, "[" + pp.installedflag(pfxmodes[status]) + "] [" + pp.maskflag(maskmodes[pkgmask]) + "] " + pp.cpv(pkg.get_cpv()) + " (" + pp.slot(slot) + ")") + + def shortHelp(self): + return pp.localoption("<local-opts> ") + pp.pkgquery("useflag") + " - list all packages with " + pp.pkgquery("useflag") + def longHelp(self): + return "List all packages with a particular USE flag" + \ + "\n" + \ + "Syntax:\n" + \ + " " + pp.command("list") + pp.localoption(" <local-opts> ") + pp.pkgquery("useflag") + \ + "\n" + \ + pp.localoption("<local-opts>") + " is either of: \n" + \ + " " + pp.localoption("-i, --installed") + " - search installed packages (default)\n" + \ + " " + pp.localoption("-I, --exclude-installed") + " - do not search installed packages\n" + \ + " " + pp.localoption("-p, --portage-tree") + " - also search in portage tree (" + gentoolkit.settings["PORTDIR"] + ")\n" + \ + " " + pp.localoption("-o, --overlay-tree") + " - also search in overlay tree (" + gentoolkit.settings["PORTDIR_OVERLAY"] + ")\n" + +# +# Command line tokens to commands mapping +# + +Known_commands = { + "list" : CmdListPackages(), + "files" : CmdListFiles(), + "belongs" : CmdListBelongs(), + "depends" : CmdListDepends(), + "hasuse" : CmdFindUSEs(), + "uses" : CmdDisplayUSEs(), + "depgraph" : CmdDisplayDepGraph(), + "changes" : CmdDisplayChanges(), + "size" : CmdDisplaySize(), + "check" : CmdCheckIntegrity(), + "stats" : CmdDisplayStatistics(), + "glsa" : CmdListGLSAs(), + "which": CmdWhich() + } + +# Short command line tokens + +Short_commands = { + "a" : "glsa", + "b" : "belongs", + "c" : "changes", + "d" : "depends", + "f" : "files", + "g" : "depgraph", + "h" : "hasuse", + "k" : "check", + "l" : "list", + "s" : "size", + "t" : "stats", + "u" : "uses", + "w" : "which" +} + +from gentoolkit import Config + +Config = { + # Query will include packages installed on the system + "installedPackages": 1, + # Query will include packages available for installation + "uninstalledPackages": 0, + # Query will include overlay packages (iff uninstalledPackages==1) + "overlayPackages": 1, + # Query will include masked packages (iff uninstalledPackages==1) + "maskedPackages": 0, + # Query will only consider packages in the following categories, empty means all. + "categoryFilter": [], + # Enable color output (-1 = use Portage setting, 0 = force off, 1 = force on) + "color": -1, + # Level of detail on the output + "verbosityLevel": 3, + # Query will display info for multiple SLOTed versions + "considerDuplicates": 1, + # Are we writing to a pipe? + "piping": 0 +} + +def printVersion(): + """Print the version of this tool to the console.""" + print_info(0, __productname__ + "(" + __version__ + ") - " + \ + __description__) + print_info(0, "Author(s): " + __author__) + +def buildReverseMap(m): + r = {} + for x in m.keys(): + r[m[x]] = x + return r + +def printUsage(): + """Print full usage information for this tool to the console.""" + + short_cmds = buildReverseMap(Short_commands); + + print_info(0, pp.emph("Usage: ") + pp.productname(__productname__) + pp.globaloption(" <global-opts> ") + pp.command("command") + pp.localoption(" <local-opts>")) + print_info(0, "where " + pp.globaloption("<global-opts>") + " is one of") + print_info(0, pp.globaloption(" -q, --quiet") + " - minimal output") + print_info(0, pp.globaloption(" -C, --nocolor") + " - turn off colours") + print_info(0, pp.globaloption(" -h, --help") + " - this help screen") + print_info(0, pp.globaloption(" -V, --version") + " - display version info") + print_info(0, pp.globaloption(" -N, --no-pipe") + " - turn off pipe detection") + + print_info(0, "where " + pp.command("command") + "(" + pp.command("short") + ") is one of") + keys = Known_commands.keys() + keys.sort() + for x in keys: + print_info(0, " " + pp.command(x) + "(" + pp.command(short_cmds[x]) + ") " + \ + Known_commands[x].shortHelp()) + print + +def configure(): + """Set up default configuration. + """ + + # Guess colour output + if (Config["color"] == -1 and \ + ((not sys.stdout.isatty()) or (gentoolkit.settings["NOCOLOR"] in ["yes","true"]))): + pp.output.nocolor() + + # Guess piping output + if not sys.stdout.isatty(): + Config["piping"] = True + else: + Config["piping"] = False + + +def parseArgs(args): + """Parse tool-specific arguments. + + Arguments are on the form equery <tool-specific> [command] <command-specific> + + This function will only parse the <tool-specific> bit. + """ + command = None + local_opts = [] + showhelp = 0 + + def expand(x): + if x in Short_commands.keys(): + return Short_commands[x] + return x + + for i in xrange(len(args)): + x = args[i] + if 0: + pass + elif x in ["-h", "--help"]: + showhelp = True + elif x in ["-V", "--version"]: + printVersion() + sys.exit(0) + elif x in ["-C", "--nocolor"]: + Config["color"] = 0 + pp.output.nocolor() + elif x in ["-N", "--no-pipe"]: + Config["piping"] = False + elif x in ["-q","--quiet"]: + Config["verbosityLevel"] = 0 + elif expand(x) in Known_commands.keys(): + command = Known_commands[expand(x)] + local_opts.extend(args[i+1:]) + if showhelp: + local_opts.append("--help") + break + else: + print_warn("unknown global option %s, reusing as local option" % x) + local_opts.append(x) + + if not command and showhelp: + printUsage() + sys.exit(0) + + return (command, local_opts) + +if __name__ == "__main__": + configure() + (cmd, local_opts) = parseArgs(sys.argv[1:]) + if cmd: + try: + cmd.perform(local_opts) + except KeyError, e: + if e and e[0].find("Specific key requires an operator") >= 0: + print_error("Invalid syntax: missing operator") + print_error("If you want only specific versions please use one of") + print_error("the following operators as prefix for the package name:") + print_error(" > >= = <= <") + print_error("Example to only match gcc versions greater or equal 3.2:") + print_error(" >=sys-devel/gcc-3.2") + print_error("") + print_error("Note: The symbols > and < are used for redirection in the shell") + print_error("and must be quoted if either one is used.") + + else: + print_error("Internal portage error, terminating") + if len(e[0]): + print_error(str(e)) + sys.exit(2) + except ValueError, e: + if isinstance(e[0], list): + print_error("Ambiguous package name " + pp.emph("\"" + local_opts[0] + "\"")) + print_error("Please use one of the following long names:") + for p in e[0]: + print_error(" " + str(p)) + else: + print_error("Internal portage error, terminating") + if len(e[0]): + print_error(str(e[0])) + sys.exit(2) + except KeyboardInterrupt: + print_info(0, "Interrupted by user, aborting.") + else: + print_error("No command or unknown command given") + printUsage() + diff --git a/src/equery/equery.1 b/src/equery/equery.1 new file mode 100644 index 0000000..27b8078 --- /dev/null +++ b/src/equery/equery.1 @@ -0,0 +1,278 @@ +.TH "equery" "1" "Oct 2005" "gentoolkit" "" +.SH "NAME" +equery \- Gentoo: Package Query Tool +.SH "SYNOPSIS" +.B equery +.I [global\-opts] command [local\-opts] +.PP + +.SH "DESCRIPTION" +equery is a flexible utility which may display various information about +packages, such as the files they own, their USE flags, the md5sum +of each file owned by a given package, and many other things. + +.SH "OPTIONS" +The 'command' is the only mandatory option to equery. Most commands require +a 'pkgspec' option, which is described by <cat/>packagename<\-version>; +namely, the package name is mandatory, while the category and version are +optional. + +[global\-opts] may be one of: + +.B \-q, \-\-quiet +causes minimal output to be emitted +.PP +.B \-C, \-\-nocolor +turns off colours +.PP +.B \-h, \-\-help +displays a help summary +.PP +.B \-V, \-\-version +displays the equery version +.PP +.B \-N, \-\-no\-pipe +turns off pipe detection +.PP + +Only one command will actually be run, at most. The possible commands are: +.TP +.B belongs <local\-opts> file +This command lists all packages owning the specified file. +.br +Note: Normally, only one package will own a file. If multiple packages own the +same file, it usually consitutes a problem, and should be reported (http://bugs.gentoo.org). +.br +.IP +<local\-opts> is either or both of: +.br +.B \-c, \-\-category cat +only search in category cat +.br +.B \-f, \-\-full\-regex +supplied query is a regex +.br +.B \-e, \-\-earlyout +stop when first match found + +.PP +.B check pkgspec +This command checks the files of the specified package against recorded MD5 +sums and timestamps. +.PP +.TP +.B depends <local\-opts> pkgspec +This command displays all dependencies matching pkgspec. +.br +<local\-opts> is either or both of: +.br +.B \-a, \-\-all\-packages +search in all available packages (slow) +.br +.B \-d, \-\-direct +search direct dependencies only (default) +.br +.B \-D, \-\-indirect +search indirect dependencies (very slow) +.br +.B \-\-depth=n +Limit depth of indirect dependency tree to n levels. Setting \-\-depth=0 is the same as not specifing \-\-indirect. +.PP +.TP +.B depgraph <local\-opts> pkgspec +This command display a dependency tree for pkgspec, by default indented to reflect +how dependancies relate to each other. +.br +.IP +<local\-opts> is either or both of: +.br +.B \-U, \-\-no\-useflags +do not show USE flags. +.br +.B \-l, \-\-linear +do not use fancy formatting +.br +.B \-\-depth=n +Limit depth of dependency graph to n levels. +.PP +.TP +.B files <local\-opts> pkgspec +This lists files owned by a particular package, optionally with extra +information specified by <local\-opts> +.br + +<local\-opts> is any combination of: +.br +.B \-\-timestamp +output the timestamp of each file +.br +.B \-\-md5sum +output the md5sum of each file +.br +.B \-\-type +output the type of each file +.br +.B \-\-tree +display results in a tree (turns off all other options) +.br +.B \-\-filter=<rules> +filter output based on files type or path +.br +.B \t<rules> +is a comma separated list of filtering rules. Available rules are: +.br +.B \t\tdir\ +regular directories +.br +.B \t\tobj\ +regular files +.br +.B \t\tsym\ +symbolic links +.br +.B \t\tdev\ +device nodes +.br +.B \t\tfifo +named pipes +.br +.B \t\tpath +shortest paths where some files where installed +.br +.B \t\tconf +configuration files (based on $CONFIG_PROTECT) +.br +.B \t\tcmd\ +user commands (based on $PATH) +.br +.B \t\tdoc\ +documentation files (from /usr/share/doc) +.br +.B \t\tman\ +manpages (from /usr/share/man) +.br +.B \t\tinfo +info pages (from /usr/share/info) +.PP +.TP +.B hasuse <local\-opts> useflag +This command lists packages matching a particular USE flag in a user\-specified combination +of installed packages, packages which are not installed, the portage tree, and +the portage overlay tree. + +<local\-opts> must not include only \-I; +if \-I is used, \-p and/or \-o must be also be present. By default, only installed +packages are searched. \-o searches only the overlay tree [and possibly +installed packages], +.I not +the main portage tree. + +.B \-i, \-\-installed +search installed packages (default) +.br +.B \-I, \-\-exclude\-installed +do not search installed packages +.br +.B \-p, \-\-portage\-tree +also search in portage tree (/usr/portage) +.br +.B \-o, \-\-overlay\-tree +also search in overlay tree (/usr/local/portage) +.PP +.TP +.B list <local\-opts> pkgspec +This command lists packages matching pkgspec in a user\-specified combination +of installed packages, packages which are not installed, the portage tree, and +the portage overlay tree. By default the list command searches for partial name matches. + +<local\-opts> \-I cannot be used by itself; +if \-I is used, \-p and/or \-o must be also be present. By default, only installed +packages are searched. \-o searches only the overlay tree [and possibly +installed packages], +\fInot\fR the main portage tree. + +.B \-i, \-\-installed +search installed packages (default) +.br +.B \-I, \-\-exclude\-installed +do not search installed packages +.br +.B \-p, \-\-portage\-tree +also search in portage tree (/usr/portage) +.br +.B \-o, \-\-overlay\-tree +also search in overlay tree (/usr/local/portage) +.br +.B \-f, \-\-full\-regex +query is a regular expression +.br +.B \-e, \-\-exact\-name +list only those packages that exactly match +.br +.B \-d, \-\-duplicates +only list installed duplicate packages +.br + +\fBOutput:\fR + +.br +The list command searches packages for the name given. If found, the following info will be displayed: the package location between the first square brackets (I for Installed packages, P for Portage, O for Overlay), the possible masks between the second (~ by keyword, - by arch or M hard masked), then the category and complete name and last of all, the slot in which the package is stored. + +\fBExamples:\fR + +equery list zilla \- list all installed versions of packages containing the string 'zilla' + +equery list \-\-exact\-name x11\-libs/gtk+ \- list all installed versions of x11\-libs/gtk+ + +equery list \-\-full\-regex '(mozilla\-firefox|mozilla\-thunderbird)' \- list all installed versions of mozilla\-firefox and mozilla\-thunderbird + +equery list \-\-duplicates \- list all installed slotted packages +.PP +.TP +.B size <local\-opts> pkgspec +This command outputs the number of files in the specified package, as well as +their total size in an appropriate unit. + +The possible values for <local\-opts>, if specified, are: +.br +.B \-b, \-\-bytes +report size in bytes +.br +.B \-f, \-\-full\-regex +query is a regular expression +.br +.B \-e, \-\-exact\-name +list only those packages that exactly match +.PP +.TP +.B uses <local\-opts> pkgspec +display USE flags for pkgspec. + +The only possible value for <local\-opts>, if specified, is: +.br +.B \-a, \-\-all +include all package versions +.PP +.B which pkgspec +print full path to ebuild for package pkgspec +.PP + +.SH "Unimplemented Options" +.PP +.B changes +.PP +.B glsa \fR \- use glsa\-check for the time being. +.PP +.B stats + + + +.SH "BUGS" +Many options aren't implemented. Command\-line parsing could use some work. +.br +Submit bug reports to http://bugs.gentoo.org +.SH "AUTHORS" +equery, original man page: Karl Trygve Kalleberg <karltk@gentoo.org>, 2003. +.br +Massive man page updates: Katerina Barone\-Adesi <katerinab@gmail.com>, 2004. + diff --git a/src/equery/tests/common-functions.sh b/src/equery/tests/common-functions.sh new file mode 100644 index 0000000..f065a0a --- /dev/null +++ b/src/equery/tests/common-functions.sh @@ -0,0 +1,52 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +function tempfilename() { + fn=$(date "+%s") + if [ ! -f ${fn}.tmp ] ; then + echo ${fn}.tmp + fi +} + +function report_pass() { + printf "%-40s - passed\n" ${1} +} + +function report_failure() { + printf "%-40s - FAILED!\n" ${1} +} + +function assert_samefile() { + diff $2 $3 && report_pass $1 || report_failure $1 +} + +function assert_eq() { + if [ $2 -eq $3 ] ; then + report_pass $1 + else + printf "FAIL: $2 ! -eq $3\n" + report_failure $1 + fi +} + +function assert_ge() { + if [ $2 -ge $3 ] ; then + report_pass $1 + else + printf "FAIL: $2 ! -ge $3\n" + report_failure $1 + fi +} + +function assert_exists() { + if [ -f $2 ] ; then + report_pass $1 + else + printf "FAIL: $2 does not exist\n" + report_failure $1 + fi +}
\ No newline at end of file diff --git a/src/equery/tests/run-all-tests.sh b/src/equery/tests/run-all-tests.sh new file mode 100755 index 0000000..4fe2dfe --- /dev/null +++ b/src/equery/tests/run-all-tests.sh @@ -0,0 +1,12 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +for x in belongs check depends depgraph files help list size uses which ; do + ./test-${x}.sh +done diff --git a/src/equery/tests/test-belongs-help.out b/src/equery/tests/test-belongs-help.out new file mode 100644 index 0000000..0d2f583 --- /dev/null +++ b/src/equery/tests/test-belongs-help.out @@ -0,0 +1,11 @@ +List all packages owning a particular set of files + +Note: Normally, only one package will own a file. If multiple packages own the same file, it usually consitutes a problem, and should be reported. + +Syntax: + belongs <local-opts> filename +<local-opts> is either of: + -c, --category cat - only search in category cat + -f, --full-regex - supplied query is a regex + -e, --earlyout - stop when first match is found + diff --git a/src/equery/tests/test-belongs.sh b/src/equery/tests/test-belongs.sh new file mode 100755 index 0000000..bd0eac4 --- /dev/null +++ b/src/equery/tests/test-belongs.sh @@ -0,0 +1,24 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_belongs() { + equery belongs $(which gcc) > ${tmpfile} + + x=$(grep "gcc-config" ${tmpfile} | wc -l) + + assert_eq ${FUNCNAME} ${x} 1 +} + +# Run tests + +test_belongs + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-changes-help.out b/src/equery/tests/test-changes-help.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/equery/tests/test-changes-help.out diff --git a/src/equery/tests/test-check-help.out b/src/equery/tests/test-check-help.out new file mode 100644 index 0000000..1e6afcf --- /dev/null +++ b/src/equery/tests/test-check-help.out @@ -0,0 +1,3 @@ +Check package's files against recorded MD5 sums and timestamps +Syntax: + size pkgspec diff --git a/src/equery/tests/test-check.sh b/src/equery/tests/test-check.sh new file mode 100755 index 0000000..803299f --- /dev/null +++ b/src/equery/tests/test-check.sh @@ -0,0 +1,39 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_check() { + equery check gcc > ${tmpfile} + + x=$(grep "sys-devel/gcc" ${tmpfile} | wc -l) + + assert_ge ${FUNCNAME} ${x} 1 + + x=$(egrep "[0-9]+ out of [0-9]+" ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} ${x} 1 +} + +test_check_permissions() { + equery check sudo > ${tmpfile} + + x=$(grep "app-admin/sudo" ${tmpfile} | wc -l) + + assert_ge ${FUNCNAME} ${x} 1 + + x=$(egrep "[0-9]+ out of [0-9]+" ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} ${x} 1 +} + +# Run tests + +test_check +test_check_permissions + +rm -f ${tmpfile} diff --git a/src/equery/tests/test-depends-help.out b/src/equery/tests/test-depends-help.out new file mode 100644 index 0000000..356cd53 --- /dev/null +++ b/src/equery/tests/test-depends-help.out @@ -0,0 +1,8 @@ +List all direct dependencies matching a query pattern +Syntax: + depends <local-opts> pkgspec +<local-opts> is either of: + -d, --direct - search direct dependencies only (default) + -D, --indirect - search indirect dependencies (VERY slow) + -i, --only-installed - search installed in installed packages only + diff --git a/src/equery/tests/test-depends.sh b/src/equery/tests/test-depends.sh new file mode 100755 index 0000000..e46d614 --- /dev/null +++ b/src/equery/tests/test-depends.sh @@ -0,0 +1,27 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_depends() { +# equery skel gcc > ${tmpfile} + +# x=$(grep "app-shells/bash" ${tmpfile} | wc -l) + true +# assert_eq ${FUNCNAME} ${x} 1 + +# x=$(grep "virtual/libc" ${tmpfile} | wc -l) +# assert_eq ${FUNCNAME} ${x} 1 +} + +# Run tests + +#test_skel + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-depgraph-help.out b/src/equery/tests/test-depgraph-help.out new file mode 100644 index 0000000..5b9fd22 --- /dev/null +++ b/src/equery/tests/test-depgraph-help.out @@ -0,0 +1,7 @@ +Display a dependency tree for a given package + +Syntax: + depgraph <local-opts> pkgspec +<local-opts> is either of: + -U, --no-useflags - do not show USE flags + -l, --linear - do not use fancy formatting diff --git a/src/equery/tests/test-depgraph.sh b/src/equery/tests/test-depgraph.sh new file mode 100755 index 0000000..016bb37 --- /dev/null +++ b/src/equery/tests/test-depgraph.sh @@ -0,0 +1,27 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_depgraph() { + equery depgraph gcc > ${tmpfile} + + x=$(grep "app-shells/bash" ${tmpfile} | wc -l) + + assert_eq ${FUNCNAME} ${x} 1 + + x=$(grep "virtual/libc" ${tmpfile} | wc -l) + assert_eq ${FUNCNAME} ${x} 1 +} + +# Run tests + +test_depgraph + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-files-help.out b/src/equery/tests/test-files-help.out new file mode 100644 index 0000000..846151f --- /dev/null +++ b/src/equery/tests/test-files-help.out @@ -0,0 +1,11 @@ +List files owned by a particular package + +Syntax: + files <local-opts> <cat/>packagename<-version> + +Note: category and version parts are optional. + +<local-opts> is either of: + --timestamp - append timestamp + --md5sum - append md5sum + --type - prepend file type diff --git a/src/equery/tests/test-files.sh b/src/equery/tests/test-files.sh new file mode 100755 index 0000000..ad0a5ea --- /dev/null +++ b/src/equery/tests/test-files.sh @@ -0,0 +1,61 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +strip_versioned_files() { + grep -v "/usr/share/doc" +} + +test_files() { + equery files bash > ${tmpfile} + + x=$(grep man ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} $x 5 + + x=$(cat ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} $x 25 +} + +test_files_timestamp() { + equery files --timestamp bash > ${tmpfile} + + x=$(grep "/bin/bash .*....-..-.. ..:..:.." ${tmpfile} | wc -l) + assert_eq ${FUNCNAME} $x 1 +} + +test_files_md5sum() { + equery files --md5sum bash > ${tmpfile} + + x=$(egrep "/bin/bash .*[0-9a-z]{30}" ${tmpfile} | wc -l) + assert_eq ${FUNCNAME} $x 1 +} + +test_files_type() { + + equery files --type bash > ${tmpfile} + + x=$(grep "file.*/bin/bash$" ${tmpfile} | wc -l) + assert_eq ${FUNCNAME} $x 1 + + x=$(grep "symlink.*/bin/rbash" ${tmpfile} | wc -l) + assert_eq ${FUNCNAME} $x 1 + + x=$(grep "dir.*/usr/share/man" ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} $x 1 +} + +# Run tests + +test_files +test_files_timestamp +test_files_md5sum +test_files_type + +rm ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-glsa-help.out b/src/equery/tests/test-glsa-help.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/equery/tests/test-glsa-help.out diff --git a/src/equery/tests/test-hasuses-help.out b/src/equery/tests/test-hasuses-help.out new file mode 100644 index 0000000..1a05645 --- /dev/null +++ b/src/equery/tests/test-hasuses-help.out @@ -0,0 +1,9 @@ +List all packages with a particular USE flag +Syntax: + list <local-opts> useflag +<local-opts> is either of: + -i, --installed - search installed packages (default) + -I, --exclude-installed - do not search installed packages + -p, --portage-tree - also search in portage tree (/usr/portage) + -o, --overlay-tree - also search in overlay tree (/usr/local/portage /usr/local/overlays/gentoo-boblycat) + diff --git a/src/equery/tests/test-help.out b/src/equery/tests/test-help.out new file mode 100644 index 0000000..26517f4 --- /dev/null +++ b/src/equery/tests/test-help.out @@ -0,0 +1,21 @@ +Usage: equery <global-opts> command <local-opts> +where <global-opts> is one of + -q, --quiet - minimal output + -C, --nocolor - turn off colours + -h, --help - this help screen + -V, --version - display version info +where command(short) is one of + belongs(b) <local-opts> files... - list all packages owning files... + changes(c) - not implemented yet + check(k) pkgspec - check MD5sums and timestamps of pkgspec's files + depends(d) <local-opts> pkgspec - list all direct dependencies matching pkgspec + depgraph(g) <local-opts> pkgspec - display a dependency tree for pkgspec + files(f) <local-opts> pkgspec - list files owned by pkgspec + glsa(a) - not implemented yet + hasuses(h) <local-opts> pkgspec - list all packages with useflag + list(l) <local-opts> pkgspec - list all packages matching pkgspec + size(s) <local-opts> pkgspec - print size of files contained in package pkgspec + stats(t) - not implemented yet + uses(u) <local-opts> pkgspec - display USE flags for pkgspec + which(w) pkgspec - print full path to ebuild for package pkgspec + diff --git a/src/equery/tests/test-help.sh b/src/equery/tests/test-help.sh new file mode 100755 index 0000000..618aac1 --- /dev/null +++ b/src/equery/tests/test-help.sh @@ -0,0 +1,105 @@ +#! /bin/sh +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_equery_help() { + equery --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-help.out + +} + +test_belongs_help() { + equery belongs --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-belongs-help.out +} + +test_changes_help() { + equery changes --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-changes-help.out +} + +test_check_help() { + equery check --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-check-help.out +} + +test_depends_help() { + equery depends --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-depends-help.out +} + +test_depgraph_help() { + equery depgraph --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-depgraph-help.out +} + +test_files_help() { + equery files --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-files-help.out +} + +test_glsa_help() { + equery glsa --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-glsa-help.out +} + +test_hasuses_help() { + equery hasuses --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-hasuses-help.out +} + +test_list_help() { + equery list --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-list-help.out +} + +test_size_help() { + equery size --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-size-help.out +} + +test_stats_help() { + equery stats --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-stats-help.out +} + +test_uses_help() { + equery uses --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-uses-help.out +} + +test_which_help() { + equery which --help > ${tmpfile} + assert_samefile ${FUNCNAME} ${tmpfile} test-which-help.out +} + + +# run tests + +if [ "`hostname`" != "sky" ] ; then + echo "Testing framework is beta and machine dependent; some tests will fail!" +fi + +test_equery_help +test_belongs_help +test_check_help +test_changes_help +test_depends_help +test_depgraph_help +test_files_help +test_glsa_help +test_hasuses_help +test_list_help +test_size_help +test_stats_help +test_uses_help +test_which_help + +rm -f *.tmp
\ No newline at end of file diff --git a/src/equery/tests/test-list-help.out b/src/equery/tests/test-list-help.out new file mode 100644 index 0000000..82d1fb0 --- /dev/null +++ b/src/equery/tests/test-list-help.out @@ -0,0 +1,9 @@ +List all packages matching a query pattern +Syntax: + list <local-opts> pkgspec +<local-opts> is either of: + -i, --installed - search installed packages (default) + -I, --exclude-installed - do not search installed packages + -p, --portage-tree - also search in portage tree (/usr/portage) + -o, --overlay-tree - also search in overlay tree (/usr/local/portage /usr/local/overlays/gentoo-boblycat) + diff --git a/src/equery/tests/test-list.sh b/src/equery/tests/test-list.sh new file mode 100755 index 0000000..a43048b --- /dev/null +++ b/src/equery/tests/test-list.sh @@ -0,0 +1,40 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_list() { + equery list > ${tmpfile} + +# should test tty output as well +# pkgs=$(cat ${tmpfile} | wc -l) +# x=$(grep "[I--]" ${tmpfile} | wc -l) +# assert_eq ${FUNCNAME} ${pkgs} ${x} + + x=$(grep "app-shells/bash" ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} $x 1 +} + +test_list_installed() { + test_list +} + +test_list_portage_tree() { + equery list -I -p > ${tmpfile} +} + +test_list_overlay_tree() { + equery list -I -o > ${tmpfile} +} + +# Run tests + +test_list + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-size-help.out b/src/equery/tests/test-size-help.out new file mode 100644 index 0000000..c0c63a4 --- /dev/null +++ b/src/equery/tests/test-size-help.out @@ -0,0 +1,6 @@ +Print size total size of files contained in a given package +Syntax: + size <local-opts> pkgspec +<local-opts> is either of: + -b, --bytes - report size in bytes + diff --git a/src/equery/tests/test-size.sh b/src/equery/tests/test-size.sh new file mode 100755 index 0000000..126a5db --- /dev/null +++ b/src/equery/tests/test-size.sh @@ -0,0 +1,27 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_size() { + equery size gcc > ${tmpfile} + + x=$(grep "sys-devel/gcc" ${tmpfile} | wc -l) + + assert_ge ${FUNCNAME} ${x} 1 + + x=$(egrep "size\([0-9]+\)" ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} ${x} 1 +} + +# Run tests + +test_size + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-stats-help.out b/src/equery/tests/test-stats-help.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/equery/tests/test-stats-help.out diff --git a/src/equery/tests/test-uses-help.out b/src/equery/tests/test-uses-help.out new file mode 100644 index 0000000..d350e00 --- /dev/null +++ b/src/equery/tests/test-uses-help.out @@ -0,0 +1,7 @@ +Display USE flags for a given package + +Syntax: + uses <local-opts> pkgspec +<local-opts> is either of: + -a, --all - include non-installed packages + diff --git a/src/equery/tests/test-uses.sh b/src/equery/tests/test-uses.sh new file mode 100755 index 0000000..d694483 --- /dev/null +++ b/src/equery/tests/test-uses.sh @@ -0,0 +1,39 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_uses() { + equery uses gcc > ${tmpfile} + + x=$(grep "static" ${tmpfile} | wc -l) + + assert_eq ${FUNCNAME} ${x} 1 + + x=$(cat ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} $x 7 +} + +test_uses_all() { + equery uses -a uclibc > ${tmpfile} + + x=$(grep "static" ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} ${x} 1 + + x=$(cat ${tmpfile} | wc -l) + assert_ge ${FUNCNAME} $x 5 + +} + +# Run tests + +test_uses +test_uses_all + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/equery/tests/test-which-help.out b/src/equery/tests/test-which-help.out new file mode 100644 index 0000000..8bf337e --- /dev/null +++ b/src/equery/tests/test-which-help.out @@ -0,0 +1,3 @@ +Print full path to ebuild for a given package +Syntax: + size pkgspec diff --git a/src/equery/tests/test-which.sh b/src/equery/tests/test-which.sh new file mode 100755 index 0000000..491868f --- /dev/null +++ b/src/equery/tests/test-which.sh @@ -0,0 +1,22 @@ +#! /bin/bash +# +# Copyright (c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright (c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 + +. common-functions.sh + +tmpfile=$(tempfilename) + +test_which() { + file=$(equery which gcc) + + assert_exists ${FUNCNAME} ${file} +} + +# Run tests + +test_which + +rm -f ${tmpfile}
\ No newline at end of file diff --git a/src/eread/AUTHORS b/src/eread/AUTHORS new file mode 100644 index 0000000..68064ce --- /dev/null +++ b/src/eread/AUTHORS @@ -0,0 +1,2 @@ +Author: Donnie Berkholz <dberkholz@gentoo.org> +Updated by: Uwe Klosa <uwe.klosa@gmail.com> diff --git a/src/eread/Makefile b/src/eread/Makefile new file mode 100644 index 0000000..1d9b284 --- /dev/null +++ b/src/eread/Makefile @@ -0,0 +1,20 @@ +# Copyright 2006 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "ELY (n.) The first, tiniest inkling you get that something, somewhere, has gone terribly wrong." + +dist: + mkdir -p ../../$(distdir)/src/eread + cp AUTHORS Makefile eread eread.1 ../../$(distdir)/src/eread/ + +install: + + install -m 0755 eread $(bindir)/ + install -d $(docdir)/eread + install -m 0644 AUTHORS $(docdir)/eread/ + install -m 0644 eread.1 $(mandir)/ diff --git a/src/eread/eread b/src/eread/eread new file mode 100755 index 0000000..c6d4de1 --- /dev/null +++ b/src/eread/eread @@ -0,0 +1,94 @@ +#!/bin/bash + +# This is a script to read portage log items from einfo, ewarn etc, new in the +# portage-2.1 series. +# +# Author: Donnie Berkholz <spyderous@gentoo.org> +# Updated by: Uwe Klosa <uwe.klosa@gmail.com> + +# set decent PATH for bug 172969 + +PATH=/usr/bin:/bin:${PATH} + +# Set ELOGDIR +PORT_LOGDIR="$(portageq envvar PORT_LOGDIR)" +[ "$PORT_LOGDIR" = "" ] && PORT_LOGDIR="/var/log/portage" +ELOGDIR="$PORT_LOGDIR/elog" + +# Verify that ELOGDIR exists +if [ ! -d "$ELOGDIR" ]; then + echo "ELOG directory: $ELOGDIR does not exist!" + exit 1 +fi + +# Use the pager from the users environment +[ -z "$PAGER" ] && PAGER="less" + +# Set up select prompt +PS3="Choice? " + +select_loop() { + ANY_FILES=$(find . -type f) + ANY_FILES=$(echo ${ANY_FILES} | sed -e "s:\./::g") + + if [[ -z ${ANY_FILES} ]]; then + echo "No log items to read" + break + fi + + echo + echo "This is a list of portage log items. Choose a number to view that file or type q to quit." + echo + + # Pick which file to read + select FILE in ${ANY_FILES}; do + case ${REPLY} in + q) + echo "Quitting" + QUIT="yes" + break + ;; + *) + if [ -f "$FILE" ]; then + ${PAGER} ${FILE} + read -p "Delete file? [y/N] " DELETE + case ${DELETE} in + q) + echo "Quitting" + QUIT="yes" + break + ;; + y|Y) + rm -f ${FILE} + SUCCESS=$? + if [[ ${SUCCESS} = 0 ]]; then + echo "Deleted ${FILE}" + else + echo "Unable to delete ${FILE}" + fi + ;; + # Empty string defaults to N (save file) + n|N|"") + echo "Saving ${FILE}" + ;; + *) + echo "Invalid response. Saving ${FILE}" + ;; + esac + else + echo + echo "Invalid response." + fi + ;; + esac + break + done +} + +pushd ${ELOGDIR} > /dev/null + +until [[ -n ${QUIT} ]]; do + select_loop +done + +popd > /dev/null diff --git a/src/eread/eread.1 b/src/eread/eread.1 new file mode 100644 index 0000000..5e18214 --- /dev/null +++ b/src/eread/eread.1 @@ -0,0 +1,12 @@ +.TH "eread" "1" "1.0" "Donnie Berkholz" "gentoolkit" +.SH "NAME" +.LP +eread \- Gentoo: Tool to display and manage ELOG files from portage +.SH "SYNTAX" +.LP +eread +.SH "DESCRIPTION" +.LP +This tool is used to display and manage ELOG files produced by portage version 2.1 and higher. +.SH "ENVIRONMENT VARIABLES" +The eread utility uses the PAGER environment variable to display the ELOG files. If the variable is not set, it defaults to /usr/bin/less. diff --git a/src/euse/AUTHORS b/src/euse/AUTHORS new file mode 100644 index 0000000..12e6db7 --- /dev/null +++ b/src/euse/AUTHORS @@ -0,0 +1,2 @@ +* original perl version: Arun Bhanu <codebear@gentoo.org> +* new bash version: Marius Mauch <genone@gentoo.org> diff --git a/src/euse/ChangeLog b/src/euse/ChangeLog new file mode 100644 index 0000000..cb50dbb --- /dev/null +++ b/src/euse/ChangeLog @@ -0,0 +1,9 @@ + +2004-01-07 Karl Trygve Kalleberg <karltk@gentoo.org> + * Added Makefile + * Updated from app-portage/gentoolkit + * Reformatted ChangeLog + +2003-05-09 Arun Bhanu <codebear@gentoo.org> + * Initial commit to gentoolkit. + diff --git a/src/euse/Makefile b/src/euse/Makefile new file mode 100644 index 0000000..d1ad804 --- /dev/null +++ b/src/euse/Makefile @@ -0,0 +1,20 @@ +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "PIMPERNE (n.) One of those rubber nodules found on the underneath side of a lavatory seat." + +dist: + mkdir -p ../../$(distdir)/src/euse/ + cp Makefile AUTHORS ChangeLog euse euse.1 ../../$(distdir)/src/euse/ + +install: + install -m 0755 euse $(bindir)/ + install -d $(docdir)/euse + install -m 0644 AUTHORS ChangeLog $(docdir)/euse/ + install -m 0644 euse.1 $(mandir)/ diff --git a/src/euse/euse b/src/euse/euse new file mode 100755 index 0000000..f9fd2fd --- /dev/null +++ b/src/euse/euse @@ -0,0 +1,551 @@ +#!/bin/bash + +# $Header$ + +# bash replacement for the original euse by Arun Bhanu +# Author: Marius Mauch <genone@gentoo.org> +# Version: 0.2 +# Licensed under the GPL v2 + +PROGRAM_NAME=euse +PROGRAM_VERSION=0.1 + +MAKE_CONF_PATH=/etc/make.conf +MAKE_GLOBALS_PATH=/etc/make.globals +MAKE_PROFILE_PATH=/etc/make.profile +MAKE_CONF_BACKUP_PATH=/etc/make.conf.euse_backup + +[ -z "${MODE}" ] && MODE="showhelp" # available operation modes: showhelp, showversion, showdesc, showflags, modify + +parse_arguments() { + if [ -z "${1}" ]; then + return + fi + while [ -n "${1}" ]; do + case "${1}" in + -h | --help) MODE="showhelp";; + -v | --version) MODE="showversion";; + -i | --info) MODE="showdesc";; + -I | --info-installed) MODE="showinstdesc";; + -l | --local) SCOPE="local";; + -g | --global) SCOPE="global";; + -a | --active) MODE="showflags";; + -E | --enable) MODE="modify"; ACTION="add";; + -D | --disable) MODE="modify"; ACTION="remove";; + -P | --prune) MODE="modify"; ACTION="prune";; + -*) + echo "ERROR: unknown option ${1} specified." + echo + MODE="showhelp" + ;; + "%active") + get_useflags + ARGUMENTS="${ARGUMENTS} ${ACTIVE_FLAGS[9]}" + ;; + *) + ARGUMENTS="${ARGUMENTS} ${1}" + ;; + esac + shift + done +} + +error() { + echo "ERROR: ${1}" + set +f + exit 1 +} + +get_real_path() { + set -P + cd "$1" + pwd + cd "$OLDPWD" + set +P +} + +check_sanity() { + # file permission tests + local descdir + local make_defaults + + descdir="$(get_portdir)/profiles" + + [ ! -r "${MAKE_CONF_PATH}" ] && error "${MAKE_CONF_PATH} is not readable" + [ ! -r "${MAKE_GLOBALS_PATH}" ] && error "${MAKE_GLOBALS_PATH} is not readable" + [ ! -h "${MAKE_PROFILE_PATH}" ] && error "${MAKE_PROFILE_PATH} is not a symlink" + [ -z "$(get_portdir)" ] && error "\$PORTDIR couldn't be determined" + [ ! -d "${descdir}" ] && error "${descdir} does not exist or is not a directory" + [ ! -r "${descdir}/use.desc" ] && error "${descdir}/use.desc is not readable" + [ ! -r "${descdir}/use.local.desc" ] && error "${descdir}/use.local.desc is not readable" + for make_defaults in $(get_all_make_defaults); do + [ ! -r "$make_defaults" ] && error "$_make_defaults is not readable" + done +# [ ! -r "$(get_make_defaults)" ] && error "$(get_make_defaults) is not readable" + [ "${MODE}" == "modify" -a ! -w "${MAKE_CONF_PATH}" ] && error ""${MAKE_CONF_PATH}" is not writable" +} + +showhelp() { +cat << HELP +${PROGRAM_NAME} v${PROGRAM_VERSION} + +Syntax: ${PROGRAM_NAME} <option> [suboptions] [useflaglist] + +Options: -h, --help - show this message + -v, --version - show version information + -i, --info - show descriptions for the given useflags + -I, --info-installed - show descriptions for the given useflags and + their current impact on the installed system + -g, --global - show only global use flags (suboption) + -l, --local - show only local use flags (suboption) + -a, --active - show currently active useflags and their origin + -E, --enable - enable the given useflags + -D, --disable - disable the given useflags + -P, --prune - remove all references to the given flags from + make.conf to revert to default settings + +Notes: ${PROGRAM_NAME} currently only works for global flags defined + in make.globals, make.defaults or make.conf, it doesn't handle + use.defaults, use.mask or package.use yet (see portage(5) for details on + these files). It also might have issues with cascaded profiles. + If multiple options are specified only the last one will be used. +HELP +} + +showversion() { +cat << VER +${PROGRAM_NAME} v${PROGRAM_VERSION} +Written by Marius Mauch + +Copyright (C) 2004-2008 Gentoo Foundation, Inc. +This is free software; see the source for copying conditions. +VER +} + +# remove duplicate flags from the given list in both positive and negative forms +# (but unlike portage always keep the last value even if it's negative) +# Otherwise the status flags could be incorrect if a flag appers multiple times in +# one location (like make.conf). +# Using python here as bash sucks for list handling. +# NOTE: bash isn't actually that bad at handling lists -- sh is. This may be +# worth another look to avoid calling python unnecessariy. Or we could +# just write the whole thing in python. ;) +reduce_incrementals() { + echo $@ | python -c "import sys +r=[] +for x in sys.stdin.read().split(): + if x[0] == '-' and x[1:] in r: + r.remove(x[1:]) + r.append(x) + elif x[0] != '-' and '-'+x in r: + r.remove('-'+x) + r.append(x) + elif x == '-*': + r = [] + r.append(x) + elif x not in r: + r.append(x) +print ' '.join(r)" +} + +# the following function creates a bash array ACTIVE_FLAGS that contains the +# global use flags, indexed by origin: 0: environment, 1: make.conf, +# 2: make.defaults, 3: make.globals +get_useflags() { + # only calculate once as calling emerge is painfully slow + [ -n "${USE_FLAGS_CALCULATED}" ] && return + + # backup portdir so get_portdir() doesn't give false results later + portdir_backup="${PORTDIR}" + + ACTIVE_FLAGS[0]="$(reduce_incrementals ${USE})" + USE="" + source "${MAKE_CONF_PATH}" + ACTIVE_FLAGS[1]="$(reduce_incrementals ${USE})" + USE="" + for x in $(get_all_make_defaults); do + source "${x}" + ACTIVE_FLAGS[2]="$(reduce_incrementals ${ACTIVE_FLAGS[2]} ${USE})" + done + USE="" + source "${MAKE_GLOBALS_PATH}" + ACTIVE_FLAGS[3]="$(reduce_incrementals ${USE})" + + # restore saved env variables + USE="${ACTIVE_FLAGS[0]}" + PORTDIR="${portdir_backup}" + + # get the currently active USE flags as seen by portage, this has to be after + # restoring USE or portage won't see the original environment + ACTIVE_FLAGS[9]="$(emerge --info | grep 'USE=' | cut -b 5- | sed -e 's:"::g')" #' + USE_FLAGS_CALCULATED=1 +} + +# get the list of all known USE flags by reading use.desc and/or use.local.desc +# (depending on the value of $SCOPE) +get_useflaglist() { + local descdir + + descdir="$(get_portdir)/profiles" + + if [ -z "${SCOPE}" -o "${SCOPE}" == "global" ]; then + egrep "^[^# ]+ +-" "${descdir}/use.desc" | cut -d\ -f 1 + fi + if [ -z "${SCOPE}" -o "${SCOPE}" == "local" ]; then + egrep "^[^# :]+:[^ ]+ +-" "${descdir}/use.local.desc" | cut -d: -f 2 | cut -d\ -f 1 + fi +} + +# get all make.defaults by traversing the cascaded profile directories +get_all_make_defaults() { + local curdir + local parent + local rvalue + + curdir="${1:-$(get_real_path ${MAKE_PROFILE_PATH})}" + + [ -f "${curdir}/make.defaults" ] && rvalue="${curdir}/make.defaults ${rvalue}" + if [ -f "${curdir}/parent" ]; then + for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do + pdir="$(get_real_path ${curdir}/${parent})" + rvalue="$(get_all_make_defaults ${pdir}) ${rvalue}" + done + fi + + echo "${rvalue}" +} + +# get the path to make.defaults by traversing the cascaded profile directories +get_make_defaults() { + local curdir + local parent + + curdir="${1:-$(get_real_path ${MAKE_PROFILE_PATH})}" + + if [ ! -f "${curdir}/make.defaults" -a -f "${curdir}/parent" ]; then + for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do + if [ -f "$(get_make_defaults ${curdir}/${parent})" ]; then + curdir="${curdir}/${parent}" + break + fi + done + fi + + echo "${curdir}/make.defaults" +} + +# little helper function to get the status of a given flag in one of the +# ACTIVE_FLAGS elements. Arguments are 1: flag to test, 2: index of ACTIVE_FLAGS, +# 3: echo value for positive (and as lowercase for negative) test result, +# 4 (optional): echo value for "missing" test result, defaults to blank +get_flagstatus_helper() { + if echo " ${ACTIVE_FLAGS[${2}]} " | grep " ${1} " > /dev/null; then + echo -n "${3}" + elif echo " ${ACTIVE_FLAGS[${2}]} " | grep " -${1} " > /dev/null; then + echo -n "$(echo ${3} | tr [[:upper:]] [[:lower:]])" + else + echo -n "${4:- }" + fi +} + +# prints a status string for the given flag, each column indicating the presence +# for portage, in the environment, in make.conf, in make.defaults and in make.globals. +# full positive value would be "[+ECDG]", full negative value would be [-ecdg], +# full missing value would be "[- ]" (portage only sees present or not present) +get_flagstatus() { + get_useflags + + echo -n '[' + get_flagstatus_helper "${1}" 9 "+" "-" + get_flagstatus_helper "${1}" 0 "E" + get_flagstatus_helper "${1}" 1 "C" + get_flagstatus_helper "${1}" 2 "D" + get_flagstatus_helper "${1}" 3 "G" + echo -n '] ' +} + +# faster replacement to `portageq portdir` +get_portdir() { + if [ -z "${PORTDIR}" ]; then + use_backup="${USE}" + source "${MAKE_GLOBALS_PATH}" + for x in $(get_all_make_defaults); do + source "${x}" + done + source "${MAKE_CONF_PATH}" + USE="${use_backup}" + fi + echo "${PORTDIR}" +} + +# This function takes a list of use flags and shows the status and +# the description for each one, honoring $SCOPE +showdesc() { + local descdir + local current_desc + local found_one + local args + + args="${*:-*}" + + if [ -z "${SCOPE}" ]; then + SCOPE="global" showdesc ${args} + echo + SCOPE="local" showdesc ${args} + return + fi + + descdir="$(get_portdir)/profiles" + + [ "${SCOPE}" == "global" ] && echo "global use flags (searching: ${args})" + [ "${SCOPE}" == "local" ] && echo "local use flags (searching: ${args})" + echo "************************************************************" + + if [ "${args}" == "*" ]; then + args="$(get_useflaglist | sort -u)" + fi + + set ${args} + + foundone=0 + while [ -n "${1}" ]; do + if [ "${SCOPE}" == "global" ]; then + if grep "^${1} *-" "${descdir}/use.desc" > /dev/null; then + get_flagstatus "${1}" + foundone=1 + fi + grep "^${1} *-" "${descdir}/use.desc" + fi + # local flags are a bit more complicated as there can be multiple + # entries per flag and we can't pipe into printf + if [ "${SCOPE}" == "local" ]; then + if grep ":${1} *-" "${descdir}/use.local.desc" > /dev/null; then + foundone=1 + fi + grep ":${1} *-" "${descdir}/use.local.desc" \ + | sed -e "s/^\([^:]\+\):\(${1}\) *- *\(.\+\)/\1|\2|\3/g" \ + | while read line; do + pkg="$(echo $line | cut -d\| -f 1)" + flag="$(echo $line | cut -d\| -f 2)" + desc="$(echo $line | cut -d\| -f 3)" + get_flagstatus "${flag}" + printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc}" + done + fi + shift + done + + if [ ${foundone} == 0 ]; then + echo "no matching entries found" + fi +} + +# Works like showdesc() but displays only descriptions of which the appropriate +# ebuild is installed and prints the name of those packages. +showinstdesc() { + local descdir + local current_desc + local args + local -i foundone=0 + local OIFS="$IFS" + + args=("${@:-*}") + + case "${SCOPE}" in + "global") echo "global use flags (searching: ${args})";; + "local") echo "local use flags (searching: ${args})";; + *) SCOPE="global" showinstdesc "${args[@]}" + echo + SCOPE="local" showinstdesc "${args[@]}" + return;; + esac + + descdir="$(get_portdir)/profiles" + echo "************************************************************" + + if [ "${args}" = "*" ]; then + args="$(get_useflaglist | sort -u)" + fi + + set "${args[@]}" + + while [ -n "${1}" ]; do + case "${SCOPE}" in + "global") + if desc=$(grep "^${1} *-" "${descdir}/use.desc"); then + get_flagstatus "${1}" + echo "$desc" + # get list of installed packages matching this USE flag. + IFS=$'\n' + packages=($(equery -q -C hasuse -i "${1}" | awk '{ print $(NF-1) }' | sort)) + foundone+=${#packages[@]} + printf "\nInstalled packages matching this USE flag: " + if [ ${foundone} -gt 0 ]; then + echo $'\n'"${packages[*]}" + else + echo "none" + fi + fi + ;; + "local") + # local flags are a bit more complicated as there can be multiple + # entries per flag and we can't pipe into printf + IFS=': ' # Use a space instead of a dash because dashes occur in cat/pkg + while read pkg flag desc; do + # print name only if package is installed + # NOTE: If we implement bug #114086 's enhancement we can just use the + # exit status of equery instead of a subshell and pipe to wc -l + if [ $(equery -q -C list -i -e "${pkg}" | wc -l) -gt 0 ]; then + foundone=1 + IFS="$OIFS" + get_flagstatus "${flag}" + IFS=': ' + printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc#- }" + fi + done < <(grep ":${1} *-" "${descdir}/use.local.desc") + ;; + esac + shift + done + + if [ ${foundone} -lt 1 ]; then + echo "no matching entries found" + fi + IFS="$OIFS" +} + +# show a list of all currently active flags and where they are activated +showflags() { + local args + + get_useflags + + args="${*:-*}" + + if [ "${args}" == "*" ]; then + args="$(get_useflaglist | sort -u)" + fi + + set ${args} + + while [ -n "${1}" ]; do + if echo " ${ACTIVE_FLAGS[9]} " | grep " ${1} " > /dev/null; then + printf "%-20s" ${1} + get_flagstatus ${1} + echo + fi + shift + done +} + +# two small helpers to add or remove a flag from a USE string +add_flag() { + NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE} ${1}" +} + +remove_flag() { + NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE// ${1} / }" +} + +# USE flag modification function. Mainly a loop with calls to add_flag and +# remove_flag to create a new USE string which is then inserted into make.conf. +modify() { + if [ -z "${*}" ]; then + if [ "${ACTION}" != "prune" ]; then + echo "WARNING: no USE flags listed for modification, do you really" + echo " want to ${ACTION} *all* known USE flags?" + echo " If you don't please press Ctrl-C NOW!!!" + sleep 5 + set $(get_useflaglist | sort -u) + fi + fi + + get_useflags + + NEW_MAKE_CONF_USE=" ${ACTIVE_FLAGS[1]} " + + while [ -n "${1}" ]; do + if [ "${ACTION}" == "add" ]; then + if echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then + shift + elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then + remove_flag "-${1}" + else + add_flag "${1}" + shift + fi + elif [ "${ACTION}" == "remove" ]; then + if echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then + shift + elif echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then + remove_flag "${1}" + else + add_flag "-${1}" + shift + fi + elif [ "${ACTION}" == "prune" ]; then + if echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then + remove_flag "${1}" + elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then + remove_flag "-${1}" + fi + shift + fi + done + + #echo "old flags:" + #echo ${ACTIVE_FLAGS[1]} + #echo + #echo "new flags:" + #echo ${NEW_MAKE_CONF_USE} + + # a little loop to add linebreaks so we don't end with one ultra-long line + NEW_MAKE_CONF_USE_2="" + for x in ${NEW_MAKE_CONF_USE}; do + if [ $(((${#NEW_MAKE_CONF_USE_2}%70)+${#x}+2)) -gt 70 ]; then + NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}\\ \\n $x " + else + NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}${x} " + fi + done + + # make a backup just in case the user doesn't like the new make.conf + cp -p "${MAKE_CONF_PATH}" "${MAKE_CONF_BACKUP_PATH}" + + # as sed doesn't really work with multi-line patterns we have to replace USE + # on our own here. Basically just skip everything between USE=" and the + # closing ", printing our new USE line there instead. + inuse=0 + had_use=0 + x=0 + (while [ "$x" -eq "0" ]; do + read -r line + x="$?" + [ "${line:0:4}" == "USE=" ] && inuse=1 + [ "${inuse}" == "0" ] && echo -E "${line}" + if [ "${inuse}" == "1" ] && echo "${line}" | egrep '" *(#.*)?$' > /dev/null; then + echo -n 'USE="' + echo -ne "${NEW_MAKE_CONF_USE_2%% }" + echo '"' + inuse=0 + had_use=1 + fi + done + if [ ${had_use} -eq 0 ]; then + echo -n 'USE="' + echo -ne "${NEW_MAKE_CONF_USE_2%% }" + echo '"' + fi ) < "${MAKE_CONF_BACKUP_PATH}" | sed -e 's:\\ $:\\:' > "${MAKE_CONF_PATH}" + + echo "${MAKE_CONF_PATH} was modified, a backup copy has been placed at ${MAKE_CONF_BACKUP_PATH}" +} + +##### main program comes now ##### + +# disable globbing as it fucks up with args=* +set -f +parse_arguments "$@" +check_sanity + +eval ${MODE} ${ARGUMENTS} +set +f diff --git a/src/euse/euse.1 b/src/euse/euse.1 new file mode 100644 index 0000000..b5148fd --- /dev/null +++ b/src/euse/euse.1 @@ -0,0 +1,102 @@ +.TH "EUSE" "1" "2004-10-17" "Gentoo Linux" "Gentoo Toolkit" +.SH "NAME" +euse \- Gentoo: command line USE flag editor +.SH "SYNOPSIS" +.B euse +\fI<option> [suboption] [useflaglist]\fB +.SH "DESCRIPTION" +.PP +.I euse +is used to set(disable/enable) USE flags in /etc/make.conf without having to edit +the file directly. It is also used to get detail information about use flags +like description, status of flags(enabled/disabled), type of flag(global/local) +etc. +.SH "OPTIONS " +.TP +\fB\-E, \-\-enable\fI +Enables USE flag(s) in make.conf. It accepts one or more space seperated +USE flags as parameters. +.TP +\fB\-D, \-\-disable\fI +Disables USE flag(s) in make.conf. Puts a '\-' sign in front of the USE flag +and appends it to the USE setting in make.conf. It accepts one or more +space seperated USE flags as parameters. +.TP +\fB\-P, \-\-prune\fI +Removes USE flag(s) in make.conf. Removes all positive and negative references to +the given USE flags from make.conf. +.TP +\fB\-i, \-\-info\fI +Prints detail information about the USE flag(s). If no arguments are given then +it assumes you want information for all USE flags. If one or more +arguments are given (space separated) then only information for those flags is +printed. +.TP +\fB\-I, \-\-info\-installed\fI +Same as \-\-info, except that it will also list the currently installed packages that are utilizing the flag. +.sp +.RS +The output is in the following format: +.br +\fB[\- cD ]\fI alpha \- indicates that architecture ... +.br +\fB[\- ]\fI moznocompose (net\-www/mozilla): +.br +Disable building of mozilla's web page composer +.br +The indicators in the first column are: +.IP is_active ++ if the flag is seen as active by portage, \- if not +.IP is_in_env +E if the flag is enabled in the environment, e if it is +disabled in the environment, nothing if it's not affected +by the environment +.IP is_in_make_conf +C if the flag is enabled in make.conf, c if it is +disabled in make.conf, nothing if it's not affected +by make.conf +.IP is_in_make_defaults +D if the flag is enabled in make.defaults, d if it is +disabled in make.defaults, nothing if it's not affected +by make.defaults +.IP is_in_make_globals +G if the flag is enabled in make.globals, g if it is +disabled in make.globals, nothing if it's not affected +by make.globals +.br +Then follows the name of the flag, for local flags the +package name and then the description (on a new line for +local flags). +.TP +\fB\-a, \-\-active\fI +Shows all currently active USE flags and where they are activated (see +description for \fB\-\-info\fI). +.TP +\fB\-h, \-\-help\fI +Show the help message listing all the available flags and a short description +.TP +\fB\-v, \-\-version\fI +Show the version information +.SH "FILES" +/etc/make.conf +.br +/etc/make.profile/make.defaults +.br +/etc/make.globals +.br +$PORTDIR/profiles/use.desc +.br +$PORTDIR/profiles/use.local.desc +.br + +.SH "AUTHOR" +Original version by Arun Bhanu <codebear@gentoo.org> +.br +Updated for rewritten euse by Marius Mauch <genone@gentoo.org> +.SH "BUGS" +euse doesn't handle USE flags enabled or disabled by use.defaults, use.mask +or package.use yet. It also doesn't completely understand the \-* flag. +.SH "SEE ALSO" +.BR ufed(8), +.TP +The \fI/usr/bin/euse\fR script. diff --git a/src/gentoolkit/AUTHORS b/src/gentoolkit/AUTHORS new file mode 100644 index 0000000..0dfa694 --- /dev/null +++ b/src/gentoolkit/AUTHORS @@ -0,0 +1,2 @@ +Original author: +Karl Trygve Kalleberg <karltk@gentoo.org>
\ No newline at end of file diff --git a/src/gentoolkit/Makefile b/src/gentoolkit/Makefile new file mode 100644 index 0000000..831bcfd --- /dev/null +++ b/src/gentoolkit/Makefile @@ -0,0 +1,22 @@ +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "LISTOWEL (n.) The small mat on the bar designed to be more absorbent than the bar, but not as absorbent as your elbows." + +dist: + mkdir -p ../../${distdir}/src/gentoolkit + cp Makefile AUTHORS README TODO errors.py package.py helpers.py pprinter.py __init__.py ../../${distdir}/src/gentoolkit/ + +install: + install -d $(docdir)/gentoolkit + install -m 0644 AUTHORS README TODO $(docdir)/gentoolkit/ + install -d $(DESTDIR)/usr/lib/gentoolkit/pym/gentoolkit + install -m 0644 package.py pprinter.py helpers.py errors.py $(DESTDIR)/usr/lib/gentoolkit/pym/gentoolkit/ + install -m 0644 __init__.py $(DESTDIR)/usr/lib/gentoolkit/pym/gentoolkit/ + diff --git a/src/gentoolkit/README b/src/gentoolkit/README new file mode 100644 index 0000000..916dc81 --- /dev/null +++ b/src/gentoolkit/README @@ -0,0 +1,17 @@ + +Package : gentoolkit +Version : see __init__.py +Author : See AUTHORS + +MOTIVATION + +This is the Python API for Gentoolkit. It contains common functionality shared +by the Gentoolkit tools written in Python. + +MECHANICS + +N/A + +IMPROVEMENTS + +N/A diff --git a/src/gentoolkit/TODO b/src/gentoolkit/TODO new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/gentoolkit/TODO diff --git a/src/gentoolkit/__init__.py b/src/gentoolkit/__init__.py new file mode 100644 index 0000000..28b56be --- /dev/null +++ b/src/gentoolkit/__init__.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# +# Copyright 2003-2004 Karl Trygve Kalleberg +# Copyright 2003-2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ +# Author: Karl Trygve Kalleberg <karltk@gentoo.org> +# +# Portions written ripped from +# - etcat, by Alistair Tse <liquidx@gentoo.org> +# + +__author__ = "Karl Trygve Kalleberg" +__email__ = "karltk@gentoo.org" +__version__ = "0.1.1" +__productname__ = "gentoolkit" +__description__ = "Gentoolkit Common Library" + +import os +import sys +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage +import re +try: + from threading import Lock +except ImportError: + # If we don't have thread support, we don't need to worry about + # locking the global settings object. So we define a "null" Lock. + class Lock: + def acquire(self): + pass + def release(self): + pass + +try: + import portage.exception as portage_exception +except ImportError: + import portage_exception + +try: + settingslock = Lock() + settings = portage.config(clone=portage.settings) + porttree = portage.db[portage.root]["porttree"] + vartree = portage.db[portage.root]["vartree"] + virtuals = portage.db[portage.root]["virtuals"] +except portage_exception.PermissionDenied, e: + sys.stderr.write("Permission denied: '%s'\n" % str(e)) + sys.exit(e.errno) + +Config = { + "verbosityLevel": 3 +} + +from helpers import * +from package import * diff --git a/src/gentoolkit/errors.py b/src/gentoolkit/errors.py new file mode 100644 index 0000000..db81721 --- /dev/null +++ b/src/gentoolkit/errors.py @@ -0,0 +1,14 @@ +#! /usr/bin/python2 +# +# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright(c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header$ + +class FatalError: + def __init__(self, s): + self._message = s + def get_message(self): + return self._message
\ No newline at end of file diff --git a/src/gentoolkit/helpers.py b/src/gentoolkit/helpers.py new file mode 100644 index 0000000..4652b2d --- /dev/null +++ b/src/gentoolkit/helpers.py @@ -0,0 +1,162 @@ +#!/usr/bin/python2 +# +# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright(c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header$ + +import portage +from gentoolkit import * +from package import * +from pprinter import print_warn +try: + from portage.util import unique_array +except ImportError: + from portage_util import unique_array + +def find_packages(search_key, masked=False): + """Returns a list of Package objects that matched the search key.""" + try: + if masked: + t = portage.db["/"]["porttree"].dbapi.xmatch("match-all", search_key) + t += portage.db["/"]["vartree"].dbapi.match(search_key) + else: + t = portage.db["/"]["porttree"].dbapi.match(search_key) + t += portage.db["/"]["vartree"].dbapi.match(search_key) + # catch the "amgigous package" Exception + except ValueError, e: + if isinstance(e[0],list): + t = [] + for cp in e[0]: + if masked: + t += portage.db["/"]["porttree"].dbapi.xmatch("match-all", cp) + t += portage.db["/"]["vartree"].dbapi.match(cp) + else: + t += portage.db["/"]["porttree"].dbapi.match(cp) + t += portage.db["/"]["vartree"].dbapi.match(cp) + else: + raise ValueError(e) + except portage_exception.InvalidAtom, e: + print_warn("Invalid Atom: '%s'" % str(e)) + return [] + # Make the list of packages unique + t = unique_array(t) + t.sort() + return [Package(x) for x in t] + +def find_installed_packages(search_key, masked=False): + """Returns a list of Package objects that matched the search key.""" + try: + t = portage.db["/"]["vartree"].dbapi.match(search_key) + # catch the "amgigous package" Exception + except ValueError, e: + if isinstance(e[0],list): + t = [] + for cp in e[0]: + t += portage.db["/"]["vartree"].dbapi.match(cp) + else: + raise ValueError(e) + except portage_exception.InvalidAtom, e: + print_warn("Invalid Atom: '%s'" % str(e)) + return [] + return [Package(x) for x in t] + +def find_best_match(search_key): + """Returns a Package object for the best available candidate that + matched the search key.""" + t = portage.db["/"]["porttree"].dep_bestmatch(search_key) + if t: + return Package(t) + return None + +def find_system_packages(prefilter=None): + """Returns a tuple of lists, first list is resolved system packages, + second is a list of unresolved packages.""" + pkglist = settings.packages + resolved = [] + unresolved = [] + for x in pkglist: + cpv = x.strip() + if len(cpv) and cpv[0] == "*": + pkg = find_best_match(cpv) + if pkg: + resolved.append(pkg) + else: + unresolved.append(cpv) + return (resolved, unresolved) + +def find_world_packages(prefilter=None): + """Returns a tuple of lists, first list is resolved world packages, + seond is unresolved package names.""" + f = open(portage.root+portage.WORLD_FILE) + pkglist = f.readlines() + resolved = [] + unresolved = [] + for x in pkglist: + cpv = x.strip() + if len(cpv) and cpv[0] != "#": + pkg = find_best_match(cpv) + if pkg: + resolved.append(pkg) + else: + unresolved.append(cpv) + return (resolved,unresolved) + +def find_all_installed_packages(prefilter=None): + """Returns a list of all installed packages, after applying the prefilter + function""" + t = vartree.dbapi.cpv_all() + if prefilter: + t = filter(prefilter,t) + return [Package(x) for x in t] + +def find_all_uninstalled_packages(prefilter=None): + """Returns a list of all uninstalled packages, after applying the prefilter + function""" + alist = find_all_packages(prefilter) + return [x for x in alist if not x.is_installed()] + +def find_all_packages(prefilter=None): + """Returns a list of all known packages, installed or not, after applying + the prefilter function""" + t = porttree.dbapi.cp_all() + t += vartree.dbapi.cp_all() + if prefilter: + t = filter(prefilter,t) + t = unique_array(t) + t2 = [] + for x in t: + t2 += porttree.dbapi.cp_list(x) + t2 += vartree.dbapi.cp_list(x) + t2 = unique_array(t2) + return [Package(x) for x in t2] + +def split_package_name(name): + """Returns a list on the form [category, name, version, revision]. Revision will + be 'r0' if none can be inferred. Category and version will be empty, if none can + be inferred.""" + r = portage.catpkgsplit(name) + if not r: + r = name.split("/") + if len(r) == 1: + return ["", name, "", "r0"] + else: + return r + ["", "r0"] + else: + r = list(r) + if r[0] == 'null': + r[0] = '' + return r + +def sort_package_list(pkglist): + """Returns the list ordered in the same way portage would do with lowest version + at the head of the list.""" + pkglist.sort(Package.compare_version) + return pkglist + +if __name__ == "__main__": + print "This module is for import only" + + diff --git a/src/gentoolkit/package.py b/src/gentoolkit/package.py new file mode 100644 index 0000000..4f28671 --- /dev/null +++ b/src/gentoolkit/package.py @@ -0,0 +1,243 @@ +#! /usr/bin/python2 +# +# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright(c) 2004, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header$ + +import os +from errors import FatalError +import portage +from gentoolkit import * + +class Package: + """Package descriptor. Contains convenience functions for querying the + state of a package, its contents, name manipulation, ebuild info and + similar.""" + + def __init__(self,cpv): + self._cpv = cpv + self._scpv = portage.catpkgsplit(self._cpv) + + if not self._scpv: + raise FatalError("invalid cpv: %s" % cpv) + self._db = None + self._settings = settings + self._settingslock = settingslock + self._portdir_path = os.path.realpath(settings["PORTDIR"]) + + def get_name(self): + """Returns base name of package, no category nor version""" + return self._scpv[1] + + def get_version(self): + """Returns version of package, with revision number""" + v = self._scpv[2] + if self._scpv[3] != "r0": + v += "-" + self._scpv[3] + return v + + def get_category(self): + """Returns category of package""" + return self._scpv[0] + + def get_settings(self, key): + """Returns the value of the given key for this package (useful + for package.* files.""" + self._settingslock.acquire() + self._settings.setcpv(self._cpv) + v = self._settings[key] + self._settingslock.release() + return v + + def get_cpv(self): + """Returns full Category/Package-Version string""" + return self._cpv + + def get_provide(self): + """Return a list of provides, if any""" + if not self.is_installed(): + try: + x = [self.get_env_var('PROVIDE')] + except KeyError: + x = [] + return x + else: + return vartree.get_provide(self._cpv) + + def get_dependants(self): + """Retrieves a list of CPVs for all packages depending on this one""" + raise NotImplementedError("Not implemented yet!") + + def get_runtime_deps(self): + """Returns a linearised list of first-level run time dependencies for this package, on + the form [(comparator, [use flags], cpv), ...]""" + # Try to use the portage tree first, since emerge only uses the tree when calculating dependencies + try: + cd = self.get_env_var("RDEPEND", porttree).split() + except KeyError: + cd = self.get_env_var("RDEPEND", vartree).split() + r,i = self._parse_deps(cd) + return r + + def get_compiletime_deps(self): + """Returns a linearised list of first-level compile time dependencies for this package, on + the form [(comparator, [use flags], cpv), ...]""" + # Try to use the portage tree first, since emerge only uses the tree when calculating dependencies + try: + rd = self.get_env_var("DEPEND", porttree).split() + except KeyError: + rd = self.get_env_var("DEPEND", vartree).split() + r,i = self._parse_deps(rd) + return r + + def get_postmerge_deps(self): + """Returns a linearised list of first-level post merge dependencies for this package, on + the form [(comparator, [use flags], cpv), ...]""" + # Try to use the portage tree first, since emerge only uses the tree when calculating dependencies + try: + pd = self.get_env_var("PDEPEND", porttree).split() + except KeyError: + pd = self.get_env_var("PDEPEND", vartree).split() + r,i = self._parse_deps(pd) + return r + + def _parse_deps(self,deps,curuse=[],level=0): + # store (comparator, [use predicates], cpv) + r = [] + comparators = ["~","<",">","=","<=",">="] + end = len(deps) + i = 0 + while i < end: + tok = deps[i] + if tok == ')': + return r,i + if tok[-1] == "?": + tok = tok.replace("?","") + sr,l = self._parse_deps(deps[i+2:],curuse=curuse+[tok],level=level+1) + r += sr + i += l + 3 + continue + if tok == "||": + sr,l = self._parse_deps(deps[i+2:],curuse,level=level+1) + r += sr + i += l + 3 + continue + # conjonction, like in "|| ( ( foo bar ) baz )" => recurse + if tok == "(": + sr,l = self._parse_deps(deps[i+1:],curuse,level=level+1) + r += sr + i += l + 2 + continue + # pkg block "!foo/bar" => ignore it + if tok[0] == "!": + i += 1 + continue + # pick out comparator, if any + cmp = "" + for c in comparators: + if tok.find(c) == 0: + cmp = c + tok = tok[len(cmp):] + r.append((cmp,curuse,tok)) + i += 1 + return r,i + + def is_installed(self): + """Returns true if this package is installed (merged)""" + self._initdb() + return os.path.exists(self._db.getpath()) + + def is_overlay(self): + """Returns true if the package is in an overlay.""" + dir,ovl = portage.portdb.findname2(self._cpv) + return ovl != self._portdir_path + + def is_masked(self): + """Returns true if this package is masked against installation. Note: We blindly assume that + the package actually exists on disk somewhere.""" + unmasked = portage.portdb.xmatch("match-visible", "=" + self._cpv) + return self._cpv not in unmasked + + def get_ebuild_path(self,in_vartree=0): + """Returns the complete path to the .ebuild file""" + if in_vartree: + return vartree.getebuildpath(self._cpv) + else: + return portage.portdb.findname(self._cpv) + + def get_package_path(self): + """Returns the path to where the ChangeLog, Manifest, .ebuild files reside""" + p = self.get_ebuild_path() + sp = p.split("/") + if len(sp): + return "/".join(sp[:-1]) + + def get_env_var(self, var, tree=""): + """Returns one of the predefined env vars DEPEND, RDEPEND, SRC_URI,....""" + if tree == "": + mytree = vartree + if not self.is_installed(): + mytree = porttree + else: + mytree = tree + r = mytree.dbapi.aux_get(self._cpv,[var]) + if not r: + raise FatalError("Could not find the package tree") + if len(r) != 1: + raise FatalError("Should only get one element!") + return r[0] + + def get_use_flags(self): + """Returns the USE flags active at time of installation""" + self._initdb() + if self.is_installed(): + return self._db.getfile("USE") + return "" + + def get_contents(self): + """Returns the full contents, as a dictionary, on the form + [ '/bin/foo' : [ 'obj', '1052505381', '45ca8b8975d5094cd75bdc61e9933691' ], ... ]""" + self._initdb() + if self.is_installed(): + return self._db.getcontents() + return {} + + def compare_version(self,other): + """Compares this package's version to another's CPV; returns -1, 0, 1""" + v1 = self._scpv + v2 = portage.catpkgsplit(other.get_cpv()) + # if category is different + if v1[0] != v2[0]: + return cmp(v1[0],v2[0]) + # if name is different + elif v1[1] != v2[1]: + return cmp(v1[1],v2[1]) + # Compare versions + else: + return portage.pkgcmp(v1[1:],v2[1:]) + + def size(self): + """Estimates the installed size of the contents of this package, if possible. + Returns [size, number of files in total, number of uncounted files]""" + contents = self.get_contents() + size = 0 + uncounted = 0 + files = 0 + for x in contents: + try: + size += os.lstat(x).st_size + files += 1 + except OSError: + uncounted += 1 + return [size, files, uncounted] + + def _initdb(self): + """Internal helper function; loads package information from disk, + when necessary""" + if not self._db: + cat = self.get_category() + pnv = self.get_name()+"-"+self.get_version() + self._db = portage.dblink(cat,pnv,settings["ROOT"],settings) diff --git a/src/gentoolkit/pprinter.py b/src/gentoolkit/pprinter.py new file mode 100644 index 0000000..ff92a26 --- /dev/null +++ b/src/gentoolkit/pprinter.py @@ -0,0 +1,116 @@ +#!/usr/bin/python +# +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +import sys +import gentoolkit + +try: + import portage.output as output +except ImportError: + import output + + +def print_error(s): + """Prints an error string to stderr.""" + sys.stderr.write(output.red("!!! ") + s + "\n") + +def print_info(lv, s, line_break = True): + """Prints an informational string to stdout.""" + if gentoolkit.Config["verbosityLevel"] >= lv: + sys.stdout.write(s) + if line_break: + sys.stdout.write("\n") + +def print_warn(s): + """Print a warning string to stderr.""" + sys.stderr.write("!!! " + s + "\n") + +def die(err, s): + """Print an error string and die with an error code.""" + print_error(s) + sys.exit(err) + +# Colour settings + +def cpv(s): + """Print a category/package-<version> string.""" + return output.green(s) + +def slot(s): + """Print a slot string""" + return output.bold(s) + +def useflag(s): + """Print a USE flag strign""" + return output.blue(s) + +def useflagon(s): + """Print an enabled USE flag string""" + # FIXME: Collapse into useflag with parameter + return output.red(s) + +def useflagoff(s): + """Print a disabled USE flag string""" + # FIXME: Collapse into useflag with parameter + return output.blue(s) + +def maskflag(s): + """Print a masking flag string""" + return output.red(s) + +def installedflag(s): + """Print an installed flag string""" + return output.bold(s) + +def number(s): + """Print a number string""" + return output.turquoise(s) + +def pkgquery(s): + """Print a package query string.""" + return output.bold(s) + +def regexpquery(s): + """Print a regular expression string""" + return output.bold(s) + +def path(s): + """Print a file or directory path string""" + return output.bold(s) + +def path_symlink(s): + """Print a symlink string.""" + return output.turquoise(s) + +def productname(s): + """Print a product name string, i.e. the program name.""" + return output.turquoise(s) + +def globaloption(s): + """Print a global option string, i.e. the program global options.""" + return output.yellow(s) + +def localoption(s): + """Print a local option string, i.e. the program local options.""" + return output.green(s) + +def command(s): + """Print a program command string.""" + return output.green(s) + +def section(s): + """Print a string as a section header.""" + return output.turquoise(s) + +def subsection(s): + """Print a string as a subsection header.""" + return output.turquoise(s) + +def emph(s): + """Print a string as emphasized.""" + return output.bold(s) diff --git a/src/glsa-check/Makefile b/src/glsa-check/Makefile new file mode 100644 index 0000000..9ad5717 --- /dev/null +++ b/src/glsa-check/Makefile @@ -0,0 +1,20 @@ +# Copyright 2003 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2003 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "YADDLETHORPE (vb.) (Of offended pooves.) To exit huffily from a boutique." + +dist: + mkdir -p ../../$(distdir)/src/glsa-check/ + cp Makefile glsa.py glsa-check glsa-check.1 ../../$(distdir)/src/glsa-check/ + +install: + install -d $(DESTDIR)/usr/lib/gentoolkit/pym/ + install -m 0755 glsa-check $(bindir)/ + install -m 0644 glsa.py $(DESTDIR)/usr/lib/gentoolkit/pym/ + install -m 0644 glsa-check.1 $(mandir)/ diff --git a/src/glsa-check/glsa-check b/src/glsa-check/glsa-check new file mode 100755 index 0000000..fe38331 --- /dev/null +++ b/src/glsa-check/glsa-check @@ -0,0 +1,371 @@ +#!/usr/bin/python + +# $Header: $ +# This program is licensed under the GPL, version 2 + +import os +import sys +sys.path.insert(0, "/usr/lib/gentoolkit/pym") +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage + +try: + from portage.output import * +except ImportError: + from output import * + +from getopt import getopt, GetoptError + +__program__ = "glsa-check" +__author__ = "Marius Mauch <genone@gentoo.org>" +__version__ = "0.9" + +optionmap = [ +["-l", "--list", "list all unapplied GLSA"], +["-d", "--dump", "--print", "show all information about the given GLSA"], +["-t", "--test", "test if this system is affected by the given GLSA"], +["-p", "--pretend", "show the necessary commands to apply this GLSA"], +["-f", "--fix", "try to auto-apply this GLSA (experimental)"], +["-i", "--inject", "inject the given GLSA into the checkfile"], +["-n", "--nocolor", "disable colors (option)"], +["-e", "--emergelike", "do not use a least-change algorithm (option)"], +["-h", "--help", "show this help message"], +["-V", "--version", "some information about this tool"], +["-v", "--verbose", "print more information (option)"], +["-c", "--cve", "show CAN ids in listing mode (option)"], +["-m", "--mail", "send a mail with the given GLSAs to the administrator"] +] + +# print a warning as this is beta code (but proven by now, so no more warning) +#sys.stderr.write("WARNING: This tool is completely new and not very tested, so it should not be\n") +#sys.stderr.write("used on production systems. It's mainly a test tool for the new GLSA release\n") +#sys.stderr.write("and distribution system, it's functionality will later be merged into emerge\n") +#sys.stderr.write("and equery.\n") +#sys.stderr.write("Please read http://www.gentoo.org/proj/en/portage/glsa-integration.xml\n") +#sys.stderr.write("before using this tool AND before reporting a bug.\n\n") + +# option parsing +args = [] +params = [] +try: + args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \ + [x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])]) +# ["dump", "print", "list", "pretend", "fix", "inject", "help", "verbose", "version", "test", "nocolor", "cve", "mail"]) + args = [a for a,b in args] + + for option in ["--nocolor", "-n"]: + if option in args: + nocolor() + args.remove(option) + + verbose = False + for option in ["--verbose", "-v"]: + if option in args: + verbose = True + args.remove(option) + + list_cve = False + for option in ["--cve", "-c"]: + if option in args: + list_cve = True + args.remove(option) + + least_change = True + for option in ["--emergelike", "-e"]: + if option in args: + least_change = False + args.remove(option) + + # sanity checking + if len(args) <= 0: + sys.stderr.write("no option given: what should I do ?\n") + mode = "HELP" + elif len(args) > 1: + sys.stderr.write("please use only one command per call\n") + mode = "HELP" + else: + # in what mode are we ? + args = args[0] + for m in optionmap: + if args in [o for o in m[:-1]]: + mode = m[1][2:] + +except GetoptError, e: + sys.stderr.write("unknown option given: ") + sys.stderr.write(str(e)+"\n") + mode = "HELP" + +# we need a set of glsa for most operation modes +if len(params) <= 0 and mode in ["fix", "test", "pretend", "dump", "inject", "mail"]: + sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n") + sys.stderr.write("If you want to run on all GLSA please tell me so \n") + sys.stderr.write("(specify \"all\" as parameter)\n\n") + mode = "HELP" +elif len(params) <= 0 and mode == "list": + params.append("new") + +# show help message +if mode == "help" or mode == "HELP": + msg = "Syntax: glsa-check <option> [glsa-list]\n\n" + for m in optionmap: + msg += m[0] + "\t" + m[1] + " \t: " + m[-1] + "\n" + for o in m[2:-1]: + msg += "\t" + o + "\n" + msg += "\nglsa-list can contain an arbitrary number of GLSA ids, \n" + msg += "filenames containing GLSAs or the special identifiers \n" + msg += "'all', 'new' and 'affected'\n" + if mode == "help": + sys.stdout.write(msg) + sys.exit(0) + else: + sys.stderr.write("\n" + msg) + sys.exit(1) + +# we need root priviledges for write access +if mode in ["fix", "inject"] and os.geteuid() != 0: + sys.stderr.write(__program__ + ": root access is needed for \""+mode+"\" mode\n") + sys.exit(2) + +# show version and copyright information +if mode == "version": + sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n") + sys.stderr.write("Author: " + __author__ + "\n") + sys.stderr.write("This program is licensed under the GPL, version 2\n\n") + sys.exit(0) + +# delay this for speed increase +from glsa import * + +glsaconfig = checkconfig(portage.config(clone=portage.settings)) + +vardb = portage.db["/"]["vartree"].dbapi +portdb = portage.db["/"]["porttree"].dbapi + +# Check that we really have a glsa dir to work on +if not (os.path.exists(glsaconfig["GLSA_DIR"]) and os.path.isdir(glsaconfig["GLSA_DIR"])): + sys.stderr.write(red("ERROR")+": GLSA_DIR %s doesn't exist. Please fix this.\n" % glsaconfig["GLSA_DIR"]) + sys.exit(1) + +# build glsa lists +completelist = get_glsa_list(glsaconfig["GLSA_DIR"], glsaconfig) + +if os.access(glsaconfig["CHECKFILE"], os.R_OK): + checklist = [line.strip() for line in open(glsaconfig["CHECKFILE"], "r").readlines()] +else: + checklist = [] +todolist = [e for e in completelist if e not in checklist] + +glsalist = [] +if "new" in params: + glsalist = todolist + params.remove("new") + +if "all" in params: + glsalist = completelist + params.remove("all") +if "affected" in params: + # replaced completelist with todolist on request of wschlich + for x in todolist: + try: + myglsa = Glsa(x, glsaconfig) + except (GlsaTypeException, GlsaFormatException), e: + if verbose: + sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e))) + continue + if myglsa.isVulnerable(): + glsalist.append(x) + params.remove("affected") + +# remove invalid parameters +for p in params[:]: + if not (p in completelist or os.path.exists(p)): + sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p)) + params.remove(p) + +glsalist.extend([g for g in params if g not in glsalist]) + +def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr): + fd2.write(white("[A]")+" means this GLSA was already applied,\n") + fd2.write(green("[U]")+" means the system is not affected and\n") + fd2.write(red("[N]")+" indicates that the system might be affected.\n\n") + + for myid in myglsalist: + try: + myglsa = Glsa(myid, glsaconfig) + except (GlsaTypeException, GlsaFormatException), e: + if verbose: + fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) + continue + if myglsa.isApplied(): + status = "[A]" + color = white + elif myglsa.isVulnerable(): + status = "[N]" + color = red + else: + status = "[U]" + color = green + + if verbose: + access = ("[%-8s] " % myglsa.access) + else: + access="" + + fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (") + if not verbose: + for pkg in myglsa.packages.keys()[:3]: + fd1.write(" " + pkg + " ") + if len(myglsa.packages) > 3: + fd1.write("... ") + else: + for pkg in myglsa.packages.keys(): + mylist = vardb.match(portage.dep_getkey(str(pkg))) + if len(mylist) > 0: + pkg = color(" ".join(mylist)) + fd1.write(" " + pkg + " ") + + fd1.write(")") + if list_cve: + fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]]))) + fd1.write("\n") + return 0 + +if mode == "list": + sys.exit(summarylist(glsalist)) + +# dump, fix, inject and fix are nearly the same code, only the glsa method call differs +if mode in ["dump", "fix", "inject", "pretend"]: + for myid in glsalist: + try: + myglsa = Glsa(myid, glsaconfig) + except (GlsaTypeException, GlsaFormatException), e: + if verbose: + sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) + continue + if mode == "dump": + myglsa.dump() + elif mode == "fix": + sys.stdout.write("fixing "+myid+"\n") + mergelist = myglsa.getMergeList(least_change=least_change) + for pkg in mergelist: + sys.stdout.write(">>> merging "+pkg+"\n") + # using emerge for the actual merging as it contains the dependency + # code and we want to be consistent in behaviour. Also this functionality + # will be integrated in emerge later, so it shouldn't hurt much. + emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg + if verbose: + sys.stderr.write(emergecmd+"\n") + exitcode = os.system(emergecmd) + # system() returns the exitcode in the high byte of a 16bit integer + if exitcode >= 1<<8: + exitcode >>= 8 + if exitcode: + sys.exit(exitcode) + if len(mergelist): + sys.stdout.write("\n") + myglsa.inject() + elif mode == "pretend": + sys.stdout.write("Checking GLSA "+myid+"\n") + mergelist = myglsa.getMergeList(least_change=least_change) + if mergelist: + sys.stdout.write("The following updates will be performed for this GLSA:\n") + for pkg in mergelist: + oldver = None + for x in vardb.match(portage.dep_getkey(pkg)): + if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]): + oldver = x + if oldver == None: + raise ValueError("could not find old version for package %s" % pkg) + oldver = oldver[len(portage.dep_getkey(oldver))+1:] + sys.stdout.write(" " + pkg + " (" + oldver + ")\n") + else: + sys.stdout.write("Nothing to do for this GLSA\n") + sys.stdout.write("\n") + elif mode == "inject": + sys.stdout.write("injecting " + myid + "\n") + myglsa.inject() + sys.exit(0) + +# test is a bit different as Glsa.test() produces no output +if mode == "test": + outputlist = [] + for myid in glsalist: + try: + myglsa = Glsa(myid, glsaconfig) + except (GlsaTypeException, GlsaFormatException), e: + if verbose: + sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) + continue + if myglsa.isVulnerable(): + if verbose: + outputlist.append(str(myglsa.nr)+" ( "+myglsa.title+" ) ") + else: + outputlist.append(str(myglsa.nr)) + if len(outputlist) > 0: + sys.stderr.write("This system is affected by the following GLSAs:\n") + sys.stdout.write("\n".join(outputlist)+"\n") + else: + sys.stderr.write("This system is not affected by any of the listed GLSAs\n") + sys.exit(0) + +# mail mode as requested by solar +if mode == "mail": + try: + import portage.mail as portage_mail + except ImportError: + import portage_mail + + import socket + from StringIO import StringIO + try: + from email.mime.text import MIMEText + except ImportError: + from email.MIMEText import MIMEText + + # color doesn't make any sense for mail + nocolor() + + if "PORTAGE_ELOG_MAILURI" in glsaconfig: + myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0] + else: + myrecipient = "root@localhost" + + if "PORTAGE_ELOG_MAILFROM" in glsaconfig: + myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"] + else: + myfrom = "glsa-check" + + mysubject = "[glsa-check] Summary for %s" % socket.getfqdn() + + # need a file object for summarylist() + myfd = StringIO() + myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn()) + myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv)) + summarylist(glsalist, fd1=myfd, fd2=myfd) + summary = str(myfd.getvalue()) + myfd.close() + + myattachments = [] + for myid in glsalist: + try: + myglsa = Glsa(myid, glsaconfig) + except (GlsaTypeException, GlsaFormatException), e: + if verbose: + sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) + continue + myfd = StringIO() + myglsa.dump(outstream=myfd) + myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8")) + myfd.close() + + mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments) + portage_mail.send_mail(glsaconfig, mymessage) + + sys.exit(0) + +# something wrong here, all valid paths are covered with sys.exit() +sys.stderr.write("nothing more to do\n") +sys.exit(2) diff --git a/src/glsa-check/glsa-check.1 b/src/glsa-check/glsa-check.1 new file mode 100644 index 0000000..8e0df42 --- /dev/null +++ b/src/glsa-check/glsa-check.1 @@ -0,0 +1,57 @@ +.TH "glsa-check" "1" "0.6" "Marius Mauch" "gentoolkit" +.SH "NAME" +.LP +glsa\-check \- Gentoo: Tool to locally monitor and manage GLSA's +.SH "SYNTAX" +.LP +glsa\-check <\fIoption\fP> [\fIglsa\-list\fP] + +[\fIglsa\-list\fR] can contain an arbitrary number of GLSA ids, filenames containing GLSAs or the special identifiers 'all', 'new' and 'affected' +.SH "DESCRIPTION" +.LP +This tool is used to locally monitor and manage Gentoo Linux Security Advisories. +Please read: +.br +http://www.gentoo.org/security +.br +before reporting a bug. +.LP +Note: In order for this tool to be effective, you must regularly sync your local portage tree. +.SH "OPTIONS" +.LP +.TP +.B \-l, \-\-list +list all unapplied GLSA +.TP +.B \-d, \-\-dump, \-\-print +show all information about the given GLSA +.TP +.B \-t, \-\-test +test if this system is affected by the given GLSA +.TP +.B \-p, \-\-pretend +show the necessary commands to apply this GLSA +.TP +.B \-f, \-\-fix +try to auto\-apply this GLSA (experimental) +.TP +.B \-i, \-\-inject +inject the given GLSA into the checkfile +.TP +.B \-n, \-\-nocolor +disable colors (option) +.TP +.B \-h, \-\-help +show this help message +.TP +.B \-V, \-\-version +some information about this tool +.TP +.B \-v, \-\-verbose +print more messages (option) +.TP +.B \-c, \-\-cve +show CAN ids in listing mode (option) +.TP +.B \-m, \-\-mail +send a mail with the given GLSAs to the administrator diff --git a/src/glsa-check/glsa.py b/src/glsa-check/glsa.py new file mode 100644 index 0000000..dfd9acd --- /dev/null +++ b/src/glsa-check/glsa.py @@ -0,0 +1,644 @@ +# $Header$ + +# This program is licensed under the GPL, version 2 + +# WARNING: this code is only tested by a few people and should NOT be used +# on production systems at this stage. There are possible security holes and probably +# bugs in this code. If you test it please report ANY success or failure to +# me (genone@gentoo.org). + +# The following planned features are currently on hold: +# - getting GLSAs from http/ftp servers (not really useful without the fixed ebuilds) +# - GPG signing/verification (until key policy is clear) + +__author__ = "Marius Mauch <genone@gentoo.org>" + +import os +import sys +import urllib +import time +import codecs +import re +import xml.dom.minidom + +if sys.version_info[0:2] < (2, 3): + raise NotImplementedError("Python versions below 2.3 have broken XML code " \ + +"and are not supported") + +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage + +# Note: the space for rgt and rlt is important !! +opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=", + "rge": ">=~", "rle": "<=~", "rgt": " >~", "rlt": " <~"} +NEWLINE_ESCAPE = "!;\\n" # some random string to mark newlines that should be preserved +SPACE_ESCAPE = "!;_" # some random string to mark spaces that should be preserved + +def center(text, width): + """ + Returns a string containing I{text} that is padded with spaces on both + sides. If C{len(text) >= width} I{text} is returned unchanged. + + @type text: String + @param text: the text to be embedded + @type width: Integer + @param width: the minimum length of the returned string + @rtype: String + @return: the expanded string or I{text} + """ + if len(text) >= width: + return text + margin = (width-len(text))/2 + rValue = " "*margin + rValue += text + if 2*margin + len(text) == width: + rValue += " "*margin + elif 2*margin + len(text) + 1 == width: + rValue += " "*(margin+1) + return rValue + + +def wrap(text, width, caption=""): + """ + Wraps the given text at column I{width}, optionally indenting + it so that no text is under I{caption}. It's possible to encode + hard linebreaks in I{text} with L{NEWLINE_ESCAPE}. + + @type text: String + @param text: the text to be wrapped + @type width: Integer + @param width: the column at which the text should be wrapped + @type caption: String + @param caption: this string is inserted at the beginning of the + return value and the paragraph is indented up to + C{len(caption)}. + @rtype: String + @return: the wrapped and indented paragraph + """ + rValue = "" + line = caption + text = text.replace(2*NEWLINE_ESCAPE, NEWLINE_ESCAPE+" "+NEWLINE_ESCAPE) + words = text.split() + indentLevel = len(caption)+1 + + for w in words: + if line[-1] == "\n": + rValue += line + line = " "*indentLevel + if len(line)+len(w.replace(NEWLINE_ESCAPE, ""))+1 > width: + rValue += line+"\n" + line = " "*indentLevel+w.replace(NEWLINE_ESCAPE, "\n") + elif w.find(NEWLINE_ESCAPE) >= 0: + if len(line.strip()) > 0: + rValue += line+" "+w.replace(NEWLINE_ESCAPE, "\n") + else: + rValue += line+w.replace(NEWLINE_ESCAPE, "\n") + line = " "*indentLevel + else: + if len(line.strip()) > 0: + line += " "+w + else: + line += w + if len(line) > 0: + rValue += line.replace(NEWLINE_ESCAPE, "\n") + rValue = rValue.replace(SPACE_ESCAPE, " ") + return rValue + +def checkconfig(myconfig): + """ + takes a portage.config instance and adds GLSA specific keys if + they are not present. TO-BE-REMOVED (should end up in make.*) + """ + mysettings = { + "GLSA_DIR": portage.settings["PORTDIR"]+"/metadata/glsa/", + "GLSA_PREFIX": "glsa-", + "GLSA_SUFFIX": ".xml", + "CHECKFILE": "/var/cache/edb/glsa", + "GLSA_SERVER": "www.gentoo.org/security/en/glsa/", # not completely implemented yet + "CHECKMODE": "local", # not completely implemented yet + "PRINTWIDTH": "76" + } + for k in mysettings.keys(): + if k not in myconfig: + myconfig[k] = mysettings[k] + return myconfig + +def get_glsa_list(repository, myconfig): + """ + Returns a list of all available GLSAs in the given repository + by comparing the filelist there with the pattern described in + the config. + + @type repository: String + @param repository: The directory or an URL that contains GLSA files + (Note: not implemented yet) + @type myconfig: portage.config + @param myconfig: a GLSA aware config instance (see L{checkconfig}) + + @rtype: List of Strings + @return: a list of GLSA IDs in this repository + """ + # TODO: remote fetch code for listing + + rValue = [] + + if not os.access(repository, os.R_OK): + return [] + dirlist = os.listdir(repository) + prefix = myconfig["GLSA_PREFIX"] + suffix = myconfig["GLSA_SUFFIX"] + + for f in dirlist: + try: + if f[:len(prefix)] == prefix: + rValue.append(f[len(prefix):-1*len(suffix)]) + except IndexError: + pass + return rValue + +def getListElements(listnode): + """ + Get all <li> elements for a given <ol> or <ul> node. + + @type listnode: xml.dom.Node + @param listnode: <ul> or <ol> list to get the elements for + @rtype: List of Strings + @return: a list that contains the value of the <li> elements + """ + rValue = [] + if not listnode.nodeName in ["ul", "ol"]: + raise GlsaFormatException("Invalid function call: listnode is not <ul> or <ol>") + for li in listnode.childNodes: + if li.nodeType != xml.dom.Node.ELEMENT_NODE: + continue + rValue.append(getText(li, format="strip")) + return rValue + +def getText(node, format): + """ + This is the main parser function. It takes a node and traverses + recursive over the subnodes, getting the text of each (and the + I{link} attribute for <uri> and <mail>). Depending on the I{format} + parameter the text might be formatted by adding/removing newlines, + tabs and spaces. This function is only useful for the GLSA DTD, + it's not applicable for other DTDs. + + @type node: xml.dom.Node + @param node: the root node to start with the parsing + @type format: String + @param format: this should be either I{strip}, I{keep} or I{xml} + I{keep} just gets the text and does no formatting. + I{strip} replaces newlines and tabs with spaces and + replaces multiple spaces with one space. + I{xml} does some more formatting, depending on the + type of the encountered nodes. + @rtype: String + @return: the (formatted) content of the node and its subnodes + """ + rValue = "" + if format in ["strip", "keep"]: + if node.nodeName in ["uri", "mail"]: + rValue += node.childNodes[0].data+": "+node.getAttribute("link") + else: + for subnode in node.childNodes: + if subnode.nodeName == "#text": + rValue += subnode.data + else: + rValue += getText(subnode, format) + else: + for subnode in node.childNodes: + if subnode.nodeName == "p": + for p_subnode in subnode.childNodes: + if p_subnode.nodeName == "#text": + rValue += p_subnode.data.strip() + elif p_subnode.nodeName in ["uri", "mail"]: + rValue += p_subnode.childNodes[0].data + rValue += " ( "+p_subnode.getAttribute("link")+" )" + rValue += NEWLINE_ESCAPE + elif subnode.nodeName == "ul": + for li in getListElements(subnode): + rValue += "-"+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" " + elif subnode.nodeName == "ol": + i = 0 + for li in getListElements(subnode): + i = i+1 + rValue += str(i)+"."+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" " + elif subnode.nodeName == "code": + rValue += getText(subnode, format="keep").replace("\n", NEWLINE_ESCAPE) + if rValue[-1*len(NEWLINE_ESCAPE):] != NEWLINE_ESCAPE: + rValue += NEWLINE_ESCAPE + elif subnode.nodeName == "#text": + rValue += subnode.data + else: + raise GlsaFormatException("Invalid Tag found: ", subnode.nodeName) + if format == "strip": + rValue = rValue.strip(" \n\t") + rValue = re.sub("[\s]{2,}", " ", rValue) + # Hope that the utf conversion doesn't break anything else + return rValue.encode("utf_8") + +def getMultiTagsText(rootnode, tagname, format): + """ + Returns a list with the text of all subnodes of type I{tagname} + under I{rootnode} (which itself is not parsed) using the given I{format}. + + @type rootnode: xml.dom.Node + @param rootnode: the node to search for I{tagname} + @type tagname: String + @param tagname: the name of the tags to search for + @type format: String + @param format: see L{getText} + @rtype: List of Strings + @return: a list containing the text of all I{tagname} childnodes + """ + rValue = [] + for e in rootnode.getElementsByTagName(tagname): + rValue.append(getText(e, format)) + return rValue + +def makeAtom(pkgname, versionNode): + """ + creates from the given package name and information in the + I{versionNode} a (syntactical) valid portage atom. + + @type pkgname: String + @param pkgname: the name of the package for this atom + @type versionNode: xml.dom.Node + @param versionNode: a <vulnerable> or <unaffected> Node that + contains the version information for this atom + @rtype: String + @return: the portage atom + """ + rValue = opMapping[versionNode.getAttribute("range")] \ + + pkgname \ + + "-" + getText(versionNode, format="strip") + return str(rValue) + +def makeVersion(versionNode): + """ + creates from the information in the I{versionNode} a + version string (format <op><version>). + + @type versionNode: xml.dom.Node + @param versionNode: a <vulnerable> or <unaffected> Node that + contains the version information for this atom + @rtype: String + @return: the version string + """ + return opMapping[versionNode.getAttribute("range")] \ + +getText(versionNode, format="strip") + +def match(atom, portdbname, match_type="default"): + """ + wrapper that calls revisionMatch() or portage.dbapi.match() depending on + the given atom. + + @type atom: string + @param atom: a <~ or >~ atom or a normal portage atom that contains the atom to match against + @type portdb: portage.dbapi + @param portdb: one of the portage databases to use as information source + @type match_type: string + @param match_type: if != "default" passed as first argument to dbapi.xmatch + to apply the wanted visibility filters + + @rtype: list of strings + @return: a list with the matching versions + """ + db = portage.db["/"][portdbname].dbapi + if atom[2] == "~": + return revisionMatch(atom, db, match_type=match_type) + elif match_type == "default" or not hasattr(db, "xmatch"): + return db.match(atom) + else: + return db.xmatch(match_type, atom) + +def revisionMatch(revisionAtom, portdb, match_type="default"): + """ + handler for the special >~, >=~, <=~ and <~ atoms that are supposed to behave + as > and < except that they are limited to the same version, the range only + applies to the revision part. + + @type revisionAtom: string + @param revisionAtom: a <~ or >~ atom that contains the atom to match against + @type portdb: portage.dbapi + @param portdb: one of the portage databases to use as information source + @type match_type: string + @param match_type: if != "default" passed as first argument to portdb.xmatch + to apply the wanted visibility filters + + @rtype: list of strings + @return: a list with the matching versions + """ + if match_type == "default" or not hasattr(portdb, "xmatch"): + mylist = portdb.match(re.sub("-r[0-9]+$", "", revisionAtom[2:])) + else: + mylist = portdb.xmatch(match_type, re.sub("-r[0-9]+$", "", revisionAtom[2:])) + rValue = [] + for v in mylist: + r1 = portage.pkgsplit(v)[-1][1:] + r2 = portage.pkgsplit(revisionAtom[3:])[-1][1:] + if eval(r1+" "+revisionAtom[0:2]+" "+r2): + rValue.append(v) + return rValue + + +def getMinUpgrade(vulnerableList, unaffectedList, minimize=True): + """ + Checks if the systemstate is matching an atom in + I{vulnerableList} and returns string describing + the lowest version for the package that matches an atom in + I{unaffectedList} and is greater than the currently installed + version or None if the system is not affected. Both + I{vulnerableList} and I{unaffectedList} should have the + same base package. + + @type vulnerableList: List of Strings + @param vulnerableList: atoms matching vulnerable package versions + @type unaffectedList: List of Strings + @param unaffectedList: atoms matching unaffected package versions + @type minimize: Boolean + @param minimize: True for a least-change upgrade, False for emerge-like algorithm + + @rtype: String | None + @return: the lowest unaffected version that is greater than + the installed version. + """ + rValue = None + v_installed = [] + u_installed = [] + for v in vulnerableList: + v_installed += match(v, "vartree") + + for u in unaffectedList: + u_installed += match(u, "vartree") + + install_unaffected = True + for i in v_installed: + if i not in u_installed: + install_unaffected = False + + if install_unaffected: + return rValue + + for u in unaffectedList: + mylist = match(u, "porttree", match_type="match-all") + for c in mylist: + c_pv = portage.catpkgsplit(c) + i_pv = portage.catpkgsplit(portage.best(v_installed)) + if portage.pkgcmp(c_pv[1:], i_pv[1:]) > 0 \ + and (rValue == None \ + or not match("="+rValue, "porttree") \ + or (minimize ^ (portage.pkgcmp(c_pv[1:], portage.catpkgsplit(rValue)[1:]) > 0)) \ + and match("="+c, "porttree")) \ + and portage.db["/"]["porttree"].dbapi.aux_get(c, ["SLOT"]) == portage.db["/"]["vartree"].dbapi.aux_get(portage.best(v_installed), ["SLOT"]): + rValue = c_pv[0]+"/"+c_pv[1]+"-"+c_pv[2] + if c_pv[3] != "r0": # we don't like -r0 for display + rValue += "-"+c_pv[3] + return rValue + + +# simple Exception classes to catch specific errors +class GlsaTypeException(Exception): + def __init__(self, doctype): + Exception.__init__(self, "wrong DOCTYPE: %s" % doctype) + +class GlsaFormatException(Exception): + pass + +class GlsaArgumentException(Exception): + pass + +# GLSA xml data wrapper class +class Glsa: + """ + This class is a wrapper for the XML data and provides methods to access + and display the contained data. + """ + def __init__(self, myid, myconfig): + """ + Simple constructor to set the ID, store the config and gets the + XML data by calling C{self.read()}. + + @type myid: String + @param myid: String describing the id for the GLSA object (standard + GLSAs have an ID of the form YYYYMM-nn) or an existing + filename containing a GLSA. + @type myconfig: portage.config + @param myconfig: the config that should be used for this object. + """ + if re.match(r'\d{6}-\d{2}', myid): + self.type = "id" + elif os.path.exists(myid): + self.type = "file" + else: + raise GlsaArgumentException("Given ID "+myid+" isn't a valid GLSA ID or filename.") + self.nr = myid + self.config = myconfig + self.read() + + def read(self): + """ + Here we build the filename from the config and the ID and pass + it to urllib to fetch it from the filesystem or a remote server. + + @rtype: None + @return: None + """ + if self.config["CHECKMODE"] == "local": + repository = "file://" + self.config["GLSA_DIR"] + else: + repository = self.config["GLSA_SERVER"] + if self.type == "file": + myurl = "file://"+self.nr + else: + myurl = repository + self.config["GLSA_PREFIX"] + str(self.nr) + self.config["GLSA_SUFFIX"] + self.parse(urllib.urlopen(myurl)) + return None + + def parse(self, myfile): + """ + This method parses the XML file and sets up the internal data + structures by calling the different helper functions in this + module. + + @type myfile: String + @param myfile: Filename to grab the XML data from + @rtype: None + @returns: None + """ + self.DOM = xml.dom.minidom.parse(myfile) + if not self.DOM.doctype: + raise GlsaTypeException(None) + elif self.DOM.doctype.systemId != "http://www.gentoo.org/dtd/glsa.dtd": + raise GlsaTypeException(self.DOM.doctype.systemId) + myroot = self.DOM.getElementsByTagName("glsa")[0] + if self.type == "id" and myroot.getAttribute("id") != self.nr: + raise GlsaFormatException("filename and internal id don't match:" + myroot.getAttribute("id") + " != " + self.nr) + + # the simple (single, required, top-level, #PCDATA) tags first + self.title = getText(myroot.getElementsByTagName("title")[0], format="strip") + self.synopsis = getText(myroot.getElementsByTagName("synopsis")[0], format="strip") + self.announced = getText(myroot.getElementsByTagName("announced")[0], format="strip") + self.revised = getText(myroot.getElementsByTagName("revised")[0], format="strip") + + # now the optional and 0-n toplevel, #PCDATA tags and references + try: + self.access = getText(myroot.getElementsByTagName("access")[0], format="strip") + except IndexError: + self.access = "" + self.bugs = getMultiTagsText(myroot, "bug", format="strip") + self.references = getMultiTagsText(myroot.getElementsByTagName("references")[0], "uri", format="keep") + + # and now the formatted text elements + self.description = getText(myroot.getElementsByTagName("description")[0], format="xml") + self.workaround = getText(myroot.getElementsByTagName("workaround")[0], format="xml") + self.resolution = getText(myroot.getElementsByTagName("resolution")[0], format="xml") + self.impact_text = getText(myroot.getElementsByTagName("impact")[0], format="xml") + self.impact_type = myroot.getElementsByTagName("impact")[0].getAttribute("type") + try: + self.background = getText(myroot.getElementsByTagName("background")[0], format="xml") + except IndexError: + self.background = "" + + # finally the interesting tags (product, affected, package) + self.glsatype = myroot.getElementsByTagName("product")[0].getAttribute("type") + self.product = getText(myroot.getElementsByTagName("product")[0], format="strip") + self.affected = myroot.getElementsByTagName("affected")[0] + self.packages = {} + for p in self.affected.getElementsByTagName("package"): + name = p.getAttribute("name") + if not name in self.packages: + self.packages[name] = [] + tmp = {} + tmp["arch"] = p.getAttribute("arch") + tmp["auto"] = (p.getAttribute("auto") == "yes") + tmp["vul_vers"] = [makeVersion(v) for v in p.getElementsByTagName("vulnerable")] + tmp["unaff_vers"] = [makeVersion(v) for v in p.getElementsByTagName("unaffected")] + tmp["vul_atoms"] = [makeAtom(name, v) for v in p.getElementsByTagName("vulnerable")] + tmp["unaff_atoms"] = [makeAtom(name, v) for v in p.getElementsByTagName("unaffected")] + self.packages[name].append(tmp) + # TODO: services aren't really used yet + self.services = self.affected.getElementsByTagName("service") + return None + + def dump(self, outstream=sys.stdout): + """ + Dumps a plaintext representation of this GLSA to I{outfile} or + B{stdout} if it is ommitted. You can specify an alternate + I{encoding} if needed (default is latin1). + + @type outstream: File + @param outfile: Stream that should be used for writing + (defaults to sys.stdout) + """ + width = int(self.config["PRINTWIDTH"]) + outstream.write(center("GLSA %s: \n%s" % (self.nr, self.title), width)+"\n") + outstream.write((width*"=")+"\n") + outstream.write(wrap(self.synopsis, width, caption="Synopsis: ")+"\n") + outstream.write("Announced on: %s\n" % self.announced) + outstream.write("Last revised on: %s\n\n" % self.revised) + if self.glsatype == "ebuild": + for k in self.packages.keys(): + pkg = self.packages[k] + for path in pkg: + vul_vers = "".join(path["vul_vers"]) + unaff_vers = "".join(path["unaff_vers"]) + outstream.write("Affected package: %s\n" % k) + outstream.write("Affected archs: ") + if path["arch"] == "*": + outstream.write("All\n") + else: + outstream.write("%s\n" % path["arch"]) + outstream.write("Vulnerable: %s\n" % vul_vers) + outstream.write("Unaffected: %s\n\n" % unaff_vers) + elif self.glsatype == "infrastructure": + pass + if len(self.bugs) > 0: + outstream.write("\nRelated bugs: ") + for i in range(0, len(self.bugs)): + outstream.write(self.bugs[i]) + if i < len(self.bugs)-1: + outstream.write(", ") + else: + outstream.write("\n") + if self.background: + outstream.write("\n"+wrap(self.background, width, caption="Background: ")) + outstream.write("\n"+wrap(self.description, width, caption="Description: ")) + outstream.write("\n"+wrap(self.impact_text, width, caption="Impact: ")) + outstream.write("\n"+wrap(self.workaround, width, caption="Workaround: ")) + outstream.write("\n"+wrap(self.resolution, width, caption="Resolution: ")) + myreferences = "" + for r in self.references: + myreferences += (r.replace(" ", SPACE_ESCAPE)+NEWLINE_ESCAPE+" ") + outstream.write("\n"+wrap(myreferences, width, caption="References: ")) + outstream.write("\n") + + def isVulnerable(self): + """ + Tests if the system is affected by this GLSA by checking if any + vulnerable package versions are installed. Also checks for affected + architectures. + + @rtype: Boolean + @returns: True if the system is affected, False if not + """ + vList = [] + rValue = False + for k in self.packages.keys(): + pkg = self.packages[k] + for path in pkg: + if path["arch"] == "*" or self.config["ARCH"] in path["arch"].split(): + for v in path["vul_atoms"]: + rValue = rValue \ + or (len(match(v, "vartree")) > 0 \ + and getMinUpgrade(path["vul_atoms"], path["unaff_atoms"])) + return rValue + + def isApplied(self): + """ + Looks if the GLSA IDis in the GLSA checkfile to check if this + GLSA was already applied. + + @rtype: Boolean + @returns: True if the GLSA was applied, False if not + """ + aList = portage.grabfile(self.config["CHECKFILE"]) + return (self.nr in aList) + + def inject(self): + """ + Puts the ID of this GLSA into the GLSA checkfile, so it won't + show up on future checks. Should be called after a GLSA is + applied or on explicit user request. + + @rtype: None + @returns: None + """ + if not self.isApplied(): + checkfile = open(self.config["CHECKFILE"], "a+") + checkfile.write(self.nr+"\n") + checkfile.close() + return None + + def getMergeList(self, least_change=True): + """ + Returns the list of package-versions that have to be merged to + apply this GLSA properly. The versions are as low as possible + while avoiding downgrades (see L{getMinUpgrade}). + + @type least_change: Boolean + @param least_change: True if the smallest possible upgrade should be selected, + False for an emerge-like algorithm + @rtype: List of Strings + @return: list of package-versions that have to be merged + """ + rValue = [] + for pkg in self.packages.keys(): + for path in self.packages[pkg]: + update = getMinUpgrade(path["vul_atoms"], path["unaff_atoms"], minimize=least_change) + if update: + rValue.append(update) + return rValue diff --git a/src/revdep-rebuild/99revdep-rebuild b/src/revdep-rebuild/99revdep-rebuild new file mode 100644 index 0000000..bdaecc7 --- /dev/null +++ b/src/revdep-rebuild/99revdep-rebuild @@ -0,0 +1,21 @@ +# Default revdep-rebuild configuration file +# +# revdep-rebuild no longer uses hardcoded paths. To change the default +# behavior the following variables can be changed: +# +# LD_LIBRARY_MASK - Mask of specially evaluated libraries +# +# SEARCH_DIRS - List of directories to search for executables and libraries +# Use this for directories that are not included in PATH or ld.so.conf. +# An application should normally not have to set this variable +# +# SEARCH_DIRS_MASK - List of directories to not search +# Use this for directories that should not be searched by revdep-rebuild +# This is normally used by binary packages such as openoffice-bin +# +# Note: This file is sourced using bash by the revdep-rebuild script + +LD_LIBRARY_MASK="libodbcinst.so libodbc.so libjava.so libjvm.so" +SEARCH_DIRS="/bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" +SEARCH_DIRS_MASK="/lib*/modules" + diff --git a/src/revdep-rebuild/AUTHORS b/src/revdep-rebuild/AUTHORS new file mode 100644 index 0000000..b3d9b32 --- /dev/null +++ b/src/revdep-rebuild/AUTHORS @@ -0,0 +1,2 @@ +Stanislav Brabec (original author) +Paul Varner <fuzzyray@gentoo.org> diff --git a/src/revdep-rebuild/ChangeLog b/src/revdep-rebuild/ChangeLog new file mode 100644 index 0000000..9060781 --- /dev/null +++ b/src/revdep-rebuild/ChangeLog @@ -0,0 +1,9 @@ +2005-06-05 Paul Varner <fuzzyray@gentoo.org> + + * ChangeLog moved to main gentoolkit ChangeLog + +2004-01-07 Karl Trygve Kalleberg <karltk@gentoo.org> + + * Added Makefile + * Copied revdep-rebuild script from app-portage/gentoolkit + diff --git a/src/revdep-rebuild/Makefile b/src/revdep-rebuild/Makefile new file mode 100644 index 0000000..d509681 --- /dev/null +++ b/src/revdep-rebuild/Makefile @@ -0,0 +1,23 @@ +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + echo "AIGBURTH (AYG-berth n.) Any piece of readily identifiable anatomy found among cooked meat." + +dist: + mkdir -p ../../$(distdir)/src/revdep-rebuild + cp Makefile AUTHORS README TODO ChangeLog revdep-rebuild revdep-rebuild.1 99revdep-rebuild ../../$(distdir)/src/revdep-rebuild/ + +install: + + install -m 0755 revdep-rebuild $(bindir)/ + install -d $(docdir)/revdep-rebuild + install -m 0644 AUTHORS README TODO $(docdir)/revdep-rebuild/ + install -m 0644 revdep-rebuild.1 $(mandir)/ + install -d $(sysconfdir)/revdep-rebuild + install -m 0644 99revdep-rebuild $(sysconfdir)/revdep-rebuild/ diff --git a/src/revdep-rebuild/README b/src/revdep-rebuild/README new file mode 100644 index 0000000..3a51d9f --- /dev/null +++ b/src/revdep-rebuild/README @@ -0,0 +1,4 @@ +This tool scans libraries and binaries for broken shared lib dependencies +and fixes them by re-emerging those broken binaries and shared libraries. + +- Alastair Tse <liquidx@gentoo.org> diff --git a/src/revdep-rebuild/TODO b/src/revdep-rebuild/TODO new file mode 100644 index 0000000..d9f6350 --- /dev/null +++ b/src/revdep-rebuild/TODO @@ -0,0 +1,7 @@ +- revdep cache in /var/cache + - list all .so files this package depends on + - use timestamps of files to know when to rebuild + - if ts of cache is older than any of the package's contained + files, we must rebuild +- update to use equery/gentoolkit +- rewrite in python and/or eclectic module diff --git a/src/revdep-rebuild/find_pkgs.py b/src/revdep-rebuild/find_pkgs.py new file mode 100755 index 0000000..7013813 --- /dev/null +++ b/src/revdep-rebuild/find_pkgs.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# Copyright 1999-2005 Gentoo Foundation +# $Header$ + +# Temporary script to find package versions and slot for revdep-rebuild + +import sys + +sys.path.insert(0, "/usr/lib/gentoolkit/pym") +import gentoolkit + +for pkgname in sys.argv[1:]: + matches = gentoolkit.find_packages(pkgname) + for pkg in matches: + (cat, name, ver, rev) = gentoolkit.split_package_name(pkg.get_cpv()) + slot = pkg.get_env_var("SLOT") + if rev == "r0": + fullversion = ver + else: + fullversion = ver + "-" + rev + + print name + " " + fullversion + " (" + slot + ")" diff --git a/src/revdep-rebuild/revdep-rebuild b/src/revdep-rebuild/revdep-rebuild new file mode 100755 index 0000000..72efba0 --- /dev/null +++ b/src/revdep-rebuild/revdep-rebuild @@ -0,0 +1,1120 @@ +#!/bin/bash +# Copyright 1999-2008 Gentoo Foundation + +# revdep-rebuild: Reverse dependency rebuilder. +# Original Author: Stanislav Brabec +# Rewrite Author: Michael A. Smith +# Current Maintainer: Paul Varner <fuzzyray@gentoo.org> + +# TODO: +# - Use more /etc/init.d/functions.sh +# - Try to reduce the number of global vars + +## +# Global Variables: + +# Must-be-blank: +unset GREP_OPTIONS + +# Readonly variables: +declare -r APP_NAME="${0##*/}" # The name of this application +declare -r OIFS="$IFS" # Save the IFS +declare -r ENV_FILE=0_env.rr # Contains environment variables +declare -r FILES_FILE=1_files.rr # Contains a list of files to search +declare -r LDPATH_FILE=2_ldpath.rr # Contains the LDPATH +declare -r BROKEN_FILE=3_broken.rr # Contains the list of broken files +declare -r ERRORS_FILE=3_errors.rr # Contains the ldd error output +declare -r RAW_FILE=4_raw.rr # Contains the raw list of packages +declare -r OWNERS_FILE=4_owners.rr # Contains the file owners +declare -r PKGS_FILE=4_pkgs.rr # Contains the unsorted bare package names +declare -r EBUILDS_FILE=4_ebuilds.rr # Contains the unsorted atoms + # (Appropriately slotted or versioned) +declare -r ORDER_FILE=5_order.rr # Contains the sorted atoms +declare -r STATUS_FILE=6_status.rr # Contains the ldd error output +declare -ra FILES=( + "$ENV_FILE" + "$FILES_FILE" + "$LDPATH_FILE" + "$BROKEN_FILE" + "$ERRORS_FILE" + "$RAW_FILE" + "$OWNERS_FILE" + "$PKGS_FILE" + "$EBUILDS_FILE" + "$ORDER_FILE" + "$STATUS_FILE" +) + +# "Boolean" variables: Considered "true" if it has any value at all +# "True" indicates we should... +declare FULL_LD_PATH # ...search across the COMPLETE_LD_LIBRARY_PATH +declare KEEP_TEMP # ...not delete tempfiles from the current run +declare ORDER_PKGS # ...sort the atoms in deep dependency order +declare PACKAGE_NAMES # ...emerge by slot, not by versionated atom +declare RM_OLD_TEMPFILES # ...remove tempfiles from prior runs +declare SEARCH_BROKEN # ...search for broken libraries and binaries +declare VERBOSE # ...give verbose output + +# Globals that impact portage directly: +declare EMERGE_DEFAULT_OPTS # String of options portage assumes to be set +declare EMERGE_OPTIONS # Array of options to pass to portage +declare PORTAGE_NICENESS # Renice to this value +declare PORTAGE_ROOT # The root path for portage + +# Customizable incremental variables: +# These variables can be prepended to either by setting the variable in +# your environment prior to execution, or by placing an entry in +# /etc/make.conf. +# +# An entry of "-*" means to clear the variable from that point forward. +# Example: env SEARCH_DIRS="/usr/bin -*" revdep-rebuild will set SEARCH_DIRS +# to contain only /usr/bin +declare LD_LIBRARY_MASK # Mask of specially evaluated libraries +declare SEARCH_DIRS # List of dirs to search for executables and libraries +declare SEARCH_DIRS_MASK # List of dirs not to search + +# Other globals: +declare OLDPROG # Previous pass through the progress meter +declare EXACT_PKG # Versionated atom to emerge +declare HEAD_TEXT # Feedback string about the search +declare NOCOLOR # Set to "true" not to output term colors +declare OK_TEXT # Feedback about a search which found no errors +declare RC_NOCOLOR # Hack to insure we respect NOCOLOR +declare REBUILD_LIST # Array of atoms to emerge +declare SKIP_LIST # Array of atoms that cannot be emerged (masked?) +declare SONAME # Soname/soname path pattern given on commandline +declare SONAME_SEARCH # Value of SONAME modified to match ldd's output +declare WORKING_TEXT # Feedback about the search +declare WORKING_DIR # Working directory where cache files are kept + +main() { + # preliminary setup + get_opts "$@" + setup_portage + setup_search_paths_and_masks + get_search_env + echo + + # Search for broken binaries + get_files + get_ldpath + main_checks + + # Associate broken binaries with packages to rebuild + if [[ $PACKAGE_NAMES ]]; then + get_packages + clean_packages + assign_packages_to_ebuilds + else + get_exact_ebuilds + fi + + # Rebuild packages owning broken binaries + get_build_order + rebuild + + # All done + cleanup +} +## +# Refuse to delete anything before we cd to our tmpdir +# (See mkdir_and_cd_to_tmpdir() +rm() { + eerror "I was instructed to rm '$@'" + die 1 "Refusing to delete anything before changing to temporary directory." +} +: <<'EW' +## +# GNU find has -executable, but if our users' finds do not have that flag +# we emulate it with this function. Also emulates -writable and -readable. +# Usage: find PATH ARGS -- use find like normal, except use -executable instead +# of various versions of -perm /+ blah blah and hacks +find() { + hash find || { die 1 'find not found!'; } + # We can be pretty sure find itself should be executable. + local testsubject="$(type -P find)" + if [[ $(command find "$testsubject" -executable 2> /dev/null) ]]; then + unset -f find # We can just use the command find + elif [[ $(command find "$testsubject" -perm /u+x 2> /dev/null) ]]; then + find() { + a=(${@//-executable/-perm \/u+x}) + a=(${a[@]//-writable/-perm \/u+w}) + a=(${a[@]//-readable/-perm \/r+w}) + command find "${a[@]}" + } + elif [[ $(command find "$testsubject" -perm +u+x 2> /dev/null) ]]; then + find() { + a=(${@//-executable/-perm +u+x}) + a=(${a[@]//-writable/-perm +u+w}) + a=(${a[@]//-readable/-perm +r+w}) + command find "${a[@]}" + } + else # Last resort + find() { + a=(${@//-executable/-exec test -x '{}' \; -print}) + a=(${a[@]//-writable/-exec test -w '{}' \; -print}) + a=(${a[@]//-readable/-exec test -r '{}' \; -print}) + command find "${a[@]}" + } + fi + find "$@" +} +EW + +print_usage() { +cat << EOF +Usage: $APP_NAME [OPTIONS] [--] [EMERGE_OPTIONS] + +Broken reverse dependency rebuilder. + + -C, --nocolor Turn off colored output + -d, --debug Print way too much information (uses bash's set -xv) + -e, --exact Emerge based on exact package version + -h, --help Print this usage + -i, --ignore Ignore temporary files from previous runs + -k, --keep-temp Do not delete temporary files on exit + -L, --library NAME Emerge existing packages that use the library with NAME + --library=NAME NAME can be a full path to the library or a basic + regular expression (man grep) + -l, --no-ld-path Do not set LD_LIBRARY_PATH + -o, --no-order Do not check the build order + (Saves time, but may cause breakage.) + -p, --pretend Do a trial run without actually emerging anything + (also passed to emerge command) + -P, --no-progress Turn off the progress meter + -q, --quiet Be less verbose (also passed to emerge command) + -v, --verbose Be more verbose (also passed to emerge command) + +Calls emerge, options after -- are ignored by $APP_NAME +and passed directly to emerge. + +Report bugs to <http://bugs.gentoo.org> +EOF +} +## +# Usage: progress i n +# i: current item +# n: total number of items to process +progress() { + if [[ -t 1 ]]; then + progress() { + local curProg=$(( $1 * 100 / $2 )) + (( curProg == OLDPROG )) && return # no change, output nothing + OLDPROG="$curProg" # must be a global variable + (( $1 == $2 )) && local lb=$'\n' + echo -ne '\r \r'"[ $curProg% ] $lb" + } + progress $@ + else # STDOUT is not a tty. Disable progress meter. + progress() { :; } + fi +} +## +# Usage: countdown n +# n: number of seconds to count +countdown() { + local i + for ((i=1; i<$1; i++)); do + echo -ne '\a.' + ((i<$1)) && sleep 1 + done + echo -e '\a.' +} +## +# Replace whitespace with linebreaks, normalize repeated '/' chars, and sort -u +# (If any libs have whitespace in their filenames, someone needs punishment.) +clean_var() { + gawk 'BEGIN {RS="[[:space:]]"} + /-\*/ {exit} + /[^[:space:]]/ {gsub(/\/\/+/, "/"); print}' | sort -u +} +## +# Exit and optionally output to sterr +die() { + local status=$1 + shift + eerror "$@" + exit $status +} +## +# What to do when dynamic linking is consistent +clean_exit() { + if [[ ! $KEEP_TEMP ]]; then + rm -f "${FILES[@]}" + if [[ "$WORKING_DIR" != "/var/cache/${APP_NAME}" ]]; then + # Remove the working directory + builtin cd; rmdir "$WORKING_DIR" + fi + fi + echo + einfo "$OK_TEXT... All done. " + exit 0 +} +## +# Get the name of the package that owns a file or list of files given as args. +get_file_owner() { + local IFS=$'\n' + # ${*/%/ } adds a space to the end of each object name to prevent false + # matches, for example /usr/bin/dia matching /usr/bin/dialog (bug #196460). + find -L /var/db/pkg -name CONTENTS -print0 | + xargs -0 grep -Fl "${*/%/ }" | + sed 's:/var/db/pkg/\(.*\)/CONTENTS:\1:' +} +## +# Normalize some EMERGE_OPTIONS +normalize_emerge_opts() { + # Normalize some EMERGE_OPTIONS + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-p/--pretend}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-f/--fetchonly}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-v/--verbose}) +} +## +# Use the color preference from portage +setup_color() { + # This should still work if NOCOLOR is set by the -C flag or in the user's + # environment. + export NOCOLOR=$(portageq envvar NOCOLOR) + [[ $NOCOLOR = yes || $NOCOLOR = true ]] && export RC_NOCOLOR=yes # HACK! (grr) + . /etc/init.d/functions.sh +} +## +# Die if an argument is missing. +die_if_missing_arg() { + [[ ! $2 || $2 = -* ]] && die 1 "Missing expected argument to $1" +} +## +# Die because an option is not recognized. +die_invalid_option() { + # Can't use eerror and einfo because this gets called before function.sh + # is sourced + echo + echo "Encountered unrecognized option $1." >&2 + echo + echo "$APP_NAME no longer automatically passes unrecognized options to portage." + echo "Separate emerge-only options from revdep-rebuild options with the -- flag." + echo + echo "For example, $APP_NAME -v -- --ask" + echo + echo "See the man page or $APP_NAME -h for more detail." + echo + exit 1 +} +## +# Warn about deprecated options. +warn_deprecated_opt() { + # Can't use eerror and einfo because this gets called before function.sh + # is sourced + echo + echo "Encountered deprecated option $1." >&2 + [[ $2 ]] && echo "Please use $2 instead." >&2 +} +## +# Get whole-word commandline options preceded by two dashes. +get_longopts() { + case $1 in + --nocolor) export NOCOLOR="yes";; + --no-color) warn_deprecated_opt "$1" "--nocolor" + export NOCOLOR="yes";; + --debug) set -xv;; + --exact) unset PACKAGE_NAMES;; + --help) print_usage + exit 0;; + --ignore) RM_OLD_TEMPFILES=1;; + --keep-temp) KEEP_TEMP=1;; + --library=*) # TODO: check for invalid values + SONAME="${1#*=}" + unset SEARCH_BROKEN;; + --soname=*|--soname-regexp=*) # TODO: check for invalid values + warn_deprecated_opt "${1%=*}" "--library" + SONAME="${1#*=}" + unset SEARCH_BROKEN;; + --library) # TODO: check for invalid values + die_if_missing_arg $1 $2 + shift + SONAME="$1" + unset SEARCH_BROKEN;; + --soname|--soname-regexp) # TODO: check for invalid values + warn_deprecated_opt "$1" "--library" + die_if_missing_arg $1 $2 + shift + SONAME="$1" + unset SEARCH_BROKEN;; + --no-ld-path) unset FULL_LD_PATH;; + --no-order) unset ORDER_PKGS;; + --no-progress) progress() { :; };; + --pretend) EMERGE_OPTIONS+=("--pretend");; + --quiet) echo_v() { :; } + progress() { :; } + quiet=1 + EMERGE_OPTIONS+=($1);; + --verbose) VERBOSE=1 + EMERGE_OPTIONS+=("--verbose");; + --extra-verbose) warn_deprecated_opt "$1" "--verbose" + VERBOSE=1 + EMERGE_OPTIONS+=("--verbose");; + --package-names) # No longer used, since it is the + # default. We accept it for + # backwards compatibility. + warn_deprecated_opt "$1" + PACKAGE_NAMES=1;; + *) die_invalid_option $1;; + esac +} + +## +# Get single-letter commandline options preceded by a single dash. +get_shortopts() { + local OPT OPTSTRING OPTARG OPTIND + while getopts ":CdehikL:loPpqu:vX" OPT; do + case "$OPT" in + C) # TODO: Match syntax with the rest of gentoolkit + export NOCOLOR="yes";; + d) set -xv;; + e) unset PACKAGE_NAMES;; + h) print_usage + exit 0;; + i) RM_OLD_TEMPFILES=1;; + k) KEEP_TEMP=1;; + L) # TODO: Check for invalid values + SONAME="${OPTARG#*=}" + unset SEARCH_BROKEN;; + l) unset FULL_LD_PATH;; + o) unset ORDER_PKGS;; + P) progress() { :; };; + p) EMERGE_OPTIONS+=("--pretend");; + q) echo_v() { :; } + progress() { :; } + quiet=1 + EMERGE_OPTIONS+=("--quiet");; + v) VERBOSE=1 + EMERGE_OPTIONS+=("--verbose");; + X) # No longer used, since it is the default. + # We accept it for backwards compatibility. + warn_deprecated_opt "-X" + PACKAGE_NAMES=1;; + *) die_invalid_option "-$OPTARG";; + esac + done +} +## +# Get command-line options. +get_opts() { + local avoid_utils + local -a args + echo_v() { ewarn "$@"; } + unset VERBOSE KEEP_TEMP EMERGE_OPTIONS RM_OLD_TEMPFILES + ORDER_PKGS=1 + PACKAGE_NAMES=1 + SONAME="not found" + SEARCH_BROKEN=1 + FULL_LD_PATH=1 + while [[ $1 ]]; do + case $1 in + --) shift + EMERGE_OPTIONS+=("$@") + break;; + -*) while true; do + args+=("$1") + shift + [[ ${1:--} = -* ]] && break + done + if [[ ${args[0]} = --* ]]; then + get_longopts "${args[@]}" + else + get_shortopts "${args[@]}" + fi;; + *) die_invalid_option "$1";; + esac + unset args + done + + setup_color + normalize_emerge_opts + + # If the user is not super, add --pretend to EMERGE_OPTIONS + if [[ ${EMERGE_OPTIONS[@]} != *--pretend* && $UID -ne 0 ]]; then + ewarn "You are not superuser. Adding --pretend to emerge options." + EMERGE_OPTIONS+=(--pretend) + fi +} +## +# Is there a --pretend or --fetchonly flag in the EMERGE_OPTIONS array? +is_real_merge() { + [[ ${EMERGE_OPTIONS[@]} != *--pretend* && + ${EMERGE_OPTIONS[@]} != *--fetchonly* ]] +} +## +# Clean up temporary files and exit +cleanup_and_die() { + rm -f "$@" + die 1 " ...terminated. Removing incomplete $@." +} +## +# Clean trap +clean_trap() { + trap "cleanup_and_die $*" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + rm -f "$@" +} +## +# Returns 0 if the first arg is found in the remaining args, 1 otherwise +# (Returns 2 if given fewer than 2 arguments) +has() { + (( $# > 1 )) || return 2 + local IFS=$'\a' target="$1" + shift + [[ $'\a'"$*"$'\a' = *$'\a'$target$'\a'* ]] +} +## +# Dies when it can't change directories +cd() { + if builtin cd -P "$@"; then + if [[ $1 != $PWD ]]; then + # Some symlink malfeasance is going on + die 1 "Working directory expected to be $1, but it is $PWD" + fi + else + die 1 "Unable to change working directory to '$@'" + fi +} +## +# Tries not to delete any files or directories it shouldn't +setup_rm() { + ## + # Anything in the FILES array in tmpdir is fair game for removal + rm() { + local i IFS=$'\a' + [[ $APP_NAME ]] || die 1 '$APP_NAME is not defined! (This is a bug.)' + case $@ in + */*|*-r*|*-R*) die 1 "Oops, I'm not allowed to delete that. ($@)";; + esac + for i; do + # Don't delete files that are not listed in the array + # Allow no slashes or recursive deletes at all. + case $i in + */*|-*r*|-*R*) :;; # Not OK + -*) continue;; # OK + esac + has "$i" "${FILES[@]}" && continue + die 1 "Oops, I'm not allowed to delete that. ($@)" + done + command rm "$@" + } + # delete this setup function so it's harmless to re-run + setup_rm() { :; } +} +## +# Make our temporary files directory +# $1 - directory name +# $2 - user name +verify_tmpdir() { + umask 007 || die $? "Unable to set umask 007" + if [[ ! $1 ]]; then + die 1 'Temporary file path is unset! (This is a bug.)' + elif [[ -d $1 ]]; then + # HACK: I hate using find this way + if [[ $(find "$1" -type d ! \( -user $2 -perm -0700 \) ) ]]; then + eerror "Incorrect permissions on $1" + eerror "or at least one file in $1." + die 1 "Please make sure it's not a symlink and then remove it." + fi + cd "$1" + else + die 1 "Unable to find a satisfactory location for temporary files ($1)" + fi + [[ $VERBOSE ]] && einfo "Temporary cache files are located in $PWD" + setup_rm +} +get_search_env() { + local new_env + local old_env + local uid=$(python -c 'import os; import pwd; print pwd.getpwuid(os.getuid())[0]') + # Find a place to put temporary files + if [[ "$uid" == "root" ]]; then + local tmp_target="/var/cache/${APP_NAME}" + else + local tmp_target="$(mktemp -d -t revdep-rebuild.XXXXXXXXXX)" + fi + + # From here on all work is done inside the temporary directory + verify_tmpdir "$tmp_target" "$uid" + WORKING_DIR="$tmp_target" + + if [[ $SEARCH_BROKEN ]]; then + SONAME_SEARCH="$SONAME" + HEAD_TEXT="broken by a package update" + OK_TEXT="Dynamic linking on your system is consistent" + WORKING_TEXT="consistency" + else + # first case is needed to test against /path/to/foo.so + if [[ $SONAME = /* ]]; then + # Set to "<space>$SONAME<space>" + SONAME_SEARCH=" $SONAME " + # Escape the "/" characters + SONAME_SEARCH="${SONAME_SEARCH//\//\\/}" + else + # Set to "<tab>$SONAME<space>" + SONAME_SEARCH=$'\t'"$SONAME " + fi + HEAD_TEXT="using $SONAME" + OK_TEXT="There are no dynamic links to $SONAME" + unset WORKING_TEXT + fi + + # If any of our temporary files are older than 1 day, remove them all + if [[ ! $KEEP_TEMP ]]; then + while read; do + RM_OLD_TEMPFILES=1 + break + done < <(find -L . -maxdepth 1 -type f -name '*.rr' -mmin +1440 -print 2>/dev/null) + fi + + # Compare old and new environments + # Don't use our previous files if environment doesn't match + new_env=$( + # We do not care if these emerge options change + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--pretend/}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--fetchonly/}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--verbose/}) + cat <<- EOF + SEARCH_DIRS="$SEARCH_DIRS" + SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK" + LD_LIBRARY_MASK="$LD_LIBRARY_MASK" + PORTAGE_ROOT="$PORTAGE_ROOT" + EMERGE_OPTIONS="${EMERGE_OPTIONS[@]}" + ORDER_PKGS="$ORDER_PKGS" + FULL_LD_PATH="$FULL_LD_PATH" + EOF + ) + if [[ -r "$ENV_FILE" && -s "$ENV_FILE" ]]; then + old_env=$(<"$ENV_FILE") + if [[ $old_env != $new_env ]]; then + ewarn 'Environment mismatch from previous run, deleting temporary files...' + RM_OLD_TEMPFILES=1 + fi + else + # No env file found, silently delete any other tempfiles that may exist + RM_OLD_TEMPFILES=1 + fi + + # If we should remove old tempfiles, do so + if [[ $RM_OLD_TEMPFILES ]]; then + rm -f "${FILES[@]}" + else + for file in "${FILES[@]}"; do + if [ -e "$file" ]; then + chown ${uid}:portage "$file" + chmod 700 "$file" + fi + done + fi + + # Save the environment in a file for next time + echo "$new_env" > "$ENV_FILE" + + [[ $VERBOSE ]] && echo $'\n'"$APP_NAME environment:"$'\n'"$new_env" + + echo + einfo "Checking reverse dependencies" + einfo "Packages containing binaries and libraries $HEAD_TEXT" + einfo "will be emerged." +} + +get_files() { + einfo "Collecting system binaries and libraries" + if [[ -r "$FILES_FILE" && -s "$FILES_FILE" ]]; then + einfo "Found existing $FILES_FILE" + else + # Be safe and remove any extraneous temporary files + # Don't remove 0_env.rr - The first file in the array + rm -f "${FILES[@]:1}" + + clean_trap "$FILES_FILE" + + if [[ $SEARCH_DIRS_MASK ]]; then + findMask=($SEARCH_DIRS_MASK) + findMask="${findMask[@]/#/-o -path }" + findMask="( ${findMask#-o } ) -prune -o" + fi + # TODO: Check this -- afaict SEARCH_DIRS isn't an array, so this should just be $SEARCH_DIRS? + find ${SEARCH_DIRS[@]} $findMask -type f \( -perm -u+x -o -perm -g+x -o -perm -o+x -o \ + -name '*.so' -o -name '*.so.*' -o -name '*.la' \) -print 2> /dev/null | + sort -u > "$FILES_FILE" || + die $? "find failed to list binary files (This is a bug.)" + einfo "Generated new $FILES_FILE" + fi +} +get_ldpath() { + local COMPLETE_LD_LIBRARY_PATH + [[ $SEARCH_BROKEN && $FULL_LD_PATH ]] || return + einfo 'Collecting complete LD_LIBRARY_PATH' + if [[ -r "$LDPATH_FILE" && -s "$LDPATH_FILE" ]]; then + einfo "Found existing $LDPATH_FILE." + else + clean_trap "$LDPATH_FILE" + # Ensure that the "trusted" lib directories are at the start of the path + COMPLETE_LD_LIBRARY_PATH=( + /lib* + /usr/lib* + $(sed '/^#/d;s/#.*$//' < /etc/ld.so.conf) + $(sed 's:/[^/]*$::' < "$FILES_FILE" | sort -ru) + ) + IFS=':' + COMPLETE_LD_LIBRARY_PATH="${COMPLETE_LD_LIBRARY_PATH[*]}" + IFS="$OIFS" + echo "$COMPLETE_LD_LIBRARY_PATH" > "$LDPATH_FILE" + einfo "Generated new $LDPATH_FILE" + fi +} +main_checks() { + local target_file + local -a files + local i=0 + local ldd_output + local ldd_status + local numFiles + local COMPLETE_LD_LIBRARY_PATH + if [[ $SEARCH_BROKEN && $FULL_LD_PATH ]]; then + [[ -r "$LDPATH_FILE" && -s "$LDPATH_FILE" ]] || + die 1 "Unable to find $LDPATH_FILE" + COMPLETE_LD_LIBRARY_PATH=$(<"$LDPATH_FILE") + fi + einfo "Checking dynamic linking $WORKING_TEXT" + if [[ -r "$BROKEN_FILE" && -s "$BROKEN_FILE" ]]; then + einfo "Found existing $BROKEN_FILE." + else + clean_trap "$BROKEN_FILE" "$ERRORS_FILE" + files=($(<"$FILES_FILE")) + numFiles="${#files[@]}" + for target_file in "${files[@]}"; do + if [[ $target_file != *.la ]]; then + # Note: double checking seems to be faster than single with complete path + # (special add ons are rare). + ldd_output=$(ldd "$target_file" 2>> "$ERRORS_FILE" | sort -u) + ldd_status=$? # TODO: Check this for problems with sort + # HACK: if LD_LIBRARY_MASK is null or undefined grep -vF doesn't work + if grep -vF "${LD_LIBRARY_MASK:=$'\a'}" <<< "$ldd_output" | + grep -q "$SONAME_SEARCH"; then + if [[ $SEARCH_BROKEN && $FULL_LD_PATH ]]; then + if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$target_file" 2>/dev/null | + grep -vF "$LD_LIBRARY_MASK" | grep -q "$SONAME_SEARCH"; then + # FIXME: I hate duplicating code + # Only build missing direct dependencies + MISSING_LIBS=$( + expr='s/[[:space:]]*\([^[:space:]]*\) => not found/\1/p' + sed -n "$expr" <<< "$ldd_output" + ) + REQUIRED_LIBS=$( + expr='s/^[[:space:]]*NEEDED[[:space:]]*\([^[:space:]]*\).*/\1/p'; + objdump -x "$target_file" | grep NEEDED | sed "$expr" | sort -u + ) + MISSING_LIBS=$(grep -F "$REQUIRED_LIBS" <<< "$MISSING_LIBS") + if [[ $MISSING_LIBS ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (requires $MISSING_LIBS)" + fi + fi + else + # FIXME: I hate duplicating code + # Only rebuild for direct dependencies + MISSING_LIBS=$( + expr="/$SONAME_SEARCH/s/^[[:space:]]*\([^[:space:]]*\).*$/\1/p" + sort -u <<< "$ldd_output" | sed -n "$expr" + ) + REQUIRED_LIBS=$( + expr='s/^[[:space:]]*NEEDED[[:space:]]*\([^[:space:]]*\).*/\1/p'; + objdump -x "$target_file" | grep NEEDED | sed "$expr" | sort -u + ) + MISSING_LIBS=$(grep -F "$REQUIRED_LIBS" <<< "$MISSING_LIBS") + if [[ $MISSING_LIBS ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + if [[ $SEARCH_BROKEN ]]; then + echo_v " broken $target_file (requires $MISSING_LIBS)" + else + echo_v " found $target_file" + fi + fi + fi + fi + elif [[ $SEARCH_BROKEN ]]; then + # Look for broken .la files + la_SEARCH_DIRS="$SEARCH_DIRS" + la_search_dir="" + la_broken="" + la_lib="" + for depend in $( + gawk -F"[=']" '/^dependency_libs/{ + print $3 + }' "$target_file" + ); do + if [[ $depend = /* && ! -e $depend ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (requires $depend)" + elif [[ $depend = -[LR]/* ]]; then + if ! [[ $'\n'${la_SEARCH_DIRS}$'\n' == *$'\n'${depend#-?}$'\n'* ]]; then + la_SEARCH_DIRS+=$'\n'"${depend#-?}" + fi + elif [[ $depend = "-l"* ]]; then + la_lib="lib${depend#-l}" + la_broken="yes" + IFS=$'\n' + for la_search_dir in $la_SEARCH_DIRS; do + if [[ -e ${la_search_dir}/${la_lib}.so || -e ${la_search_dir}/${la_lib}.a ]]; then + la_broken="no" + fi + done + IFS="$OIFS" + if [[ $la_broken = yes ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (requires $depend)" + fi + fi + done + unset la_SEARCH_DIRS la_search_dir la_broken la_lib + fi + [[ $VERBOSE ]] && + progress $((++i)) $numFiles $target_file || + progress $((++i)) $numFiles + done + if [[ $SEARCH_BROKEN ]]; then + # Look for missing version + while read target_file; do + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (no version information available)" + done < <( + # Regexify LD_LIBRARY_MASK. Exclude it from the search. + LD_LIBRARY_MASK="${LD_LIBRARY_MASK//$'\n'/|}" + gawk -v ldmask="(${LD_LIBRARY_MASK//./\\\.})" ' + /no version information available/ && $0 !~ ldmask { + gsub(/[()]/, "", $NF) + if (seen[$NF]++) next + print $NF + }' "$ERRORS_FILE" + ) + fi + [[ -r "$BROKEN_FILE" && -s "$BROKEN_FILE" ]] || clean_exit + sort -u "$BROKEN_FILE" -o "$BROKEN_FILE" + einfo "Generated new $BROKEN_FILE" + fi +} +get_packages() { + local target_file + local EXACT_PKG + local PKG + local obj + einfo 'Assigning files to packages' + if [[ -r "$RAW_FILE" && -s "$RAW_FILE" ]]; then + einfo "Found existing $RAW_FILE" + else + clean_trap "$RAW_FILE" "$OWNERS_FILE" + while read obj target_file; do + EXACT_PKG=$(get_file_owner $target_file) + if [[ $EXACT_PKG ]]; then + # Strip version information + PKG="${EXACT_PKG%%-r[[:digit:]]*}" + PKG="${PKG%-*}" + echo "$EXACT_PKG" >> "$RAW_FILE" + echo "$target_file -> $EXACT_PKG" >> "$OWNERS_FILE" + echo_v " $target_file -> $PKG" + else + ewarn " !!! $target_file not owned by any package is broken !!!" + echo "$target_file -> (none)" >> "$OWNERS_FILE" + echo_v " $target_file -> (none)" + fi + done < "$BROKEN_FILE" + einfo "Generated new $RAW_FILE and $OWNERS_FILE" + fi + # if we find '(none)' on every line, exit out + if ! grep -qvF '(none)' "$OWNERS_FILE"; then + ewarn "Found some broken files, but none of them were associated with known packages" + ewarn "Unable to proceed with automatic repairs." + ewarn "The broken files are listed in $OWNERS_FILE" + if [[ $VERBOSE ]]; then + ewarn "The broken files are:" + while read filename junk; do + ewarn " $filename" + done < "$OWNERS_FILE" + fi + exit 0 # FIXME: Should we exit 1 here? + fi +} +clean_packages() { + einfo 'Cleaning list of packages to rebuild' + if [[ -r "$PKGS_FILE" && -s "$PKGS_FILE" ]]; then + einfo "Found existing $PKGS_FILE" + else + sort -u "$RAW_FILE" > "$PKGS_FILE" + einfo "Generated new $PKGS_FILE" + fi +} +assign_packages_to_ebuilds() { + local EXACT_PKG + local PKG + local SLOT + einfo 'Assigning packages to ebuilds' + if [[ -r "$EBUILDS_FILE" && -s "$EBUILDS_FILE" ]]; then + einfo "Found existing $EBUILDS_FILE" + elif [[ -r "$PKGS_FILE" && -s "$PKGS_FILE" ]]; then + clean_trap "$EBUILDS_FILE" + while read EXACT_PKG; do + # Get the slot + PKG="${EXACT_PKG%%-r[[:digit:]]*}" + PKG="${PKG%-*}" + SLOT=$(</var/db/pkg/$EXACT_PKG/SLOT) + echo "$PKG:$SLOT" + done < "$PKGS_FILE" > "$EBUILDS_FILE" + einfo "Generated new $EBUILDS_FILE" + else + einfo 'Nothing to rebuild.' + die 1 '(The program should have already quit, so this is a minor bug.)' + fi +} +get_exact_ebuilds() { + einfo 'Assigning files to ebuilds' + if [[ -r $EBUILDS_FILE && -s $EBUILDS_FILE ]]; then + einfo "Found existing $EBUILDS_FILE" + elif [[ -r $BROKEN_FILE && -s $BROKEN_FILE ]]; then + rebuildList=" $(<"$BROKEN_FILE") " + rebuildList=(${rebuildList//[[:space:]]obj[[:space:]]/ }) + get_file_owner "${rebuildList[@]}" | sed 's/^/=/' > "$EBUILDS_FILE" + einfo "Generated new $EBUILDS_FILE" + else + einfo 'Nothing to rebuild.' + die 1 '(The program should have already quit, so this is a minor bug.)' + fi +} +list_skipped_packages() { + ewarn + ewarn 'Portage could not find any version of the following packages it could build:' + ewarn "${SKIP_LIST[@]}" + ewarn + ewarn '(Perhaps they are masked, blocked, or removed from portage.)' + ewarn 'Try to emerge them manually.' + ewarn +} +get_build_order() { + local -r OLD_EMERGE_DEFAULT_OPTS="$EMERGE_DEFAULT_OPTS" + local RAW_REBUILD_LIST + local REBUILD_GREP + local i + if [[ ! $ORDER_PKGS ]]; then + einfo 'Skipping package ordering' + return + fi + einfo 'Evaluating package order' + if [[ -r "$ORDER_FILE" && -s "$ORDER_FILE" ]]; then + einfo "Found existing $ORDER_FILE" + else + clean_trap "$ORDER_FILE" + RAW_REBUILD_LIST=$(<"$EBUILDS_FILE") + if [[ $RAW_REBUILD_LIST ]]; then + export EMERGE_DEFAULT_OPTS="--nospinner --pretend --oneshot --quiet" + RAW_REBUILD_LIST=($RAW_REBUILD_LIST) # convert into array + # If PACKAGE_NAMES is defined we're using slots, not versions + if [[ $PACKAGE_NAMES ]]; then + # Eliminate atoms that can't be built + for i in "${!RAW_REBUILD_LIST[@]}"; do + if [[ "${RAW_REBUILD_LIST[i]}" = *[A-Za-z]* ]]; then + portageq best_visible "$PORTAGE_ROOT" "${RAW_REBUILD_LIST[i]}" >/dev/null && continue + SKIP_LIST+=("${RAW_REBUILD_LIST[i]}") + fi + unset RAW_REBUILD_LIST[i] + done + # If RAW_REBUILD_LIST is empty, then we have nothing to build. + if (( ${#RAW_REBUILD_LIST[@]} == 0 )); then + if (( ${#SKIP_LIST[@]} == 0 )); then + ewarn "The list of packages to skip is empty, but there are no" + ewarn "packages listed to rebuild either. (This is a bug.)" + else + list_skipped_packages + fi + die 1 'Warning: Portage cannot rebuild any of the necessary packages.' + fi + fi + RAW_REBUILD_LIST="${RAW_REBUILD_LIST[@]}" + REBUILD_GREP=$(emerge --nodeps $RAW_REBUILD_LIST | sed 's/\[[^]]*\]//g') + if (( ${PIPESTATUS[0]} == 0 )); then + emerge --deep $RAW_REBUILD_LIST | + sed 's/\[[^]]*\]//g' | + grep -F "$REBUILD_GREP" > "$ORDER_FILE" + fi + + # Here we use the PIPESTATUS from the second emerge, the --deep one. + if (( ${PIPESTATUS[0]} != 0 )); then + eerror + eerror 'Warning: Failed to resolve package order.' + eerror 'Will merge in arbitrary order' + eerror + cat <<- EOF + Possible reasons: + - An ebuild is no longer in the portage tree. + - An ebuild is masked, use /etc/portage/packages.keyword + and/or /etc/portage/package.unmask to unmask it + EOF + countdown 5 + rm -f "$ORDER_FILE" + fi + export EMERGE_DEFAULT_OPTS="$OLD_EMERGE_DEFAULT_OPTS" + else + einfo 'Nothing to rebuild.' + die 1 '(The program should have already quit, so this is a minor bug.)' + fi + fi + [[ -r "$ORDER_FILE" && -s "$ORDER_FILE" ]] && einfo "Generated new $ORDER_FILE" +} + +show_unowned_files() { + if grep -qF '(none)' "$OWNERS_FILE"; then + ewarn "Found some broken files that weren't associated with known packages" + ewarn "The broken files are:" + while read filename junk; do + [[ $junk = *none* ]] && ewarn " $filename" + done < "$OWNERS_FILE" | gawk '!s[$0]++' # (omit dupes) + fi +} +## +# Setup portage and the search paths +setup_portage() { + local PORTAGE_NICENESS=$(portageq envvar PORTAGE_NICENESS) + PORTAGE_ROOT=$(portageq envvar ROOT) + + # Obey PORTAGE_NICENESS + if [[ $PORTAGE_NICENESS ]]; then + renice $PORTAGE_NICENESS $$ > /dev/null + # Since we have already set our nice value for our processes, + # reset PORTAGE_NICENESS to zero to avoid having emerge renice again. + export PORTAGE_NICENESS="0" + fi + + PORTAGE_ROOT="${PORTAGE_ROOT:-/}" +} + +## +# Setup the paths to search (and filter the ones to avoid) +setup_search_paths_and_masks() { + local configfile sdir mdir skip_me filter_SEARCH_DIRS + + einfo "Configuring search environment for $APP_NAME" + + # Update the incremental variables using /etc/profile.env, /etc/ld.so.conf, + # portage, and the environment + + # Read the incremental variables from environment and portage + # Until such time as portage supports these variables as incrementals + # The value will be what is in /etc/make.conf + SEARCH_DIRS+=" "$(unset SEARCH_DIRS; portageq envvar SEARCH_DIRS) + SEARCH_DIRS_MASK+=" "$(unset SEARCH_DIRS_MASK; portageq envvar SEARCH_DIRS_MASK) + LD_LIBRARY_MASK+=" "$(unset LD_LIBRARY_MASK; portageq envvar LD_LIBRARY_MASK) + + # Add the defaults + if [[ -d /etc/revdep-rebuild ]]; then + for configfile in /etc/revdep-rebuild/*; do + SEARCH_DIRS+=" "$(. $configfile; echo $SEARCH_DIRS) + SEARCH_DIRS_MASK+=" "$(. $configfile; echo $SEARCH_DIRS_MASK) + LD_LIBRARY_MASK+=" "$(. $configfile; echo $LD_LIBRARY_MASK) + done + else + SEARCH_DIRS+=" /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" + SEARCH_DIRS_MASK+=" /opt/OpenOffice /usr/lib/openoffice" + LD_LIBRARY_MASK+=" libodbcinst.so libodbc.so libjava.so libjvm.so" + fi + + # Get the ROOTPATH and PATH from /etc/profile.env + if [[ -r "/etc/profile.env" && -s "/etc/profile.env" ]]; then + SEARCH_DIRS+=" "$(. /etc/profile.env; /usr/bin/tr ':' ' ' <<< "$ROOTPATH $PATH") + fi + + # Get the directories from /etc/ld.so.conf + if [[ -r /etc/ld.so.conf && -s /etc/ld.so.conf ]]; then + SEARCH_DIRS+=" "$(sed '/^#/d;s/#.*$//' /etc/ld.so.conf) + fi + + # Set the final variables + SEARCH_DIRS=$(clean_var <<< "$SEARCH_DIRS") + SEARCH_DIRS_MASK=$(clean_var <<< "$SEARCH_DIRS_MASK") + LD_LIBRARY_MASK=$(clean_var <<< "$LD_LIBRARY_MASK") + # Filter masked paths from SEARCH_DIRS + for sdir in ${SEARCH_DIRS} ; do + skip_me= + for mdir in ${SEARCH_DIRS_MASK}; do + [[ ${sdir} == ${mdir}/* ]] && skip_me=1 && break + done + [[ -n ${skip_me} ]] || filter_SEARCH_DIRS+=" ${sdir}" + done + SEARCH_DIRS=$(clean_var <<< "${filter_SEARCH_DIRS}") + [[ $SEARCH_DIRS ]] || die 1 "No search defined -- this is a bug." +} +## +# Rebuild packages owning broken binaries +rebuild() { + if [[ -r $LIST.5_order && -s $LIST.5_order ]]; then + REBUILD_LIST=( $(<"$LIST.5_order") ) + REBUILD_LIST="${REBUILD_LIST[@]/#/=}" + else + REBUILD_LIST=$(sort -u "$EBUILDS_FILE") + fi + + trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + + einfo 'All prepared. Starting rebuild' + echo "emerge --oneshot ${EMERGE_OPTIONS[@]} $REBUILD_LIST" + + is_real_merge && countdown 10 + + # Link file descriptor #6 with stdin so --ask will work + exec 6<&0 + + # Run in background to correctly handle Ctrl-C + { + EMERGE_DEFAULT_OPTS="--oneshot ${EMERGE_OPTIONS[@]}" emerge $REBUILD_LIST <&6 + echo $? > "$STATUS_FILE" + } & + wait + + # Now restore stdin from fd #6, where it had been saved, and close fd #6 ( 6<&- ) to free it for other processes to use. + exec 0<&6 6<&- +} +## +# Finish up +cleanup() { + if (( $(<"$STATUS_FILE") != 0 )); then + ewarn + ewarn "$APP_NAME failed to emerge all packages." + ewarn 'you have the following choices:' + einfo "- If emerge failed during the build, fix the problems and re-run $APP_NAME." + einfo '- Use /etc/portage/package.keywords to unmask a newer version of the package.' + einfo " (and remove $ORDER_FILE to be evaluated again)" + einfo '- Modify the above emerge command and run it manually.' + einfo '- Compile or unmerge unsatisfied packages manually,' + einfo ' remove temporary files, and try again.' + einfo ' (you can edit package/ebuild list first)' + einfo + einfo 'To remove temporary files, please run:' + einfo "rm ${WORKING_DIR}/*.rr" + show_unowned_files + exit $EMERGE_STATUS + elif is_real_merge; then + trap_cmd() { + eerror "terminated. Please remove the temporary files manually:" + eerror "rm ${WORKING_DIR}/*.rr" + exit 1 + } + [[ "${SKIP_LIST[@]}" != "" ]] && list_skipped_packages + trap trap_cmd SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + einfo 'Build finished correctly. Removing temporary files...' + einfo + einfo 'You can re-run revdep-rebuild to verify that all libraries and binaries' + einfo 'are fixed. Possible reasons for remaining inconsistencies include:' + einfo ' orphaned files' + einfo ' deep dependencies' + einfo " packages installed outside of portage's control" + einfo ' specially-evaluated libraries' + if [[ -r "$OWNERS_FILE" && -s "$OWNERS_FILE" ]]; then + show_unowned_files + fi + [[ $KEEP_TEMP ]] || rm "${FILES[@]}" + else + einfo 'Now you can remove -p (or --pretend) from arguments and re-run revdep-rebuild.' + fi +} + +main "$@" diff --git a/src/revdep-rebuild/revdep-rebuild-old b/src/revdep-rebuild/revdep-rebuild-old new file mode 100755 index 0000000..52d6d19 --- /dev/null +++ b/src/revdep-rebuild/revdep-rebuild-old @@ -0,0 +1,720 @@ +#!/bin/bash +# Copyright 1999-2007 Gentoo Foundation +# $Header$ + +# revdep-rebuild: Reverse dependency rebuilder. +# Original Author: Stanislav Brabec +# Current Maintainer: Paul Varner <fuzzyray@gentoo.org> + +# Known problems: +# +# In exact ebuild mode revdep-rebuild can fail to properly order packages, +# which are not up to date. +# http://bugs.gentoo.org/show_bug.cgi?id=23018 +# +# Rebuilding using --package-names mode should be default, but emerge has no +# feature to update to latest version of defined SLOT. +# http://bugs.gentoo.org/show_bug.cgi?id=4698 + +# Customizable variables: +# +# LD_LIBRARY_MASK - Mask of specially evaluated libraries +# SEARCH_DIRS - List of directories to search for executables and libraries +# SEARCH_DIRS_MASK - List of directories to not search +# +# These variables can be prepended to either by setting the variable in +# your environment prior to execution, or by placing an entry in +# /etc/make.conf. +# +# An entry of "-*" means to clear the variable from that point forward. +# Example: env SEARCH_DIRS="/usr/bin -*" revdep-rebuild will set SEARCH_DIRS +# to contain only /usr/bin + +if [ "$1" = "-h" -o "$1" = "--help" ] +then + echo "Usage: $0 [OPTIONS] [--] [EMERGE_OPTIONS]" + echo + echo "Broken reverse dependency rebuilder." + echo + echo " -X, --package-names Emerge based on package names, not exact versions" + echo " --library NAME Emerge existing packages that use the library with NAME" + echo " --library=NAME NAME can be a full path to the library or a basic" + echo " regular expression (man grep)" + echo " -np, --no-ld-path Do not set LD_LIBRARY_PATH" + echo " -nc, --nocolor Turn off colored output" + echo " -i, --ignore Ignore temporary files from previous runs" + echo " -q, --quiet Be less verbose (also passed to emerge command)" + echo " -vv, --extra-verbose Be extra verbose" + echo + echo "Calls emerge, all other options are used for it (e. g. -p, --pretend)." + echo + echo "Report bugs to <http://bugs.gentoo.org>" + exit 0 +fi + +echo "Configuring search environment for revdep-rebuild" + +# Obey PORTAGE_NICENESS +PORTAGE_NICENESS=$(portageq envvar PORTAGE_NICENESS) +if [ ! -z "$PORTAGE_NICENESS" ] +then + renice $PORTAGE_NICENESS $$ > /dev/null + # Since we have already set our nice value for our processes, + # reset PORTAGE_NICENESS to zero to avoid having emerge renice again. + export PORTAGE_NICENESS="0" +fi + +PORTAGE_ROOT=$(portageq envvar ROOT) +[ -z "$PORTAGE_ROOT" ] && PORTAGE_ROOT="/" + +# Update the incremental variables using /etc/profile.env, /etc/ld.so.conf, +# portage, and the environment + +# Read the incremental variables from environment and portage +# Until such time as portage supports these variables as incrementals +# The value will be what is in /etc/make.conf +PRELIMINARY_SEARCH_DIRS="$SEARCH_DIRS $(unset SEARCH_DIRS; portageq envvar SEARCH_DIRS)" +PRELIMINARY_SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK $(unset SEARCH_DIRS_MASK; portageq envvar SEARCH_DIRS_MASK)" +PRELIMINARY_LD_LIBRARY_MASK="$LD_LIBRARY_MASK $(unset LD_LIBRARY_MASK; portageq envvar LD_LIBRARY_MASK)" + +# Add the defaults +if [ -d /etc/revdep-rebuild ] +then + for file in $(ls /etc/revdep-rebuild) + do + PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(. /etc/revdep-rebuild/${file}; echo $SEARCH_DIRS)" + PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK $(. /etc/revdep-rebuild/${file}; echo $SEARCH_DIRS_MASK)" + PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK $(. /etc/revdep-rebuild/${file}; echo $LD_LIBRARY_MASK)" + done +else + PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" + PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK /opt/OpenOffice /usr/lib/openoffice" + PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK libodbcinst.so libodbc.so libjava.so libjvm.so" +fi + +# Get the ROOTPATH and PATH from /etc/profile.env +if [ -e "/etc/profile.env" ] +then + PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $((. /etc/profile.env; echo ${ROOTPATH}:${PATH}) | tr ':' ' ')" +fi + +# Get the directories from /etc/ld.so.conf +if [ -e /etc/ld.so.conf ] +then + PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(grep -v "^#" /etc/ld.so.conf | tr '\n' ' ')" +fi + +# Set the final variables +# Note: Using $(echo $variable) removes extraneous spaces from variable assignment +unset SEARCH_DIRS +for i in $(echo $PRELIMINARY_SEARCH_DIRS) +do + [ "$i" = "-*" ] && break + # Append a / at the end so that links and directories are treated the same by find + # Remove any existing trailing slashes to prevent double-slashes + SEARCH_DIRS="$(echo $SEARCH_DIRS ${i/%\//}/)" +done +# Remove any double-slashes from the path +SEARCH_DIRS="$(echo $SEARCH_DIRS | sed 's:/\+:/:g')" + +unset SEARCH_DIRS_MASK +for i in $(echo $PRELIMINARY_SEARCH_DIRS_MASK) +do + [ "$i" = "-*" ] && break + SEARCH_DIRS_MASK="$(echo $SEARCH_DIRS_MASK $i)" +done + +unset LD_LIBRARY_MASK +for i in $(echo $PRELIMINARY_LD_LIBRARY_MASK) +do + [ "$i" = "-*" ] && break + LD_LIBRARY_MASK="$(echo $LD_LIBRARY_MASK $i)" +done + +# Use the color preference from portage +NOCOLOR=$(portageq envvar NOCOLOR) + +# Base of temporary files names. +touch ${HOME}/.revdep-rebuild_0.test 2>/dev/null +if [ $? -eq 0 ] +then + LIST="${HOME}/.revdep-rebuild" + rm ~/.revdep-rebuild_0.test +else + # Try to use /var/tmp since $HOME is not available + touch /var/tmp/.revdep-rebuild_0.test 2>/dev/null + if [ $? -eq 0 ] + then + LIST="/var/tmp/.revdep-rebuild" + rm /var/tmp/.revdep-rebuild_0.test + else + echo + echo "!!! Unable to write temporary files to either $HOME or /var/tmp !!!" + echo + exit 1 + fi +fi + +shopt -s nullglob +shopt -s expand_aliases +unalias -a + +# Color Definitions +NO="\x1b[0m" +BR="\x1b[0;01m" +CY="\x1b[36;01m" +GR="\x1b[32;01m" +RD="\x1b[31;01m" +YL="\x1b[33;01m" +BL="\x1b[34;01m" + +# Check if portage-utils are installed +portageq has_version $PORTAGE_ROOT portage-utils +if [ "$?" -eq 0 ] +then + PORTAGE_UTILS=true +else + PORTAGE_UTILS=false +fi + +alias echo_v=echo + +PACKAGE_NAMES=false +SONAME="not found" +SONAME_GREP=grep +SEARCH_BROKEN=true +EXTRA_VERBOSE=false +KEEP_TEMP=false +FULL_LD_PATH=true + +EMERGE_OPTIONS="" +PRELIMINARY_CALLED_OPTIONS="" +while [ ! -z "$1" ] ; do + case "$1" in + -X | --package-names ) + PACKAGE_NAMES=true + PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --package_names" + shift + ;; + -q | --quiet ) + alias echo_v=: + EMERGE_OPTIONS="${EMERGE_OPTIONS} $1" + shift + ;; + --library=* | --soname=* | --soname-regexp=* ) + SONAME="${1#*=}" + SEARCH_BROKEN=false + PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --library=${SONAME}" + shift + ;; + --library | --soname | --soname-regexp ) + SONAME="$2" + SEARCH_BROKEN=false + PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --library=${SONAME}" + shift 2 + ;; + -nc | --no-color | --nocolor ) + NOCOLOR=true + shift + ;; + -np | --no-ld-path ) + FULL_LD_PATH=false + PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --no-ld-path" + shift + ;; + -i | --ignore ) + rm -f ${LIST}* + shift + ;; + --keep-temp ) + KEEPTEMP=true + shift + ;; + -vv | --extra-verbose ) + EXTRA_VERBOSE=true + shift + ;; + -- ) + shift + ;; + * ) + EMERGE_OPTIONS="${EMERGE_OPTIONS} $1" + shift + ;; + esac +done + +EMERGE_OPTIONS=$(echo $EMERGE_OPTIONS | sed 's/^ //') + +if [ -z "$PRELIMINARY_CALLED_OPTIONS" ] +then + CALLED_OPTIONS="" +else + for i in $(echo $PRELIMINARY_CALLED_OPTIONS | tr ' ' '\n'| sort) + do + CALLED_OPTIONS="$(echo $CALLED_OPTIONS $i)" + done +fi + +if [ "$NOCOLOR" = "yes" -o "$NOCOLOR" = "true" ] +then + NOCOLOR=true +else + NOCOLOR=false +fi + +# Make the NOCOLOR variable visible to emerge +export NOCOLOR + +if $NOCOLOR +then + NO="" + BR="" + CY="" + GR="" + RD="" + YL="" + BL="" +fi + +function set_trap () { + trap "rm_temp $1" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM +} + +function rm_temp () { + echo " terminated." + echo "Removing incomplete $1." + rm $1 + echo + exit 1 +} + +if $SEARCH_BROKEN ; then + SONAME_SEARCH="$SONAME" + LLIST=$LIST + HEAD_TEXT="broken by a package update" + OK_TEXT="Dynamic linking on your system is consistent" + WORKING_TEXT=" consistency" +else + # first case is needed to test against /path/to/foo.so + if [ ${SONAME:0:1} == '/' ] ; then + # Set to "<space>$SONAME<space>" + SONAME_SEARCH=" $SONAME " + else + # Set to "<tab>$SONAME<space>" + SONAME_SEARCH=" $SONAME " + fi + LLIST=${LIST}_$(echo "$SONAME_SEARCH$SONAME" | md5sum | head -c 8) + HEAD_TEXT="using $SONAME" + OK_TEXT="There are no dynamic links to $SONAME" + WORKING_TEXT="" +fi + +# If any of our temporary files are older than 1 day, remove them all +[ "$(find "${LIST%/*}/." ! -name . -prune -name "${LIST##*/}*" -type f -mmin +1440)" != "" ] && rm -f ${LIST}* + +# Don't use our previous files if environment doesn't match +if [ -f $LIST.0_env ] +then + PREVIOUS_SEARCH_DIRS=$(. ${LIST}.0_env; echo "$SEARCH_DIRS") + PREVIOUS_SEARCH_DIRS_MASK=$(. ${LIST}.0_env; echo "$SEARCH_DIRS_MASK") + PREVIOUS_LD_LIBRARY_MASK=$(. ${LIST}.0_env; echo "$LD_LIBRARY_MASK") + PREVIOUS_PORTAGE_ROOT=$(. ${LIST}.0_env; echo "$PORTAGE_ROOT") + PREVIOUS_OPTIONS=$(. ${LIST}.0_env; echo "$CALLED_OPTIONS") + if [ "$PREVIOUS_SEARCH_DIRS" != "$SEARCH_DIRS" ] || \ + [ "$PREVIOUS_SEARCH_DIRS_MASK" != "$SEARCH_DIRS_MASK" ] || \ + [ "$PREVIOUS_LD_LIBRARY_MASK" != "$LD_LIBRARY_MASK" ] || \ + [ "$PREVIOUS_PORTAGE_ROOT" != "$PORTAGE_ROOT" ] || \ + [ "$PREVIOUS_OPTIONS" != "$CALLED_OPTIONS" ] + then + echo + echo "Environment mismatch from previous run, deleting temporary files..." + rm -f ${LIST}* + fi +fi + +# Clean up no longer needed environment variables +unset PREVIOUS_SEARCH_DIRS PREVIOUS_SEARCH_DIRS_MASK PREVIOUS_LD_LIBRARY_MASK PREVIOUS_PORTAGE_ROOT PREVIOUS_OPTIONS +unset PRELIMINARY_SEARCH_DIRS PRELIMINARY_SEARCH_DIRS_MASK PRELIMINARY_LD_LIBRARY_MASK PRELIMINARY_CALLED_OPTIONS + +# Log our environment +echo "SEARCH_DIRS=\"$SEARCH_DIRS\"" > $LIST.0_env +echo "SEARCH_DIRS_MASK=\"$SEARCH_DIRS_MASK\"" >> $LIST.0_env +echo "LD_LIBRARY_MASK=\"$LD_LIBRARY_MASK\"" >> $LIST.0_env +echo "PORTAGE_ROOT=\"$PORTAGE_ROOT\"" >> $LIST.0_env +echo "CALLED_OPTIONS=\"$CALLED_OPTIONS\"" >> $LIST.0_env +echo "EMERGE_OPTIONS=\"$EMERGE_OPTIONS\"" >> $LIST.0_env + +if $EXTRA_VERBOSE +then + echo + echo "revdep-rebuild environment:" + cat $LIST.0_env +fi + +echo +echo "Checking reverse dependencies..." +echo +echo "Packages containing binaries and libraries $HEAD_TEXT" +echo "will be emerged." + +echo +echo -n -e "${GR}Collecting system binaries and libraries...${NO}" + +if [ -f $LIST.1_files ] +then + echo " using existing $LIST.1_files." +else + # Be safe and remove any extraneous temporary files + rm -f ${LIST}.[1-9]_* + + set_trap "$LIST.1_*" + + # Hack for the different versions of find. + # Be extra paranoid and pipe results through sed to remove multiple slashes + find_results=$(find /usr/bin/revdep-rebuild -type f -perm /u+x 2>/dev/null) + if [ -z $find_results ] + then + find_results=$(find /usr/bin/revdep-rebuild -type f -perm +u+x 2>/dev/null) + if [ -z $find_results ] + then + echo -e "\n" + echo -e "${RD}Unable to determine how to use find to locate executable files${NO}" + echo -e "${RD}Open a bug at http://bugs.gentoo.org${NO}" + echo + exit 1 + else + # using -perm +u+x for find command + find $SEARCH_DIRS -type f \( -perm +u+x -o -name '*.so' -o -name '*.so.*' -o -name '*.la' \) 2>/dev/null | sort | uniq | sed 's:/\+:/:g' >$LIST.0_files + fi + else + # using -perm /u+x for find command + find $SEARCH_DIRS -type f \( -perm /u+x -o -name '*.so' -o -name '*.so.*' -o -name '*.la' \) 2>/dev/null | sort | uniq | sed 's:/\+:/:g' >$LIST.0_files + fi + + # Remove files that match SEARCH_DIR_MASK + for dir in $SEARCH_DIRS_MASK + do + grep -v "^$dir" $LIST.0_files > $LIST.1_files + mv $LIST.1_files $LIST.0_files + done + + mv $LIST.0_files $LIST.1_files + echo -e " done.\n ($LIST.1_files)" +fi + +if $SEARCH_BROKEN && $FULL_LD_PATH ; then + echo + echo -n -e "${GR}Collecting complete LD_LIBRARY_PATH...${NO}" + if [ -f $LIST.2_ldpath ] ; then + echo " using existing $LIST.2_ldpath." + else + set_trap "$LIST.2_ldpath" + # Ensure that the "trusted" lib directories are at the start of the path + ( + echo /lib* /usr/lib* | sed 's/ /:/g' + sed '/^#/d;s/#.*$//' </etc/ld.so.conf + sed 's:/[^/]*$::' <$LIST.1_files | sort -ru + ) | tr '\n' : | tr -d '\r' | sed 's/:$//' >$LIST.2_ldpath + echo -e " done.\n ($LIST.2_ldpath)" + fi + COMPLETE_LD_LIBRARY_PATH="$(cat $LIST.2_ldpath)" +fi + +echo +echo -n -e "${GR}Checking dynamic linking$WORKING_TEXT...${NO}" +if [ -f $LLIST.3_rebuild ] ; then + echo " using existing $LLIST.3_rebuild." +else + echo_v + set_trap "$LLIST.3_rebuild" + LD_MASK="\\( $(echo "$LD_LIBRARY_MASK" | sed 's/\./\\./g;s/ / \\| /g') \\)" + echo -n >$LLIST.3_rebuild + echo -n >$LLIST.3_ldd_errors + cat $LIST.1_files | egrep -v '*\.la$' | while read FILE ; do + # Note: double checking seems to be faster than single + # with complete path (special add ons are rare). + if ldd "$FILE" 2>>$LLIST.3_ldd_errors | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then + if $SEARCH_BROKEN && $FULL_LD_PATH ; then + if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$FILE" 2>/dev/null | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then + # FIX: I hate duplicating code + # Only build missing direct dependencies + ALL_MISSING_LIBS=$(ldd "$FILE" 2>/dev/null | sort -u | sed -n 's/ \(.*\) => not found/\1/p' | tr '\n' ' ' | sed 's/ $//' ) + REQUIRED_LIBS=$(objdump -x $FILE | grep NEEDED | awk '{print $2}' | tr '\n' ' ' | sed 's/ $//') + MISSING_LIBS="" + for lib in $ALL_MISSING_LIBS + do + if echo $REQUIRED_LIBS | grep -q $lib + then + MISSING_LIBS="$MISSING_LIBS $lib" + fi + done + if [ "$MISSING_LIBS" != "" ] + then + echo "obj $FILE" >>$LLIST.3_rebuild + echo_v " broken $FILE (requires ${MISSING_LIBS})" + fi + fi + else + # FIX: I hate duplicating code + # Only rebuild for direct dependencies + ALL_MISSING_LIBS=$(ldd "$FILE" 2>/dev/null | sort -u | $SONAME_GREP "$SONAME_SEARCH" | awk '{print $1}' | tr '\n' ' ' | sed 's/ $//' ) + REQUIRED_LIBS=$(objdump -x $FILE | grep NEEDED | awk '{print $2}' | tr '\n' ' ' | sed 's/ $//') + MISSING_LIBS="" + for lib in $ALL_MISSING_LIBS + do + if echo $REQUIRED_LIBS | grep -q $lib + then + MISSING_LIBS="$MISSING_LIBS $lib" + fi + done + if [ "$MISSING_LIBS" != "" ] + then + echo "obj $FILE" >>$LLIST.3_rebuild + if $SEARCH_BROKEN ; then + echo_v " broken $FILE (requires ${MISSING_LIBS})" + else + echo_v " found $FILE" + fi + fi + fi + fi + done + if $SEARCH_BROKEN ; then + # Look for missing version + for FILE in $(grep "no version information available" $LLIST.3_ldd_errors | awk '{print $NF}' | sed 's/[()]//g' | sort -u) ; do + echo "obj $FILE" >>$LLIST.3_rebuild + echo_v " broken $FILE (no version information available)" + done + # Look for broken .la files + cat $LIST.1_files | egrep '*\.la$' | while read FILE ; do + for depend in $(grep '^dependency_libs' $FILE | awk -F'=' '{print $2}' | sed "s/'//g") ; do + [ ${depend:0:1} != '/' ] && continue + if [ ! -e $depend ] ; then + echo "obj $FILE" >>$LLIST.3_rebuild + echo_v " broken $FILE (requires ${depend})" + fi + done + done + fi + echo -e " done.\n ($LLIST.3_rebuild)" +fi + +if $PACKAGE_NAMES ; then + EXACT_EBUILDS=false + + echo + echo -n -e "${GR}Assigning files to packages...${NO}" + if [ -f $LLIST.4_packages_raw ] ; then + echo " using existing $LLIST.4_packages_raw." + else + set_trap "$LLIST.4_packages*" + echo -n >$LLIST.4_packages_raw + echo -n >$LLIST.4_package_owners + cat $LLIST.3_rebuild | while read obj FILE ; do + if $PORTAGE_UTILS ; then + EXACT_PKG="$(qfile -qvC ${FILE} )" + else + EXACT_PKG=$(find /var/db/pkg -name CONTENTS | xargs fgrep -l "obj $FILE " | sed -e 's:/var/db/pkg/\(.*\)/CONTENTS:\1:g') + fi + # Ugly sed hack to strip version information + PKG="$(echo $EXACT_PKG | sed 's/-r[0-9].*$//;s/\(^.*\/*\)-.*$/\1/')" + if [ -z "$PKG" ] ; then + echo -n -e "\n ${RD}*** $FILE not owned by any package is broken! ***${NO}" + echo "$FILE -> (none)" >> $LLIST.4_package_owners + echo_v -n -e "\n $FILE -> (none)" + else + echo "$EXACT_PKG" >> $LLIST.4_packages_raw + echo "$FILE -> $EXACT_PKG" >> $LLIST.4_package_owners + echo_v -n -e "\n $FILE -> $PKG" + fi + done + echo_v + echo -e " done.\n ($LLIST.4_packages_raw, $LLIST.4_package_owners)" + fi + + echo + echo -n -e "${GR}Cleaning list of packages to rebuild...${NO}" + if [ -f $LLIST.4_packages ] ; then + echo " using existing $LLIST.4_packages." + else + sort -u $LLIST.4_packages_raw >$LLIST.4_packages + echo -e " done.\n ($LLIST.4_packages)" + fi + + echo + echo -n -e "${GR}Assigning packages to ebuilds...${NO}" + if [ -f $LLIST.4_ebuilds ] ; then + echo " using existing $LLIST.4_ebuilds." + else + if [ -s "$LLIST.4_packages" ] + then + set_trap "$LLIST.4_ebuilds" + cat $LLIST.4_packages | while read EXACT_PKG + do + PKG="$(echo $EXACT_PKG | sed 's/-r[0-9].*$//;s/\(^.*\/*\)-.*$/\1/')" + SLOT=$(cat /var/db/pkg/${EXACT_PKG}/SLOT) + best_visible=$(portageq best_visible $PORTAGE_ROOT ${PKG}:${SLOT}) + [ "x" != "x$best_visible" ] && echo $best_visible + done > $LLIST.4_ebuilds + echo -e " done.\n ($LLIST.4_ebuilds)" + else + echo " Nothing to rebuild" + echo -n > $LLIST.4_ebuilds + fi + fi +else + EXACT_EBUILDS=true + + echo + echo -n -e "${GR}Assigning files to ebuilds...${NO}" + if [ -f $LLIST.4_ebuilds ] ; then + echo " using existing $LLIST.4_ebuilds." + else + if [ -s "$LLIST.3_rebuild" ] ; then + set_trap "$LLIST.4_ebuilds" + find /var/db/pkg -name CONTENTS | xargs fgrep -l -f $LLIST.3_rebuild | + sed 's:/var/db/pkg/\(.*\)/CONTENTS:\1:' > $LLIST.4_ebuilds + echo -e " done.\n ($LLIST.4_ebuilds)" + else + echo " Nothing to rebuild" + echo -n > $LLIST.4_ebuilds + fi + fi + +fi + +echo +echo -n -e "${GR}Evaluating package order...${NO}" +if [ -f $LLIST.5_order ] ; then + echo " using existing $LLIST.5_order." +else + set_trap "$LLIST.5_order" + RAW_REBUILD_LIST="$(cat $LLIST.4_ebuilds | sed s/^/=/ | tr '\n' ' ')" + if [ ! -z "$RAW_REBUILD_LIST" ] ; then + REBUILD_GREP="^\\($( (EMERGE_DEFAULT_OPTS="" emerge --nospinner --pretend --oneshot --nodeps --quiet $RAW_REBUILD_LIST ; echo $? >$LLIST.5a_status ) | sed -n 's/\./\\&/g;s/ //g;s/$/\\/;s/\[[^]]*\]//gp' | tr '\n' '|' | sed 's/|$//'))\$" + if [ $(cat $LLIST.5a_status) -gt 0 ] ; then + echo "" + echo -e "${RD}Warning: Failed to resolve package order." + echo -e "Will merge in \"random\" order!${NO}" + echo "Possible reasons:" + echo "- An ebuild is no longer in the portage tree." + echo "- An ebuild is masked, use /etc/portage/packages.keyword" + echo " and/or /etc/portage/package.unmask to unmask it" + for i in . . . . . ; do + echo -n -e '\a.' + sleep 1 + done + ln -f $LLIST.4_ebuilds $LLIST.5_order + else + (EMERGE_DEFAULT_OPTS="" emerge --nospinner --pretend --oneshot --deep --quiet $RAW_REBUILD_LIST ; echo $? >$LLIST.5b_status ) | sed -n 's/ *$//;s/^\[.*\] //p' | awk '{print $1}' | grep "$REBUILD_GREP" >$LLIST.5_order + if [ $(cat $LLIST.5b_status) -gt 0 ] ; then + echo "" + echo -e "${RD}Warning: Failed to resolve package order." + echo -e "Will merge in \"random\" order!${NO}" + echo "Possible reasons:" + echo "- An ebuild is no longer in the portage tree." + echo "- An ebuild is masked, use /etc/portage/packages.keyword" + echo " and/or /etc/portage/package.unmask to unmask it" + for i in . . . . . ; do + echo -n -e '\a.' + sleep 1 + done + rm -f $LLIST.5_order + ln -f $LLIST.4_ebuilds $LLIST.5_order + fi + fi + else + echo -n "" >$LLIST.5_order + fi + echo -e " done.\n ($LLIST.5_order)" +fi + +# Clean up no longer needed environment variables +unset COMPLETE_LD_LIBRARY_PATH SEARCH_DIRS SEARCH_DIRS_MASK LD_LIBRARY_MASK PORTAGE_ROOT CALLED_OPTIONS + +REBUILD_LIST="$(cat $LLIST.5_order | sed s/^/=/ | tr '\n' ' ')" + +trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + +if [ -z "$REBUILD_LIST" ] ; then + echo -e "\n${GR}$OK_TEXT... All done.${NO} " + if [ ! $KEEPTEMP ] + then + rm $LIST.[0-2]_* + rm $LLIST.[3-9]_* + fi + exit 0 +fi + +IS_REAL_MERGE=true +echo " $EMERGE_OPTIONS " | grep -q '\( -p \| --pretend \| -f \| --fetchonly \)' && IS_REAL_MERGE=false + +echo +echo -e "${GR}All prepared. Starting rebuild...${NO}" + +echo "emerge --oneshot $EMERGE_OPTIONS $REBUILD_LIST" + +if $IS_REAL_MERGE ; then + for i in . . . . . . . . . . ; do + echo -n -e '\a.' + sleep 1 + done + echo +fi + +# Link file descriptor #6 with stdin +exec 6<&0 + +# Run in background to correctly handle Ctrl-C +( + EMERGE_DEFAULT_OPTS="" emerge --oneshot $EMERGE_OPTIONS $REBUILD_LIST <&6 + echo $? >$LLIST.6_status +) & +wait + +# Now restore stdin from fd #6, where it had been saved, and close fd #6 ( 6<&- ) to free it for other processes to use. +exec 0<&6 6<&- + +#if $EXACT_EBUILDS ; then +# mv -i /usr/portage/profiles/package.mask.hidden /usr/portage/profiles/package.mask +# trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM +#fi + +if [ "$(cat $LLIST.6_status)" -gt 0 ] ; then + echo + echo -e "${RD}revdep-rebuild failed to emerge all packages${NO}" + echo -e "${RD}you have the following choices:${NO}" + echo + echo "- if emerge failed during the build, fix the problems and re-run revdep-rebuild" + echo " or" + echo "- use -X or --package-names as first argument (trys to rebuild package, not exact" + echo " ebuild)" + echo " or" + echo "- set ACCEPT_KEYWORDS=\"~<your platform>\" and/or /etc/portage/package.unmask" + echo " (and remove $LLIST.5_order to be evaluated again)" + echo " or" + echo "- modify the above emerge command and run it manually" + echo " or" + echo "- compile or unmerge unsatisfied packages manually, remove temporary files and" + echo " try again (you can edit package/ebuild list first)" + echo + echo -e "${GR}To remove temporary files, please run:${NO}" + echo "rm $LIST*.?_*" + exit $(cat $LLIST.6_status) +else + if $IS_REAL_MERGE ; then + trap "echo -e \" terminated. Please remove them manually:\nrm $LIST*.?_*\" ; exit 1" \ + SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + echo -n -e "${GR}Build finished correctly. Removing temporary files...${NO} " + echo + rm $LIST.[0-2]_* + rm $LLIST.[3-9]_* + echo "You can re-run revdep-rebuild to verify that all libraries and binaries" + echo "are fixed. If some inconsistency remains, it can be orphaned file, deep" + echo "dependency, binary package or specially evaluated library." + else + echo -e "${GR}Now you can remove -p (or --pretend) from arguments and re-run revdep-rebuild.${NO}" + fi +fi +exit 0 diff --git a/src/revdep-rebuild/revdep-rebuild-sh b/src/revdep-rebuild/revdep-rebuild-sh new file mode 100755 index 0000000..c7acdc6 --- /dev/null +++ b/src/revdep-rebuild/revdep-rebuild-sh @@ -0,0 +1,332 @@ +#!/bin/sh +# Copyright 1999-2007 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +appname=${0##*/} + +# If baselayout is broken, define our own functions +[ -r /etc/init.d/functions.sh ] && . /etc/init.d/functions.sh +if ! type eend >/dev/null 2>&1 || ! eend 0 >/dev/null 2>&1; then + einfo() { echo " * $*"; } + eerror() { echo " * $*" >&2; return 1; } + eindent() { :; } + eoutdent() { :; } +fi + +# No temporary files used, so nothing to clean up :) +trap "export RC_EINDENT=; echo; eerror 'Caught interrupt'; exit 1" \ + SIGINT SIGQUIT + +print_usage() { + cat << EOF +Usage: ${appname} [OPTIONS] [--] [EMERGE_OPTIONS] + +Broken reverse dependency rebuilder. + + -h, --help Print this usage + -e, --exact Emerge based on exact package version + -C, --nocolor Turn off colored output + -L, --library NAME Emerge existing packages that use the library with NAME + --library=NAME NAME can be a full path to the library or a basic + regular expression (man grep) + +Calls emerge, all other options are used for it (e. g. -p, --pretend). + +Report bugs to <http://bugs.gentoo.org> +EOF +} + +# Have we linked to this library? +elf_linked() { + local f=$1 + shift + while [ -n "$1" ]; do + ldd "${f}" 2>/dev/null | grep -q "=> $1 " && return 0 + shift + done + return 1 +} + +# Work out of we really need this library or not +elf_needed() { + local f=$1 + shift + while [ -n "$1" ]; do + objdump -p "${f}" 2>/dev/null | \ + grep -vF "${ld_mask:=$'\a'}" | \ + grep -q "^ NEEDED ${1##*/}" && return 0 + shift + done + return 1 +} + +elf_broken() { + local lib= + + for lib in $(ldd "$1" 2>/dev/null | \ + sed -n -e 's/[[:space:]]*\(.*\) => not found.*/\1/p'); do + if elf_needed "$1" "${lib}"; then + echo "(missing ${lib})" + return 0 + fi + done + return 1 +} + +# Check that all direct files exist in .la files +la_broken() { + local x= + for x in $(sed -n -e "s/^dependency_libs=\'\(.*\)'\$/\1/p" "$1"); do + case "${x}" in + /*) + if [ ! -e "${x}" ]; then + echo "(missing ${x})" + return 0 + fi + ;; + esac + done + + return 1 +} + +# Return a $PATH style variable based on ld.so.conf +read_so_conf() { + local line= + while read line; do + case "${line}" in + "#"*) ;; + *) printf ":%s" "${line}";; + esac + done < /etc/ld.so.conf +} + +# Check to see if we have already scanned a dir or not +scanned() { + local dir=$1 IFS=: + set -- ${scanned} + + while [ -n "$1" ]; do + [ "$1" = "$dir" ] && return 0 + shift + done + + scanned="${scanned}${scanned:+:}${dir}" + return 1 +} + +# Hit the portage vdb to work out our ebuilds +# If everything is 100% then this happens in one very fast pass +# Otherwise we have to take the slow approach to inform the user which files +# are orphans +get_exact_ebuilds() { + local regex= ebuilds= x= IFS=: + set -- $@ + IFS=" " + + # Hit the vdb in one go - this is fast! + regex=$(printf "%s|" "$@") + regex=${regex%*|} + find /var/db/pkg -name CONTENTS | \ + xargs egrep "^obj (${regex}) " | \ + sed -e 's,/var/db/pkg/\(.*\/.*\)/CONTENTS:.*,=\1,g' | \ + tr '\n' ' ' +} + +# Get our args +libs= +exact=false +order=true +while [ -n "$1" ]; do + case "$1" in + --*=*) + arg1=${1%%=*} + arg2=${1#*=} + shift + set -- ${arg1} ${arg2} $@ + continue + ;; + -h|--help) print_usage; exit 0;; + -L|--library|--soname|--soname-regexp) + if [ -z "$2" ]; then + eerror "Missing expected argument to $1" + exit 1 + fi + libs="${libs}${libs:+ }$2" + shift + ;; + -e|--exact) exact=true;; + -X|--package-names) ;; #compat + --) shift; emerge_opts="$@"; break;; + *) eerror "$0: unknown option $1"; exit 1;; + esac + shift +done + +einfo "Configuring search environment for ${appname}" +# OK, this truely sucks. Paths can have spaces in, but our config format +# is space separated? +sdirs=$(unset SEARCH_DIRS; portageq envvar SEARCH_DIRS) +sdirs_mask=$(unset SEARCH_DIRS_MASK; portageq envvar SEARCH_DIRS_MASK) +ld_mask=$(unset LD_LIBRARY_MASK; portageq envvar LD_LIBRARY_MASK) + +if [ -d /etc/revdep-rebuild ]; then + for x in /etc/revdep-rebuild/*; do + sdirs="${sdirs}${sdirs:+ }$(unset SEARCH_DIRS; . "${x}"; echo "${SEARCH_DIRS}")" + sdirs_mask="${sdirs_mask}${sdirs_mask:+ }$(unset SEARCH_DIRS_MASK; . "${x}" ; echo "${SEARCH_DIRS_MASK}")" + ld_mask="${ld_mask}${ld_mask:+ }$(unset LD_LIBRARY_MASK; . "${x}"; echo "${LD_LIBRARY_MASK}")" + done +else + sdirs="${sdirs}${sdirs:+ }/bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" + sdirs_mask="${sdirs_mask}${sdirs_mask:+ }/opt/OpenOffice /usr/lib/openoffice" + ld_mask="${ld_mask}${ld_mask:+ }libodbcinst.so libodbc.so libjava.so libjvm.so" +fi + +sdirs=$(find ${sdirs} -type d) + +einfo "Starting scan" +eindent +# Mark our masked dirs already scanned +scanned= +for dir in ${sdirs_mask}; do + scanned "${dir}" +done + +# Now scan our dirs +for dir in ${sdirs}; do + scanned "${dir}" && continue + + einfo "in ${dir}" + eindent + for x in "${dir}"/*; do + [ -d "${x}" ] && continue + [ -L "${x}" ] && continue + + scan=true + process=false + reason= + case "${x}" in + *.so|*.so.*) process=true;; + *.la) + scan=false + if [ -z "${libs}" ]; then + reason=$(la_broken "${x}") + [ $? = 0 ] && process=true + fi + ;; + esac + [ -x "${x}" ] && ${scan} && process=true + ${process} || continue + + if ${scan}; then + process=false + if [ -n "${libs}" ]; then + for lib in ${libs}; do + if [ "${lib#/}" != "${lib}" ]; then + # If lib starts with / then check if the exact + # lib is linked + elf_linked "${x}" "${lib}" || continue + fi + if elf_needed "${x}" ${lib}; then + process=true + break + fi + done + else + reason=$(elf_broken "${x}") + [ $? = 0 ] && process=true + fi + fi + + ${process} || continue + einfo "found ${x} ${reason}" + files="${files}${files:+:}${x}" + done + eoutdent +done +eoutdent + +if [ -z "${files}" ]; then + if [ -z "${libs}" ]; then + einfo "Nothing found that needs rebuilding" + else + einfo "No dynamic binaries found with these libraries" + fi + exit 0 +fi + +einfo "Assigning files to packages" +eindent +ebuilds=$(get_exact_ebuilds "${files}") + +if [ -z "${ebuilds}" ]; then + eerror "No packages own these files" + exit 1 +fi + +# Work out the best visible package for the slot +if ! ${exact}; then + root=$(portageq envvar ROOT) + root=${root:-/} + + set -- ${ebuilds} + ebuilds= + for x in "$@"; do + x=${x#=*} + pkg=${x%-r[0-9]*} + pkg=${pkg%-*} + slot=$(cat "/var/db/pkg/${x}/SLOT") + ebd=$(portageq best_visible "${root}" "${pkg}:${slot}") + if [ -z "${ebd}" ]; then + eerror "Cannot find an ebuild visible for ${x}" + else + ebuilds="${ebuilds}${ebuilds:+ }=${ebd}" + fi + done +fi +eoutdent + +# Work out the build order +if ${order}; then + einfo "Ordering packages" + order="$(EMERGE_DEFAULT_OPTS="" \ + emerge --nospinner --pretend --deep --quiet ${ebuilds})" + if [ $? = 0 ]; then + ebuilds=$(echo "${order}" | \ + sed -e 's:^\[.*\] \([^ ]*\)[ ].*$:=\1:' | \ + grep -F "$(printf "%s\n" ${ebuilds})" | \ + tr '\n' ' ') + else + eerror "Unable to order packages!" + fi +fi + +if [ -z "${ebuilds}" ]; then + eerror "Don't know how to find which package owns what file :/" + exit 1 +fi + +echo +einfo "About to execute" +echo "emerge --oneshot ${emerge_opts} ${ebuilds}" +echo + +i=5 +printf "in" +while [ ${i} -gt 0 ]; do + printf " ${i}" + sleep 1 + i=$((${i} - 1)) +done +printf "\n\n" + +EMERGE_DEFAULT_OPTS="" emerge --oneshot ${emerge_opts} ${ebuilds} +retval=$? + +if [ "${retval}" = 0 ]; then + einfo "All done" + exit 0 +fi + +eerror "There was an error trying to emerge the broken packages" +exit "${retval}" diff --git a/src/revdep-rebuild/revdep-rebuild.1 b/src/revdep-rebuild/revdep-rebuild.1 new file mode 100644 index 0000000..bcf1e26 --- /dev/null +++ b/src/revdep-rebuild/revdep-rebuild.1 @@ -0,0 +1,138 @@ +.TH "revdep\-rebuild" "1" "" "gentoolkit" "" +.SH "NAME" +revdep\-rebuild \- Gentoo: Reverse Dependency Rebuilder +.SH "SYNOPSIS" +.B revdep\-rebuild +[OPTIONS] [\-\-] [EMERGE OPTIONS] +.SH "DESCRIPTION" +revdep\-rebuild scans libraries and binaries for missing shared library dependencies and attempts to fix them by re\-emerging those broken binaries and shared libraries. It is useful when an upgraded package breaks other software packages that are dependent upon the upgraded package. +.SH "OPTIONS" +.TP +.B \-C | \-\-nocolor +Turn off colored output. (This option is also passed to portage.) +.TP +.B \-e | \-\-exact +Emerge the most recent version of found packages, without regard to SLOT. +.TP +.B \-h | \-\-help +Print usage. +.TP +.B \-i | \-\-ignore +Delete temporary files from previous runs. +.TP +.B \-k | \-\-keep\-temp +Force revdep\-rebuild not to delete temporary files after it successfully rebuilds packages. This option will NOT prevent revdep\-rebuild from deleting inconsistent or out\-of\-date temporary files. +.TP +.B \-\-library NAME | -L NAME +Search for reverse dependencies for a particular library or group of libraries, rather than every library on the system. Emerge packages that use the named library. NAME can be a full path to a library or basic regular expression. (See regex(7).) +.TP +.B \-l | \-\-no\-ld\-path +Do not set LD_LIBRARY_PATH. \fBNote:\fR Using this option will cause revdep-rebuild to report some false positives. +.TP +.B \-o | \-\-no-order +Do not check the build order against the deep dependency list. This will make revdep-rebuild faster, but it can cause emerge failures. Please try revdep\-rebuild without \-o before reporting any bugs. +.TP +.B \-p | \-\-pretend +Do a dry-run. Do not delete temporary files. (\-k \-p is redundant, but harmless.) \-\-pretend is assumed when not running revdep\-rebuild as root. +.TP +.B \-P | \-\-no\-progress +Turn off the progress meter +.TP +.B \-q | \-\-quiet +Print less output and disable the progress meter. (This option is also passed to portage.) +.TP +.B \-u UTIL | \-\-no-util UTIL +Do not use features provided by UTIL. +UTIL can be one of portage-utils or pkgcore, or it can be a \fBquoted\fR space-delimited list. +.TP +.B \-v | \-\-verbose +More output. (Prints the revdep\-rebuild search environment.) +.TP +.B All other options (including unrecognized ones) are passed to the emerge command. Single\-letter options may not be combined, so for example, \-pv is not valid. Please use \-p \-v. +.SH "CONFIGURATION" +revdep\-rebuild no longer uses hardcoded paths. To change the default behavior the following variables can be changed by the user. + +LD_LIBRARY_MASK \- Mask of specially evaluated libraries +.LP +SEARCH_DIRS \- List of directories to search for executables and libraries +.LP +SEARCH_DIRS_MASK \- List of directories to not search + +You can prepend to these variables by setting the variable in your environment prior to execution, by placing an entry in /etc/make.conf, or by placing a file in /etc/revdep\-rebuild containing the appropriate variables. + +The variables are read and set in the following order: + +environment settings \- one time changes by user +.br +/etc/make.conf \- persistent changes by user +.br +/etc/revdep\-rebuild/* \- persistent changes by ebuild authors + +While a user can edit and modify the files in the /etc/revdep\-rebuild directory, please be aware that the /etc/revdep\-rebuild directory is not under configuration protection and files can be removed and/or overwritten by an ebuild. To change this add /etc/revdep\-rebuild to the CONFIG_PROTECT variable in /etc/make.conf. + +An entry of "\-*" means to clear the variable from that point forward. +Example: SEARCH_DIRS="/usr/bin \-*" will set SEARCH_DIRS to contain only /usr/bin + +revdep\-rebuild honors the NOCOLOR and PORTAGE_NICENESS variables from /etc/make.conf +.SH "EXAMPLES" +It is recommended that when running revdep\-rebuild that the following command be used initially: +.br +\fBrevdep\-rebuild \-\-ignore \-\-pretend\fR + +To search the entire system, while excluding /mnt and /home: +.br +\fBenv SEARCH_DIRS="/ \-*" SEARCH_DIRS_MASK="/mnt /home" revdep\-rebuild\fR + +To rebuild packages that depend on libkdecore.so.4 from KDE 3.3: +.br +\fBrevdep\-rebuild \-\-library /usr/kde/3.3/lib/libkdecore.so.4\fR + +To rebuild packages that depend upon libImlib.so and libImlib2.so: +.br +\fBrevdep\-rebuild \-\-library libImlib[2]*.so.*\fR + +.SH "FILES" +.P +revdep\-rebuild keeps several pseudo-temporary files in /var/cache/revdep\-rebuild/. Deleting these files can improve accuracy at the cost of speed: +.TP 15 +.I 0_env.rr +Contains environment variables +.TP +.I 1_files.rr +Contains a list of files to search +.TP +.I 2_ldpath.rr +Contains the LDPATH +.TP +.I 3_broken.rr +Contains the list of broken files +.TP +.I 3_errors.rr +Contains the ldd error output +.TP +.I 4_raw.rr +Contains the raw list of packages +.TP +.I 4_owners.rr +Contains the file owners +.TP +.I 4_pkgs.rr +Contains the unsorted bare package names +.TP +.I 4_ebuilds.rr +Contains the unsorted atoms +.TP +.I 5_order.rr +Contains the sorted atoms +.TP +.I 6_status.rr +Contains the ldd error output + +.SH "EXIT STATUS" +revdep\-rebuild returns a zero exit status if it \fBand emerge\fR succeeds, and a nonzero exit status otherwise. +.SH "BUGS" +.LP +Report bugs to <http://bugs.gentoo.org>. Please do not report emerge failures caused by \-o or \-e. Please include your .revdep\-rebuild* files, your emerge \-\-info, and patches. ;) + +.SH "SEE ALSO" +emerge(1), portage(5), regex(7) |