bug#79139: cp --reflink truncates sparse files on ZFS

Leah Neukirchen <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <[email protected]>
Hello,

I found the following issue with coreutils 9.7, Linux 6.12.40-1-lts,
zfs 2.3.3 on Arch x86_64, glibc 2.42:

Copying a file with sparse holes using "cp --reflink=auto" truncates
the file before the final segment.  The relevant strace is:

openat(AT_FDCWD, "celestis.img", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/.zfs/snapshot/pre-fixup/var/lib/libvirt/images/celestis.img", {st_mode=S_IFREG|0644,>
openat(AT_FDCWD, "/.zfs/snapshot/pre-fixup/var/lib/libvirt/images/celestis.img", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=137438953472, ...}) = 0
openat(AT_FDCWD, "celestis.img", O_WRONLY|O_CREAT|O_EXCL, 0644) = 4
ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EXDEV (Invalid cross-device link)
fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_DATA)                  = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
lseek(3, 0, SEEK_HOLE)                  = 131072
lseek(3, 0, SEEK_SET)                   = 0
copy_file_range(3, NULL, 4, NULL, 131072, 0) = 131072
lseek(3, 131072, SEEK_DATA)             = 1048576
lseek(3, 1048576, SEEK_HOLE)            = 1179648
lseek(3, 1048576, SEEK_SET)             = 1048576
lseek(4, 917504, SEEK_CUR)              = 1048576
copy_file_range(3, NULL, 4, NULL, 131072, 0) = 131072
lseek(3, 1179648, SEEK_DATA)            = 4194304
lseek(3, 4194304, SEEK_HOLE)            = 16646144
lseek(3, 4194304, SEEK_SET)             = 4194304
lseek(4, 3014656, SEEK_CUR)             = 4194304
copy_file_range(3, NULL, 4, NULL, 12451840, 0) = 12451840
lseek(3, 16646144, SEEK_DATA)           = 134217728
lseek(3, 134217728, SEEK_HOLE)          = 137438953472
lseek(3, 134217728, SEEK_SET)           = 134217728
lseek(4, 117571584, SEEK_CUR)           = 134217728
copy_file_range(3, NULL, 4, NULL, 137304735744, 0) = 137304735744
mmap(NULL, 270336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76c5df0ce000
read(3, "", 262144)                     = 0
ftruncate(4, 134217728)                 = 0
close(4)                                = 0
close(3)                                = 0

As we can see, there's a hole from 16646144 to 134217728, then data up
to the end at 137438953472 (= the total file size).  Both fd are thus
moved to 134217728, and a copy_file_range for the rest of the file is
issued and successful.

However, in the end the file is truncated to the first 128MB... why?

For comparison, a plain cat simply does this:

fstat(1, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
openat(AT_FDCWD, "/.zfs/snapshot/pre-fixup/var/lib/libvirt/images/celestis.img", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=137438953472, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
copy_file_range(3, NULL, 1, NULL, 9223372035781033984, 0) = 137438953472
mmap(NULL, 270336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7e7af83a9000
read(3, "", 262144)                     = 0
munmap(0x7e7af83a9000, 270336)          = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0

This works correctly and the source and destination agree in the end.
Likewise for xcp(1), which uses copy_file_range in 1MB blocks by
default and does not care for holes.

Thus I think this is a logic bug in cp and not a ZFS issue.

Do not hesitate to contact me if you inquire further details.

Thanks,
-- 
Leah Neukirchen  <[email protected]>  https://leahneukirchen.org/
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.