Paul, Your understanding is correct. The problem in cp is that it uses acl_get_fd or acl_get_file which will always return an ACL on EXT4, therefore contriving an ACL to set on the target when it doesn't exist on the source. This causes an extraneous fsetxattr, and hits several arguably unnecessary functions from libattr and libacl (since if there are no xattrs, it doesn't make sense to check for ACLs when they're built on top of xattrs)
Since ACLs cannot exist without xattrs on EXT4, I propose we simply check for the existence of xattrs early on and bailout early if they're not found. A simple patch that attempts to do this, and seems to work for the cp -a case is below. It basically checks for the size of the list of xattrs, and if it's > 0 or ERANGE is returned (signifying that the buffer isn't large enough to hold the entire list), then we know that we have xattrs, otherwise bail out. --- src/copy.c 2020-04-09 09:30:08.279516111 -0400 +++ src/copy.c 2020-04-09 15:21:47.939897542 -0400 @@ -16,11 +16,13 @@ /* Extracted from cp.c and librarified by Jim Meyering. */ + #include <config.h> #include <stdio.h> #include <assert.h> #include <sys/ioctl.h> #include <sys/types.h> +#include <attr/xattr.h> #include <selinux/selinux.h> #if HAVE_HURD_H @@ -536,6 +538,9 @@ bool all_errors = (!x->data_copy_required || x->require_preserve_xattr); bool some_errors = (!all_errors && !x->reduce_diagnostics); bool selinux_done = (x->preserve_security_context || x->set_security_context); + ssize_t xattr_size; + size_t size = 0; + char *list; struct error_context ctx = { .error = all_errors ? copy_attr_allerror : copy_attr_error, @@ -543,13 +548,28 @@ .quote_free = copy_attr_free }; if (0 <= src_fd && 0 <= dst_fd) - ret = attr_copy_fd (src_path, src_fd, dst_path, dst_fd, - selinux_done ? check_selinux_attr : NULL, - (all_errors || some_errors ? &ctx : NULL)); - else - ret = attr_copy_file (src_path, dst_path, + { + + xattr_size = flistxattr(src_fd, list, size); + if ( xattr_size || errno == ERANGE ) + { + ret = attr_copy_fd (src_path, src_fd, dst_path, dst_fd, selinux_done ? check_selinux_attr : NULL, (all_errors || some_errors ? &ctx : NULL)); + } + else + ret = (int)xattr_size; + } + else + { + xattr_size = listxattr(src_path, list, size); + if ( xattr_size || errno == ERANGE ) + ret = attr_copy_file (src_path, dst_path, + selinux_done ? check_selinux_attr : NULL, + (all_errors || some_errors ? &ctx : NULL)); + else + ret = (int)xattr_size; + } return ret == 0; } If you agree with this direction, I can continue, addressing other affected code paths (i.e --preserve=mode). Either way, I am interested to hear your thoughts. It also might make sense to wrap this in a file system check and only do this for EXT4, since I don't know if xattrs and acls are mutually inclusive on all supported file systems. *EXAMPLE of behavior difference between rsync -A and cp -a* # Create non-empty source files $ echo source file | tee cp_src rsync_src # cp attempts to setxattr even though no extended attributes exist $ strace -f cp -a cp_src cp_dest |& egrep -i 'acl|attr' open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3 open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3 flistxattr(3, NULL, 0) = 0 flistxattr(3, 0x7fffee2db2c0, 0) = 0 fgetxattr(3, "system.posix_acl_access", 0x7fffee2db1c0, 132) = -1 ENODATA (No data available) fsetxattr(4, "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\4\0\6\0\377\377\377\377 \0\4\0\377\377\377\377", 28, 0) = 0 # Rsync doesn't attempt an extraneous fsetxattr $ strace -f rsync -A rsync_src rsync_dest |& egrep -i 'acl|attr' open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3 open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3 getxattr("rsync_src", "system.posix_acl_access", 0x7ffe9e150290, 132) = -1 ENODATA (No data available) getxattr(".rsync_dest.HgYAGV", "system.posix_acl_access", 0x7ffe9e14f100, 132) = -1 ENODATA (No data available) # Add ACLS (and by extension, xattrs) to the source files $ setfacl -m user:gleventhal:rwx cp_src $ setfacl -m user:gleventhal:rwx rsync_src # cp behaves the same $ strace -f cp -a cp_src cp_dest2 |& egrep -i 'acl|attr' open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3 open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3 flistxattr(3, NULL, 0) = 24 flistxattr(3, "system.posix_acl_access\0", 24) = 24 open("/etc/xattr.conf", O_RDONLY) = -1 ENOENT (No such file or directory) fgetxattr(3, "system.posix_acl_access", NULL, 0) = 44 fgetxattr(3, "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\2\0\7\0\3300\0\0\4\0\6\0\377\377\377\377\20\0\7\0\377\377\377\377 \0\4\0\377\377\377\377", 44) = 44 fsetxattr(4, "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\2\0\7\0\3300\0\0\4\0\6\0\377\377\377\377\20\0\7\0\377\377\377\377 \0\4\0\377\377\377\377", 44, 0) = 0 fgetxattr(3, "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\2\0\7\0\3300\0\0\4\0\6\0\377\377\377\377\20\0\7\0\377\377\377\377 \0\4\0\377\377\377\377", 132) = 44 fsetxattr(4, "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\2\0\7\0\3300\0\0\4\0\6\0\377\377\377\377\20\0\7\0\377\377\377\377 \0\4\0\377\377\377\377", 44, 0) = 0 # rsync now does the set, because xattrs actually exist $ strace -f rsync -A rsync_src rsync_dest2 |& egrep -i 'acl|attr' open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3 open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3 getxattr("rsync_src", "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\2\0\7\0\3300\0\0\4\0\6\0\377\377\377\377\20\0\7\0\377\377\377\377 \0\4\0\377\377\377\377", 132) = 44 getxattr(".rsync_dest2.DjNiRs", "system.posix_acl_access", 0x7ffeac4780d0, 132) = -1 ENODATA (No data available) setxattr(".rsync_dest2.DjNiRs", "system.posix_acl_access", "\2\0\0\0\1\0\6\0\377\377\377\377\2\0\7\0\3300\0\0\4\0\6\0\377\377\377\377\20\0\7\0\377\377\377\377 \0\4\0\377\377\377\377", 44, 0) = 0 On Wed, Apr 8, 2020 at 1:32 PM Paul Eggert <egg...@cs.ucla.edu> wrote: > On 4/8/20 7:15 AM, Gregg Leventhal wrote: > > > rsync doesn't make set/get xattr calls and purports to preserve ACLs with > > -A. > > I'm not quite following your bug report, but it appears that you're saying > that > cp could somehow discover that it needn't use fgetxattr and fsetxattr on > files > that lack extended attributes, and for those files cp could stick with > ordinary > POSIX syscalls (e.g., umask, chmod) to give files proper permissions, and > in > that case 'cp' would presumably (a) operate more efficiently and (b) not > trigger > a bug in the EXT filesystem. > > This sounds like a worthy suggestion, though of course it would be better > to > have a concrete proposal in the form of a coreutils patch, along with a > few > performance measurements. For starters, how does rsync do it? > > Also, of course EXT should be fixed regardless of what coreutils does here. >