From b2e1ec0199f709df9bf62327dcb503f285eeb707 Mon Sep 17 00:00:00 2001 From: Arfrever Frehtes Taifersar Arahesis Date: Tue, 23 Jul 2019 08:23:51 +0200 Subject: [PATCH] dblink._collision_protect: Detect internal collisions. Implement detection of internal collisions (between files of the same package, located in separate directories in the installation image (${D}) corresponding to merged directories in the target filesystem (${ROOT})). This provides protection against overwriting some files when performing merging of files from ${D} to ${ROOT} in some filesystem layouts (such as /-merged layout or /usr-merged layout). Automatically resolve collision between symbolic link and its target by dropping symbolic link. Bug: https://bugs.gentoo.org/690484 Signed-off-by: Arfrever Frehtes Taifersar Arahesis --- lib/portage/dbapi/vartree.py | 65 +++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/lib/portage/dbapi/vartree.py b/lib/portage/dbapi/vartree.py index e2fce7736..1d2496249 100644 --- a/lib/portage/dbapi/vartree.py +++ b/lib/portage/dbapi/vartree.py @@ -3418,6 +3418,8 @@ class dblink(object): os = _os_merge + real_relative_paths = {} + collision_ignore = [] for x in portage.util.shlex_split( self.settings.get("COLLISION_IGNORE", "")): @@ -3469,8 +3471,35 @@ class dblink(object): previous = current progress_shown = True - dest_path = normalize_path( - os.path.join(destroot, f.lstrip(os.path.sep))) + src_path = normalize_path(os.path.join(srcroot, f.lstrip(os.path.sep))) + dest_path = normalize_path(os.path.join(destroot, f.lstrip(os.path.sep))) + + # Relative path with symbolic links resolved only in parent directories + real_relative_path = os.path.join(os.path.realpath(os.path.dirname(dest_path)), os.path.basename(dest_path))[len(destroot):] + + # Automatic resolution of selected types of internal collisions + src_file_deleted = False + for other_colliding_file in real_relative_paths.get(real_relative_path, []): + if src_file_deleted: + break + if f_type == "sym": + # When symbolic link collides with its target, then + # this symbolic link is dropped. + symlink_target = _unicode_decode(_os.readlink(_unicode_encode(src_path, + encoding=_encodings["merge"], errors="strict")), + encoding=_encodings["merge"], errors="replace") + if os.path.isabs(symlink_target): + if normalize_path(symlink_target.lstrip(os.path.sep)) == other_colliding_file: + os.unlink(src_path) + src_file_deleted = True + else: + if normalize_path(os.path.join(parent, symlink_target).lstrip(os.path.sep)) == other_colliding_file: + os.unlink(src_path) + src_file_deleted = True + if src_file_deleted: + continue + + real_relative_paths.setdefault(real_relative_path, []).append(f.lstrip(os.path.sep)) parent = os.path.dirname(dest_path) if parent not in dirs: @@ -3556,9 +3585,16 @@ class dblink(object): break if stopmerge: collisions.append(f) + + internal_collisions = {} + for k, v in real_relative_paths.items(): + if len(v) >= 2: + internal_collisions[k] = v + if progress_shown: showMessage(_("100% done\n")) - return collisions, dirs_ro, symlink_collisions, plib_collisions + + return collisions, internal_collisions, dirs_ro, symlink_collisions, plib_collisions def _lstat_inode_map(self, path_iter): """ @@ -4081,7 +4117,7 @@ class dblink(object): if blocker.exists(): blockers.append(blocker) - collisions, dirs_ro, symlink_collisions, plib_collisions = \ + collisions, internal_collisions, dirs_ro, symlink_collisions, plib_collisions = \ self._collision_protect(srcroot, destroot, others_in_slot + blockers, filelist, linklist) @@ -4109,6 +4145,27 @@ class dblink(object): eerror(msg) return 1 + if internal_collisions: + msg = _("Package '%s' has internal collisions (between files " + "in separate directories in the installation image (${D}) " + "corresponding to merged directories in the target " + "filesystem (${ROOT})):") % self.settings.mycpv + msg = textwrap.wrap(msg, 70) + msg.append("") + for k, v in sorted(internal_collisions.items()): + msg.append("\t%s" % os.path.join(destroot, k.lstrip(os.path.sep))) + for f in sorted(v): + msg.append("\t\t%s" % os.path.join(destroot, f.lstrip(os.path.sep))) + msg.append("") + self._elog("eerror", "preinst", msg) + + msg = _("Package '%s' NOT merged due to internal collisions.") % \ + self.settings.mycpv + msg += _(" If necessary, refer to your elog messages for the whole " + "content of the above message.") + eerror(textwrap.wrap(msg, 70)) + return 1 + if symlink_collisions: # Symlink collisions need to be distinguished from other types # of collisions, in order to avoid confusion (see bug #409359). -- 2.22.0