Support FEATURES={downgrade,unmerge}-backup
authorZac Medico <zmedico@gentoo.org>
Sun, 1 Jul 2012 07:53:52 +0000 (00:53 -0700)
committerZac Medico <zmedico@gentoo.org>
Sun, 1 Jul 2012 07:53:52 +0000 (00:53 -0700)
This will fix bug #156282 and bug #424275.

bin/quickpkg
man/make.conf.5
pym/portage/const.py
pym/portage/dbapi/vartree.py

index d908c03469e38528b6a1624709b1b9316043732e..a6bd4d4bd5413e91e67a29d1aa1e36ae59c3a04e 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/python
-# Copyright 1999-2010 Gentoo Foundation
+# Copyright 1999-2012 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 from __future__ import print_function
@@ -68,11 +68,14 @@ def quickpkg_atom(options, infos, arg, eout):
                bintree.prevent_collision(cpv)
                dblnk = vardb._dblink(cpv)
                have_lock = False
-               try:
-                       dblnk.lockdb()
-                       have_lock = True
-               except PermissionDenied:
-                       pass
+
+               if "__PORTAGE_INHERIT_VARDB_LOCK" not in settings:
+                       try:
+                               dblnk.lockdb()
+                               have_lock = True
+                       except PermissionDenied:
+                               pass
+
                try:
                        if not dblnk.exists():
                                # unmerged by a concurrent process
index 7d07344bde0e734d501c3fb8c896de2d0f43d00f..876a8a330fee9ddfe0513e1d4679b601a0da0ac8 100644 (file)
@@ -1,4 +1,4 @@
-.TH "MAKE.CONF" "5" "May 2012" "Portage VERSION" "Portage"
+.TH "MAKE.CONF" "5" "Jul 2012" "Portage VERSION" "Portage"
 .SH "NAME"
 make.conf \- custom settings for Portage
 .SH "SYNOPSIS"
@@ -293,6 +293,12 @@ strangely configured Samba server (oplocks off, NFS re\-export). A tool
 /usr/lib/portage/bin/clean_locks exists to help handle lock issues
 when a problem arises (normally due to a crash or disconnect).
 .TP
+.B downgrade\-backup
+When a package is downgraded to a lower version, call \fBquickpkg\fR(1)
+in order to create a backup of the installed version before it is
+unmerged (if a binary package of the same version does not already
+exist). Also see the related \fIunmerge\-backup\fR feature.
+.TP
 .B ebuild\-locks
 Use locks to ensure that unsandboxed ebuild phases never execute
 concurrently. Also see \fIparallel\-install\fR.
@@ -514,6 +520,11 @@ continue to execute the remaining phases as if the failure had not occurred.
 Note that the test phase for a specific package may be disabled by masking
 the "test" \fBUSE\fR flag in \fBpackage.use.mask\fR (see \fBportage\fR(5)).
 .TP
+.B unmerge\-backup
+Call \fBquickpkg\fR(1) to create a backup of each package before it is
+unmerged (if a binary package of the same version does not already exist).
+Also see the related \fIdowngrade\-backup\fR feature.
+.TP
 .B unmerge\-logs
 Keep logs from successful unmerge phases. This is relevant only when
 \fBPORT_LOGDIR\fR is set.
