Old "Change USE" message: Don't screw up if the highest version is masked and parent...
[portage.git] / bin / portageq
index ffbc13f62d4c5d031dda7dd57747cb0253038951..940575b1fee90f7e21b5e157657fb6e8d64cd9df 100755 (executable)
@@ -1,7 +1,8 @@
 #!/usr/bin/python -O
 # Copyright 1999-2006 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
-# $Id$
+
+from __future__ import print_function
 
 import sys
 # This block ensures that ^C interrupts are handled quietly.
@@ -20,10 +21,35 @@ except KeyboardInterrupt:
        sys.exit(1)
 
 import os
-sys.path = ["/usr/lib/portage/pym"]+sys.path
 
-import types,string
+import types
+
+# Avoid sandbox violations after python upgrade.
+pym_path = os.path.join(os.path.dirname(
+       os.path.dirname(os.path.realpath(__file__))), "pym")
+if os.environ.get("SANDBOX_ON") == "1":
+       sandbox_write = os.environ.get("SANDBOX_WRITE", "").split(":")
+       if pym_path not in sandbox_write:
+               sandbox_write.append(pym_path)
+               os.environ["SANDBOX_WRITE"] = \
+                       ":".join(filter(None, sandbox_write))
+       del sandbox_write
+
+try:
+       import portage
+except ImportError:
+       sys.path.insert(0, pym_path)
+       import portage
+del pym_path
+
+from portage import os
+from portage.util import writemsg, writemsg_stdout
 
+def eval_atom_use(atom):
+       if 'USE' in os.environ:
+               use = frozenset(os.environ['USE'].split())
+               atom = atom.evaluate_conditionals(use)
+       return atom
 
 #-----------------------------------------------------------------------------
 #
@@ -46,22 +72,37 @@ import types,string
 # and will automaticly add a command by the same name as the function!
 #
 
-
 def has_version(argv):
        """<root> <category/package>
        Return code 0 if it's available, 1 otherwise.
        """
        if (len(argv) < 2):
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
                sys.exit(2)
        try:
-               mylist=portage.db[argv[0]]["vartree"].dbapi.match(argv[1])
+               atom = portage.dep.Atom(argv[1])
+       except portage.exception.InvalidAtom:
+               if atom_validate_strict:
+                       portage.writemsg("ERROR: Invalid atom: '%s'\n" % argv[1],
+                               noiselevel=-1)
+                       return 2
+               else:
+                       atom = argv[1]
+       else:
+               atom = eval_atom_use(atom)
+
+       try:
+               mylist = portage.db[argv[0]]["vartree"].dbapi.match(atom)
                if mylist:
                        sys.exit(0)
                else:
                        sys.exit(1)
        except KeyError:
                sys.exit(1)
+       except portage.exception.InvalidAtom:
+               portage.writemsg("ERROR: Invalid atom: '%s'\n" % argv[1],
+                       noiselevel=-1)
+               return 2
 has_version.uses_root = True
 
 
@@ -70,11 +111,22 @@ def best_version(argv):
        Returns category/package-version (without .ebuild).
        """
        if (len(argv) < 2):
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
                sys.exit(2)
        try:
-               mylist=portage.db[argv[0]]["vartree"].dbapi.match(argv[1])
-               print portage.best(mylist)
+               atom = portage.dep.Atom(argv[1])
+       except portage.exception.InvalidAtom:
+               if atom_validate_strict:
+                       portage.writemsg("ERROR: Invalid atom: '%s'\n" % argv[1],
+                               noiselevel=-1)
+                       return 2
+               else:
+                       atom = argv[1]
+       else:
+               atom = eval_atom_use(atom)
+       try:
+               mylist = portage.db[argv[0]]["vartree"].dbapi.match(atom)
+               print(portage.best(mylist))
        except KeyError:
                sys.exit(1)
 best_version.uses_root = True
@@ -85,22 +137,19 @@ def mass_best_version(argv):
        Returns category/package-version (without .ebuild).
        """
        if (len(argv) < 2):
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
                sys.exit(2)
        try:
                for pack in argv[1:]:
                        mylist=portage.db[argv[0]]["vartree"].dbapi.match(pack)
