aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfuzzyray <fuzzyray@gentoo.org>2009-05-05 17:39:24 +0000
committerfuzzyray <fuzzyray@gentoo.org>2009-05-05 17:39:24 +0000
commitc819d146be6bce86d97019494173253e71b85d2f (patch)
tree200d00c2b9a420540ff9c4e0d8b3080b762fb562 /src
parentAdd some useful informations when using $EDITOR. (diff)
downloadgentoolkit-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')
-rw-r--r--src/eclean/AUTHORS1
-rw-r--r--src/eclean/ChangeLog27
-rw-r--r--src/eclean/Makefile24
-rw-r--r--src/eclean/THANKS7
-rw-r--r--src/eclean/TODO16
-rw-r--r--src/eclean/distfiles.exclude5
-rw-r--r--src/eclean/eclean838
-rw-r--r--src/eclean/eclean.1176
-rw-r--r--src/eclean/packages.exclude4
-rw-r--r--src/epkginfo/Makefile17
-rwxr-xr-xsrc/epkginfo/epkginfo210
-rw-r--r--src/epkginfo/epkginfo.134
-rw-r--r--src/equery/AUTHORS3
-rw-r--r--src/equery/Makefile20
-rw-r--r--src/equery/README0
-rw-r--r--src/equery/TODO63
-rwxr-xr-xsrc/equery/equery1865
-rw-r--r--src/equery/equery.1278
-rw-r--r--src/equery/tests/common-functions.sh52
-rwxr-xr-xsrc/equery/tests/run-all-tests.sh12
-rw-r--r--src/equery/tests/test-belongs-help.out11
-rwxr-xr-xsrc/equery/tests/test-belongs.sh24
-rw-r--r--src/equery/tests/test-changes-help.out0
-rw-r--r--src/equery/tests/test-check-help.out3
-rwxr-xr-xsrc/equery/tests/test-check.sh39
-rw-r--r--src/equery/tests/test-depends-help.out8
-rwxr-xr-xsrc/equery/tests/test-depends.sh27
-rw-r--r--src/equery/tests/test-depgraph-help.out7
-rwxr-xr-xsrc/equery/tests/test-depgraph.sh27
-rw-r--r--src/equery/tests/test-files-help.out11
-rwxr-xr-xsrc/equery/tests/test-files.sh61
-rw-r--r--src/equery/tests/test-glsa-help.out0
-rw-r--r--src/equery/tests/test-hasuses-help.out9
-rw-r--r--src/equery/tests/test-help.out21
-rwxr-xr-xsrc/equery/tests/test-help.sh105
-rw-r--r--src/equery/tests/test-list-help.out9
-rwxr-xr-xsrc/equery/tests/test-list.sh40
-rw-r--r--src/equery/tests/test-size-help.out6
-rwxr-xr-xsrc/equery/tests/test-size.sh27
-rw-r--r--src/equery/tests/test-stats-help.out0
-rw-r--r--src/equery/tests/test-uses-help.out7
-rwxr-xr-xsrc/equery/tests/test-uses.sh39
-rw-r--r--src/equery/tests/test-which-help.out3
-rwxr-xr-xsrc/equery/tests/test-which.sh22
-rw-r--r--src/eread/AUTHORS2
-rw-r--r--src/eread/Makefile20
-rwxr-xr-xsrc/eread/eread94
-rw-r--r--src/eread/eread.112
-rw-r--r--src/euse/AUTHORS2
-rw-r--r--src/euse/ChangeLog9
-rw-r--r--src/euse/Makefile20
-rwxr-xr-xsrc/euse/euse551
-rw-r--r--src/euse/euse.1102
-rw-r--r--src/gentoolkit/AUTHORS2
-rw-r--r--src/gentoolkit/Makefile22
-rw-r--r--src/gentoolkit/README17
-rw-r--r--src/gentoolkit/TODO0
-rw-r--r--src/gentoolkit/__init__.py59
-rw-r--r--src/gentoolkit/errors.py14
-rw-r--r--src/gentoolkit/helpers.py162
-rw-r--r--src/gentoolkit/package.py243
-rw-r--r--src/gentoolkit/pprinter.py116
-rw-r--r--src/glsa-check/Makefile20
-rwxr-xr-xsrc/glsa-check/glsa-check371
-rw-r--r--src/glsa-check/glsa-check.157
-rw-r--r--src/glsa-check/glsa.py644
-rw-r--r--src/revdep-rebuild/99revdep-rebuild21
-rw-r--r--src/revdep-rebuild/AUTHORS2
-rw-r--r--src/revdep-rebuild/ChangeLog9
-rw-r--r--src/revdep-rebuild/Makefile23
-rw-r--r--src/revdep-rebuild/README4
-rw-r--r--src/revdep-rebuild/TODO7
-rwxr-xr-xsrc/revdep-rebuild/find_pkgs.py22
-rwxr-xr-xsrc/revdep-rebuild/revdep-rebuild1120
-rwxr-xr-xsrc/revdep-rebuild/revdep-rebuild-old720
-rwxr-xr-xsrc/revdep-rebuild/revdep-rebuild-sh332
-rw-r--r--src/revdep-rebuild/revdep-rebuild.1138
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)