// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // The byte range locking layer of RFC 8881 sections 15.10 to 15.13 and // 18.11 to 18.13: LOCK, LOCKT and LOCKU with their share of unions. package nfs4 import ( "sourcedock.dev/petrbalvin/nfs/internal/xdr" ) // Lock types of the nfs_lock_type4 enum. const ( LockTypeRead = 1 LockTypeWrite = 2 ) // AppendLockArgsNew encodes the LOCK4args for the first LOCK of a new // lock owner: the locker union carries the open stateid the lock hangs // from and the identity of the lock owner. func AppendLockArgsNew(b []byte, openStateid Stateid, lockClientid uint64, lockOwner []byte, lockType uint32, reclaim bool, offset, length uint64) []byte { b = xdr.AppendUint32(b, OpLock) b = xdr.AppendUint32(b, lockType) b = xdr.AppendBool(b, reclaim) b = xdr.AppendUint64(b, offset) b = xdr.AppendUint64(b, length) b = xdr.AppendBool(b, true) // new_lock_owner b = xdr.AppendUint32(b, 0) // open_seqid, deprecated b = openStateid.Append(b) b = xdr.AppendUint32(b, 0) // lock_seqid, deprecated b = xdr.AppendUint64(b, lockClientid) return xdr.AppendVarOpaque(b, lockOwner) } // AppendLockArgsExisting encodes the LOCK4args for a lock owner that // already holds a lock stateid. func AppendLockArgsExisting(b []byte, lockStateid Stateid, lockType uint32, offset, length uint64) []byte { b = xdr.AppendUint32(b, OpLock) b = xdr.AppendUint32(b, lockType) b = xdr.AppendBool(b, reclaimMarker) b = xdr.AppendUint64(b, offset) b = xdr.AppendUint64(b, length) b = xdr.AppendBool(b, false) // not a new lock owner b = lockStateid.Append(b) return xdr.AppendUint32(b, 0) // lock_seqid, deprecated } // reclaimMarker keeps the bool arguments of the locker union readable. const reclaimMarker = false // AppendLockuArgs encodes the LOCKU argop: the release of one range of a // lock stateid. func AppendLockuArgs(b []byte, lockStateid Stateid, offset, length uint64) []byte { b = xdr.AppendUint32(b, OpLocku) b = xdr.AppendUint32(b, LockTypeWrite) // the standard echoes a type; the stateid rules b = xdr.AppendUint32(b, 0) // seqid, deprecated b = lockStateid.Append(b) b = xdr.AppendUint64(b, offset) return xdr.AppendUint64(b, length) } // AppendLocktArgs encodes the LOCKT argop: a probe whether a lock of the // type over the range would conflict, for the named owner. func AppendLocktArgs(b []byte, lockType uint32, offset, length uint64, clientid uint64, owner []byte) []byte { b = xdr.AppendUint32(b, OpLockt) b = xdr.AppendUint32(b, lockType) b = xdr.AppendUint64(b, offset) b = xdr.AppendUint64(b, length) b = xdr.AppendUint64(b, clientid) return xdr.AppendVarOpaque(b, owner) } // AppendLockRes encodes a successful LOCK4res: the lock stateid. func AppendLockRes(b []byte, stateid Stateid) []byte { return stateid.Append(b) } // AppendLockuRes encodes a successful LOCKU4res: the lock stateid, with // its sequence bumped. func AppendLockuRes(b []byte, stateid Stateid) []byte { return stateid.Append(b) } // AppendLocktResOK appends a LOCKT result for the OK status, which carries // nothing: no conflict was found. func AppendLocktResOK(b []byte) []byte { return b } // AppendLocktResDenied appends a LOCKT result body for the DENIED status: // the conflicting lock, with its holder. func AppendLocktResDenied(b []byte, offset, length uint64, lockType uint32, clientid uint64, owner []byte) []byte { b = xdr.AppendUint64(b, offset) b = xdr.AppendUint64(b, length) b = xdr.AppendUint32(b, lockType) b = xdr.AppendUint64(b, clientid) return xdr.AppendVarOpaque(b, owner) } // A LockDenied is the conflicting lock a LOCKT answers. type LockDenied struct { Offset uint64 Length uint64 LockType uint32 ClientID uint64 Owner []byte } // DecodeLocktResBody decodes a LOCKT body. On the DENIED status it returns // the conflicting lock; on OK the body is empty. func DecodeLocktResBody(status uint32, body []byte) (LockDenied, error) { if status != ErrDenied { return LockDenied{}, nil } d := xdr.NewDecoder(body) var denied LockDenied var err error if denied.Offset, err = d.Uint64(); err != nil { return denied, err } if denied.Length, err = d.Uint64(); err != nil { return denied, err } if denied.LockType, err = d.Uint32(); err != nil { return denied, err } if denied.ClientID, err = d.Uint64(); err != nil { return denied, err } denied.Owner, err = d.VarOpaque() return denied, err } // resBodyLenLockt measures the LOCKT body: empty on OK, a full LOCK4denied // on DENIED. func resBodyLenLockt(status uint32, body []byte) (int, error) { if status != ErrDenied { return 0, nil } d := xdr.NewDecoder(body) if _, err := d.Uint64(); err != nil { return 0, err } if _, err := d.Uint64(); err != nil { return 0, err } if _, err := d.Uint32(); err != nil { return 0, err } if _, err := d.Uint64(); err != nil { return 0, err } if _, err := d.VarOpaque(); err != nil { return 0, err } return len(body) - d.Remaining(), nil }