Subject: Re: bug#61050: mv: add support for --one-file-system
From: Paul Eggert <[email-masked]>
To: Mike Frysinger <[email-masked]>
Cc: [email-masked]
Date: Fri, 27 Jan 2023 11:13:30 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

On 2023-01-25 05:34, Mike Frysinger wrote:
> should there be a `--copy` flag to be able to undo `--no-copy` ?

We can add one if there's a need, but mv doesn't have a --clobber option 
to match its --no-clobber and I'm hoping --no-copy is similar.

> personally i always argue against using negative variable names

Yes, I considered calling it "rename_only" instead of "no_copy", but the 
pull of the --no-copy option's name was too strong. Besides, 
"rename_only" wasn't quite right either as "mv --no-copy" does more than 
just renaming (e.g., if the destination is not writeable).

If we can come up with a better name now's a good time to switch. In the 
meantime I installed the attached coreutils patch.

From 20005ca733e111a61212ee8a6890a54583c55562 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Fri, 27 Jan 2023 10:59:13 -0800
Subject: [PATCH] mv: new option --no-copy

Wishlist item from Mike Frysinger (Bug#61050).
* src/copy.c (copy_internal):
Do not fall back on copying if x->no_copy.
* src/copy.h (struct cp_options): New member no_copy.
* src/mv.c (NO_COPY_OPTION): New constant.
(long_options, usage, main): Support --no-copy.
* tests/mv/no-copy.sh: New test.
* tests/local.mk (all_tests): Add it.
---
 NEWS                |  3 +++
 doc/coreutils.texi  | 27 +++++++++++++++------------
 src/copy.c          |  2 +-
 src/copy.h          |  6 +++---
 src/mv.c            |  8 +++++++-
 tests/local.mk      |  1 +
 tests/mv/no-copy.sh | 33 +++++++++++++++++++++++++++++++++
 7 files changed, 63 insertions(+), 17 deletions(-)
 create mode 100755 tests/mv/no-copy.sh

diff --git a/NEWS b/NEWS
index 9594179b4..d714b8f3b 100644
--- a/NEWS
+++ b/NEWS
@@ -95,6 +95,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   ls now supports the --time=modification option, to explicitly
   select the default mtime timestamp for display and sorting.
 
+  mv now supports the --no-copy option, which causes it to fail when
+  asked to move a file to a different file system.
+
   wc now accepts the --total={auto,never,always,only} option
   to give explicit control over when the total is output.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index f5e531d65..c7494ba47 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -10038,19 +10038,16 @@ failing that if the last file is a directory and the
 directory, using the @var{source}s' names.
 @end itemize
 
-@command{mv} can move any type of file from one file system to another.
-Prior to version @code{4.0} of the fileutils,
-@command{mv} could move only regular files between file systems.
-For example, now @command{mv} can move an entire directory hierarchy
-including special device files from one partition to another.  It first
-uses some of the same code that's used by @code{cp -a} to copy the
-requested directories and files, then (assuming the copy succeeded)
-it removes the originals.  If the copy fails, then the part that was
-copied to the destination partition is removed.  If you were to copy
-three directories from one partition to another and the copy of the first
+To move a file, @command{mv} ordinarily simply renames it.
+However, if renaming does not work because the destination's file
+system differs, @command{mv} falls back on copying as if by @code{cp -a},
+then (assuming the copy succeeded) it removes the original.
+If the copy fails, then @command{mv} removes any partially created
+copy in the destination.  If you were to copy three directories from
+one file system to another and the copy of the first
 directory succeeded, but the second didn't, the first would be left on
-the destination partition and the second and third would be left on the
-original partition.
+the destination file system and the second and third would be left on the
+original file system.
 
 @cindex extended attributes, xattr
 @command{mv} always tries to copy extended attributes (xattr), which may
@@ -10114,6 +10111,12 @@ Do not overwrite an existing file; silently do nothing instead.
 @mvOptsIfn
 This option is mutually exclusive with @option{-b} or @option{--backup} option.
 
+@item --no-copy
+@opindex --no-copy
+@cindex renaming files without copying them
+If a file cannot be renamed because the destination file system differs,
+fail with a diagnostic instead of copying and then removing the file.
+
 @item -u
 @itemx --update
 @opindex -u
diff --git a/src/copy.c b/src/copy.c
index 98f2ba45a..99834434f 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -2617,7 +2617,7 @@ copy_internal (char const *src_name, char const *dst_name,
          where you'd replace '18' with the integer in parentheses that
          was output from the perl one-liner above.
          If necessary, of course, change '/tmp' to some other directory.  */
-      if (rename_errno != EXDEV)
+      if (rename_errno != EXDEV || x->no_copy)
         {
           /* There are many ways this can happen due to a race condition.
              When something happens between the initial follow_fstatat and the
diff --git a/src/copy.h b/src/copy.h
index d775db047..9d3884403 100644
--- a/src/copy.h
+++ b/src/copy.h
@@ -134,9 +134,9 @@ struct cp_options
      Create destination directories as usual. */
   bool hard_link;
 
-  /* If true, rather than copying, first attempt to use rename.
-     If that fails, then resort to copying.  */
-  bool move_mode;
+  /* If MOVE_MODE, first try to rename.
+     If that fails and NO_COPY, fail instead of copying.  */
+  bool move_mode, no_copy;
 
   /* If true, install(1) is the caller.  */
   bool install_mode;
diff --git a/src/mv.c b/src/mv.c
index 72bbe8e46..f27a07a1c 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -48,7 +48,8 @@
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1
+  NO_COPY_OPTION = CHAR_MAX + 1,
+  STRIP_TRAILING_SLASHES_OPTION
 };
 
 static struct option const long_options[] =
@@ -58,6 +59,7 @@ static struct option const long_options[] =
   {"force", no_argument, NULL, 'f'},
   {"interactive", no_argument, NULL, 'i'},
   {"no-clobber", no_argument, NULL, 'n'},
+  {"no-copy", no_argument, NULL, NO_COPY_OPTION},
   {"no-target-directory", no_argument, NULL, 'T'},
   {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION},
   {"suffix", required_argument, NULL, 'S'},
@@ -260,6 +262,7 @@ Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
 If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
 "), stdout);
       fputs (_("\
+      --no-copy                do not copy if renaming fails\n\
       --strip-trailing-slashes  remove any trailing slashes from each SOURCE\n\
                                  argument\n\
   -S, --suffix=SUFFIX          override the usual backup suffix\n\
@@ -330,6 +333,9 @@ main (int argc, char **argv)
         case 'n':
           x.interactive = I_ALWAYS_NO;
           break;
+        case NO_COPY_OPTION:
+          x.no_copy = true;
+          break;
         case STRIP_TRAILING_SLASHES_OPTION:
           remove_trailing_slashes = true;
           break;
diff --git a/tests/local.mk b/tests/local.mk
index 86908ef4f..f6e3746b6 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -686,6 +686,7 @@ all_tests =					\
   tests/mv/leak-fd.sh				\
   tests/mv/mv-n.sh				\
   tests/mv/mv-special-1.sh			\
+  tests/mv/no-copy.sh				\
   tests/mv/no-target-dir.sh			\
   tests/mv/part-fail.sh				\
   tests/mv/part-hardlink.sh			\
diff --git a/tests/mv/no-copy.sh b/tests/mv/no-copy.sh
new file mode 100755
index 000000000..fba475c03
--- /dev/null
+++ b/tests/mv/no-copy.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Test mv --no-copy.
+
+# Copyright (C) 2023 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
+cleanup_() { rm -rf "$other_partition_tmpdir"; }
+. "$abs_srcdir/tests/other-fs-tmpdir"
+
+mkdir dir || framework_failure_
+> dir/a || framework_failure_
+> file || framework_failure_
+
+mv --no-copy dir "$other_partition_tmpdir" && fail=1
+mv --no-copy file "$other_partition_tmpdir" && fail=1
+mv dir "$other_partition_tmpdir" || fail=1
+mv file "$other_partition_tmpdir" || fail=1
+
+Exit $fail
-- 
2.37.2