Files
nfs/internal/nfsfs/xattr_linux.go
T

141 lines
3.2 KiB
Go
Raw Normal View History

// 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
}