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
238 lines
7.1 KiB
Go
238 lines
7.1 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// The handlers of the remaining NFSv4.1 operations: the parent lookup,
|
|
// the attribute comparisons, the lock owner retirement and the session
|
|
// binding family.
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
// maxOps bounds the TEST_STATEID array, matching the COMPOUND bound of
|
|
// the wire codecs.
|
|
const maxOps = 1024
|
|
|
|
// lookuppOp serves LOOKUPP: the current handle becomes its parent
|
|
// directory.
|
|
func (h *Handler) lookuppOp(reg *fhreg) ([]byte, uint32, error) {
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
parent, _, err := h.FS.Parent(reg.cur)
|
|
if err != nil {
|
|
return nil, mapErr(err), nil
|
|
}
|
|
reg.cur, reg.haveCur = parent, true
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// verifyOp serves VERIFY and NVERIFY: the compound proceeds only when the
|
|
// file carries, or with the negated form does not carry, every attribute
|
|
// the arguments assert. A value this build cannot compare answers
|
|
// ATTRNOTSUPP.
|
|
func (h *Handler) verifyOp(d *xdr.Decoder, reg *fhreg, negate bool) ([]byte, uint32, error) {
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
request, err := nfs4.ReadBitmap(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
blob, err := d.VarOpaque()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
updates, err := nfs4.DecodeSetattrBlob(blob, request)
|
|
if err != nil {
|
|
if err == nfs4.ErrAttrNotSettable {
|
|
return nil, nfs4.ErrAttrNotSupp, nil
|
|
}
|
|
return nil, 0, err
|
|
}
|
|
info, ferr := h.FS.Getattr(reg.cur)
|
|
if ferr != nil {
|
|
return nil, mapErr(ferr), nil
|
|
}
|
|
mismatch := false
|
|
switch {
|
|
case updates.HasMode && uint32(info.Mode.Perm()) != updates.Mode:
|
|
mismatch = true
|
|
case updates.HasSize && uint64(info.Size) != updates.Size:
|
|
mismatch = true
|
|
case updates.UID != nil && info.UID != *updates.UID:
|
|
mismatch = true
|
|
case updates.GID != nil && info.GID != *updates.GID:
|
|
mismatch = true
|
|
case updates.Atime != nil && !nfsTimeMatches(updates.Atime, info):
|
|
mismatch = true
|
|
case updates.Mtime != nil && !nfsTimeMatches(updates.Mtime, info):
|
|
mismatch = true
|
|
}
|
|
if mismatch == negate {
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
if negate {
|
|
return nil, nfs4.ErrSame, nil
|
|
}
|
|
return nil, nfs4.ErrNotSame, nil
|
|
}
|
|
|
|
// nfsTimeMatches reports whether the asserted time is the file time the
|
|
// backend reports. A server time assertion never matches a fixed value;
|
|
// only the seconds and nanoseconds a client carries count.
|
|
func nfsTimeMatches(set *nfs4.NfsTimeSet, info nfsfs.Info) bool {
|
|
if set.Server {
|
|
return false
|
|
}
|
|
got := info.ModTime
|
|
return got.Unix() == set.Time.Seconds && int64(uint32(got.Nanosecond())) == int64(set.Time.Nseconds)
|
|
}
|
|
|
|
// errTooManyStateids marks a TEST_STATEID whose array exceeds the bound
|
|
// every COMPOUND array of this build obeys.
|
|
var errTooManyStateids = errors.New("nfs4server: too many stateids in one test")
|
|
|
|
// releaseLockOwnerOp serves RELEASE_LOCKOWNER: every lock state the
|
|
// owner holds dies with the owner. The owner must belong to the asking
|
|
// client, RFC 8881 section 18.38.
|
|
func (h *Handler) releaseLockOwnerOp(d *xdr.Decoder, sessionClientid uint64) ([]byte, uint32, error) {
|
|
clientid, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
owner, err := d.VarOpaque()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if sessionClientid != 0 && clientid != sessionClientid {
|
|
return nil, nfs4.ErrBadOwner, nil
|
|
}
|
|
h.locks().releaseOwner(clientid, owner)
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// delegReturnOp serves DELEGRETURN: the client gives its own delegation
|
|
// back. A stateid the store holds is dropped; anything else is answered
|
|
// OK just the same, because the release of a state the server does not
|
|
// carry is already done and the reply only tells the client to forget
|
|
// the stateid.
|
|
func (h *Handler) delegReturnOp(d *xdr.Decoder, sessionClientid uint64) ([]byte, uint32, error) {
|
|
var st nfs4.Stateid
|
|
raw, err := d.Raw(16)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
copy(st[:], raw)
|
|
h.delegs().dropStateid(st, sessionClientid)
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// backchannelCtlOp serves BACKCHANNEL_CTL: the client names the program
|
|
// its future back channels run.
|
|
func (h *Handler) backchannelCtlOp(d *xdr.Decoder, ctx *connCB) ([]byte, uint32, error) {
|
|
program, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if ctx != nil {
|
|
ctx.setProgram(program)
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// bindConnToSessionOp serves BIND_CONN_TO_SESSION: the connection the
|
|
// request rode in on joins the session as a fore channel, a back channel
|
|
// or both. The session must belong to the asking client, so one client
|
|
// cannot graft its connection onto another's callbacks, and the
|
|
// callback program comes from CREATE_SESSION, which stores it with the
|
|
// session.
|
|
func (h *Handler) bindConnToSessionOp(d *xdr.Decoder, ctx *connCB, sessionClientid uint64) ([]byte, uint32, error) {
|
|
var id nfs4.SessionID
|
|
raw, err := d.Raw(16)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
copy(id[:], raw)
|
|
dir, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Bool(); err != nil { // use conn in RDMA mode
|
|
return nil, 0, err
|
|
}
|
|
if _, status := h.sessions().lookupSession(id); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
// A compound that carries a session may bind only its own: one
|
|
// client must not graft its connection onto another's callbacks.
|
|
if sessionClientid != 0 && id.ClientIDOf() != sessionClientid {
|
|
return nil, nfs4.ErrBadSession, nil
|
|
}
|
|
switch dir {
|
|
case nfs4.Cdfc4Back, nfs4.Cdfc4BackOrBoth:
|
|
if ctx == nil {
|
|
return nil, nfs4.ErrConnNotBound, nil
|
|
}
|
|
h.sessions().attachCB(id, ctx)
|
|
return nfs4.AppendBindConnToSessionRes(nil, id, nfs4.Cdfs4Both), nfs4.ErrOK, nil
|
|
case nfs4.Cdfc4Fore, nfs4.Cdfc4ForeOrBoth:
|
|
return nfs4.AppendBindConnToSessionRes(nil, id, nfs4.Cdfs4Fore), nfs4.ErrOK, nil
|
|
default:
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
}
|
|
|
|
// freeStateidOp serves FREE_STATEID: the client retires a lock stateid
|
|
// of its own whose owner holds no locks any more.
|
|
func (h *Handler) freeStateidOp(d *xdr.Decoder, sessionClientid uint64) ([]byte, uint32, error) {
|
|
var st nfs4.Stateid
|
|
raw, err := d.Raw(16)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
copy(st[:], raw)
|
|
if status := h.locks().freeStateid(st, sessionClientid); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// testStateidOp serves TEST_STATEID: one status per stateid, no state
|
|
// touched.
|
|
func (h *Handler) testStateidOp(d *xdr.Decoder) ([]byte, uint32, error) {
|
|
n, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if n > maxOps {
|
|
return nil, 0, errTooManyStateids
|
|
}
|
|
var statuses []uint32
|
|
for range n {
|
|
var st nfs4.Stateid
|
|
raw, rerr := d.Raw(16)
|
|
if rerr != nil {
|
|
return nil, 0, rerr
|
|
}
|
|
copy(st[:], raw)
|
|
status := h.openStates().testStateid(st)
|
|
if status == nfs4.ErrBadStateid {
|
|
switch {
|
|
case h.locks().hasStateid(st):
|
|
status = nfs4.ErrOK
|
|
case h.delegs().hasStateid(st):
|
|
status = nfs4.ErrOK
|
|
}
|
|
}
|
|
statuses = append(statuses, status)
|
|
}
|
|
return nfs4.AppendTestStateidRes(nil, statuses), nfs4.ErrOK, nil
|
|
}
|