commit:     fa82725ba03ffa5edf0b82db7307c87fc97e83f2
Author:     Mike Frysinger <vapier <AT> chromium <DOT> org>
AuthorDate: Wed Mar 30 06:32:07 2022 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Apr 15 02:46:19 2022 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=fa82725b

movefile: merge symlinks atomically

Since POSIX allows renaming symlinks with symlinks, use that to get
atomic updates.  This is faster and less flaky to parallel processes:
removing a live symlink and then recreating it leaves a small window
where other things might try to use it but fail.

[sam: cherry-picked from chromiumos' third_party/portage_tool repo]
(cherry picked from commit cherry-pick 80ed2dac95db81acac8043e6685d0a853a08d268)
Bug: https://bugs.gentoo.org/836400
Signed-off-by: Sam James <sam <AT> gentoo.org>
Closes: https://github.com/gentoo/portage/pull/816
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/portage/util/movefile.py | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/lib/portage/util/movefile.py b/lib/portage/util/movefile.py
index ddafe5571..4dc08af26 100644
--- a/lib/portage/util/movefile.py
+++ b/lib/portage/util/movefile.py
@@ -171,7 +171,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 +185,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)

Reply via email to