-                       print pack+":"+portage.best(mylist)
+                       print(pack+":"+portage.best(mylist))
        except KeyError:
                sys.exit(1)
 mass_best_version.uses_root = True
 
 def metadata(argv):
-       """<root> <pkgtype> <category/package> [<key>]+
-       Returns metadata values for the specified package.
-       """
        if (len(argv) < 4):
-               print >> sys.stderr, "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!", file=sys.stderr)
                sys.exit(2)
 
        root, pkgtype, pkgspec = argv[0:3]
@@ -110,7 +159,7 @@ def metadata(argv):
                "binary":"bintree",
                "installed":"vartree"}
        if pkgtype not in type_map:
-               print >> sys.stderr, "Unrecognized package type: '%s'" % pkgtype
+               print("Unrecognized package type: '%s'" % pkgtype, file=sys.stderr)
                sys.exit(1)
        trees = portage.db
        if os.path.realpath(root) == os.path.realpath(portage.settings["ROOT"]):
@@ -118,24 +167,229 @@ def metadata(argv):
        try:
                        values = trees[root][type_map[pkgtype]].dbapi.aux_get(
                                pkgspec, metakeys)
-                       for value in values:
-                               print value
+                       writemsg_stdout(''.join('%s\n' % x for x in values), noiselevel=-1)
        except KeyError:
-               print >> sys.stderr, "Package not found: '%s'" % pkgspec
+               print("Package not found: '%s'" % pkgspec, file=sys.stderr)
                sys.exit(1)
 
+metadata.__doc__ = """
+<root> <pkgtype> <category/package> [<key>]+
+Returns metadata values for the specified package.
+Available keys: %s
+"""  % ','.join(sorted(x for x in portage.auxdbkeys \
+if not x.startswith('UNUSED_')))
+
 metadata.uses_root = True
 
