]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
cp --reflink: add an "auto" parameter to fall back to a normal copy
authorPádraig Brady <P@draigBrady.com>
Tue, 25 Aug 2009 23:32:43 +0000 (00:32 +0100)
committerPádraig Brady <P@draigBrady.com>
Fri, 28 Aug 2009 23:24:49 +0000 (00:24 +0100)
* doc/coreutils.texi (cp invocation): Document the new
"auto" and "always" options to --reflink.
* src/copy.c (copy_reg): Fall back to a standard copy
when reflink() is not supported and --reflink=auto specified.
* src/copy.h [struct cp_options] (reflink): Change type s/bool/enum/.
* src/cp.c (usage): Describe the --reflink={always,auto} options
and expand a little on what --reflink does.
(main): parse the new parameters to --reflink and allow all
--sparse options with --reflink=auto.
* src/install.c (cp_option_init): Init the enum instead of bool.
* src/mv.c (cp_option_init): Likewise.
* tests/cp/reflink-auto: A new test for falling back to normal copy.
* tests/Makefile.am: Reference the new test.
* NEWS: Mention the new feature.

NEWS
doc/coreutils.texi
src/copy.c
src/copy.h
src/cp.c
src/install.c
src/mv.c
tests/Makefile.am
tests/cp/reflink-auto [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index c3b2e989b42959e1b4749c9c2336f9520606615c..90721357b534aa253687d651339f0619d58ab7f8 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,10 @@ GNU coreutils NEWS                                    -*- outline -*-
   printing a summary to stderr.
   [bug introduced in coreutils-6.11]
 
+** New features
+
+  cp --reflink accepts a new "auto" parameter which falls back to
+  a standard copy if creating a copy-on-write clone is not possible.
 
 * Noteworthy changes in release 7.5 (2009-08-20) [stable]
 
index 8e1b73d6371edf92ab7d3f1fec2f0a71cdb3392a..f4be2c2f0c40a3aa57131665b0f16c04f4cd3d6d 100644 (file)
@@ -7543,15 +7543,31 @@ Also, it is not portable to use @option{-R} to copy symbolic links
 unless you also specify @option{-P}, as @acronym{POSIX} allows
 implementations that dereference symbolic links by default.
 
-@item --reflink
-@opindex --reflink
+@item --reflink[=@var{when}]
+@opindex --reflink[=@var{when}]
+@cindex COW
+@cindex clone
+@cindex copy on write
 Perform a lightweight, copy-on-write (COW) copy.
-Copying with this option can succeed only on some relatively new file systems.
+Copying with this option can succeed only on some file systems.
 Once it has succeeded, beware that the source and destination files
 share the same disk data blocks as long as they remain unmodified.
 Thus, if a disk I/O error affects data blocks of one of the files,
 the other suffers the exact same fate.
 
+The @var{when} value can be one of the following:
+
+@table @samp
+@item always
+The default behavior: if the copy-on-write operation is not supported
+then report the failure for each file and exit with a failure status.
+
+@item auto
+If the copy-on-write operation is not supported then fall back
+to the standard copy behaviour.
+@end table
+
+
 @item --remove-destination
 @opindex --remove-destination
 Remove each existing destination file before attempting to open it
index 5f84f7e43f97760f71362343b81f918e69454d88..17be63e56df600686ca6b47dcd7d6dc72922b4a1 100644 (file)
@@ -635,14 +635,18 @@ copy_reg (char const *src_name, char const *dst_name,
       goto close_src_and_dst_desc;
     }
 
-  if (x->reflink)
+  if (x->reflink_mode)
     {
-      if (clone_file (dest_desc, source_desc))
+      bool clone_ok = clone_file (dest_desc, source_desc) == 0;
+      if (clone_ok || x->reflink_mode == REFLINK_ALWAYS)
         {
-          error (0, errno, _("failed to clone %s"), quote (dst_name));
-          return_val = false;
+          if (!clone_ok)
+            {
+              error (0, errno, _("failed to clone %s"), quote (dst_name));
+              return_val = false;
+            }
+          goto close_src_and_dst_desc;
         }
-      goto close_src_and_dst_desc;
     }
 
   {
@@ -2248,8 +2252,11 @@ valid_options (const struct cp_options *co)
   assert (co != NULL);
   assert (VALID_BACKUP_TYPE (co->backup_type));
   assert (VALID_SPARSE_MODE (co->sparse_mode));
+  assert (VALID_REFLINK_MODE (co->reflink_mode));
   assert (!(co->hard_link && co->symbolic_link));
-  assert (!(co->reflink && co->sparse_mode != SPARSE_AUTO));
+  assert (!
+          (co->reflink_mode == REFLINK_ALWAYS
+           && co->sparse_mode != SPARSE_AUTO));
   return true;
 }
 
index cdef64a7f7b0d6ec36f922b55b95cd2a0f1562d0..745533aecd406cb8f6417e1a076c2b9e94e59e22 100644 (file)
@@ -43,6 +43,19 @@ enum Sparse_type
   SPARSE_ALWAYS
 };
 
