Subject: bug#52193: mv broken on non-APFS filesystems on macOS on coreutils >= 9.0

From: Paul Eggert <[email-masked]>
Date: Wed, 8 Dec 2021 19:17:56 -0800

On 12/8/21 16:08, Sudhip Nashi wrote:

> The issue is that all `mv` operations within the mountpoint fail with an ENOTSUPP error.

Thanks, I think I see the problem now. Please try the attached patch; I 
haven't tested myself as I lack access to macOS. Thanks.
From 9fe50d31aa75dd2d896225c2d1993f0b8eef2f26 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Wed, 8 Dec 2021 19:14:25 -0800
Subject: [PATCH] renameatu: port to macOS tmpfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem reported by Sudhip Nashi (Bug#52193).
* lib/renameatu.c (renameat2ish) [HAVE_RENAMEAT]: New function.
(renameatu): Use the new function, to avoid a bug when
renameatx_np fails with errno == ENOTSUP.  Don’t try to support
RENAME_EXCHANGE; the old code didn’t work and nobody using using
RENAME_EXCHANGE anyway.
---
 ChangeLog       | 10 +++++++
 lib/renameatu.c | 69 +++++++++++++++++++++++++------------------------
 2 files changed, 45 insertions(+), 34 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index bb3edbe44a..413a090749 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2021-12-08  Paul Eggert  <[email-masked]>
+
+	renameatu: port to macOS tmpfs
+	Problem reported by Sudhip Nashi (Bug#52193).
+	* lib/renameatu.c (renameat2ish) [HAVE_RENAMEAT]: New function.
+	(renameatu): Use the new function, to avoid a bug when
+	renameatx_np fails with errno == ENOTSUP.  Don’t try to support
+	RENAME_EXCHANGE; the old code didn’t work and nobody using using
+	RENAME_EXCHANGE anyway.
+
 2021-12-07  Paul Eggert  <[email-masked]>
 
 	regex: pacify Coverity clean_state_log_if_needed
diff --git a/lib/renameatu.c b/lib/renameatu.c
index 38438a4ef1..b75f95269f 100644
--- a/lib/renameatu.c
+++ b/lib/renameatu.c
@@ -61,6 +61,29 @@ rename_noreplace (char const *src, char const *dst)
 
 #undef renameat
 
+#if HAVE_RENAMEAT
+
+/* Act like renameat (FD1, SRC, FD2, DST), except fail with EEXIST if
+   FLAGS is nonzero and it is easy to fail atomically if DST already exists.
+   This lets renameatu be atomic when it can be implemented in terms
+   of renameatx_np.  */
+static int
+renameat2ish (int fd1, char const *src, int fd2, char const *dst,
+              unsigned int flags)
+{
+# ifdef RENAME_EXCL
+  if (flags)
+    {
+      int r = renameatx_np (fd1, src, fd2, dst, RENAME_EXCL);
+      if (r == 0 || errno != ENOTSUP)
+        return r;
+    }
+# endif
+
+  return renameat (fd1, src, fd2, dst);
+}
+#endif
+
 /* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
    the directory open on descriptor FD2.  If possible, do it without
    changing the working directory.  Otherwise, resort to using
@@ -93,9 +116,6 @@ renameatu (int fd1, char const *src, int fd2, char const *dst,
 
 #if HAVE_RENAMEAT
   {
-# if defined RENAME_EXCL                /* macOS */
-  unsigned int uflags;
-# endif
   size_t src_len;
   size_t dst_len;
   char *src_temp = (char *) src;
@@ -107,23 +127,12 @@ renameatu (int fd1, char const *src, int fd2, char const *dst,
   struct stat dst_st;
   bool dst_found_nonexistent = false;
 
-  /* Check the flags.  */
-# if defined RENAME_EXCL
-  /* We can support RENAME_EXCHANGE and RENAME_NOREPLACE.  */
-  if (flags & ~(RENAME_EXCHANGE | RENAME_NOREPLACE))
-# else
-  /* RENAME_NOREPLACE is the only flag currently supported.  */
-  if (flags & ~RENAME_NOREPLACE)
-# endif
-    return errno_fail (ENOTSUP);
-
-# if defined RENAME_EXCL
-  uflags = ((flags & RENAME_EXCHANGE ? RENAME_SWAP : 0)
-            | (flags & RENAME_NOREPLACE ? RENAME_EXCL : 0));
-# endif
-
-  if ((flags & RENAME_NOREPLACE) != 0)
+  switch (flags)
     {
+    case 0:
+      break;
+
+    case RENAME_NOREPLACE:
       /* This has a race between the call to lstatat and the calls to
          renameat below.  This lstatat is needed even if RENAME_EXCL
          is defined, because RENAME_EXCL is buggy on macOS 11.2:
@@ -134,26 +143,22 @@ renameatu (int fd1, char const *src, int fd2, char const *dst,
       if (errno != ENOENT)
         return -1;
       dst_found_nonexistent = true;
+      break;
+
+    default:
+      return errno_fail (ENOTSUP);
     }
 
   /* Let strace see any ENOENT failure.  */
   src_len = strlen (src);
   dst_len = strlen (dst);
   if (!src_len || !dst_len)
-# if defined RENAME_EXCL
-    return renameatx_np (fd1, src, fd2, dst, uflags);
-# else
-    return renameat (fd1, src, fd2, dst);
-# endif
+    return renameat2ish (fd1, src, fd2, dst, flags);
 
   src_slash = src[src_len - 1] == '/';
   dst_slash = dst[dst_len - 1] == '/';
   if (!src_slash && !dst_slash)
-# if defined RENAME_EXCL
-    return renameatx_np (fd1, src, fd2, dst, uflags);
-# else
-    return renameat (fd1, src, fd2, dst);
-# endif
+    return renameat2ish (fd1, src, fd2, dst, flags);
 
   /* Presence of a trailing slash requires directory semantics.  If
      the source does not exist, or if the destination cannot be turned
@@ -226,11 +231,7 @@ renameatu (int fd1, char const *src, int fd2, char const *dst,
      on Solaris, since all other systems either lack renameat or honor
      trailing slash correctly.  */
 
-# if defined RENAME_EXCL
-  ret_val = renameatx_np (fd1, src_temp, fd2, dst_temp, uflags);
-# else
-  ret_val = renameat (fd1, src_temp, fd2, dst_temp);
-# endif
+  ret_val = renameat2ish (fd1, src_temp, fd2, dst_temp, flags);
   rename_errno = errno;
   goto out;
  out:
-- 
2.33.1