Merged from trunk 5381:5392
authorFabian Groffen <grobian@gentoo.org>
Mon, 25 Dec 2006 21:34:10 +0000 (21:34 -0000)
committerFabian Groffen <grobian@gentoo.org>
Mon, 25 Dec 2006 21:34:10 +0000 (21:34 -0000)
Clone cached match results before returning them from portdbapi.xmatch().
Thanks to Brian <dol-sen@telus.net> for reporting this issue.

Make bindbapi.aux_get() selectively cache metadata in order to optimize dep
matching (like portdbapi does).

Make sure that bindbapi makes EAPI correctly default to 0 in all possible cases.

more docstrings.. (WarnerBro)

Docstrings for mergeme... (WarnerBro)

Drop the warning messages for users that aren't in the portage group since
tpordbapi now correctly puts the on-disk cache into readonly mode in this case
(and allows metadata generation in-memory if necessary).

Prevent the regen action from running in --pretend mode.

When the user has insufficient access privileges, specify whether superuser
access or portage group access is required.  When only portage group access is
required but the user is not in the portage group, call
portage_data.portage_group_warning() to remind people that the portage group is
a system administration group.

Make sure that portage_group_warning() will be shown even in quiet mode.

Use a SIGTERM signal to support a die call inside of a subshell.

For bug #148603, when an ebuild dies, print eclasses that are from overlays.
Thanks to Alec Warner for the initial patch.

svn path=/main/branches/prefix/; revision=5393

bin/ebuild.sh
bin/emerge
pym/portage.py
pym/portage_data.py

index e1df76da44f5ee4f4dc5fede215c6ea757f5a9c9..bde20300199421f729fbfd9995ed5fe21f84d49a 100755 (executable)
@@ -24,6 +24,10 @@ if [ "$*" != "depend" ] && [ "$*" != "clean" ] && [ "$*" != "nofetch" ]; then
        fi
 fi
 
+# subshell die support
+EBUILD_MASTER_PID=$$
+trap 'exit 1' SIGTERM
+
 EBUILD_SH_ARGS="$*"
 
 shift $#
@@ -409,12 +413,24 @@ diefunc() {
        echo "!!! ${*:-(no error message)}" >&2
        echo "!!! If you need support, post the topmost build error, and the call stack if relevant." >&2
        echo >&2
+       if [ -n "${EBUILD_OVERLAY_ECLASSES}" ] ; then
+               echo "This ebuild used the following eclasses from overlays:" >&2
+               echo >&2
+               for x in ${EBUILD_OVERLAY_ECLASSES} ; do
+                       echo "  ${x}" >&2
+               done
+               echo >&2
+       fi
+
        if [ "${EBUILD_PHASE/depend}" == "${EBUILD_PHASE}" ]; then
                local x
                for x in $EBUILD_DEATH_HOOKS; do
                        ${x} "$@" >&2 1>&2
                done
        fi
+
+       # subshell die support
+       kill -s SIGTERM ${EBUILD_MASTER_PID}
        exit 1
 }
 
