Files
nfs/internal/nfsfs/clone_linux.go
T
petrbalvin a9b8039ef7
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
feat: full NFSv4.2 server and client in pure Go
Assisted-by: GLM 5.3 Flash
2026-09-21 18:51:17 +02:00

113 lines
3.4 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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
}
}