+def contents(argv):
+       """<root> <category/package>
+       List the files that are installed for a given package, with
+       one file listed on each line. All file names will begin with
+       <root>.
+       """
+       if len(argv) != 2:
+               print("ERROR: expected 2 parameters, got %d!" % len(argv))
+               return 2
+
+       root, cpv = argv
+       vartree = portage.db[root]["vartree"]
+       if not vartree.dbapi.cpv_exists(cpv):
+               sys.stderr.write("Package not found: '%s'\n" % cpv)
+               return 1
+       cat, pkg = portage.catsplit(cpv)
+       db = portage.dblink(cat, pkg, root, vartree.settings,
+               treetype="vartree", vartree=vartree)
+       writemsg_stdout(''.join('%s\n' % x for x in sorted(db.getcontents())),
+               noiselevel=-1)
+contents.uses_root = True
+
+def owners(argv):
+       """<root> [<filename>]+
+       Given a list of files, print the packages that own the files and which
+       files belong to each package. Files owned by a package are listed on
+       the lines below it, indented by a single tab character (\\t). All file
+       paths must either start with <root> or be a basename alone.
+       Returns 1 if no owners could be found, and 0 otherwise.
+       """
+       if len(argv) < 2:
+               sys.stderr.write("ERROR: insufficient parameters!\n")
+               sys.stderr.flush()
+               return 2
+
+       from portage import catsplit, dblink
+       settings = portage.settings
+       root = settings["ROOT"]
+       vardb = portage.db[root]["vartree"].dbapi
+
+       cwd = None
+       try:
+               cwd = os.getcwd()
+       except OSError:
+               pass
+
+       files = []
+       for f in argv[1:]:
+               f = portage.normalize_path(f)
+               is_basename = os.sep not in f
+               if not is_basename and f[:1] != os.sep:
+                       if cwd is None:
+                               sys.stderr.write("ERROR: cwd does not exist!\n")
+                               sys.stderr.flush()
+                               return 2
+                       f = os.path.join(cwd, f)
+                       f = portage.normalize_path(f)
+               if not is_basename and not f.startswith(root):
+                       sys.stderr.write("ERROR: file paths must begin with <root>!\n")
+                       sys.stderr.flush()
+                       return 2
+               if is_basename:
+                       files.append(f)
+               else:
+                       files.append(f[len(root)-1:])
+
+       owners = vardb._owners.get_owners(files)
+
+       msg = []
+       for pkg, owned_files in owners.items():
+               cpv = pkg.mycpv
+               msg.append("%s\n" % cpv)
+               for f in sorted(owned_files):
+                       msg.append("\t%s\n" % \
+                               os.path.join(root, f.lstrip(os.path.sep)))
+
+       writemsg_stdout(''.join(msg), noiselevel=-1)
+
+       if owners:
+               return 0
+
+       sys.stderr.write("None of the installed packages claim the file(s).\n")
+       sys.stderr.flush()
+       return 1
+
+owners.uses_root = True
+
+def is_protected(argv):
+       """<root> <filename>
+       Given a single filename, return code 0 if it's protected, 1 otherwise.
+       The filename must begin with <root>.
+       """
+       if len(argv) != 2:
+               sys.stderr.write("ERROR: expected 2 parameters, got %d!\n" % len(argv))
+               sys.stderr.flush()
+               return 2
+
+       root, filename = argv
+
+       err = sys.stderr
+       cwd = None
+       try:
+               cwd = os.getcwd()
+       except OSError:
+               pass
+
+       f = portage.normalize_path(filename)
+       if not f.startswith(os.path.sep):
+               if cwd is None:
+                       err.write("ERROR: cwd does not exist!\n")
+                       err.flush()
+                       return 2
+               f = os.path.join(cwd, f)
+               f = portage.normalize_path(f)
+
+       if not f.startswith(root):
+               err.write("ERROR: file paths must begin with <root>!\n")
+               err.flush()
+               return 2
+
+       from portage.util import ConfigProtect
+
+       settings = portage.settings
+       protect = portage.util.shlex_split(settings.get("CONFIG_PROTECT", ""))
+       protect_mask = portage.util.shlex_split(
+               settings.get("CONFIG_PROTECT_MASK", ""))
+       protect_obj = ConfigProtect(root, protect, protect_mask)
+
+       if protect_obj.isprotected(f):
+               return 0
+       return 1
+
+is_protected.uses_root = True
+
+def filter_protected(argv):
+       """<root>
+       Read filenames from stdin and write them to stdout if they are protected.
+       All filenames are delimited by \\n and must begin with <root>.
+       """
+       if len(argv) != 1:
+               sys.stderr.write("ERROR: expected 1 parameter, got %d!\n" % len(argv))
+               sys.stderr.flush()
+               return 2
+
+       root, = argv
+       out = sys.stdout
+       err = sys.stderr
+       cwd = None
+       try:
+               cwd = os.getcwd()
+       except OSError:
+               pass
+
+       from portage.util import ConfigProtect
+
+       settings = portage.settings
+       protect = portage.util.shlex_split(settings.get("CONFIG_PROTECT", ""))
+       protect_mask = portage.util.shlex_split(
+               settings.get("CONFIG_PROTECT_MASK", ""))
+       protect_obj = ConfigProtect(root, protect, protect_mask)
+
+       protected = 0
+       errors = 0
+
+       for line in sys.stdin:
+               filename = line.rstrip("\n")
+               f = portage.normalize_path(filename)
+               if not f.startswith(os.path.sep):
+                       if cwd is None:
+                               err.write("ERROR: cwd does not exist!\n")
+                               err.flush()
+                               errors += 1
+                               continue
+                       f = os.path.join(cwd, f)
+                       f = portage.normalize_path(f)
+
+               if not f.startswith(root):
+                       err.write("ERROR: file paths must begin with <root>!\n")
+                       err.flush()
+                       errors += 1
+                       continue
+
+               if protect_obj.isprotected(f):
+                       protected += 1
+                       out.write("%s\n" % filename)
+       out.flush()
+
+       if errors:
+               return 2
+
+       return 0
+
+filter_protected.uses_root = True
+
 def best_visible(argv):
        """<root> [<category/package>]+
        Returns category/package-version (without .ebuild).
        """
        if (len(argv) < 2):
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
                sys.exit(2)
        try:
                mylist=portage.db[argv[0]]["porttree"].dbapi.match(argv[1])
