feat: full NFSv4.2 server and client in pure Go
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
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
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user