feat: full NFSv4.2 server and client in pure Go
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
This commit is contained in:
2026-09-21 18:51:17 +02:00
commit a9b8039ef7
153 changed files with 34403 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
.TH NFS 1 2026-09-21 nfs "User Commands"
.SH NAME
nfs \- speak to an NFSv4.2 server from the command line
.SH SYNOPSIS
.B nfs
.RB [ \-addr
.IR host:port ]
.RB [ \-concurrency
.IR n ]
.B version
|
.B ls
.RI [ path ]
|
.B cat
.I path
|
.B put
.I local remote
|
.B get
.I remote local
|
.B rm
.I path
|
.B mkdir
.I path
|
.B stat
.I path
|
.B selftest
.SH DESCRIPTION
.B nfs
runs one operation against an NFSv4.2 server over TCP, as RFC 8881 and
RFC 7862 describe. It dials the address, establishes a session and
speaks the operation through the internal nfsclient package, which
carries the full protocol surface of the project. The client works
against any NFSv4.2 server, the
.BR nfsd (1)
of this project included.
.PP
Paths address the server's NFS namespace from its root, with the
leading slash optional: a lookup walks one component at a time, so a
name that carries a slash is a path, never an escaped component.
.SH COMMANDS
.TP
.B version
Print the version and exit. A build made at a tag reports the tag, a
build outside version control reports a development label.
.TP
.BI ls " [path]"
List the directory
.IR path ,
the root by default. One line per entry: the name, the size and the
mode in octal, separated by tabs.
.TP
.BI cat " path"
Stream the file
.I path
to standard output, reading in megabyte chunks until the server
reports the end of file.
.TP
.BI put " local remote"
Write the local file
.I local
to
.I remote
on the server. The remote file is created if it is missing, with mode
0644, and truncated before the first write, so a shorter file leaves
no tail behind. On success one line reports how many bytes landed.
.TP
.BI get " remote local"
Copy the remote file
.I remote
into the local file
.IR local ,
reading in megabyte chunks until the server reports the end of file.
The local file is truncated first, so a shorter remote leaves no tail
behind. On success one line reports how many bytes landed.
.TP
.BI rm " path"
Remove the object
.I path
from the server. Removing a directory that still holds entries is
refused by the server.
.TP
.BI mkdir " path"
Make the directory
.I path
on the server. The parent directory must exist; the walk to it follows
one component at a time.
.TP
.BI stat " path"
Print the attributes of
.IR path :
the type, the size, the mode in octal and the modification time, one
per line.
.TP
.B selftest
Run the whole operation matrix against the server the session points
at: the work directory, an empty file, a 64 KiB write and its byte for
byte comparison, the listing, a rename, a symlink and its target, a
nested directory, the ownership of a file created as another uid, a
mode change, and the removals. One line per check and a summary at the
end; the exit status reports whether every check passed. The work
directory is removed on success and left in place on failure, so a
failing server can be examined.
.SH OPTIONS
.TP
.BI \-addr " host:port"
The server address. Defaults to
.IR 127.0.0.1:2049 .
.TP
.BI \-concurrency " n"
How many compounds of
.B get
and
.B put
run in flight at once, one to eight, each on its own session slot of
the one connection. The default of one keeps the transfers sequential.
.SH EXIT STATUS
.TP
.B 0
The operation completed.
.TP
.B 1
The operation failed: the dial, the session or the operation itself
returned an error, or the local file of
.B put
could not be read.
.TP
.B 2
The arguments were wrong: no subcommand, an unknown subcommand, or a
subcommand missing its required argument.
.SH EXAMPLES
List the root of a server on the default port and read a file from it:
.PP
.RS
.nf
nfs ls
nfs cat /hello.txt
.RE
.PP
Copy a local file to the server and check it landed:
.PP
.RS
.nf
nfs put README.md /readme.md
nfs stat /readme.md
.RE
.PP
Check a server speaks the whole matrix the client exercises:
.PP
.RS
.nf
nfs \-addr 127.0.0.1:2049 selftest
.RE
.PP
Speak to a server on another host:
.PP
.RS
.nf
nfs \-addr 192.0.2.10:2049 ls /srv
.RE
.SH AUTHOR
Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
.SH LICENCE
MIT. See the LICENSE file in the repository.
.SH SEE ALSO
.BR nfsd (1),
https://sourcedock.dev/petrbalvin/nfs
+177
View File
@@ -0,0 +1,177 @@
.TH NFSD 1 2026-09-21 nfs "User Commands"
.SH NAME
nfsd \- serve a local directory tree over NFSv4.2
.SH SYNOPSIS
.B nfsd
.RB [ \-addr
.IR addr ]
.RB [ \-export
.IR dir ]
.RB [ \-ro ]
.RB [ \-tls-cert
.IR file ]
.RB [ \-tls-key
.IR file ]
.RB [ \-log-ops ]
.RB [ \-max-connections
.IR n ]
.RB [ \-state-dir
.IR dir ]
.RB [ \-config
.IR file ]
.RB [ \-version ]
.SH DESCRIPTION
.B nfsd
exports one local directory tree over NFSv4.2 on a single TCP port, as
RFC 8881 and RFC 7862 describe and RFC 8276, RFC 7861 and RFC 9289
extend. NFSv4 carries everything on the one port: there is no
portmapper, no mountd and no separate locking protocol.
.PP
The tree is served read and write. Every request is evaluated against
the identity the client presents, so the permission bits of the served
files decide what each caller may do, and the objects a client creates
carry the identity it presented. The server keeps its open, lock and
delegation state across the connections of a client and drops it when
the client reboots or its lease lapses.
.PP
On
.B SIGINT
or
.B SIGTERM
the listener closes and the process exits cleanly; clients recover
through their session replay caches, so a stopped server costs no
state.
.SH OPTIONS
.TP
.BI \-addr " addr"
The TCP address to listen on. Defaults to
.IR :2049 .
.TP
.BI \-export " dir"
The directory to serve, relative or absolute. The directory must exist;
a missing or non directory path ends the start up. Required: without it
the server prints a reminder and exits.
.TP
.B \-ro
Serve the export read only. Every operation that would change the tree
answers NFS4ERR_ROFS; the reads of every half, the attributes, the
extended attributes and the hole seeking included, work unchanged.
.TP
.B \-root-squash
Map a client claiming uid 0 onto nobody: the credential acts as uid
65534 with group 65534, the superuser grant is gone, and the objects
root creates carry nobody. The default keeps the trust AUTH_SYS hands
to the claim; an operator serving untrusted clients turns this on.
.TP
.BI \-tls-cert " file"
The certificate chain in PEM that enables RPC-with-TLS of RFC 9289.
A client probes with AUTH_TLS, the connection upgrades in place, and a
client that skips the upgrade is refused with auth too weak for every
procedure but the NULL of the probe. Goes together with
.BR \-tls-key ;
either alone ends the start up.
.TP
.BI \-tls-key " file"
The private key in PEM for RPC-with-TLS, matching
.BR \-tls-cert .
.TP
.B \-log-ops
Log one line per operation to standard error: the operation, the status
it answered and the time it took, as
.BR "nfs: LOOKUP status 0 84\es" .
Off by default: a quiet server answers nothing on the log.
.HP
.B \-config
.I file
The configuration file in TOML, described under
.B CONFIGURATION
below. Never read unless the flag names it; the flags override the
file. A file that fails the read, the schema or the validation ends
the start up with the file and the line named.
.TP
.BI \-max-connections " n"
The cap on connections served at once. A connection offered above the
cap closes at once and the client sees an immediate end of file; the
server answers nothing on it. Zero, the default, means no cap.
.TP
.BI \-state-dir " dir"
The directory for the persisted client state. The file handle map and
the open state are written there as they change, a restart loads them
back, and the grace window after a start lets a client reclaim its
opens with CLAIM_PREVIOUS. The directory is created with owner only
permissions when it is missing. Without the flag nothing persists and
a restart starts from an empty state, as before.
.TP
.B \-version
Print the version and exit. A build made at a tag reports the tag, a
build outside version control reports
.IR devel .
.SH CONFIGURATION
The server reads a TOML configuration file when
.B \-config
names one, and never otherwise. The file carries
.BR listen ,
.BR log-ops ,
.BR state-dir ,
.BR max-connections ,
a
.B [tls]
table with
.B cert
and
.BR key ,
and one
.B [[export]]
table with
.BR path ,
.B read-only
and
.BR root-squash .
The flags override the file; every key, type, default and effect is
described in docs/CONFIGURATION.md of the repository. A file that
breaks the schema or the syntax ends the start up with the file and
the line of the fault.
.SH EXIT STATUS
.TP
.B 0
The version was printed, or the server shut down cleanly on
.B SIGINT
or
.BR SIGTERM .
.TP
.B 1
The start up or the service failed: no export was given, the export
path is missing or is not a directory, the listen address could not be
bound, or the listener failed.
.SH EXAMPLES
Serve
.I /srv/demo
on the default port:
.PP
.RS
.nf
nfsd \-export /srv/demo
.RE
.PP
Serve on a loopback address and print the version first:
.PP
.RS
.nf
nfsd \-version
nfsd \-export /srv/demo \-addr 127.0.0.1:2049
.RE
.PP
Read the tree with
.BR nfs (1):
.PP
.RS
.nf
nfs \-addr 127.0.0.1:2049 ls
.RE
.SH AUTHOR
Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
.SH LICENCE
MIT. See the LICENSE file in the repository.
.SH SEE ALSO
.BR nfs (1),
https://sourcedock.dev/petrbalvin/nfs