// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT //go:build linux package nfsfs import ( "runtime" "syscall" "unsafe" ) // The reflink ioctl of linux/fs.h, FICLONERANGE, and the syscall number // of copy_file_range(2), which the standard library does not export on // linux. FICLONERANGE is _IOW(0x94, 13, struct file_clone_range) with a // 32 byte struct: (1<<30)|(32<<16)|(0x94<<8)|13. The copy_file_range // numbers follow the kernel's syscall tables: 319 on amd64 and 286 on // the asm generic table of arm64, riscv64 and loong64. An architecture // outside the table answers unshareable, and the caller keeps its // userspace path. const ioctlFICLONERANGE = 0x4020940D // fileCloneRange mirrors struct file_clone_range of linux/fs.h, the // argument of FICLONERANGE. type fileCloneRange struct { srcFD int64 srcOffset uint64 srcLength uint64 destOffset uint64 } // CloneRange makes the destination carry the source's bytes through the // reflink of the filesystem, XFS, btrfs and ZFS among them. The // descriptor cache hands out both ends, so a range cloned through cached // descriptors stays as verified as any other read or write. func (l *Local) CloneRange(src Handle, srcOff int64, dst Handle, dstOff int64, length int64) error { sf, releaseSrc, err := l.dataFD(src, false) if err != nil { return err } defer releaseSrc() df, releaseDst, err := l.dataFD(dst, true) if err != nil { return err } defer releaseDst() cr := fileCloneRange{ srcFD: int64(sf.Fd()), srcOffset: uint64(srcOff), srcLength: uint64(length), destOffset: uint64(dstOff), } // SAFETY: ioctl takes the address of exactly the 32 byte struct the // FICLONERANGE command name carries; the kernel reads it and writes // nothing through it. if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, df.Fd(), ioctlFICLONERANGE, uintptr(unsafe.Pointer(&cr))); errno != 0 { return errno } return nil } // CopyRange copies the bytes through copy_file_range(2), in chunks until // the length is served. The syscall answers how much moved; a short move // on the first call means the kernel refused for these files and the // error travels to the caller's fallback. func (l *Local) CopyRange(src Handle, srcOff int64, dst Handle, dstOff int64, length int64) error { sf, releaseSrc, err := l.dataFD(src, false) if err != nil { return err } defer releaseSrc() df, releaseDst, err := l.dataFD(dst, true) if err != nil { return err } defer releaseDst() number := copyFileRangeSyscall() if number == 0 { return syscall.ENOSYS } var inOff, outOff int64 = srcOff, dstOff for length > 0 { chunk := min(length, 8<<20) // SAFETY: the syscall copies from and to the file offsets behind // the two pointers and updates them; both live across the call. n, _, errno := syscall.Syscall6(number, sf.Fd(), uintptr(unsafe.Pointer(&inOff)), df.Fd(), uintptr(unsafe.Pointer(&outOff)), uintptr(chunk), 0) if errno != 0 { return errno } if n == 0 { return syscall.EINVAL } length -= int64(n) } return nil } // copyFileRangeSyscall answers the syscall number of copy_file_range on // the architectures this project builds for linux, zero elsewhere. func copyFileRangeSyscall() uintptr { switch runtime.GOARCH { case "amd64": return 319 case "arm64", "riscv64", "loong64": return 286 default: return 0 } }