-               print portage.best(mylist)
+               visible=portage.best(mylist)
+               if visible:
+                       print(visible)
+                       sys.exit(0)
+               else:
+                       sys.exit(1)
        except KeyError:
                sys.exit(1)
 best_visible.uses_root = True
@@ -146,12 +400,12 @@ def mass_best_visible(argv):
        Returns category/package-version (without .ebuild).
        """
        if (len(argv) < 2):
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
                sys.exit(2)
        try:
                for pack in argv[1:]:
                        mylist=portage.db[argv[0]]["porttree"].dbapi.match(pack)
-                       print pack+":"+portage.best(mylist)
+                       print(pack+":"+portage.best(mylist))
        except KeyError:
                sys.exit(1)
 mass_best_visible.uses_root = True
@@ -162,37 +416,37 @@ def all_best_visible(argv):
        Returns all best_visible packages (without .ebuild).
        """
        if (len(argv) < 1):
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
        
        #print portage.db[argv[0]]["porttree"].dbapi.cp_all()
        for pkg in portage.db[argv[0]]["porttree"].dbapi.cp_all():
                mybest=portage.best(portage.db[argv[0]]["porttree"].dbapi.match(pkg))
                if mybest:
-                       print mybest
+                       print(mybest)
 all_best_visible.uses_root = True
 
 
 def match(argv):
-       """<root> <category/package>
-       Returns \n seperated list of category/package-version
+       """<root> <atom>
+       Returns a \\n separated list of category/package-version.
+       When given an empty string, all installed packages will
+       be listed.
        """
-       if (len(argv) < 2):
-               print "ERROR: insufficient parameters!"
+       if len(argv) != 2:
+               print("ERROR: expected 2 parameters, got %d!" % len(argv))
                sys.exit(2)
-       try:
-               print string.join(portage.db[argv[0]]["vartree"].dbapi.match(argv[1]),"\n")
-       except ValueError, e:
-               # Multiple matches thrown from cpv_expand
-               pkgs = e.args[0]
-               # An error has occurred so we writemsg to stderr and exit nonzero.
-               portage.writemsg("The following packages available:\n", noiselevel=-1)
-               for pkg in pkgs:
-                       portage.writemsg("* %s\n" % pkg, noiselevel=-1)
-               portage.writemsg("\nPlease use a more specific atom.\n", noiselevel=-1)
-               sys.exit(1)
-       except KeyError, e:
-               portage.writemsg("%s\n" % str(e), noiselevel=-1)
-               sys.exit(1)
+       root, atom = argv
+       if atom:
+               if atom_validate_strict and not portage.isvalidatom(atom):
+                       portage.writemsg("ERROR: Invalid atom: '%s'\n" % atom,
+                               noiselevel=-1)
+                       return 2
+               results = portage.db[root]["vartree"].dbapi.match(atom)
+       else:
+               results = portage.db[root]["vartree"].dbapi.cpv_all()
+               results.sort()
+       for cpv in results:
+               print(cpv)
 match.uses_root = True
 
 
@@ -201,56 +455,58 @@ def vdb_path(argv):
        Returns the path used for the var(installed) package database for the
        set environment/configuration options.
        """
-       print portage.root+portage.VDB_PATH
-
+       out = sys.stdout
+       out.write(os.path.join(portage.settings["ROOT"], portage.VDB_PATH) + "\n")
+       out.flush()
+       return os.EX_OK
 
 def gentoo_mirrors(argv):
        """
        Returns the mirrors set to use in the portage configuration.
        """
-       print portage.settings["GENTOO_MIRRORS"]
+       print(portage.settings["GENTOO_MIRRORS"])
 
 
 def portdir(argv):
        """
        Returns the PORTDIR path.
        """
-       print portage.settings["PORTDIR"]
+       print(portage.settings["PORTDIR"])
 
 
 def config_protect(argv):
        """
        Returns the CONFIG_PROTECT paths.
        """
