141 lines
3.2 KiB
Go
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
|
||
|
|
}
|