+/* Control creation of COW files.  */
+enum Reflink_type
+{
+  /* Default to a standard copy.  */
+  REFLINK_NEVER,
+
+  /* Try a COW copy and fall back to a standard copy.  */
+  REFLINK_AUTO,
+
+  /* Require a COW copy and fail if not available.  */
+  REFLINK_ALWAYS
+};
+
 /* This type is used to help mv (via copy.c) distinguish these cases.  */
 enum Interactive
 {
@@ -73,6 +86,11 @@ enum Dereference_symlink
    || (Mode) == SPARSE_AUTO            \
    || (Mode) == SPARSE_ALWAYS)
 
+# define VALID_REFLINK_MODE(Mode)      \
+  ((Mode) == REFLINK_NEVER             \
+   || (Mode) == REFLINK_AUTO           \
+   || (Mode) == REFLINK_ALWAYS)
+
 /* These options control how files are copied by at least the
    following programs: mv (when rename doesn't work), cp, install.
    So, if you add a new member, be sure to initialize it in
@@ -219,8 +237,8 @@ struct cp_options
      such a symlink) and returns false.  */
   bool open_dangling_dest_symlink;
 
-  /* If true, attempt to clone the file instead of copying it.  */
-  bool reflink;
+  /* Control creation of COW files.  */
+  enum Reflink_type reflink_mode;
 
   /* This is a set of destination name/inode/dev triples.  Each such triple
      represents a file we have created corresponding to a source file name
index 7424f9b3cff4c8bcb51b6e7f28a92159ac7519ba..2ba1dbf09a4cc3c98286c50b60fc55f58156b117 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
@@ -102,6 +102,16 @@ static enum Sparse_type const sparse_type[] =
 };
 ARGMATCH_VERIFY (sparse_type_string, sparse_type);
 
+static char const *const reflink_type_string[] =
+{
+  "auto", "always", NULL
+};
+static enum Reflink_type const reflink_type[] =
+{
+  REFLINK_AUTO, REFLINK_ALWAYS
+};
+ARGMATCH_VERIFY (reflink_type_string, reflink_type);
+
 static struct option const long_opts[] =
 {
   {"archive", no_argument, NULL, 'a'},
@@ -122,7 +132,7 @@ static struct option const long_opts[] =
   {"recursive", no_argument, NULL, 'R'},
   {"remove-destination", no_argument, NULL, UNLINK_DEST_BEFORE_OPENING},
   {"sparse", required_argument, NULL, SPARSE_OPTION},
-  {"reflink", no_argument, NULL, REFLINK_OPTION},
+  {"reflink", optional_argument, NULL, REFLINK_OPTION},
   {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION},
   {"suffix", required_argument, NULL, 'S'},
   {"symbolic-link", no_argument, NULL, 's'},
@@ -192,12 +202,12 @@ Mandatory arguments to long options are mandatory for short options too.\n\
 "), stdout);
       fputs (_("\
   -R, -r, --recursive          copy directories recursively\n\
-      --reflink                perform a lightweight (CoW/clone) copy\n\
+      --reflink[=WHEN]         control clone/CoW copies. See below.\n\
       --remove-destination     remove each existing destination file before\n\
                                  attempting to open it (contrast with --force)\n\
 "), stdout);
       fputs (_("\
-      --sparse=WHEN            control creation of sparse files\n\
+      --sparse=WHEN            control creation of sparse files. See below.\n\
       --strip-trailing-slashes  remove any trailing slashes from each SOURCE\n\
                                  argument\n\
 "), stdout);
@@ -223,6 +233,10 @@ corresponding DEST file is made sparse as well.  That is the behavior\n\
 selected by --sparse=auto.  Specify --sparse=always to create a sparse DEST\n\
 file whenever the SOURCE file contains a long enough sequence of zero bytes.\n\
 Use --sparse=never to inhibit creation of sparse files.\n\
+\n\
+When --reflink[=always] is specified, perform a lightweight copy, where the\n\
+data blocks are copied only when modified.  If this is not possible the copy\n\
+fails, or if --reflink=auto is specified, fall back to a standard copy.\n\
 "), stdout);
       fputs (_("\
 \n\
@@ -755,7 +769,7 @@ cp_option_init (struct cp_options *x)
   x->interactive = I_UNSPECIFIED;
   x->move_mode = false;
   x->one_file_system = false;
-  x->reflink = false;
+  x->reflink_mode = REFLINK_NEVER;
 
   x->preserve_ownership = false;
   x->preserve_links = false;
@@ -921,7 +935,11 @@ main (int argc, char **argv)
           break;
 
         case REFLINK_OPTION:
-          x.reflink = true;
+          if (optarg == NULL)
+            x.reflink_mode = REFLINK_ALWAYS;
+          else
+            x.reflink_mode = XARGMATCH ("--reflink", optarg,
+                                       reflink_type_string, reflink_type);
           break;
 
         case 'a':              /* Like -dR --preserve=all with reduced failure diagnostics. */
