Subject: bug#79433: 9.7: cp(1): lchownat(2/3) fallback bogus?

From: Paul Eggert <[email-masked]>
Date: Fri, 12 Sep 2025 08:17:26 -0700

On 2025-09-11 04:49, Steffen Nurpmeso wrote:
>              if (lchownat (dst_dirfd, relname, p->st.st_uid, p->st.st_gid)
>                  != 0)
>    ...
>                      error (0, errno, _("failed to preserve ownership for %s"),
>                             quoteaf (dst_name));
> 
>    Here there is no lchownat(3/2),

No kernel or C library has lchownat; that's a convenience function 
defined in coreutils/lib/openat.h (taken from Gnulib), equivalent to 
fchownat (..., AT_SYMLINK_NOFOLLOW).

What does "strace cp -a xb xc" say? I see this:

   ...
   geteuid32()                             = 1000
   openat(AT_FDCWD, "xb", O_RDONLY|O_LARGEFILE|O_PATH|O_DIRECTORY) = -1 
ENOENT (No such file or directory)
   statx(AT_FDCWD, "xa", 
AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, 
STATX_BASIC_STATS, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, 
stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=4, ...}) = 0
   lgetxattr("xa", "security.selinux", 
"unconfined_u:object_r:user_tmp_t"..., 255) = 36
   futex(0xf7f7ccc4, FUTEX_WAKE_PRIVATE, 2147483647) = 0
   openat(AT_FDCWD, "/proc/thread-self/attr/fscreate", 
O_RDWR|O_LARGEFILE|O_CLOEXEC) = 3
   write(3, "unconfined_u:object_r:user_tmp_t"..., 36) = 36
   close(3)                                = 0
   openat(AT_FDCWD, "xa", O_RDONLY|O_LARGEFILE|O_NOFOLLOW) = 3
   statx(3, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, 
STATX_BASIC_STATS, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, 
stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=4, ...}) = 0
   openat(AT_FDCWD, "xb", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
   ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EOPNOTSUPP (Operation 
not supported)
   statx(4, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, 
STATX_BASIC_STATS, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, 
stx_attributes=0, stx_mode=S_IFREG|0600, stx_size=0, ...}) = 0
   fadvise64_64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
   uname({sysname="Linux", nodename="penguin.cs.ucla.edu", ...}) = 0
   copy_file_range(3, NULL, 4, NULL, 1073741824, 0) = 4
   copy_file_range(3, NULL, 4, NULL, 1073741824, 0) = 0
   utimensat(4, NULL, [{tv_sec=1757689789, tv_nsec=946283700} /* 
2025-09-12T08:09:49.946283700-0700 */, {tv_sec=1757689789, 
tv_nsec=946283700} /* 2025-09-12T08:09:49.946283700-0700 */], 0) = 0
   flistxattr(3, NULL, 0)                  = 17
   flistxattr(3, "security.selinux\0", 17) = 17
   fchmod(4, 0100664)                      = 0
   flistxattr(3, NULL, 0)                  = 17
   flistxattr(3, "security.selinux\0", 17) = 17
   openat(AT_FDCWD, "/etc/xattr.conf", O_RDONLY|O_LARGEFILE) = 5
   statx(5, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, 
STATX_BASIC_STATS, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, 
stx_attributes=0, stx_mode=S_IFREG|0644, stx_size=817, ...}) = 0
   read(5, "# /etc/xattr.conf\n#\n# Format:\n# "..., 4096) = 817
   read(5, "", 4096)                       = 0
   close(5)                                = 0
   openat(AT_FDCWD, "/usr/lib/gconv/gconv-modules.cache", 
O_RDONLY|O_CLOEXEC) = 5
   statx(5, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, 
STATX_BASIC_STATS, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, 
stx_attributes=0, stx_mode=S_IFREG|0644, stx_size=27010, ...}) = 0
   mmap2(NULL, 27010, PROT_READ, MAP_SHARED, 5, 0) = 0xf7f99000
   close(5)                                = 0
   futex(0xf7f2eee8, FUTEX_WAKE_PRIVATE, 2147483647) = 0
   close(4)                                = 0
   close(3)                                = 0
   ...

What happens if you use fchownat directly? Something like the following:

   #include <errno.h>
   #include <fcntl.h>
   #include <grp.h>
   #include <pwd.h>
   #include <stdio.h>
   #include <stdlib.h>
   #include <unistd.h>

   int
   main (void)
   {
     char const *user = getenv ("LOGNAME");
     struct passwd const *pwd = getpwnam (user);
     struct group const *grp = getgrnam (user);
     int x = fchownat (AT_FDCWD, "xb",
		      pwd->pw_uid, grp->gr_gid,
		      AT_SYMLINK_NOFOLLOW);
     if (x < 0)
       perror ("fchownat");
     int y = lchown ("xb", pwd->pw_uid, grp->gr_gid);
     if (y < 0)
       perror ("lchown");
   }

I see this:

   ...
   fchownat(AT_FDCWD, "xb", 1000, 1000, AT_SYMLINK_NOFOLLOW) = 0
   lchown("xb", 1000, 1000)                = 0
   ...