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
254 lines
6.7 KiB
Go
254 lines
6.7 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build freebsd
|
|
|
|
package nfsfs
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// 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 FreeBSD server is the extattr user namespace.
|
|
const xattrNamespace = "user."
|
|
|
|
// extattrNamespaceUser is EXTATTR_NAMESPACE_USER of sys/sys/extattr.h,
|
|
// the numeric namespace the extattr family of system calls takes. The
|
|
// syscall package of FreeBSD exports the traps of the family but not
|
|
// this constant.
|
|
const extattrNamespaceUser = 0x1
|
|
|
|
// xattrPath resolves a handle for the extattr 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
|
|
}
|
|
attr, ok := strings.CutPrefix(name, xattrNamespace)
|
|
if !ok {
|
|
return nil, ErrNoXattr
|
|
}
|
|
buf := make([]byte, maxOr(max, 256))
|
|
for {
|
|
n, err := extattrGetFile(path, attr, buf)
|
|
if err == syscall.ERANGE {
|
|
if len(buf) > 1<<20 {
|
|
return nil, syscall.ERANGE
|
|
}
|
|
buf = make([]byte, len(buf)*2)
|
|
continue
|
|
}
|
|
if err == syscall.ENOATTR {
|
|
return nil, ErrNoXattr
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf[:n], nil
|
|
}
|
|
}
|
|
|
|
// SetXattr writes one named attribute under the RFC 8276 mode. The
|
|
// extattr interface of FreeBSD carries no create and replace flags, so
|
|
// the two strict modes ask for the attribute first and write it after:
|
|
// a create of an existing name answers EEXIST and a replace of a
|
|
// missing one ENOATTR, the same answers the flags of Linux produce.
|
|
func (l *Local) SetXattr(h Handle, name string, value []byte, mode uint32) error {
|
|
path, err := l.xattrPath(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
attr, ok := strings.CutPrefix(name, xattrNamespace)
|
|
if !ok {
|
|
return syscall.EOPNOTSUPP
|
|
}
|
|
switch mode {
|
|
case XattrModeCreate, XattrModeReplace:
|
|
_, err := extattrGetFile(path, attr, nil)
|
|
switch {
|
|
case err == nil && mode == XattrModeCreate:
|
|
return syscall.EEXIST
|
|
case err == syscall.ENOATTR && mode == XattrModeReplace:
|
|
return syscall.ENOATTR
|
|
case err != nil && err != syscall.ENOATTR:
|
|
return err
|
|
}
|
|
}
|
|
return extattrSetFile(path, attr, value)
|
|
}
|
|
|
|
// ListXattr names the user namespace attributes of the object. The list
|
|
// of extattr_list_file is a sequence of one length byte and name pairs
|
|
// with no separators, extattr(2), and its names carry no namespace, so
|
|
// each name is dressed with the namespace prefix the protocol speaks.
|
|
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 := extattrListFile(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
|
|
}
|
|
buf = buf[:n]
|
|
break
|
|
}
|
|
var names []string
|
|
for len(buf) > 0 {
|
|
size := int(buf[0])
|
|
if size+1 > len(buf) {
|
|
break
|
|
}
|
|
names = append(names, xattrNamespace+string(buf[1:1+size]))
|
|
buf = buf[1+size:]
|
|
}
|
|
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
|
|
}
|
|
attr, ok := strings.CutPrefix(name, xattrNamespace)
|
|
if !ok {
|
|
return ErrNoXattr
|
|
}
|
|
if err := extattrDeleteFile(path, attr); err == syscall.ENOATTR {
|
|
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
|
|
}
|
|
|
|
// extattrGetFile reads the attribute attr of path into buf, or names its
|
|
// size when buf is nil, extattr(2).
|
|
func extattrGetFile(path, attr string, buf []byte) (int, error) {
|
|
name, err := syscall.ByteSliceFromString(attr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// SAFETY: the kernel reads the buffer for the length of the call
|
|
// only, and data stays alive through the unsafe pointer until the
|
|
// system call returns.
|
|
var data unsafe.Pointer
|
|
if len(buf) > 0 {
|
|
data = unsafe.Pointer(&buf[0])
|
|
}
|
|
n, _, errno := syscall.Syscall6(syscall.SYS_EXTATTR_GET_FILE,
|
|
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
|
extattrNamespaceUser,
|
|
uintptr(unsafe.Pointer(&name[0])),
|
|
uintptr(data),
|
|
uintptr(len(buf)),
|
|
0)
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
return int(n), nil
|
|
}
|
|
|
|
// extattrSetFile writes value into the attribute attr of path,
|
|
// extattr(2).
|
|
func extattrSetFile(path, attr string, value []byte) error {
|
|
name, err := syscall.ByteSliceFromString(attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// SAFETY: the kernel reads the buffer for the length of the call
|
|
// only, and value stays alive through the unsafe pointer until the
|
|
// system call returns.
|
|
var data unsafe.Pointer
|
|
if len(value) > 0 {
|
|
data = unsafe.Pointer(&value[0])
|
|
}
|
|
_, _, errno := syscall.Syscall6(syscall.SYS_EXTATTR_SET_FILE,
|
|
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
|
extattrNamespaceUser,
|
|
uintptr(unsafe.Pointer(&name[0])),
|
|
uintptr(data),
|
|
uintptr(len(value)),
|
|
0)
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// extattrListFile names the user namespace attributes of path into buf,
|
|
// or names the size of the list when buf is nil, extattr(2).
|
|
func extattrListFile(path string, buf []byte) (int, error) {
|
|
// SAFETY: the kernel writes the buffer for the length of the call
|
|
// only, and buf stays alive through the unsafe pointer until the
|
|
// system call returns.
|
|
var data unsafe.Pointer
|
|
if len(buf) > 0 {
|
|
data = unsafe.Pointer(&buf[0])
|
|
}
|
|
n, _, errno := syscall.Syscall6(syscall.SYS_EXTATTR_LIST_FILE,
|
|
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
|
extattrNamespaceUser,
|
|
uintptr(data),
|
|
uintptr(len(buf)),
|
|
0, 0)
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
return int(n), nil
|
|
}
|
|
|
|
// extattrDeleteFile takes the attribute attr off path, extattr(2).
|
|
func extattrDeleteFile(path, attr string) error {
|
|
name, err := syscall.ByteSliceFromString(attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, _, errno := syscall.Syscall(syscall.SYS_EXTATTR_DELETE_FILE,
|
|
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
|
extattrNamespaceUser,
|
|
uintptr(unsafe.Pointer(&name[0])))
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return nil
|
|
}
|