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

Paul Eggert <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Organization UCLA Computer Science Department
Message-ID <[email protected]>
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
   ...
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.