[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] mv: add --swap (-s) option to atomically swap 2 paths
From: |
Petr Malat |
Subject: |
[PATCH] mv: add --swap (-s) option to atomically swap 2 paths |
Date: |
Thu, 29 Feb 2024 23:02:03 +0100 |
renameat2() syscall allows atomically swapping 2 paths on one
file system. Expose this ability to the user using -s option.
* NEWS: Mention the new option
* doc/coreutils.texi: Describe mv -s option
* src/mv.c: Introduce -s option
* tests/local.mk: Include new mv-s.sh test
* tests/mv/mv-s.sh: Add test for mv -s
---
NEWS | 3 +++
doc/coreutils.texi | 8 ++++++++
src/mv.c | 34 ++++++++++++++++++++++++++++++++--
tests/local.mk | 1 +
tests/mv/mv-s.sh | 42 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 86 insertions(+), 2 deletions(-)
create mode 100755 tests/mv/mv-s.sh
diff --git a/NEWS b/NEWS
index 7a5fbfd28..cee73acee 100644
--- a/NEWS
+++ b/NEWS
@@ -76,6 +76,9 @@ GNU coreutils NEWS -*-
outline -*-
od now supports printing IEEE half precision floating point with -t fH,
or brain 16 bit floating point with -t fB, where supported by the compiler.
+ mv now accepts the --swap (-s) option, which atomically swaps two files on
+ one file system.
+
tail now supports following multiple processes, with repeated --pid options.
** Improvements
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 9e3aa6c1c..2d95841cc 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -10302,6 +10302,14 @@ to cause @command{cp} write to arbitrary target
directories.
@optBackupSuffix
+@item -s
+@itemx --swap
+@opindex -s
+@opindex --swap
+@cindex swapping files
+Atomically swap two files. They can be of a different type, but must reside
+on the same file system.
+
@optTargetDirectory
@optNoTargetDirectory
diff --git a/src/mv.c b/src/mv.c
index 9dc40fe3e..21abd4a07 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -75,6 +75,7 @@ static struct option const long_options[] =
{"strip-trailing-slashes", no_argument, nullptr,
STRIP_TRAILING_SLASHES_OPTION},
{"suffix", required_argument, nullptr, 'S'},
+ {"swap", no_argument, nullptr, 's'},
{"target-directory", required_argument, nullptr, 't'},
{"update", optional_argument, nullptr, 'u'},
{"verbose", no_argument, nullptr, 'v'},
@@ -256,8 +257,9 @@ usage (int status)
Usage: %s [OPTION]... [-T] SOURCE DEST\n\
or: %s [OPTION]... SOURCE... DIRECTORY\n\
or: %s [OPTION]... -t DIRECTORY SOURCE...\n\
+ or: %s -s SOURCE DEST\n\
"),
- program_name, program_name, program_name);
+ program_name, program_name, program_name, program_name);
fputs (_("\
Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
"), stdout);
@@ -285,6 +287,8 @@ If you specify more than one of -i, -f, -n, only the final
one takes effect.\n\
-S, --suffix=SUFFIX override the usual backup suffix\n\
"), stdout);
fputs (_("\
+ -s, --swap atomically exchange SOURCE and DEST, they may
be\n\
+ of different type, but on the same
file-system\n\
-t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\
-T, --no-target-directory treat DEST as a normal file\n\
"), stdout);
@@ -323,6 +327,7 @@ main (int argc, char **argv)
char **file;
bool selinux_enabled = (0 < is_selinux_enabled ());
bool no_clobber = false;
+ bool swap = false;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
@@ -337,7 +342,7 @@ main (int argc, char **argv)
/* Try to disable the ability to unlink a directory. */
priv_set_remove_linkdir ();
- while ((c = getopt_long (argc, argv, "bfint:uvS:TZ", long_options, nullptr))
+ while ((c = getopt_long (argc, argv, "bfint:uvS:sTZ", long_options, nullptr))
!= -1)
{
switch (c)
@@ -412,6 +417,9 @@ main (int argc, char **argv)
make_backups = true;
backup_suffix = optarg;
break;
+ case 's':
+ swap = true;
+ break;
case 'Z':
/* As a performance enhancement, don't even bother trying
to "restorecon" when not on an selinux-enabled kernel. */
@@ -434,6 +442,28 @@ main (int argc, char **argv)
n_files = argc - optind;
file = argv + optind;
+ if (swap)
+ {
+ if (target_directory || x.update)
+ {
+ error (0, 0, _("cannot combine --swap (-s) with "
+ "--target-directory (-t) or --update (-u)"));
+ usage (EXIT_FAILURE);
+ }
+ if (n_files != 2)
+ {
+ error (0, 0, _("option --swap (-s) takes 2 file operands, "
+ "but %d were given."), n_files);
+ usage (EXIT_FAILURE);
+ }
+ if (renameatu (AT_FDCWD, file[0], AT_FDCWD, file[1],
+ RENAME_EXCHANGE))
+ error (EXIT_FAILURE, errno, "Can't swap %s and %s",
+ quoteaf_n (0, file[0]), quoteaf_n (1, file[1]));
+
+ main_exit (EXIT_SUCCESS);
+ }
+
if (n_files <= !target_directory)
{
if (n_files <= 0)
diff --git a/tests/local.mk b/tests/local.mk
index 7cd1ef7b5..6a5aa2325 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -699,6 +699,7 @@ all_tests = \
tests/mv/into-self-4.sh \
tests/mv/leak-fd.sh \
tests/mv/mv-n.sh \
+ tests/mv/mv-s.sh \
tests/mv/mv-special-1.sh \
tests/mv/no-copy.sh \
tests/mv/no-target-dir.sh \
diff --git a/tests/mv/mv-s.sh b/tests/mv/mv-s.sh
new file mode 100755
index 000000000..9c26206cb
--- /dev/null
+++ b/tests/mv/mv-s.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# Test whether mv -s swaps targets
+
+# Copyright (C) 2024 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ mv
+
+
+# test swapping files
+touch b || framework_failure_
+mkdir b || framework_failure_
+mv -s a b || fail=1
+rmdir a || fail=1
+rm b || fail=1
+
+# test wrong number of arguments
+touch a b c || framework_failure_
+returns_ 1 mv -s a 2>/dev/null || fail=1
+returns_ 1 mv -s a b c 2>/dev/null || fail=1
+
+# swapping can't be used with -t or -u
+touch a b || framework_failure_
+mkdir d
+returns_ 1 mv -s -t d a b 2>/dev/null || fail=1
+returns_ 1 mv -s -t d a 2>/dev/null || fail=1
+returns_ 1 mv -s -u a b 2>/dev/null || fail=1
+
+Exit $fail
--
2.39.2
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [PATCH] mv: add --swap (-s) option to atomically swap 2 paths,
Petr Malat <=