coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC PATCH] mv: add --swap option


From: Dominique Martinet
Subject: [RFC PATCH] mv: add --swap option
Date: Mon, 12 Apr 2021 14:23:41 +0900

From: Dominique Martinet <dominique.martinet@atmark-techno.com>

gnulib supports RENAME_EXCHANGE in renameatu, but there is currently
no way of using it.
Add a new switch to allow users to swap files atomically.
---

Note: I'm not suggesting this as a final version of the patch; it's just
something I hacked in a few minutes just now and appears to work.
A real patch would need at least adding to help string, proper
documentation, possibly some test cases... The code probably could be
made prettier too. Anyway, some rationale:

Linux and BSDs ave had ways of swapping two files atomically for a while
(linux has renameat2 with RENAME_EXCHANGE flag since 3.15 (2014), with
most filesystems implementing it since around 2015, some BSDs have had
renamex_np and renameatx_np with RENAME_SWAP for at least 5 years as
well (didn't check as much))

Yet I do not see any tool making use of it; if you try to search online
the best hits seem to be people suggesting to use tcc in a shell
function to JIT code that calls renameat2[1] or to use a gist on
github[2]... Not exactly a good UX.

[1] https://unix.stackexchange.com/a/625900
[2] https://gist.github.com/eatnumber1/f97ac7dad7b1f5a9721f



The gnulib's renameatu helper that mv uses supports the flag, so there
is no technical reason not to expose it through a new e.g. --swap
command line switch if we so wish to do -- it doesn't have fallback for
the case the flag is not supported, but I personally think it's a good
thing: this will warn user the swap is not atomic, so they need to
handle some sort of recovery in case of hard crash between the renames.
If mv would just emulate the operation there would be no way of
detecting that.


Looking at the list archives, one person offered to implement such a
flag and never got any reply in 2018[3], but the subject never came up
otherwise that I can see.

[3] https://lists.gnu.org/archive/html/coreutils/2018-12/msg00004.html


Since there were no reply I took it a step further with a simple proof
of concept, but my request is basically the same: if I were to polish
this up a bit, clear up documentation etc would such a patch be accepted?

I'm really surprised the topic didn't come up at least once, so perhaps
I have missed something; please just tell me if you're not interested or
if there is a reason not to do this.


 src/mv.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/mv.c b/src/mv.c
index e82cc097218b..451bc4bc6823 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -47,7 +47,8 @@
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1
+  STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1,
+  SWAP_OPTION = CHAR_MAX + 2,
 };
 
 /* Remove any trailing slashes from each SOURCE argument.  */
@@ -63,6 +64,7 @@ static struct option const long_options[] =
   {"no-target-directory", no_argument, NULL, 'T'},
   {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION},
   {"suffix", required_argument, NULL, 'S'},
+  {"swap", no_argument, NULL, SWAP_OPTION},
   {"target-directory", required_argument, NULL, 't'},
   {"update", no_argument, NULL, 'u'},
   {"verbose", no_argument, NULL, 'v'},
@@ -344,6 +346,7 @@ main (int argc, char **argv)
   struct cp_options x;
   char *target_directory = NULL;
   bool no_target_directory = false;
+  bool swap_targets = false;
   int n_files;
   char **file;
   bool selinux_enabled = (0 < is_selinux_enabled ());
@@ -383,6 +386,10 @@ main (int argc, char **argv)
         case STRIP_TRAILING_SLASHES_OPTION:
           remove_trailing_slashes = true;
           break;
+       case SWAP_OPTION:
+         swap_targets = true;
+         no_target_directory = true;
+         break;
         case 't':
           if (target_directory)
             die (EXIT_FAILURE, 0, _("multiple target directories specified"));
@@ -442,6 +449,9 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
+  if (swap_targets && target_directory)
+     die (EXIT_FAILURE, 0, _("cannot combine --swap and --target-directory"));
+
   if (no_target_directory)
     {
       if (target_directory)
@@ -471,6 +481,14 @@ main (int argc, char **argv)
              quoteaf (file[n_files - 1]));
     }
 
+  if (swap_targets) {
+    x.rename_errno = renameatu (AT_FDCWD, file[0], AT_FDCWD, file[1],
+                               RENAME_EXCHANGE) ? errno : 0;
+    /* die early to avoid fallbacks such as copy on EXDEV */
+    if (x.rename_errno)
+      die (EXIT_FAILURE, x.rename_errno, _("swapping files failed"));
+  }
+
   if (x.interactive == I_ALWAYS_NO)
     x.update = false;
 
-- 
2.30.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]