index 3607df0e038d53154c759ea8837e4e37ac01b8bf..4a077102e1567ea71b38a1a8eb8ebd7c39749801 100644 (file)
@@ -90,7 +90,8 @@ SUPPORTED_FEATURES       = frozenset([
                            "ccache", "chflags", "clean-logs",
                            "collision-protect", "compress-build-logs", "compressdebug",
                            "config-protect-if-modified",
-                           "digest", "distcc", "distcc-pump", "distlocks", "ebuild-locks", "fakeroot",
+                           "digest", "distcc", "distcc-pump", "distlocks",
+                           "downgrade-backup", "ebuild-locks", "fakeroot",
                            "fail-clean", "force-mirror", "force-prefix", "getbinpkg",
                            "installsources", "keeptemp", "keepwork", "fixlafiles", "lmirror",
                            "metadata-transfer", "mirror", "multilib-strict", "news",
@@ -103,6 +104,7 @@ SUPPORTED_FEATURES       = frozenset([
                            "sign", "skiprocheck", "split-elog", "split-log", "splitdebug",
                            "strict", "stricter", "suidctl", "test", "test-fail-continue",
                            "unknown-features-filter", "unknown-features-warn",
+                           "unmerge-backup",
                            "unmerge-logs", "unmerge-orphans", "userfetch", "userpriv",
                            "usersandbox", "usersync", "webrsync-gpg", "xattr"])
 
index 34098eab197ff975f69abde1797947405181d533..0d7327ad418fbea3154af370e95baf436ff9fa51 100644 (file)
@@ -21,6 +21,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
        'portage.package.ebuild.doebuild:doebuild_environment,' + \
                '_merge_unicode_error', '_spawn_phase',
        'portage.package.ebuild.prepare_build_dirs:prepare_build_dirs',
+       'portage.package.ebuild._ipc.QueryCommand:QueryCommand',
        'portage.update:fixdbentries',
        'portage.util:apply_secpass_permissions,ConfigProtect,ensure_dirs,' + \
                'writemsg,writemsg_level,write_atomic,atomic_ofstream,writedict,' + \
@@ -62,6 +63,7 @@ from _emerge.EbuildPhase import EbuildPhase
 from _emerge.emergelog import emergelog
 from _emerge.PollScheduler import PollScheduler
 from _emerge.MiscFunctionsProcess import MiscFunctionsProcess
+from _emerge.SpawnProcess import SpawnProcess
 
 import errno
 import fnmatch
@@ -1777,6 +1779,11 @@ class dblink(object):
                showMessage = self._display_merge
                if self.vartree.dbapi._categories is not None:
                        self.vartree.dbapi._categories = None
+
+               # When others_in_slot is not None, the backup has already been
+               # handled by the caller.
+               caller_handles_backup = others_in_slot is not None
+
                # When others_in_slot is supplied, the security check has already been
                # done for this slot, so it shouldn't be repeated until the next
                # replacement or unmerge operation.
@@ -1842,6 +1849,11 @@ class dblink(object):
                                prepare_build_dirs(settings=self.settings, cleanup=True)
                                log_path = self.settings.get("PORTAGE_LOG_FILE")
 
+                       if not caller_handles_backup:
+                               retval = self._pre_unmerge_backup(background)
+                               if retval != os.EX_OK:
+                                       return retval
+
                        # Log the error after PORTAGE_LOG_FILE is initialized
                        # by prepare_build_dirs above.
                        if eapi_unsupported:
@@ -3833,6 +3845,20 @@ class dblink(object):
                self.delete()
                ensure_dirs(self.dbtmpdir)
 
+               downgrade = False
+               if self._installed_instance is not None and \
+                       vercmp(self.mycpv.version,
+                       self._installed_instance.mycpv.version) < 0:
+                       downgrade = True
+
+               if self._installed_instance is not None:
+                       rval = self._pre_merge_backup(self._installed_instance, downgrade)
+                       if rval != os.EX_OK:
+                               showMessage(_("!!! FAILED preinst: ") +
+                                       "quickpkg: %s\n" % rval,
+                                       level=logging.ERROR, noiselevel=-1)
+                               return rval
+
                # run preinst script
                showMessage(_(">>> Merging %(cpv)s to %(destroot)s\n") % \
                        {"cpv":self.mycpv, "destroot":destroot})
@@ -3866,20 +3892,15 @@ class dblink(object):
                #if we have a file containing previously-merged config file md5sums, grab it.
                self.vartree.dbapi._fs_lock()
                try:
+                       # Always behave like --noconfmem is enabled for downgrades
+                       # so that people who don't know about this option are less
+                       # likely to get confused when doing upgrade/downgrade cycles.
                        cfgfiledict = grabdict(self.vartree.dbapi._conf_mem_file)
-                       if "NOCONFMEM" in self.settings:
+                       if "NOCONFMEM" in self.settings or downgrade:
                                cfgfiledict["IGNORE"]=1
                        else:
                                cfgfiledict["IGNORE"]=0
 
-                       # Always behave like --noconfmem is enabled for downgrades
-                       # so that people who don't know about this option are less
-                       # likely to get confused when doing upgrade/downgrade cycles.
-                       for other in others_in_slot:
-                               if vercmp(self.mycpv.version, other.mycpv.version) < 0:
-                                       cfgfiledict["IGNORE"] = 1
-                                       break
-
                        rval = self._merge_contents(srcroot, destroot, cfgfiledict)
                        if rval != os.EX_OK:
                                return rval
@@ -4682,6 +4703,60 @@ class dblink(object):
                "Is this a regular package (does it have a CATEGORY file?  A dblink can be virtual *and* regular)"
                return os.path.exists(os.path.join(self.dbdir, "CATEGORY"))
 
+       def _pre_merge_backup(self, backup_dblink, downgrade):
+
+               if ("unmerge-backup" in self.settings.features or
+                       (downgrade and "downgrade-backup" in self.settings.features)):
+                       return self._quickpkg_dblink(backup_dblink, False, None)
+
+               return os.EX_OK
+
+       def _pre_unmerge_backup(self, background):
+
+               if "unmerge-backup" in self.settings.features :
+                       logfile = None
+                       if self.settings.get("PORTAGE_BACKGROUND") != "subprocess":
+                               logfile = self.settings.get("PORTAGE_LOG_FILE")
+                       return self._quickpkg_dblink(self, background, logfile)
+
+               return os.EX_OK
+
+       def _quickpkg_dblink(self, backup_dblink, background, logfile):
+
+               trees = QueryCommand.get_db()[self.settings["EROOT"]]
+               bintree = trees["bintree"]
+               binpkg_path = bintree.getname(backup_dblink.mycpv)
+               if os.path.exists(binpkg_path) and \
+                       backup_dblink.mycpv not in bintree.invalids:
+                       return os.EX_OK
+
+               self.lockdb()
+               try:
+
+                       if not backup_dblink.exists():
+                               # It got unmerged by a concurrent process.
+                               return os.EX_OK
+
+                       # Call quickpkg for support of QUICKPKG_DEFAULT_OPTS and stuff.
+                       quickpkg_binary = os.path.join(self.settings["PORTAGE_BIN_PATH"],
+                               "quickpkg")
+
+                       # Let quickpkg inherit the global vartree config's env.
+                       env = dict(self.vartree.settings.items())
+                       env["__PORTAGE_INHERIT_VARDB_LOCK"] = "1"
+
+                       quickpkg_proc = SpawnProcess(
+                               args=[portage._python_interpreter, quickpkg_binary,
+                                       "=%s" % (backup_dblink.mycpv,)],
+                               background=background, env=env,
+                               scheduler=self._scheduler, logfile=logfile)
+                       quickpkg_proc.start()
+
+                       return quickpkg_proc.wait()
+
+               finally:
+                       self.unlockdb()
+
 def merge(mycat, mypkg, pkgloc, infloc,
        myroot=None, settings=None, myebuild=None,
        mytree=None, mydbapi=None, vartree=None, prev_mtimes=None, blockers=None,