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