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
121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
// OPENATTR opens the synthetic named attribute directory of a.txt, and
|
|
// CREATE, LOOKUP, READ and REMOVE over it map to the object's extended
|
|
// attributes, RFC 8881 section 5.3.
|
|
func TestNamedAttributes(t *testing.T) {
|
|
h := testTree(t)
|
|
sid, seq := newSession(t, h)
|
|
run := func(s uint32, ops [][]byte) (nfs4.CompoundRes, [][]byte) {
|
|
t.Helper()
|
|
all := append([][]byte{nfs4.AppendSequenceArgs(nil, sid, s, 0, defaultSlots-1, true)}, ops...)
|
|
payload := nfs4.AppendCompoundArgs(nil, "nattr", nfs4.MinorVersion, all)
|
|
body, ok := h.compound(payload, cred{uid: 0, gid: 0})
|
|
if !ok {
|
|
t.Fatal("garbage")
|
|
}
|
|
r, bodies, derr := nfs4.DecodeCompoundResBodies(body)
|
|
if derr != nil {
|
|
t.Fatalf("decode: %v", derr)
|
|
}
|
|
return r, bodies[1:]
|
|
}
|
|
|
|
// OPENATTR on a.txt answers the synthetic directory.
|
|
seq++
|
|
res, bodies := run(seq, [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "a.txt"),
|
|
nfs4.AppendOpenattrArgs(nil, false),
|
|
nfs4.AppendGetfh(nil),
|
|
})
|
|
if res.Status != nfs4.ErrOK {
|
|
t.Fatalf("openattr: status %d", res.Status)
|
|
}
|
|
dirHandle, derr := xdrDecoder(bodies[3])
|
|
if derr != nil || len(dirHandle) == 0 {
|
|
t.Fatalf("dir handle: %v", derr)
|
|
}
|
|
|
|
// CREATE makes the attribute with its initial size, WRITE fills it.
|
|
seq++
|
|
res, _ = run(seq, [][]byte{
|
|
nfs4.AppendPutfh(nil, dirHandle),
|
|
nfs4.AppendCreateArgs(nil, nfs4.NF4Reg, "user.comment", "", 0, 0,
|
|
0o644),
|
|
})
|
|
wantStatus(t, "nattr create", res.Status, nfs4.ErrOK)
|
|
seq++
|
|
res, bodies = run(seq, [][]byte{
|
|
nfs4.AppendPutfh(nil, dirHandle),
|
|
nfs4.AppendLookup(nil, "user.comment"),
|
|
nfs4.AppendGetfh(nil),
|
|
})
|
|
wantStatus(t, "nattr lookup", res.Status, nfs4.ErrOK)
|
|
fileHandle, derr := xdrDecoder(bodies[2])
|
|
if derr != nil {
|
|
t.Fatal(derr)
|
|
}
|
|
seq++
|
|
res, _ = run(seq, [][]byte{
|
|
nfs4.AppendPutfh(nil, fileHandle),
|
|
nfs4.AppendWriteArgs(nil, nfs4.Stateid{}, 0, nfs4.StableFileSync, []byte("hello")),
|
|
})
|
|
if res.Status != nfs4.ErrOK {
|
|
t.Fatalf("nattr write: status %d", res.Status)
|
|
}
|
|
seq++
|
|
res, bodies = run(seq, [][]byte{
|
|
nfs4.AppendPutfh(nil, fileHandle),
|
|
nfs4.AppendReadArgs(nil, nfs4.Stateid{}, 0, 64),
|
|
})
|
|
if res.Status != nfs4.ErrOK {
|
|
t.Fatalf("nattr read: status %d", res.Status)
|
|
}
|
|
rd := xdr.NewDecoder(bodies[1])
|
|
if eof, rerr := rd.Bool(); rerr != nil || !eof {
|
|
t.Fatalf("eof %v: %v", eof, rerr)
|
|
}
|
|
value, rerr := rd.VarOpaque()
|
|
if rerr != nil || string(value) != "hello" {
|
|
t.Fatalf("value %q: %v", value, rerr)
|
|
}
|
|
|
|
// The value is visible through the xattr half of the same object.
|
|
fh := mustLookup(t, h, "a.txt")
|
|
got, gerr := h.FS.(nfsfs.XattrFS).GetXattr(fh, "user.comment", 0)
|
|
if gerr != nil || string(got) != "hello" {
|
|
t.Fatalf("xattr view %q: %v", got, gerr)
|
|
}
|
|
|
|
// REMOVE deletes the attribute.
|
|
seq++
|
|
res, _ = run(seq, [][]byte{
|
|
nfs4.AppendPutfh(nil, dirHandle),
|
|
nfs4.AppendRemoveArgs(nil, "user.comment"),
|
|
})
|
|
wantStatus(t, "nattr remove", res.Status, nfs4.ErrOK)
|
|
seq++
|
|
res, _ = run(seq, [][]byte{
|
|
nfs4.AppendPutfh(nil, dirHandle),
|
|
nfs4.AppendLookup(nil, "user.comment"),
|
|
})
|
|
wantStatus(t, "nattr lookup gone", res.Status, nfs4.ErrNoEnt)
|
|
}
|
|
|
|
// xdrDecoder decodes the file handle out of a GETFH result body.
|
|
func xdrDecoder(body []byte) ([]byte, error) {
|
|
return xdr.NewDecoder(body).VarOpaque()
|
|
}
|