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

142 lines
3.7 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
// The handlers of the extended attribute family, RFC 8276. A backend
// without the XattrFS half answers NOT_SUPP to all of them.
package nfs4server
import (
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
)
// xattrFS resolves the backend's xattr half, or nil when it has none.
func (h *Handler) xattrFS() nfsfs.XattrFS {
x, _ := h.FS.(nfsfs.XattrFS)
return x
}
// getXattrOp serves GETXATTR: one named value of the current object.
// The argument is the name alone, RFC 8276.
func (h *Handler) getXattrOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
name, err := d.String()
if err != nil {
return nil, 0, err
}
if !reg.haveCur {
return nil, nfs4.ErrNoFileHandle, nil
}
x := h.xattrFS()
if x == nil {
return nil, nfs4.ErrNotSupp, nil
}
value, xerr := x.GetXattr(reg.cur, name, 0)
if xerr == nfsfs.ErrNoXattr {
return nil, nfs4.ErrNoXattr, nil
}
if xerr == nfsfs.ErrXattrNotSupp {
return nil, nfs4.ErrNotSupp, nil
}
if xerr != nil {
return nil, mapErr(xerr), nil
}
return nfs4.AppendGetXattrRes(nil, value), nfs4.ErrOK, nil
}
// setXattrOp serves SETXATTR: it writes or creates or replaces one named
// value of the current object. The argument order is the mode, the key
// and the value, RFC 8276.
func (h *Handler) setXattrOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
mode, err := d.Uint32()
if err != nil {
return nil, 0, err
}
name, err := d.String()
if err != nil {
return nil, 0, err
}
value, err := d.VarOpaque()
if err != nil {
return nil, 0, err
}
if !reg.haveCur {
return nil, nfs4.ErrNoFileHandle, nil
}
x := h.xattrFS()
if x == nil {
return nil, nfs4.ErrNotSupp, nil
}
if err := x.SetXattr(reg.cur, name, value, mode); err != nil {
if err == nfsfs.ErrXattrNotSupp {
return nil, nfs4.ErrNotSupp, nil
}
return nil, mapErr(err), nil
}
return nil, nfs4.ErrOK, nil
}
// listXattrOp serves LISTXATTR: the names of the current object's
// attributes, one page bounded by the budget. The whole list answers in
// one page with the end of list flag set, so a resume cookie never
// names a remaining entry: cookie zero answers everything and any other
// cookie answers an empty final page, RFC 8276 section 5.
func (h *Handler) listXattrOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
cookie, err := d.Uint64()
if err != nil {
return nil, 0, err
}
maxcount, err := d.Uint32()
if err != nil {
return nil, 0, err
}
if maxcount > 1<<20 {
maxcount = 1 << 20
}
if !reg.haveCur {
return nil, nfs4.ErrNoFileHandle, nil
}
x := h.xattrFS()
if x == nil {
return nil, nfs4.ErrNotSupp, nil
}
names := []string{}
if cookie == 0 {
names, err = x.ListXattr(reg.cur, int(maxcount))
if err == nfsfs.ErrXattrNotSupp {
return nil, nfs4.ErrNotSupp, nil
}
if err != nil {
return nil, mapErr(err), nil
}
}
return nfs4.AppendListXattrRes(nil, cookie, names, true), nfs4.ErrOK, nil
}
// removeXattrOp serves REMOVEXATTR: it deletes one named value of the
// current object.
func (h *Handler) removeXattrOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
name, err := d.String()
if err != nil {
return nil, 0, err
}
if !reg.haveCur {
return nil, nfs4.ErrNoFileHandle, nil
}
x := h.xattrFS()
if x == nil {
return nil, nfs4.ErrNotSupp, nil
}
if err := x.RemoveXattr(reg.cur, name); err != nil {
if err == nfsfs.ErrNoXattr {
return nil, nfs4.ErrNoXattr, nil
}
if err == nfsfs.ErrXattrNotSupp {
return nil, nfs4.ErrNotSupp, nil
}
return nil, mapErr(err), nil
}
return nil, nfs4.ErrOK, nil
}