Files
nfs/internal/nfsclient/nattr_test.go
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

85 lines
2.2 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
package nfsclient
import (
"net"
"os"
"path/filepath"
"testing"
"sourcedock.dev/petrbalvin/nfs/internal/krb5"
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
"sourcedock.dev/petrbalvin/nfs/internal/nfs4server"
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
"sourcedock.dev/petrbalvin/nfs/internal/rpc"
"sourcedock.dev/petrbalvin/nfs/internal/server"
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
)
// The named attribute high-level API over a protected session: OPENATTR,
// create, write and read of one named attribute on a real file.
func TestNamedAttrAPI(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "n.txt"), []byte("obj"), 0o644); err != nil {
t.Fatal(err)
}
backend, err := nfsfs.NewLocal(root)
if err != nil {
t.Fatal(err)
}
key := make([]byte, 32)
for i := range key {
key[i] = byte(i + 11)
}
h := &nfs4server.Handler{FS: backend, ServerKey: key, ServiceName: "nfs"}
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
srv := &server.Server{Handle: h.HandleConn}
go srv.Serve(t.Context(), ln)
defer ln.Close()
cl, err := Dial(ln.Addr().String())
if err != nil {
t.Fatal(err)
}
defer cl.Close()
if err := cl.Establish("nattr"); err != nil {
t.Fatalf("establish: %v", err)
}
if err := cl.EnableGSS(krb5.EtypeAES128, key, "EXAMPLE.ORG", "nfs",
"petr@EXAMPLE.ORG", rpc.SvcIntegrity); err != nil {
t.Fatalf("enable gss: %v", err)
}
// Open the file and write the named attribute through the API.
res, bodies, err := cl.Compound("open", [][]byte{
nfs4.AppendPutRootfh(nil),
nfs4.AppendLookup(nil, "n.txt"),
nfs4.AppendGetfh(nil),
})
if err != nil || res.Status != nfs4.ErrOK {
t.Fatalf("open: %d %v", res.Status, err)
}
fh, ferr := xdr.NewDecoder(bodies[2]).VarOpaque()
if ferr != nil {
t.Fatal(ferr)
}
if err := cl.SetXattrNamed(fh, "user.note", []byte("hello note")); err != nil {
t.Fatalf("set xattr: %v", err)
}
// Read it back through the OPENATTR path.
got, err := cl.GetXattrNamed(fh, "user.note", 256)
if err != nil {
t.Fatalf("get xattr: %v", err)
}
if string(got) != "hello note" {
t.Fatalf("value %q", got)
}
}