diff options
author | Zac Medico <zmedico@gentoo.org> | 2011-07-28 04:29:25 -0700 |
---|---|---|
committer | Zac Medico <zmedico@gentoo.org> | 2011-07-28 04:29:25 -0700 |
commit | 4bb08136f073024c5d31dceb1618b6f4e7246369 (patch) | |
tree | 54ee1f6c5a537afa47bbc9772252b2c9efaae129 | |
parent | depgraph: handle invalid SRC_URI (diff) | |
download | portage-4bb08136f073024c5d31dceb1618b6f4e7246369.tar.gz portage-4bb08136f073024c5d31dceb1618b6f4e7246369.tar.bz2 portage-4bb08136f073024c5d31dceb1618b6f4e7246369.zip |
emerge: protect symlinks to directories sometimes
Before, it was possible to unmerge a symlink to a directory, such that
files installed via the path of the symlink could become inaccessible
via that path (and also making it impossible to unmerge them via that
path).
Now, the symlink will only be unmerged if the directory that it points
to only contains regular files which are all being unmerged. In any
other case, the symlink will be preserved and an eerror log message
will record the event. This will give the user an opportunity to take
further action if they deem it necessary, and such symlink preservation
will not be silent as it was reported in bug #326685, comment #3.
-rw-r--r-- | pym/portage/dbapi/vartree.py | 90 |
1 files changed, 88 insertions, 2 deletions
diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 47f5eb2e1..6bb68d344 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -70,6 +70,7 @@ import shutil import stat import sys import tempfile +import textwrap import time import warnings @@ -1908,6 +1909,7 @@ class dblink(object): cfgfiledict = grabdict(self.vartree.dbapi._conf_mem_file) stale_confmem = [] + protected_symlinks = [] unmerge_orphans = "unmerge-orphans" in self.settings.features calc_prelink = "prelink-checksums" in self.settings.features @@ -2027,9 +2029,34 @@ class dblink(object): if dblnk.isowner(relative_path): is_owned = True break - if is_owned: + + if is_owned and \ + (islink and statobj and stat.S_ISDIR(statobj.st_mode)): # A new instance of this package claims the file, so - # don't unmerge it. + # don't unmerge it. If the file is symlink to a + # directory and the unmerging package installed it as + # a symlink, but the new owner has it listed as a + # directory, then we'll produce a warning since the + # symlink is a sort of orphan in this case (see + # bug #326685). + symlink_orphan = False + for dblnk in others_in_slot: + parent_contents_key = \ + dblnk._match_contents(relative_path) + if not parent_contents_key: + continue + if not parent_contents_key.startswith( + real_root): + continue + if dblnk.getcontents()[ + parent_contents_key][0] == "dir": + symlink_orphan = True + break + + if symlink_orphan: + protected_symlinks.append(relative_path) + + if is_owned: show_unmerge("---", unmerge_desc["replaced"], file_type, obj) continue elif relative_path in cfgfiledict: @@ -2076,6 +2103,52 @@ class dblink(object): if not islink: show_unmerge("---", unmerge_desc["!sym"], file_type, obj) continue + + # If this symlink points to a direcory then we don't want + # to unmerge it if there are any other packages that + # installed files into the directory via this symlink + # (see bug #326685). + # TODO: Resolving a symlink to a directory will require + # simulation if $ROOT != / and the link is not relative. + if islink and statobj and stat.S_ISDIR(statobj.st_mode) \ + and obj.startswith(real_root): + + relative_path = obj[real_root_len:] + try: + target_dir_contents = os.listdir(obj) + except OSError: + pass + else: + if target_dir_contents: + # If all the children are regular files owned + # by this package, then the symlink should be + # safe to unmerge. + all_owned = True + for child in target_dir_contents: + child = os.path.join(relative_path, child) + if not self.isowner(child): + all_owned = False + break + try: + child_lstat = os.lstat(os.path.join( + real_root, child.lstrip(os.sep))) + except OSError: + continue + + if not stat.S_ISREG(child_lstat.st_mode): + # Nested symlinks or directories make + # the issue very complex, so just + # preserve the symlink in order to be + # on the safe side. + all_owned = False + break + + if not all_owned: + protected_symlinks.append(relative_path) + show_unmerge("---", unmerge_desc["!empty"], + file_type, obj) + continue + # Go ahead and unlink symlinks to directories here when # they're actually recorded as symlinks in the contents. # Normally, symlinks such as /lib -> lib64 are not recorded @@ -2152,6 +2225,19 @@ class dblink(object): show_unmerge("---", unmerge_desc["!empty"], "dir", obj) del e + if protected_symlinks: + msg = "One or more symlinks to directories have been " + \ + "preserved in order to ensure that files installed " + \ + "via these symlinks remain accessible:" + lines = textwrap.wrap(msg, 72) + lines.append("") + protected_symlinks.reverse() + for f in protected_symlinks: + lines.append("\t%s" % (os.path.join(real_root, + f.lstrip(os.sep)))) + lines.append("") + self._elog("eerror", "postrm", lines) + # Remove stale entries from config memory. if stale_confmem: for filename in stale_confmem: |