cp: add an option to only copy the file attributes
authorPádraig Brady <P@draigBrady.com>
Mon, 21 Sep 2009 07:43:03 +0000 (08:43 +0100)
committerPádraig Brady <P@draigBrady.com>
Thu, 1 Jul 2010 13:33:27 +0000 (14:33 +0100)
* src/copy.c (copy_attr): A new function which merges copy_attr_by_fd
and copy_attr_by_name.  Also display all errors when --attributes-only
* src/copy.c (copy_reg): Skip copying the file contents if specified.
Refactor the SELinux error handling code a little and display all
SELinux errors when only copying attributes.
* src/copy.h (struct cp_options): Add a data_copy_required boolean
* src/cp.c (main): Default to copying data but don't if specified
* src/install.c: Default to copying data
* src/mv.c: Likewise
tests/cp/reflink-perm: Add a test to check that --attributes-only
does not copy data
* tests/cp/acl: Likewise. Also refactor to remove redundant
acl manipulation
* doc/coreutils.texi (cp invocation): Describe the new option
* 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/cp/acl
tests/cp/reflink-perm

diff --git a/NEWS b/NEWS
index 2bacb7f..3a24925 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,9 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** New features
 
+  cp now accepts the --attributes-only option to not copy file data,
+  which is useful for efficiently modifying files.
+
   du recognizes -d N as equivalent to --max-depth=N, for compatibility
   with FreeBSD.
 
index 5c2bd1a..21cf36d 100644 (file)
@@ -7357,6 +7357,12 @@ Try to preserve SELinux security context and extended attributes (xattr),
 but ignore any failure to do that and print no corresponding diagnostic.
 Equivalent to @option{-dR --preserve=all} with the reduced diagnostics.
 
+@itemx --attributes-only
+@opindex --attributes-only
+Preserve the specified attributes of the original files in the copy,
+but do not copy any data.  See the @option{--preserve} option for
+controlling which attributes to copy.
+
 @item -b
 @itemx @w{@kbd{--backup}[=@var{method}]}
 @opindex -b
