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
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:
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user