-       print portage.settings["CONFIG_PROTECT"]
+       print(portage.settings["CONFIG_PROTECT"])
 
 
 def config_protect_mask(argv):
        """
        Returns the CONFIG_PROTECT_MASK paths.
        """
-       print portage.settings["CONFIG_PROTECT_MASK"]
+       print(portage.settings["CONFIG_PROTECT_MASK"])
 
 
 def portdir_overlay(argv):
        """
        Returns the PORTDIR_OVERLAY path.
        """
-       print portage.settings["PORTDIR_OVERLAY"]
+       print(portage.settings["PORTDIR_OVERLAY"])
 
 
 def pkgdir(argv):
        """
        Returns the PKGDIR path.
        """
-       print portage.settings["PKGDIR"]
+       print(portage.settings["PKGDIR"])
 
 
 def distdir(argv):
        """
        Returns the DISTDIR path.
        """
-       print portage.settings["DISTDIR"]
+       print(portage.settings["DISTDIR"])
 
 
 def envvar(argv):
@@ -263,59 +519,104 @@ def envvar(argv):
                argv.pop(argv.index("-v"))
 
        if len(argv) == 0:
-               print "ERROR: insufficient parameters!"
+               print("ERROR: insufficient parameters!")
                sys.exit(2)
 
        for arg in argv:
                if verbose:
-                       print arg +"='"+ portage.settings[arg] +"'"
+                       print(arg +"='"+ portage.settings[arg] +"'")
                else:
-                       print portage.settings[arg]
+                       print(portage.settings[arg])
 
+def get_repos(argv):
+       """<root>
+       Returns all repos with names (repo_name file) argv[0] = $ROOT
+       """
+       if len(argv) < 1:
+               print("ERROR: insufficient parameters!")
+               sys.exit(2)
+       print(" ".join(portage.db[argv[0]]["porttree"].dbapi.getRepositories()))
+
+def get_repo_path(argv):
+       """<root> <repo_id>+
+       Returns the path to the repo named argv[1], argv[0] = $ROOT
+       """
+       if len(argv) < 2:
+               print("ERROR: insufficient parameters!")
+               sys.exit(2)
+       for arg in argv[1:]:
+               print(portage.db[argv[0]]["porttree"].dbapi.getRepositoryPath(arg))
+
+def list_preserved_libs(argv):
+       """<root>
+       Print a list of libraries preserved during a package update in the form
+       package: path. Returns 0 if no preserved libraries could be found, 
+       1 otherwise.
+       """
+
+       if len(argv) != 1:
+               print("ERROR: wrong number of arguments")
+               sys.exit(2)
+       mylibs = portage.db[argv[0]]["vartree"].dbapi._plib_registry.getPreservedLibs()
+       rValue = 0
+       msg = []
+       for cpv in sorted(mylibs):
+               msg.append(cpv)
+               for path in mylibs[cpv]:
+                       msg.append(' ' + path)
+                       rValue = 1
+               msg.append('\n')
+       writemsg_stdout(''.join(msg), noiselevel=-1)
+       return rValue
+list_preserved_libs.uses_root = True
 
 #-----------------------------------------------------------------------------
 #
 # DO NOT CHANGE CODE BEYOND THIS POINT - IT'S NOT NEEDED!
 #
 
+if not portage.const._ENABLE_PRESERVE_LIBS:
+       del list_preserved_libs
+
+non_commands = frozenset(['eval_atom_use', 'exithandler', 'main',
+       'usage', 'writemsg', 'writemsg_stdout'])
+commands = sorted(k for k, v in globals().items() \
+       if type(v) is types.FunctionType and k not in non_commands)
+
 def usage(argv):
-       rev="$Revision: 1.13.2.1 $"
-       ver=string.split(rev, ' ')[1]
-       print ">>> Portage information query tool -- version "+ver
-       print ">>> Usage: portageq <command> [<option> ...]"
-       print ""
-       print "Available commands:"
+       print(">>> Portage information query tool")
+       print(">>> %s" % portage.VERSION)
+       print(">>> Usage: portageq <command> [<option> ...]")
+       print("")
+       print("Available commands:")
 
        #
        # Show our commands -- we do this by scanning the functions in this
        # file, and formatting each functions documentation.
        #