@@ -1084,7 +1102,7 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
-  if (x.reflink && x.sparse_mode != SPARSE_AUTO)
+  if (x.reflink_mode == REFLINK_ALWAYS && x.sparse_mode != SPARSE_AUTO)
     {
       error (0, 0, _("--reflink can be used only with --sparse=auto"));
       usage (EXIT_FAILURE);
index a4e0cab323bcc58d5825a434e06943c7f8147b49..fafa21a3cf414cb93139043a6eb5e2417e5fc009 100644 (file)
@@ -269,7 +269,7 @@ cp_option_init (struct cp_options *x)
 {
   cp_options_default (x);
   x->copy_as_regular = true;
-  x->reflink = false;
+  x->reflink_mode = REFLINK_NEVER;
   x->dereference = DEREF_ALWAYS;
   x->unlink_dest_before_opening = true;
   x->unlink_dest_after_failed_open = false;
index 814e55dfefbbf7e67365877c154377f8654e98e9..29fdde308dd6771a5a9338b27d53a79af8825c8e 100644 (file)
--- a/src/mv.c
+++ b/src/mv.c
@@ -105,7 +105,7 @@ cp_option_init (struct cp_options *x)
 
   cp_options_default (x);
   x->copy_as_regular = false;  /* FIXME: maybe make this an option */
-  x->reflink = false;
+  x->reflink_mode = REFLINK_NEVER;
   x->dereference = DEREF_NEVER;
   x->unlink_dest_before_opening = false;
   x->unlink_dest_after_failed_open = false;
index c88dd9c4472f3fc0aa15185c84d939c5de4382ae..9a823921520d0374ebca222ad45f3b19f7be94f4 100644 (file)
@@ -291,6 +291,7 @@ TESTS =                                             \
   cp/proc-short-read                           \
   cp/proc-zero-len                             \
   cp/r-vs-symlink                              \
+  cp/reflink-auto                              \
   cp/same-file                                 \
   cp/slink-2-slink                             \
   cp/sparse                                    \
diff --git a/tests/cp/reflink-auto b/tests/cp/reflink-auto
new file mode 100755 (executable)
index 0000000..ff2b1b3
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+# Test cp --reflink=auto
+
+# Copyright (C) 2009 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 <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+  set -x
+  cp --version
+fi
+
+. $srcdir/test-lib.sh
+
+cleanup_() { rm -rf "$other_partition_tmpdir"; }
+. "$abs_srcdir/other-fs-tmpdir"
+a_other="$other_partition_tmpdir/a"
+rm -f $a_other || framework_failure
+
+echo non_zero_size > "$a_other"
+
+# we shouldn't be able to reflink() files on separate partitions
+cp --reflink      "$a_other" b && fail=1
+
+# --reflink=auto should fall back to a normal copy
+cp --reflink=auto "$a_other" b || fail=1
+test -s b || fail=1
+
+# --reflink=auto should allow --sparse for fallback copies.
+# This command can be used to create minimal sized copies.
+cp --reflink=auto --sparse=always "$a_other" b || fail=1
+test -s b || fail=1
+
+Exit $fail