@@ -1318,6 +1334,7 @@ inherit() {
        fi
 
        local location
+       local olocation
        local PECLASS
 
        local B_IUSE
@@ -1326,6 +1343,7 @@ inherit() {
        local B_PDEPEND
        while [ "$1" ]; do
                location="${ECLASSDIR}/${1}.eclass"
+               olocation=""
 
                # PECLASS is used to restore the ECLASS var after recursion.
                PECLASS="$ECLASS"
@@ -1357,6 +1375,11 @@ inherit() {
                debug-print "inherit: $1 -> $location"
                [ ! -e "$location" ] && die "${1}.eclass could not be found by inherit()"
 
+               if [ "${location}" == "${olocation}" ] && \
+                       ! hasq "${location}" ${EBUILD_OVERLAY_ECLASSES} ; then
+                               EBUILD_OVERLAY_ECLASSES="${EBUILD_OVERLAY_ECLASSES} ${location}"
+               fi
+
                #We need to back up the value of DEPEND and RDEPEND to B_DEPEND and B_RDEPEND
                #(if set).. and then restore them after the inherit call.
 
index c8757c49a1ab96a5b165620906de005c019a5e54..12930bc9f16f1593a4965960436ca954fd34fff8 100755 (executable)
@@ -4982,18 +4982,32 @@ def emerge_main():
                # We've already allowed "--version" and "--help" above.
                if "--pretend" not in myopts and \
                myaction not in ("search","info"):
-                       if portage.secpass < 1 or \
-                               not ("--fetchonly" in myopts or \
+                       need_superuser = not \
+                               ("--fetchonly" in myopts or \
                                "--fetch-all-uri" in myopts or \
-                               myaction in ("metadata", "regen")):
+                               myaction in ("metadata", "regen"))
+                       if portage.secpass < 1 or \
+                               need_superuser:
+                               if need_superuser:
+                                       access_desc = "superuser"
+                               else:
+                                       access_desc = "portage group"
+                               # Always show portage_group_warning() when only portage group
+                               # access is required but the user is not in the portage group.
+                               from portage_data import portage_group_warning
                                if "--ask" in myopts:
                                        myopts["--pretend"] = True
                                        del myopts["--ask"]
-                                       print "root access would be required..." + \
-                                               " adding --pretend to options."
+                                       print ("%s access would be required... " + \
+                                               "adding --pretend to options.\n") % access_desc
+                                       if portage.secpass < 1 and not need_superuser:
+                                               portage_group_warning()
                                else:
-                                       print "emerge: root access required."
-                                       sys.exit(1)
+                                       sys.stderr.write(("emerge: %s access would be " + \
+                                               "required.\n\n") % access_desc)
+                                       if portage.secpass < 1 and not need_superuser:
+                                               portage_group_warning()
+                                       return 1
 
        disable_emergelog = False
        for x in ("--pretend", "--fetchonly", "--fetch-all-uri"):
@@ -5039,23 +5053,20 @@ def emerge_main():
                        xtermTitleReset()
        portage.atexit_register(emergeexit)
 
-       if myaction in ["sync","metadata"] and "--help" not in myopts:
+       if myaction in ("config", "metadata", "regen", "sync"):
                if "--pretend" in myopts:
-                       print "emerge: \"sync\" actions do not support \"--pretend.\""
-                       sys.exit(1)
-               if "sync" == myaction:
-                       action_sync(settings, trees, mtimedb, myopts, myaction)
-               else:
-                       action_metadata(settings, portdb, myopts)
+                       sys.stderr.write(("emerge: The '%s' action does " + \
+                               "not support '--pretend'.\n") % myaction)
+                       return 1
+       if "sync" == myaction:
+               action_sync(settings, trees, mtimedb, myopts, myaction)
+       elif "metadata" == myaction:
+               action_metadata(settings, portdb, myopts)
        elif myaction=="regen":
                validate_ebuild_environment(trees)
                action_regen(settings, portdb)
        # HELP action
        elif "config"==myaction:
-               if "--pretend" in myopts:
-                       sys.stderr.write("emerge: The --config action " + \
-                               "does not support --pretend.\n")
-                       return 1
                validate_ebuild_environment(trees)
                action_config(settings, trees, myopts, myfiles)
        
index 17df76f59e9052961cb801530b79b8de14eb9389..b7e7f0d4541e7ff933afa372493d881528a77db6 100644 (file)
@@ -4626,6 +4626,9 @@ class bindbapi(fakedbapi):
                        settings = globals()["settings"]
                self.settings = settings
                self._match_cache = {}
+               # Selectively cache metadata in order to optimize dep matching.
+               self._aux_cache_keys = set(["SLOT"])
+               self._aux_cache = {}
 
        def match(self, *pargs, **kwargs):
                if self.bintree and not self.bintree.populated:
@@ -4635,28 +4638,39 @@ class bindbapi(fakedbapi):
        def aux_get(self,mycpv,wants):
                if self.bintree and not self.bintree.populated:
                        self.bintree.populate()
+               cache_me = False
+               if not set(wants).difference(self._aux_cache_keys):
+                       aux_cache = self._aux_cache.get(mycpv)
+                       if aux_cache is not None:
+                               return [aux_cache[x] for x in wants]
+                       cache_me = True
                mysplit = string.split(mycpv,"/")
                mylist  = []
                tbz2name = mysplit[1]+".tbz2"
                if self.bintree and not self.bintree.isremote(mycpv):
                        tbz2 = xpak.tbz2(self.bintree.getname(mycpv))
-               for x in wants:
-                       if self.bintree and self.bintree.isremote(mycpv):
-                               # We use the cache for remote packages
-                               mylist.append(" ".join(
-                                       self.bintree.remotepkgs[tbz2name].get(x,"").split()))
-                       else:
-                               myval = tbz2.getfile(x)
-                               if myval is None:
-                                       myval = ""
-                               else:
-                                       myval = string.join(myval.split(),' ')
-                               mylist.append(myval)
-               if "EAPI" in wants:
-                       idx = wants.index("EAPI")
-                       if not mylist[idx]:
-                               mylist[idx] = "0"
-               return mylist
+                       getitem = tbz2.getfile
+               else:
+                       getitem = self.bintree.remotepkgs[tbz2name].get
+               mydata = {}
+               mykeys = wants
+               if cache_me:
+                       mykeys = self._aux_cache_keys.union(wants)
+               for x in mykeys:
+                       myval = getitem(x)
+                       # myval is None if the key doesn't exist
+                       # or the tbz2 is corrupt.
+                       if myval:
+                               mydata[x] = " ".join(myval.split())
+               if "EAPI" in mykeys:
+                       if not mydata.setdefault("EAPI", "0"):
+                               mydata["EAPI"] = "0"
+               if cache_me:
+                       aux_cache = {}
+                       for x in self._aux_cache_keys:
+                               aux_cache[x] = mydata.get(x, "")
+                       self._aux_cache[mycpv] = aux_cache
+               return [mydata.get(x, "") for x in wants]
 
        def aux_update(self, cpv, values):
                if not self.bintree.populated:
@@ -5695,7 +5709,7 @@ class portdbapi(dbapi):
                #if no updates are being made to the tree, we can consult our xcache...
                if self.frozen:
                        try:
-                               return self.xcache[level][origdep]
+                               return self.xcache[level][origdep][:]
                        except KeyError:
                                pass
 
@@ -5739,7 +5753,7 @@ class portdbapi(dbapi):
                        self.xcache[level][mydep]=myval
                        if origdep and origdep != mydep:
                                self.xcache[level][origdep] = myval
-               return myval
+               return myval[:]
 
        def match(self,mydep,use_cache=1):
                return self.xmatch("match-visible",mydep)
@@ -6298,10 +6312,30 @@ class binarytree(object):
                return myslot
 
 class dblink:
-       "this class provides an interface to the standard text package database"
+       """
+       This class provides an interface to the installed package database
+       At present this is implemented as a text backend in /var/db/pkg.
+       """
        def __init__(self, cat, pkg, myroot, mysettings, treetype=None,
                vartree=None):
-               "create a dblink object for cat/pkg.  This dblink entry may or may not exist"
+               """
+               Creates a DBlink object for a given CPV.
+               The given CPV may not be present in the database already.
+               
+               @param cat: Category
+               @type cat: String
+               @param pkg: Package (PV)
+               @type pkg: String
+               @param myroot: Typically ${ROOT}
+               @type myroot: String (Path)
+               @param mysettings: Typically portage.config
+               @type mysettings: An instance of portage.config
+               @param treetype: one of ['porttree','bintree','vartree']
+               @type treetype: String
+               @param vartree: an instance of vartree corresponding to myroot.
+               @type vartree: vartree
+               """
+               
                self.cat     = cat
                self.pkg     = pkg
                self.mycpv   = self.cat+"/"+self.pkg
@@ -6356,13 +6390,18 @@ class dblink:
 
        def create(self):
                "create the skeleton db directory structure.  No contents, virtuals, provides or anything.  Also will create /var/db/pkg if necessary."
+               """
+               This function should never get called (there is no reason to use it).
+               """
                # XXXXX Delete this eventually
                raise Exception, "This is bad. Don't use it."
                if not os.path.exists(self.dbdir):
                        os.makedirs(self.dbdir)
 
        def delete(self):
-               "erase this db entry completely"
+               """
+               Remove this entry from the database
+               """
                if not os.path.exists(self.dbdir):
                        return
                try:
@@ -6379,10 +6418,16 @@ class dblink:
                        sys.exit(1)
 
        def clearcontents(self):
+               """
+               For a given db entry (self), erase the CONTENTS values.
+               """
                if os.path.exists(self.dbdir+"/CONTENTS"):
                        os.unlink(self.dbdir+"/CONTENTS")
 
        def getcontents(self):
+               """
+               Get the installed files of a given package (aka what that package installed)
+               """
                if not os.path.exists(self.dbdir+"/CONTENTS"):
                        return None
                if self.contentscache != []:
@@ -6437,8 +6482,30 @@ class dblink:
 
        def unmerge(self, pkgfiles=None, trimworld=1, cleanup=1,
                ldpath_mtimes=None):
-               """The caller must ensure that lockdb() and unlockdb() are called
-               before and after this method."""
+               """
+               Calls prerm
+               Unmerges a given package (CPV)
+               calls postrm
+               calls cleanrm
+               calls env_update
+               
+               @param pkgfiles: files to unmerge (generally self.getcontents() )
+               @type pkgfiles: Dictionary
+               @param trimworld: Remove CPV from world file if True, not if False
+               @type trimworld: Boolean
+               @param cleanup: cleanup to pass to doebuild (see doebuild)
+               @type cleanup: Boolean
+               @param ldpath_mtimes: mtimes to pass to env_update (see env_update)
+               @type ldpath_mtimes: Dictionary
+               @rtype: Integer
+               @returns:
+               1. os.EX_OK if everything went well.
+               2. return code of the failed phase (for prerm, postrm, cleanrm)
+               
+               Notes:
+               The caller must ensure that lockdb() and unlockdb() are called
+               before and after this method.
+               """
 
                contents = self.getcontents()
                # Now, don't assume that the name of the ebuild is the same as the
@@ -6525,7 +6592,15 @@ class dblink:
                return os.EX_OK
 
        def _unmerge_pkgfiles(self, pkgfiles):
-
+               """
+               
+               Unmerges the contents of a package from the liveFS
+               Removes the VDB entry for self
+               
+               @param pkgfiles: typically self.getcontents()
+               @type pkgfiles: Dictionary { filename: [ 'type', '?', 'md5sum' ] }
+               @rtype: None
+               """
                global dircache
                dircache={}
 
@@ -6644,8 +6719,19 @@ class dblink:
                self.vartree.zap(self.mycpv)
 
        def isowner(self,filename,destroot):
-               """ check if filename is a new file or belongs to this package
-               (for this or a previous version)"""
+               """ 
+               Check if filename is a new file or belongs to this package
+               (for this or a previous version)
+               
+               @param filename:
+               @type filename:
+               @param destroot:
+               @type destroot:
+               @rtype: Boolean
+               @returns:
+               1. True if this package owns the file.
+               2. False if this package does not own the file.
+               """
                destfile = normalize_path(
                        os.path.join(destroot, filename.lstrip(os.path.sep)))
                try:
@@ -6697,12 +6783,37 @@ class dblink:
 
        def treewalk(self, srcroot, destroot, inforoot, myebuild, cleanup=0,
                mydbapi=None, prev_mtimes=None):
-               # srcroot  = ${D};
-               # destroot = where to merge, ie. ${ROOT},
-               # inforoot = root of db entry,
-               # secondhand = list of symlinks that have been skipped due to
-               #              their target not existing (will merge later),
-
+               """
+               
+               This function does the following:
+               
+               Collision Protection.
+               calls doebuild(mydo=pkg_preinst)
+               Merges the package to the livefs
+               unmerges old version (if required)
+               calls doebuild(mydo=pkg_postinst)
+               calls env_update
+               
+               @param srcroot: Typically this is ${D}
+               @type srcroot: String (Path)
+               @param destroot: Path to merge to (usually ${ROOT})
+               @type destroot: String (Path)
+               @param inforoot: root of the vardb entry ?
+               @type inforoot: String (Path)
+               @param myebuild: path to the ebuild that we are processing
+               @type myebuild: String (Path)
+               @param mydbapi: dbapi which is handed to doebuild.
+               @type mydbapi: portdbapi instance
+               @param prev_mtimes: { Filename:mtime } mapping for env_update
+               @type prev_mtimes: Dictionary
+               @rtype: Boolean
+               @returns:
+               1. 0 on success
+               2. 1 on failure
+               
+               secondhand is a list of symlinks that have been skipped due to their target
+               not existing; we will merge these symlinks at a later time.
+               """
                if not os.path.isdir(srcroot):
                        writemsg("!!! Directory Not Found: D='%s'\n" % srcroot,
                        noiselevel=-1)
@@ -6990,6 +7101,32 @@ class dblink:
                return 0
 
        def mergeme(self,srcroot,destroot,outfile,secondhand,stufftomerge,cfgfiledict,thismtime):
+               """
+               
+               This function handles actual merging of the package contents to the livefs.
+               It also handles config protection.
+               
+               @param srcroot: Where are we copying files from (usually ${D})
+               @type srcroot: String (Path)
+               @param destroot: Typically ${ROOT}
+               @type destroot: String (Path)
+               @param outfile: File to log operations to
+               @type outfile: File Object
+               @param secondhand: A set of items to merge in pass two (usually
+               or symlinks that point to non-existing files that may get merged later)
+               @type secondhand: List
+               @param stufftomerge: Either a diretory to merge, or a list of items.
+               @type stufftomerge: String or List
+               @param cfgfiledict: { File:mtime } mapping for config_protected files
+               @type cfgfiledict: Dictionary
+               @param thismtime: The current time (typically long(time.time())
+               @type thismtime: Long
+               @rtype: None or Boolean
+               @returns:
+               1. True on failure
+               2. None otherwise
+               
+               """
                from os.path import sep, join
                srcroot = normalize_path(srcroot).rstrip(sep) + sep
                destroot = normalize_path(destroot).rstrip(sep) + sep
index 822b4c8beb8f059229d5fffbea362d57f83bfc04..76316bd2e48a260eb6fc9dff1061755f147a0aff 100644 (file)
@@ -56,10 +56,17 @@ os.environ["USERLAND"]=userland
 
 def portage_group_warning():
        warn_prefix = bad("*** WARNING ***  ")
-       writemsg(warn_prefix + "For security reasons, only system administrators should be\n")
-       writemsg(warn_prefix + "allowed in the portage group.  Untrusted users or processes\n")
-       writemsg(warn_prefix + "can potentially exploit the portage group for attacks such as\n")
-       writemsg(warn_prefix + "local privilege escalation.\n\n")
+       mylines = [
+               "For security reasons, only system administrators should be",
+               "allowed in the portage group.  Untrusted users or processes",
+               "can potentially exploit the portage group for attacks such as",
+               "local privilege escalation."
+       ]
+       for x in mylines:
+               writemsg(warn_prefix, noiselevel=-1)
+               writemsg(x, noiselevel=-1)
+               writemsg("\n", noiselevel=-1)
+       writemsg("\n", noiselevel=-1)
 
 # Portage has 3 security levels that depend on the uid and gid of the main
 # process and are assigned according to the following table:
@@ -112,14 +119,6 @@ except KeyError:
        writemsg("\n")
        portage_group_warning()
 
-if (uid!=0) and (portage_gid not in os.getgroups()):
-       writemsg("\n")
-       writemsg(red("*** You are not in the portage group. You may experience cache problems\n"))
-       writemsg(red("*** due to permissions preventing the creation of the on-disk cache.\n"))
-       writemsg(red("*** Please add this user to the portage group if you wish to use portage.\n"))
-       writemsg("\n")
-       portage_group_warning()
-
 userpriv_groups = [portage_gid]
 if secpass >= 2:
        for g in grp.getgrall():