aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/portage/util/movefile.py')
-rw-r--r--lib/portage/util/movefile.py94
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: