From 20005ca733e111a61212ee8a6890a54583c55562 Mon Sep 17 00:00:00 2001 From: Paul Eggert 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 . + +. "${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