index 171499c..6d11ed8 100644 (file)
@@ -177,11 +177,11 @@ static void
 copy_attr_error (struct error_context *ctx ATTRIBUTE_UNUSED,
                  char const *fmt, ...)
 {
-  int err = errno;
-  va_list ap;
-
   if (!errno_unsupported (errno))
     {
+      int err = errno;
+      va_list ap;
+
       /* use verror module to print error message */
       va_start (ap, fmt);
       verror (0, err, fmt, ap);
@@ -214,51 +214,39 @@ copy_attr_free (struct error_context *ctx ATTRIBUTE_UNUSED,
 {
 }
 
-static bool
-copy_attr_by_fd (char const *src_path, int src_fd,
-                 char const *dst_path, int dst_fd, const struct cp_options *x)
-{
-  struct error_context ctx =
-  {
-    .error = x->require_preserve_xattr ? copy_attr_allerror : copy_attr_error,
-    .quote = copy_attr_quote,
-    .quote_free = copy_attr_free
-  };
-  return 0 == attr_copy_fd (src_path, src_fd, dst_path, dst_fd, 0,
-                            (x->reduce_diagnostics
-                             && !x->require_preserve_xattr)? NULL : &ctx);
-}
+/* If positive SRC_FD and DST_FD descriptors are passed,
+   then copy by fd, otherwise copy by name.  */
 
 static bool
-copy_attr_by_name (char const *src_path, char const *dst_path,
-                   const struct cp_options *x)
+copy_attr (char const *src_path, int src_fd,
+           char const *dst_path, int dst_fd, struct cp_options const *x)
 {
+  int ret;
+  bool all_errors = (!x->data_copy_required || x->require_preserve_xattr);
+  bool some_errors = (!all_errors && !x->reduce_diagnostics);
   struct error_context ctx =
   {
-    .error = x->require_preserve_xattr ? copy_attr_allerror : copy_attr_error,
+    .error = all_errors ? copy_attr_allerror : copy_attr_error,
     .quote = copy_attr_quote,
     .quote_free = copy_attr_free
   };
-  return 0 == attr_copy_file (src_path, dst_path, 0,
-                              (x-> reduce_diagnostics
-                               && !x->require_preserve_xattr) ? NULL : &ctx);
-}
-#else /* USE_XATTR */
+  if (0 <= src_fd && 0 <= dst_fd)
+    ret = attr_copy_fd (src_path, src_fd, dst_path, dst_fd, 0,
+                        (all_errors || some_errors ? &ctx : NULL));
+  else
+    ret = attr_copy_file (src_path, dst_path, 0,
+                          (all_errors || some_errors ? &ctx : NULL));
 
-static bool
-copy_attr_by_fd (char const *src_path ATTRIBUTE_UNUSED,
-                 int src_fd ATTRIBUTE_UNUSED,
-                 char const *dst_path ATTRIBUTE_UNUSED,
-                 int dst_fd ATTRIBUTE_UNUSED,
-                 const struct cp_options *x ATTRIBUTE_UNUSED)
-{
-  return true;
+  return ret == 0;
 }
+#else /* USE_XATTR */
 
 static bool
-copy_attr_by_name (char const *src_path ATTRIBUTE_UNUSED,
-                   char const *dst_path ATTRIBUTE_UNUSED,
-                   const struct cp_options *x ATTRIBUTE_UNUSED)
+copy_attr (char const *src_path ATTRIBUTE_UNUSED,
+           int src_fd ATTRIBUTE_UNUSED,
+           char const *dst_path ATTRIBUTE_UNUSED,
+           int dst_fd ATTRIBUTE_UNUSED,
+           struct cp_options const *x ATTRIBUTE_UNUSED)
 {
   return true;
 }
@@ -483,7 +471,7 @@ copy_reg (char const *src_name, char const *dst_name,
   struct stat sb;
   struct stat src_open_sb;
   bool return_val = true;
-  bool data_copy_required = true;
+  bool data_copy_required = x->data_copy_required;
 
   source_desc = open (src_name,
                       (O_RDONLY | O_BINARY
@@ -526,11 +514,14 @@ copy_reg (char const *src_name, char const *dst_name,
          that is used when the destination file doesn't already exist.  */
       if (x->preserve_security_context && 0 <= dest_desc)
         {
+          bool all_errors = (!x->data_copy_required
+                             || x->require_preserve_context);
+          bool some_errors = !all_errors && !x->reduce_diagnostics;
           security_context_t con = NULL;
+
           if (getfscreatecon (&con) < 0)
             {
-              if (x->require_preserve_context ||
-                  (!x->reduce_diagnostics && !errno_unsupported (errno)))
+              if (all_errors || (some_errors && !errno_unsupported (errno)))
                 error (0, errno, _("failed to get file system create context"));
               if (x->require_preserve_context)
                 {
@@ -543,8 +534,7 @@ copy_reg (char const *src_name, char const *dst_name,
             {
               if (fsetfilecon (dest_desc, con) < 0)
                 {
-                  if (x->require_preserve_context ||
-                      (!x->reduce_diagnostics && !errno_unsupported (errno)))
+                  if (all_errors || (some_errors && !errno_unsupported (errno)))
                     error (0, errno,
                            _("failed to set the security context of %s to %s"),
                            quote_n (0, dst_name), quote_n (1, con));
@@ -850,7 +840,7 @@ copy_reg (char const *src_name, char const *dst_name,
       if (!(sb.st_mode & S_IWUSR) && geteuid () != 0)
         access_changed = fchmod_or_lchmod (dest_desc, dst_name, 0600) == 0;
 
-      if (!copy_attr_by_fd (src_name, source_desc, dst_name, dest_desc, x)
+      if (!copy_attr (src_name, source_desc, dst_name, dest_desc, x)
           && x->require_preserve_xattr)
         return_val = false;
 
@@ -1821,14 +1811,15 @@ copy_internal (char const *src_name, char const *dst_name,
 
   if (x->preserve_security_context)
     {
+      bool all_errors = !x->data_copy_required || x->require_preserve_context;
+      bool some_errors = !all_errors && !x->reduce_diagnostics;
       security_context_t con;
 
       if (0 <= lgetfilecon (src_name, &con))
         {
           if (setfscreatecon (con) < 0)
             {
-              if (x->require_preserve_context ||
-                  (!x->reduce_diagnostics && !errno_unsupported (errno)))
+              if (all_errors || (some_errors && !errno_unsupported (errno)))
                 error (0, errno,
                        _("failed to set default file creation context to %s"),
                        quote (con));
@@ -1842,8 +1833,7 @@ copy_internal (char const *src_name, char const *dst_name,
         }
       else
         {
-          if (x->require_preserve_context ||
-              (!x->reduce_diagnostics && !errno_unsupported (errno)))
+          if (all_errors || (some_errors && !errno_unsupported (errno)))
             {
               error (0, errno,
                      _("failed to get security context of %s"),
@@ -2168,7 +2158,7 @@ copy_internal (char const *src_name, char const *dst_name,
 
   set_author (dst_name, -1, &src_sb);
 
-  if (x->preserve_xattr && ! copy_attr_by_name (src_name, dst_name, x)
+  if (x->preserve_xattr && ! copy_attr (src_name, -1, dst_name, -1, x)
       && x->require_preserve_xattr)
     return false;
 
index 59e29f5..7c7e3f3 100644 (file)
@@ -170,6 +170,10 @@ struct cp_options
      will be hard links to the same file (a copy of F).  */
   bool preserve_links;
 
+  /* Optionally don't copy the data, either with CoW reflink files or
+     explicitly with the --attributes-only option.  */
+  bool data_copy_required;
+
   /* If true and any of the above (for preserve) file attributes cannot
      be applied to a destination file, treat it as a failure and return
      nonzero immediately.  E.g. for cp -p this must be true, for mv it
index 0355269..5b14f3a 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
@@ -72,7 +72,8 @@ struct dir_attr
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  COPY_CONTENTS_OPTION = CHAR_MAX + 1,
+  ATTRIBUTES_ONLY_OPTION = CHAR_MAX + 1,
+  COPY_CONTENTS_OPTION,
   NO_PRESERVE_ATTRIBUTES_OPTION,
   PARENTS_OPTION,
   PRESERVE_ATTRIBUTES_OPTION,
@@ -115,6 +116,7 @@ ARGMATCH_VERIFY (reflink_type_string, reflink_type);
 static struct option const long_opts[] =
 {
   {"archive", no_argument, NULL, 'a'},
+  {"attributes-only", no_argument, NULL, ATTRIBUTES_ONLY_OPTION},
   {"backup", optional_argument, NULL, 'b'},
   {"copy-contents", no_argument, NULL, COPY_CONTENTS_OPTION},
   {"dereference", no_argument, NULL, 'L'},
@@ -167,6 +169,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\
 "), stdout);
       fputs (_("\
   -a, --archive                same as -dR --preserve=all\n\
+      --attributes-only        don't copy the file data, just the attributes\n\
       --backup[=CONTROL]       make a backup of each existing destination file\n\
   -b                           like --backup but does not accept an argument\n\
       --copy-contents          copy contents of special files when recursive\n\
@@ -781,6 +784,7 @@ cp_option_init (struct cp_options *x)
   x->reduce_diagnostics = false;
   x->require_preserve_xattr = false;
 
+  x->data_copy_required = true;
   x->require_preserve = false;
   x->recursive = false;
   x->sparse_mode = SPARSE_AUTO;
@@ -962,6 +966,10 @@ main (int argc, char **argv)
             version_control_string = optarg;
           break;
 
+        case ATTRIBUTES_ONLY_OPTION:
+          x.data_copy_required = false;
+          break;
+
         case COPY_CONTENTS_OPTION:
           copy_contents = true;
           break;
index 038e869..9702914 100644 (file)
@@ -280,6 +280,7 @@ cp_option_init (struct cp_options *x)
   x->preserve_mode = false;
   x->preserve_timestamps = false;
   x->reduce_diagnostics=false;
+  x->data_copy_required = true;
   x->require_preserve = false;
   x->require_preserve_context = false;
   x->require_preserve_xattr = false;
index 8a41d16..91aadfa 100644 (file)
--- a/src/mv.c
+++ b/src/mv.c
@@ -119,6 +119,7 @@ cp_option_init (struct cp_options *x)
   x->preserve_timestamps = true;
   x->preserve_security_context = selinux_enabled;
   x->reduce_diagnostics = false;
+  x->data_copy_required = true;
   x->require_preserve = false;  /* FIXME: maybe make this an option */
   x->require_preserve_context = false;
   x->preserve_xattr = true;
index 010348a..c19eb4e 100755 (executable)
@@ -36,28 +36,32 @@ grep '^#define USE_ACL 1' $CONFIG_HEADER > /dev/null ||
 mkdir -p a b || framework_failure
 touch a/file || framework_failure
 
-skip=no
 # Ensure that setfacl and getfacl work on this file system.
+skip=no
+acl1=`cd a && getfacl file` || skip=yes
 setfacl -m user:bin:rw a/file 2> /dev/null || skip=yes
-acl1=`cd a && getfacl file | grep -v ':bin:' | grep -v 'mask::'` \
-  || skip=yes
-
 test $skip = yes &&
   skip_test_ "'.' is not on a suitable file system for this test"
 
 # copy a file without preserving permissions
 cp a/file b/ || fail=1
-
 acl2=`cd b && getfacl file` || framework_failure
 test "$acl1" = "$acl2" || fail=1
-rm a/file || framework_failure
 
-# copy a file, preserving permissions
-touch a/file || framework_failure
-setfacl -m user:bin:rw a/file || framework_failure
+# Update with acl set above
 acl1=`cd a && getfacl file` || framework_failure
+
+# copy a file, preserving permissions
 cp -p a/file b/ || fail=1
 acl2=`cd b && getfacl file` || framework_failure
 test "$acl1" = "$acl2" || fail=1
 
+# copy a file, preserving permissions, with --attributes-only
+echo > a/file || framework_failure # add some data
+test -s a/file || framework_failure
+cp -p --attributes-only a/file b/ || fail=1
+test -s b/file && fail=1
+acl2=`cd b && getfacl file` || framework_failure
+test "$acl1" = "$acl2" || fail=1
+
 Exit $fail
index 92cb7ae..77f119f 100755 (executable)
@@ -39,4 +39,8 @@ test "$mode" = "-rwxrwxrwx" || fail=1
 
 test copy -nt file && fail=1
 
+echo > file2 # file with data
+cp --reflink=auto --preserve --attributes-only file2 empty_copy || fail=1
+test -s empty_copy && fail=1
+
 Exit $fail