// Copyright (c) 2026 Petr BalvĂ­n (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 }