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
224 lines
6.0 KiB
Go
224 lines
6.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Named attributes of RFC 8881 section 5.3: the OPENATTR operation
|
|
// opens a synthetic directory of an object's extended attributes, and
|
|
// the file operations over the synthetic handles map to the backend's
|
|
// xattr half.
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
)
|
|
|
|
// The synthetic handle prefixes of the named attribute space.
|
|
const (
|
|
nattrDirPrefix = "NAD/" // the named attribute directory of an object
|
|
nattrFilePrefix = "NAF/" // one named attribute of an object
|
|
)
|
|
|
|
// nattrStore keeps the mapping between synthetic handles and the object
|
|
// they hang from, so a named attribute file remembers its parent.
|
|
type nattrStore struct {
|
|
mu sync.Mutex
|
|
dirs map[string]nfsfs.Handle // parent object -> dir handle
|
|
}
|
|
|
|
func newNattrStore() *nattrStore {
|
|
return &nattrStore{dirs: make(map[string]nfsfs.Handle)}
|
|
}
|
|
|
|
func (s *nattrStore) dirFor(parent nfsfs.Handle) nfsfs.Handle {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if d, ok := s.dirs[string(parent)]; ok {
|
|
return d
|
|
}
|
|
d := nfsfs.Handle(append([]byte(nattrDirPrefix), parent...))
|
|
s.dirs[string(parent)] = d
|
|
return d
|
|
}
|
|
|
|
// dirOf pulls the directory handle and the attribute name out of a
|
|
// synthetic file handle.
|
|
func (s *nattrStore) fileFor(dir, name string) nfsfs.Handle {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return nfsfs.Handle(nattrFilePrefix + name + "/" + dir)
|
|
}
|
|
|
|
func (s *nattrStore) dirOf(file nfsfs.Handle) (nfsfs.Handle, string, bool) {
|
|
text := string(file)
|
|
if !strings.HasPrefix(text, nattrFilePrefix) {
|
|
return nil, "", false
|
|
}
|
|
text = text[len(nattrFilePrefix):]
|
|
slash := strings.IndexByte(text, '/')
|
|
if slash < 0 {
|
|
return nil, "", false
|
|
}
|
|
return nfsfs.Handle(text[slash+1:]), text[:slash], true
|
|
}
|
|
|
|
// parentOf pulls the parent object out of a synthetic directory handle.
|
|
func (s *nattrStore) parentOf(dir nfsfs.Handle) (nfsfs.Handle, bool) {
|
|
text := string(dir)
|
|
if !strings.HasPrefix(text, nattrDirPrefix) {
|
|
return nil, false
|
|
}
|
|
return nfsfs.Handle(text[len(nattrDirPrefix):]), true
|
|
}
|
|
|
|
// nattrs returns the named attribute store, made once per handler.
|
|
func (h *Handler) nattrs() *nattrStore {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if h.nattrSt == nil {
|
|
h.nattrSt = newNattrStore()
|
|
}
|
|
return h.nattrSt
|
|
}
|
|
|
|
// openattrOp serves OPENATTR: the current object's named attribute
|
|
// directory answers as a synthetic handle, RFC 8881 section 18.16.
|
|
func (h *Handler) openattrOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
if _, err := d.Bool(); err != nil { // created
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
x, _ := h.FS.(nfsfs.XattrFS)
|
|
if x == nil {
|
|
return nil, nfs4.ErrNotSupp, nil
|
|
}
|
|
reg.cur, reg.haveCur = h.nattrs().dirFor(reg.cur), true
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// isNattrDir reports whether the handle names a named attribute
|
|
// directory.
|
|
func (h *Handler) isNattrDir(fh nfsfs.Handle) bool {
|
|
_, ok := h.nattrs().parentOf(fh)
|
|
return ok
|
|
}
|
|
|
|
// isNattrFile reports whether the handle names a named attribute file.
|
|
func (h *Handler) isNattrFile(fh nfsfs.Handle) bool {
|
|
_, _, ok := h.nattrs().dirOf(fh)
|
|
return ok
|
|
}
|
|
|
|
// nattrLookup resolves one attribute name inside the synthetic
|
|
// directory; the attribute must exist.
|
|
func (h *Handler) nattrLookup(dir nfsfs.Handle, name string) (nfsfs.Handle, uint32) {
|
|
parent, ok := h.nattrs().parentOf(dir)
|
|
if !ok {
|
|
return nil, nfs4.ErrBadHandle
|
|
}
|
|
x, _ := h.FS.(nfsfs.XattrFS)
|
|
if x == nil {
|
|
return nil, nfs4.ErrNotSupp
|
|
}
|
|
if _, err := x.GetXattr(parent, name, 0); err == nfsfs.ErrNoXattr {
|
|
return nil, nfs4.ErrNoEnt
|
|
} else if err != nil && err != nfsfs.ErrXattrNotSupp {
|
|
return nil, nfs4.ErrNoEnt
|
|
}
|
|
return h.nattrs().fileFor(string(dir), name), nfs4.ErrOK
|
|
}
|
|
|
|
// nattrRead reads the value of a named attribute file.
|
|
func (h *Handler) nattrRead(fh nfsfs.Handle, offset uint64) ([]byte, uint32) {
|
|
dir, name, ok := h.nattrs().dirOf(fh)
|
|
if !ok {
|
|
return nil, nfs4.ErrBadHandle
|
|
}
|
|
x, _ := h.FS.(nfsfs.XattrFS)
|
|
if x == nil {
|
|
return nil, nfs4.ErrNotSupp
|
|
}
|
|
parent, ok := h.nattrs().parentOf(dir)
|
|
if !ok {
|
|
return nil, nfs4.ErrBadHandle
|
|
}
|
|
value, err := x.GetXattr(parent, name, 0)
|
|
if err == nfsfs.ErrNoXattr {
|
|
return nil, nfs4.ErrNoEnt
|
|
}
|
|
if err != nil {
|
|
return nil, mapErr(err)
|
|
}
|
|
if offset >= uint64(len(value)) {
|
|
return nil, nfs4.ErrOK
|
|
}
|
|
return value[offset:], nfs4.ErrOK
|
|
}
|
|
|
|
// nattrWrite writes the value of a named attribute file; named
|
|
// attributes rewrite whole, so an offset is refused.
|
|
func (h *Handler) nattrWrite(fh nfsfs.Handle, offset uint64, data []byte) (uint32, uint32) {
|
|
dir, name, ok := h.nattrs().dirOf(fh)
|
|
if !ok {
|
|
return 0, nfs4.ErrBadHandle
|
|
}
|
|
x, _ := h.FS.(nfsfs.XattrFS)
|
|
if x == nil {
|
|
return 0, nfs4.ErrNotSupp
|
|
}
|
|
parent, ok := h.nattrs().parentOf(dir)
|
|
if !ok {
|
|
return 0, nfs4.ErrBadHandle
|
|
}
|
|
if offset != 0 {
|
|
return 0, nfs4.ErrInval
|
|
}
|
|
if err := x.SetXattr(parent, name, data, nfsfs.XattrModeReplace); err != nil {
|
|
return 0, mapErr(err)
|
|
}
|
|
return uint32(len(data)), nfs4.ErrOK
|
|
}
|
|
|
|
// nattrCreate makes one named attribute with its initial value, the
|
|
// CREATE of the synthetic directory.
|
|
func (h *Handler) nattrCreate(dir nfsfs.Handle, name string, value []byte) uint32 {
|
|
parent, ok := h.nattrs().parentOf(dir)
|
|
if !ok {
|
|
return nfs4.ErrBadHandle
|
|
}
|
|
x, _ := h.FS.(nfsfs.XattrFS)
|
|
if x == nil {
|
|
return nfs4.ErrNotSupp
|
|
}
|
|
if err := x.SetXattr(parent, name, value, nfsfs.XattrModeCreate); err != nil {
|
|
return mapErr(err)
|
|
}
|
|
return nfs4.ErrOK
|
|
}
|
|
|
|
// nattrRemove deletes one named attribute of the synthetic directory.
|
|
func (h *Handler) nattrRemove(dir nfsfs.Handle, name string) uint32 {
|
|
parent, ok := h.nattrs().parentOf(dir)
|
|
if !ok {
|
|
return nfs4.ErrBadHandle
|
|
}
|
|
x, _ := h.FS.(nfsfs.XattrFS)
|
|
if x == nil {
|
|
return nfs4.ErrNotSupp
|
|
}
|
|
if err := x.RemoveXattr(parent, name); err != nil {
|
|
if err == nfsfs.ErrNoXattr {
|
|
return nfs4.ErrNoEnt
|
|
}
|
|
return mapErr(err)
|
|
}
|
|
return nfs4.ErrOK
|
|
}
|