-       for name in globals().keys():
-               # Drop python stuff, modules, and our own support functions.
-               if (name in ("usage", "__doc__", "__name__", "main", "os", "portage", "sys", "__builtins__", "types", "string")):
-                       continue
-
+       help_mode = '--help' in sys.argv
+       for name in commands:
                # Drop non-functions
                obj = globals()[name]
-               if  (type(obj) != types.FunctionType):
-                       continue
 
                doc = obj.__doc__
                if (doc == None):
-                       print "   "+name
-                       print "      MISSING DOCUMENTATION!"
-                       print ""
+                       print("   " + name)
+                       print("      MISSING DOCUMENTATION!")
+                       print("")
                        continue
 
-               lines = string.split(doc, '\n')
-               print "   "+name+" "+string.strip(lines[0])
+               lines = doc.split("\n")
+               print("   " + name + " " + lines[0].strip())
                if (len(sys.argv) > 1):
-                       if ("--help" not in sys.argv):
+                       if (not help_mode):
                                lines = lines[:-1]
                        for line in lines[1:]:
-                               print "      "+string.strip(line)
+                               print("      " + line.strip())
        if (len(sys.argv) == 1):
-               print "\nRun portageq with --help for info"
+               print("\nRun portageq with --help for info")
+
+atom_validate_strict = "EBUILD_PHASE" in os.environ
 
 def main():
        if "-h" in sys.argv or "--help" in sys.argv:
@@ -326,19 +627,46 @@ def main():
                sys.exit(os.EX_USAGE)
 
        cmd = sys.argv[1]
-       try:
-               function = globals()[cmd]
-               uses_root = (getattr(function, "uses_root", False) and len(sys.argv) > 2)
-               if uses_root:
-                       os.environ["ROOT"] = sys.argv[2]
-               global portage
-               import portage
-               if uses_root:
-                       sys.argv[2] = portage.root
-               function(sys.argv[2:])
-       except KeyError:
+       function = globals().get(cmd)
+       if function is None or cmd not in commands:
                usage(sys.argv)
                sys.exit(os.EX_USAGE)
+       function = globals()[cmd]
+       uses_root = getattr(function, "uses_root", False) and len(sys.argv) > 2
+       if uses_root:
+               if not os.path.isdir(sys.argv[2]):
+                       sys.stderr.write("Not a directory: '%s'\n" % sys.argv[2])
+                       sys.stderr.write("Run portageq with --help for info\n")
+                       sys.stderr.flush()
+                       sys.exit(os.EX_USAGE)
+               os.environ["ROOT"] = sys.argv[2]
+
+       args = sys.argv[2:]
+       if args and sys.hexversion < 0x3000000 and not isinstance(args[0], unicode):
+               for i in range(len(args)):
+                       args[i] = portage._unicode_decode(args[i])
+
+       try:
+               if uses_root:
+                       args[0] = portage.settings["ROOT"]
+               retval = function(args)
+               if retval:
+                       sys.exit(retval)
+       except portage.exception.PermissionDenied as e:
+               sys.stderr.write("Permission denied: '%s'\n" % str(e))
+               sys.exit(e.errno)
+       except portage.exception.ParseError as e:
+               sys.stderr.write("%s\n" % str(e))
+               sys.exit(1)
+       except portage.exception.AmbiguousPackageName as e:
+               # Multiple matches thrown from cpv_expand
+               pkgs = e.args[0]
+               # An error has occurred so we writemsg to stderr and exit nonzero.
+               portage.writemsg("You specified an unqualified atom that matched multiple packages:\n", noiselevel=-1)
+               for pkg in pkgs:
+                       portage.writemsg("* %s\n" % pkg, noiselevel=-1)
+               portage.writemsg("\nPlease use a more specific atom.\n", noiselevel=-1)
+               sys.exit(1)
 
 main()