Files
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

141 lines
3.2 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
//go:build linux
package nfsfs
import (
"os"
"strings"
"syscall"
)
// xattrNamespace is the only namespace this backend serves: RFC 8276
// section 3.3 names the attributes with their namespace, and the local
// mapping of the Linux server is the user namespace.
const xattrNamespace = "user."
// Flags of the xattr system calls.
const (
xattrCreateFlag = 0x1
xattrReplaceFlag = 0x2
)
// xattrPath resolves a handle for the xattr family: the registered path
// must still name the handle's device, inode and kind. A symlink is
// refused the way the kernel refuses the user namespace on one, so the
// path calls never follow a link out of the export.
func (l *Local) xattrPath(h Handle) (string, error) {
_, path, fi, err := l.revalidate(h)
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink != 0 {
return "", syscall.EPERM
}
return path, nil
}
// GetXattr reads one named attribute of the object.
func (l *Local) GetXattr(h Handle, name string, max int) ([]byte, error) {
path, err := l.xattrPath(h)
if err != nil {
return nil, err
}
if !strings.HasPrefix(name, xattrNamespace) {
return nil, ErrNoXattr
}
buf := make([]byte, maxOr(max, 256))
for {
n, err := syscall.Getxattr(path, name, buf)
if err == syscall.ERANGE {
if len(buf) > 1<<20 {
return nil, syscall.ERANGE
}
buf = make([]byte, len(buf)*2)
continue
}
if err == syscall.ENODATA {
return nil, ErrNoXattr
}
if err != nil {
return nil, err
}
return buf[:n], nil
}
}
// SetXattr writes one named attribute under the RFC 8276 mode.
func (l *Local) SetXattr(h Handle, name string, value []byte, mode uint32) error {
path, err := l.xattrPath(h)
if err != nil {
return err
}
if !strings.HasPrefix(name, xattrNamespace) {
return syscall.EOPNOTSUPP
}
flags := 0
switch mode {
case XattrModeCreate:
flags = xattrCreateFlag
case XattrModeReplace:
flags = xattrReplaceFlag
}
return syscall.Setxattr(path, name, value, flags)
}
// ListXattr names the user namespace attributes of the object.
func (l *Local) ListXattr(h Handle, max int) ([]string, error) {
path, err := l.xattrPath(h)
if err != nil {
return nil, err
}
buf := make([]byte, maxOr(max, 1024))
for {
n, err := syscall.Listxattr(path, buf)
if err == syscall.ERANGE {
if len(buf) > 1<<20 {
return nil, syscall.ERANGE
}
buf = make([]byte, len(buf)*2)
continue
}
if err != nil {
return nil, err
}
var names []string
for entry := range strings.SplitSeq(string(buf[:n]), "\x00") {
if strings.HasPrefix(entry, xattrNamespace) {
names = append(names, entry)
}
}
return names, nil
}
}
// RemoveXattr deletes one named attribute.
func (l *Local) RemoveXattr(h Handle, name string) error {
path, err := l.xattrPath(h)
if err != nil {
return err
}
if !strings.HasPrefix(name, xattrNamespace) {
return ErrNoXattr
}
if err := syscall.Removexattr(path, name); err == syscall.ENODATA {
return ErrNoXattr
} else if err != nil {
return err
}
return nil
}
// maxOr replaces a zero budget with the given default.
func maxOr(max, def int) int {
if max == 0 || max > 1<<20 {
return def
}
return max
}