diff options
Diffstat (limited to 'lib/portage/util/movefile.py')
-rw-r--r-- | lib/portage/util/movefile.py | 94 |
1 files changed, 62 insertions, 32 deletions
diff --git a/lib/portage/util/movefile.py b/lib/portage/util/movefile.py index ddafe5571..75100a3ac 100644 --- a/lib/portage/util/movefile.py +++ b/lib/portage/util/movefile.py @@ -38,7 +38,6 @@ _xattr_excluder_cache = {} def _get_xattr_excluder(pattern): - try: value = _xattr_excluder_cache[pattern] except KeyError: @@ -49,11 +48,9 @@ def _get_xattr_excluder(pattern): class _xattr_excluder: - __slots__ = ("_pattern_split",) def __init__(self, pattern): - if pattern is None: self._pattern_split = None else: @@ -65,7 +62,6 @@ class _xattr_excluder: self._pattern_split = tuple(pattern) def __call__(self, attr): - if self._pattern_split is None: return False @@ -81,7 +77,7 @@ def _copyxattr(src, dest, exclude=None): """Copy the extended attributes from |src| to |dest|""" try: attrs = xattr.list(src) - except (OSError, IOError) as e: + except OSError as e: if e.errno != OperationNotSupported.errno: raise attrs = () @@ -97,7 +93,7 @@ def _copyxattr(src, dest, exclude=None): try: xattr.set(dest, attr, xattr.get(src, attr)) raise_exception = False - except (OSError, IOError): + except OSError: raise_exception = True if raise_exception: raise OperationNotSupported( @@ -109,6 +105,36 @@ def _copyxattr(src, dest, exclude=None): ) +def _cmpxattr(src: bytes, dest: bytes, exclude=None) -> bool: + """ + Compares extended attributes between |src| and |dest| and returns True + if they are equal or xattrs are not supported, False otherwise. + Assumes all given paths are UTF-8 encoded. + """ + try: + src_attrs = xattr.list(src) + dest_attrs = xattr.list(dest) + except OSError as e: + if e.errno != OperationNotSupported.errno: + raise + return True + + if src_attrs: + if exclude is not None and isinstance(src_attrs[0], bytes): + exclude = exclude.encode(_encodings["fs"]) + exclude = _get_xattr_excluder(exclude) + + src_attrs = {attr for attr in src_attrs if not exclude(attr)} + dest_attrs = {attr for attr in dest_attrs if not exclude(attr)} + if src_attrs != dest_attrs: + return False + + for attr in src_attrs: + if xattr.get(src, attr) != xattr.get(dest, attr): + return False + return True + + def movefile( src, dest, @@ -149,15 +175,15 @@ def movefile( raise except Exception as e: writemsg( - "!!! %s\n" % _("Stating source file failed... movefile()"), noiselevel=-1 + f"!!! {_('Stating source file failed... movefile()')}\n", noiselevel=-1 ) - writemsg("!!! %s\n" % (e,), noiselevel=-1) + writemsg(f"!!! {e}\n", noiselevel=-1) return None destexists = 1 try: dstat = os.lstat(dest) - except (OSError, IOError): + except OSError: dstat = os.lstat(os.path.dirname(dest)) destexists = 0 @@ -171,7 +197,7 @@ def movefile( bsd_chflags.chflags(os.path.dirname(dest), 0) if destexists: - if stat.S_ISLNK(dstat[stat.ST_MODE]): + if not stat.S_ISLNK(sstat[stat.ST_MODE]) and stat.S_ISLNK(dstat[stat.ST_MODE]): try: os.unlink(dest) destexists = 0 @@ -185,8 +211,18 @@ def movefile( target = os.readlink(src) if mysettings and "D" in mysettings and target.startswith(mysettings["D"]): target = target[len(mysettings["D"]) - 1 :] - if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]): - os.unlink(dest) + # Atomically update the path if it exists. + try: + os.rename(src, dest) + return sstat.st_mtime_ns + except OSError: + # If it failed due to cross-link device, fallthru below. + # Clear the target first so we can create it. + try: + os.unlink(dest) + except FileNotFoundError: + pass + try: if selinux_enabled: selinux.symlink(target, dest, src) @@ -222,11 +258,9 @@ def movefile( except SystemExit as e: raise except Exception as e: - writemsg( - "!!! %s\n" % _("failed to properly create symlink:"), noiselevel=-1 - ) - writemsg("!!! %s -> %s\n" % (dest, target), noiselevel=-1) - writemsg("!!! %s\n" % (e,), noiselevel=-1) + writemsg(f"!!! {_('failed to properly create symlink:')}\n", noiselevel=-1) + writemsg(f"!!! {dest} -> {target}\n", noiselevel=-1) + writemsg(f"!!! {e}\n", noiselevel=-1) return None hardlinked = False @@ -236,9 +270,7 @@ def movefile( # and them use os.rename() to replace the destination. if hardlink_candidates: head, tail = os.path.split(dest) - hardlink_tmp = os.path.join( - head, ".%s._portage_merge_.%s" % (tail, portage.getpid()) - ) + hardlink_tmp = os.path.join(head, f".{tail}._portage_merge_.{portage.getpid()}") try: os.unlink(hardlink_tmp) except OSError as e: @@ -248,7 +280,7 @@ def movefile( % (hardlink_tmp,), noiselevel=-1, ) - writemsg("!!! %s\n" % (e,), noiselevel=-1) + writemsg(f"!!! {e}\n", noiselevel=-1) return None del e for hardlink_src in hardlink_candidates: @@ -264,7 +296,7 @@ def movefile( _("!!! Failed to rename %s to %s\n") % (hardlink_tmp, dest), noiselevel=-1, ) - writemsg("!!! %s\n" % (e,), noiselevel=-1) + writemsg(f"!!! {e}\n", noiselevel=-1) return None hardlinked = True try: @@ -287,12 +319,11 @@ def movefile( if e.errno != errno.EXDEV: # Some random error. writemsg( - "!!! %s\n" - % _("Failed to move %(src)s to %(dest)s") + f"!!! {_('Failed to move %(src)s to %(dest)s')}\n" % {"src": src, "dest": dest}, noiselevel=-1, ) - writemsg("!!! %s\n" % (e,), noiselevel=-1) + writemsg(f"!!! {e}\n", noiselevel=-1) return None # Invalid cross-device-link 'bind' mounted or actually Cross-Device if renamefailed: @@ -322,19 +353,18 @@ def movefile( ) msg = textwrap.wrap(msg, 65) for line in msg: - writemsg("!!! %s\n" % (line,), noiselevel=-1) + writemsg(f"!!! {line}\n", noiselevel=-1) raise _rename(dest_tmp_bytes, dest_bytes) _os.unlink(src_bytes) success = True except Exception as e: writemsg( - "!!! %s\n" - % _("copy %(src)s -> %(dest)s failed.") + f"!!! {_('copy %(src)s -> %(dest)s failed.')}\n" % {"src": src, "dest": dest}, noiselevel=-1, ) - writemsg("!!! %s\n" % (e,), noiselevel=-1) + writemsg(f"!!! {e}\n", noiselevel=-1) return None finally: if not success: @@ -355,7 +385,7 @@ def movefile( }, noiselevel=-1, ) - writemsg("!!! %s\n" % a, noiselevel=-1) + writemsg(f"!!! {a}\n", noiselevel=-1) return None # failure # In Python <3.3 always use stat_obj[stat.ST_MTIME] for the integral timestamp @@ -385,8 +415,8 @@ def movefile( newmtime = os.stat(dest).st_mtime_ns except OSError as e: writemsg(_("!!! Failed to stat in movefile()\n"), noiselevel=-1) - writemsg("!!! %s\n" % dest, noiselevel=-1) - writemsg("!!! %s\n" % str(e), noiselevel=-1) + writemsg(f"!!! {dest}\n", noiselevel=-1) + writemsg(f"!!! {str(e)}\n", noiselevel=-1) return None if bsd_chflags: |