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

Pádraig Brady <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <[email protected]>
On 01/08/2025 16:00, Leah Neukirchen wrote:
> 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.

I haven't tried to repro yet.
The syscalls look OK, so the only thing I can think of
is that last large copy_file_range() syscall didn't propagate appropriately back up to cp,
as there is a fallback read() which should not have occurred.

What is the destination file system type?

It would give us a bit more info if you gave the output from cp
when run with the --debug option.

thank you,
Padraig
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.