summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPádraig Brady <P@draigBrady.com>2009-09-21 08:43:03 +0100
committerPádraig Brady <P@draigBrady.com>2010-07-01 14:33:27 +0100
commit1af81dfb4d24d104777b8917544d81097db0deab (patch)
tree3030f158798f4246a3b7d38ba9262adb57705849
parent8aa15b2be2bd2ab4b76d197a279abf8b9091680a (diff)
downloadcoreutils-1af81dfb4d24d104777b8917544d81097db0deab.tar.xz
cp: add an option to only copy the file attributes
* 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
-rw-r--r--NEWS3
-rw-r--r--doc/coreutils.texi6
-rw-r--r--src/copy.c84
-rw-r--r--src/copy.h4
-rw-r--r--src/cp.c10
-rw-r--r--src/install.c1
-rw-r--r--src/mv.c1
-rwxr-xr-xtests/cp/acl22
-rwxr-xr-xtests/cp/reflink-perm4
9 files changed, 78 insertions, 57 deletions
diff --git a/NEWS b/NEWS
index 2bacb7f0e..3a249254f 100644
--- 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.
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 5c2bd1a84..21cf36d1d 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -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
diff --git a/src/copy.c b/src/copy.c
index 171499c47..6d11ed868 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -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;
diff --git a/src/copy.h b/src/copy.h
index 59e29f53b..7c7e3f312 100644
--- a/src/copy.h
+++ b/src/copy.h
@@ -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
diff --git a/src/cp.c b/src/cp.c
index 035526922..5b14f3a7b 100644
--- 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;
diff --git a/src/install.c b/src/install.c
index 038e86976..97029140c 100644
--- a/src/install.c
+++ b/src/install.c
@@ -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;
diff --git a/src/mv.c b/src/mv.c
index 8a41d16f9..91aadfa0e 100644
--- 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;
diff --git a/tests/cp/acl b/tests/cp/acl
index 010348a9d..c19eb4e93 100755
--- a/tests/cp/acl
+++ b/tests/cp/acl
@@ -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
diff --git a/tests/cp/reflink-perm b/tests/cp/reflink-perm
index 92cb7ae8f..77f119f4a 100755
--- a/tests/cp/reflink-perm
+++ b/tests/cp/reflink-perm
@@ -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