Documentation
¶
Overview ¶
Package server implements a 9P2000.L file server with a capability-based API inspired by go-fuse/v2/fs. Filesystem authors embed *Inode in their node types and implement only the capability interfaces they need; all unimplemented operations automatically return ENOSYS.
Capability Pattern ¶
Define a struct, embed *Inode, and implement capability interfaces such as NodeReader, NodeWriter, NodeOpener, NodeGetattrer, NodeReaddirer, and others. The server's dispatch layer detects implemented interfaces at runtime and routes 9P messages accordingly.
type MyFile struct {
server.Inode
}
func (f *MyFile) Read(_ context.Context, buf []byte, _ uint64) (int, error) {
return copy(buf, "hello"), nil
}
Approximately 22 capability interfaces are defined (see node.go), covering file I/O, directory operations, symlinks, device nodes, xattrs, locking, and filesystem statistics.
Composable Helpers ¶
ReadOnlyFile and ReadOnlyDir are pre-built composable types that embed Inode and signal intent: a read-only file that cannot be written, a read-only directory that cannot be mutated.
FileHandle ¶
NodeOpener returns a FileHandle to carry per-open state. If the FileHandle implements FileReader, FileWriter, or FileReaddirer, those take priority over the corresponding Node-level methods for that open instance. This allows stateful I/O (e.g., seekable directory enumeration) without polluting the node itself.
Server Lifecycle ¶
Create a server with New, passing the root Node and any Option values. Call Server.Serve with a context and net.Listener to accept connections:
srv := server.New(root,
server.WithMaxMsize(1 << 20),
server.WithLogger(slog.Default()),
)
ln, _ := net.Listen("tcp", ":5640")
srv.Serve(ctx, ln)
Each accepted connection runs in its own goroutine. Within a connection, a single writer goroutine serializes responses while requests are dispatched concurrently (bounded by WithMaxInflight).
Functional Options ¶
Configure the server with:
- WithMaxMsize -- maximum negotiated message size (default 1MiB)
- WithMaxInflight -- concurrent request limit per connection (default 64)
- WithLogger -- structured logger with automatic trace ID correlation
- WithIdleTimeout -- per-connection idle timeout
- WithAnames -- vhost-style root dispatch by attach name
- WithAttacher -- full-control attach handling
- WithTracer -- OpenTelemetry TracerProvider
- WithMeter -- OpenTelemetry MeterProvider
- WithMiddleware -- dispatch middleware chain
Middleware ¶
Middleware wraps the dispatch Handler, enabling cross-cutting concerns such as logging, metrics, or access control. When OpenTelemetry providers are configured, tracing and metrics middleware is prepended automatically.
Observability ¶
The server supports OpenTelemetry traces and metrics via the OTel API (no SDK dependency). Configure with WithTracer and WithMeter. Structured logging uses log/slog with automatic trace ID correlation via NewTraceHandler.
Sub-packages ¶
- github.com/dotwaffle/ninep/server/memfs -- in-memory file and directory helpers with a fluent builder API
- github.com/dotwaffle/ninep/server/passthrough -- reference passthrough filesystem using *at syscalls
- github.com/dotwaffle/ninep/server/fstest -- protocol-level test harness for validating filesystem implementations
Example ¶
A minimal read-only file server:
package main
import (
"context"
"log"
"net"
"github.com/dotwaffle/ninep/proto"
"github.com/dotwaffle/ninep/server"
)
type HelloFile struct {
server.Inode
}
func (f *HelloFile) Getattr(_ context.Context, _ proto.AttrMask) (proto.Attr, error) {
return proto.Attr{
Valid: proto.AttrMode | proto.AttrSize,
Mode: 0o444,
Size: 11,
}, nil
}
func (f *HelloFile) Read(_ context.Context, buf []byte, offset uint64) (int, error) {
data := []byte("hello world")
if offset >= uint64(len(data)) {
return 0, nil
}
end := min(offset+uint64(len(buf)), uint64(len(data)))
return copy(buf, data[offset:end]), nil
}
func main() {
root := &HelloFile{}
root.Init(proto.QID{Type: proto.QTFILE, Path: 1}, root)
srv := server.New(root)
ln, err := net.Listen("tcp", ":5640")
if err != nil {
log.Fatal(err)
}
log.Fatal(srv.Serve(context.Background(), ln))
}
Index ¶
- Variables
- func EncodeDirents(dirents []proto.Dirent, maxBytes uint32) ([]byte, int)
- func EncodeDirentsInto(dst []byte, dirents []proto.Dirent) (int, int)
- func NewTraceHandler(inner slog.Handler) slog.Handler
- func PathQID(t proto.QIDType, path string) proto.QID
- type Attacher
- type ConnInfo
- type Device
- type FileHandle
- type FileRawReaddirer
- type FileReaddirer
- type FileReader
- type FileReleaser
- type FileSyncer
- type FileWriter
- type Handler
- type Inode
- func (i *Inode) AddChild(name string, child *Inode)
- func (i *Inode) Children() map[string]*Inode
- func (i *Inode) Close(_ context.Context) error
- func (i *Inode) Create(_ context.Context, _ string, _ uint32, _ proto.FileMode, _ uint32) (Node, FileHandle, uint32, error)
- func (i *Inode) EmbeddedInode() *Inode
- func (i *Inode) Fsync(_ context.Context) error
- func (i *Inode) GetLock(_ context.Context, _ proto.LockType, _, _ uint64, _ uint32, _ string) (proto.LockType, uint64, uint64, uint32, string, error)
- func (i *Inode) GetXattr(_ context.Context, _ string) ([]byte, error)
- func (i *Inode) Getattr(_ context.Context, _ proto.AttrMask) (proto.Attr, error)
- func (i *Inode) Init(qid proto.QID, node InodeEmbedder)
- func (i *Inode) Link(_ context.Context, _ Node, _ string) error
- func (i *Inode) ListXattrs(_ context.Context) ([]string, error)
- func (i *Inode) Lock(_ context.Context, _ proto.LockType, _ proto.LockFlags, _, _ uint64, _ uint32, ...) (proto.LockStatus, error)
- func (i *Inode) Lookup(_ context.Context, name string) (Node, error)
- func (i *Inode) Mkdir(_ context.Context, _ string, _ proto.FileMode, _ uint32) (Node, error)
- func (i *Inode) Mknod(_ context.Context, _ string, _ proto.FileMode, _, _, _ uint32) (Node, error)
- func (i *Inode) Open(_ context.Context, _ uint32) (FileHandle, uint32, error)
- func (i *Inode) Parent() *Inode
- func (i *Inode) QID() proto.QID
- func (i *Inode) Read(_ context.Context, _ []byte, _ uint64) (int, error)
- func (i *Inode) Readdir(_ context.Context) ([]proto.Dirent, error)
- func (i *Inode) Readlink(_ context.Context) (string, error)
- func (i *Inode) RemoveChild(name string)
- func (i *Inode) RemoveXattr(_ context.Context, _ string) error
- func (i *Inode) Rename(_ context.Context, _ string, _ Node, _ string) error
- func (i *Inode) SetXattr(_ context.Context, _ string, _ []byte, _ uint32) error
- func (i *Inode) Setattr(_ context.Context, _ proto.SetAttr) error
- func (i *Inode) StatFS(_ context.Context) (proto.FSStat, error)
- func (i *Inode) Symlink(_ context.Context, _, _ string, _ uint32) (Node, error)
- func (i *Inode) Unlink(_ context.Context, _ string, _ uint32) error
- func (i *Inode) Write(_ context.Context, _ []byte, _ uint64) (uint32, error)
- type InodeEmbedder
- type Middleware
- type Node
- type NodeCloser
- type NodeCreater
- type NodeFsyncer
- type NodeGetattrer
- type NodeLinker
- type NodeLocker
- type NodeLookuper
- type NodeMkdirer
- type NodeMknoder
- type NodeOpener
- type NodeRawReaddirer
- type NodeReaddirer
- type NodeReader
- type NodeReadlinker
- type NodeRenamer
- type NodeSetattrer
- type NodeStatFSer
- type NodeSymlinker
- type NodeUnlinker
- type NodeWriter
- type NodeXattrGetter
- type NodeXattrLister
- type NodeXattrRemover
- type NodeXattrSetter
- type Option
- func WithAnames(m map[string]Node) Option
- func WithAttacher(a Attacher) Option
- func WithIdleTimeout(d time.Duration) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMaxConnections(n int) Option
- func WithMaxFids(n int) Option
- func WithMaxInflight(n int) Option
- func WithMaxMsize(msize uint32) Option
- func WithMeter(mp metric.MeterProvider) Option
- func WithMiddleware(mw ...Middleware) Option
- func WithTracer(tp trace.TracerProvider) Option
- type QIDGenerator
- type QIDer
- type RawXattrer
- type ReadOnlyDir
- type ReadOnlyFile
- type Server
- type StaticFS
- type Symlink
- type XattrWriter
Constants ¶
This section is empty.
Variables ¶
var ( // ErrFidInUse is returned when attempting to allocate a fid that is // already present in the fid table. ErrFidInUse = errors.New("fid already in use") // ErrFidNotFound is returned when a fid lookup fails. ErrFidNotFound = errors.New("fid not found") // ErrNotNegotiated is returned when a message arrives before version // negotiation has completed. ErrNotNegotiated = errors.New("version not negotiated") // ErrMsizeTooSmall is returned when the client proposes an msize that // is too small to carry any useful payload. ErrMsizeTooSmall = errors.New("msize too small") // ErrNotDirectory is returned when a walk targets a node that does not // implement NodeLookuper. ErrNotDirectory = errors.New("not a directory") // ErrFidLimitExceeded is returned by fidTable.add when the configured // per-connection fid cap (see WithMaxFids) has been reached. Handlers // map this to proto.EMFILE in the response. ErrFidLimitExceeded = errors.New("fid limit exceeded") )
Sentinel errors for the server package.
Functions ¶
func EncodeDirents ¶
EncodeDirents packs dirents into bytes fitting within maxBytes. Returns the packed bytes and the number of entries that fit.
Each entry is encoded as:
qid[13] + offset[8] + type[1] + name[s]
where name[s] = len[2] + name_bytes.
The returned []byte is a freshly-allocated copy-out — safe to retain past the call boundary.
func EncodeDirentsInto ¶ added in v1.6.0
EncodeDirentsInto packs dirents into dst. It returns the number of bytes written and the number of entries that fit.
It is the zero-allocation backend for EncodeDirents and readdirSimple.
func NewTraceHandler ¶
NewTraceHandler wraps a slog.Handler with OTel trace ID correlation. Log records emitted within an active span context will include trace_id and span_id attributes. Use this to wrap custom handlers when providing a logger via WithLogger.
func PathQID ¶
PathQID returns a deterministic QID derived from the given path string using FNV-1a 64-bit hashing. Useful for nodes with stable, known paths.
Collision behavior: FNV-1a is not cryptographic; two distinct path strings can hash to the same 64-bit value with birthday-paradox probability around 2^32 paths. Suitable for small, stable namespaces where the path set is controlled by the server; unsuitable for hashing untrusted user-supplied path components. Use QIDGenerator for collision-free allocation when the QIDs do not need to be path-deterministic.
Types ¶
type Attacher ¶
type Attacher interface {
// Attach resolves the root node for a connection given the uname and
// aname from Tattach.
Attach(ctx context.Context, uname, aname string) (Node, error)
}
Attacher provides full-control attach handling. When set via WithAttacher, it overrides the default root-node and aname-dispatch behavior.
type ConnInfo ¶
type ConnInfo struct {
Protocol string // "9P2000.L" or "9P2000.u"
Msize uint32 // Negotiated message size
RemoteAddr string // Remote address of the client
}
ConnInfo exposes per-connection metadata to node handlers. A pointer to ConnInfo is injected into the request context by the server before each handler invocation; retrieve it with ConnFromContext.
Fields are read-only. Callers MUST NOT mutate a ConnInfo returned from ConnFromContext -- the same pointer is shared across every request on the same connection, and mutation would race with concurrent request goroutines and corrupt observability labels.
func ConnFromContext ¶
ConnFromContext returns the connection info for the current request. Returns nil if not called within a connection handler.
There is intentionally no NewContext(ctx, *ConnInfo) helper: ConnInfo is injected by the server from negotiated connection state, and user code cannot construct a valid ConnInfo externally. This asymmetric accessor pattern mirrors stdlib context keys that represent server-owned state (e.g., the net/http pattern for server-injected values).
type Device ¶
Device is a node that represents a device node (block or character). Create with DeviceNode.
func DeviceNode ¶
func DeviceNode(gen *QIDGenerator, major, minor uint32) *Device
DeviceNode creates a device node with the given major and minor numbers. The node's QID is assigned from gen with type QTFILE.
type FileHandle ¶
type FileHandle any
FileHandle is a marker type for per-open state returned by NodeOpener.Open. Implement FileReader, FileWriter, FileReleaser, FileSyncer, FileReaddirer, or FileRawReaddirer on the returned value to handle the corresponding wire operations.
A nil FileHandle is permitted for nodes that don't need per-open state; the server will not invoke File-level interfaces against a nil handle and instead falls back to Node-level capability dispatch on the underlying node.
type FileRawReaddirer ¶
type FileRawReaddirer interface {
// RawReaddir reads raw dirent bytes for the given offset into buf
// and returns the number of bytes read. The caller provides a buffer
// sized to the 9P Treaddir count (clamped to msize).
RawReaddir(ctx context.Context, buf []byte, offset uint64) (int, error)
}
FileRawReaddirer is implemented by file handles that manage their own readdir offset tracking.
type FileReaddirer ¶
type FileReaddirer interface {
// Readdir returns all directory entries for the open handle.
Readdir(ctx context.Context) ([]proto.Dirent, error)
}
FileReaddirer is implemented by file handles that support reading directory entries.
type FileReader ¶
type FileReader interface {
// Read reads up to len(buf) bytes starting at offset into buf and
// returns the number of bytes read. The caller provides a buffer
// sized to the 9P Tread count (clamped to msize); implementations
// fill it and return n.
Read(ctx context.Context, buf []byte, offset uint64) (int, error)
}
FileReader is implemented by file handles that support reading.
type FileReleaser ¶
type FileReleaser interface {
// Release releases resources associated with this file handle.
Release(ctx context.Context) error
}
FileReleaser is implemented by file handles that need cleanup on clunk.
type FileSyncer ¶ added in v1.1.0
type FileSyncer interface {
// Fsync flushes pending writes on the open file to durable storage.
Fsync(ctx context.Context) error
}
FileSyncer is implemented by file handles that support flushing buffered writes on the open handle to durable storage. Checked before NodeFsyncer by the bridge: Tfsync on an opened fid with a handle that implements FileSyncer takes the handle path; only if the handle does not implement FileSyncer does the bridge fall back to NodeFsyncer on the underlying node.
type FileWriter ¶
type FileWriter interface {
// Write writes data at the given offset and returns the count of bytes written.
Write(ctx context.Context, data []byte, offset uint64) (uint32, error)
}
FileWriter is implemented by file handles that support writing.
type Handler ¶
Handler processes a decoded 9P message and returns the response. Middleware wraps Handler values to add cross-cutting behavior (tracing, metrics, logging) without modifying dispatch logic.
type Inode ¶
type Inode struct {
// contains filtered or unexported fields
}
Inode provides default implementations for all capability interfaces, returning ENOSYS for unimplemented operations. Embed *Inode in your node struct and call Init to set up the QID and back-reference.
Inode also manages the filesystem tree: parent/child relationships, child lookup, and child enumeration.
func (*Inode) AddChild ¶
AddChild adds a child inode under the given name. The child's parent is set to this inode. The children map is lazily initialized. Lock ordering: parent lock acquired before child lock to avoid deadlock.
func (*Inode) Create ¶
func (i *Inode) Create(_ context.Context, _ string, _ uint32, _ proto.FileMode, _ uint32) (Node, FileHandle, uint32, error)
Create returns (nil, nil, 0, proto.ENOSYS). Override by implementing NodeCreater.
func (*Inode) EmbeddedInode ¶
EmbeddedInode returns a pointer to the embedded Inode. Satisfies InodeEmbedder.
func (*Inode) Fsync ¶ added in v1.1.0
Fsync returns proto.ENOSYS. Override by implementing NodeFsyncer.
func (*Inode) GetLock ¶
func (i *Inode) GetLock(_ context.Context, _ proto.LockType, _, _ uint64, _ uint32, _ string) (proto.LockType, uint64, uint64, uint32, string, error)
GetLock returns zero values and proto.ENOSYS. Override by implementing NodeLocker.
func (*Inode) GetXattr ¶
GetXattr returns (nil, proto.ENOSYS). Override by implementing NodeXattrGetter.
func (*Inode) Getattr ¶
Getattr returns (proto.Attr{}, proto.ENOSYS). Override by implementing NodeGetattrer.
func (*Inode) Init ¶
func (i *Inode) Init(qid proto.QID, node InodeEmbedder)
Init initializes the Inode with a QID and a back-reference to the embedding node. If node is nil, the Inode references itself.
func (*Inode) ListXattrs ¶
ListXattrs returns (nil, proto.ENOSYS). Override by implementing NodeXattrLister.
func (*Inode) Lock ¶
func (i *Inode) Lock(_ context.Context, _ proto.LockType, _ proto.LockFlags, _, _ uint64, _ uint32, _ string) (proto.LockStatus, error)
Lock returns (0, proto.ENOSYS). Override by implementing NodeLocker.
func (*Inode) Lookup ¶
Lookup resolves a child by name. If the child exists, it returns the user's node (via the back-reference). If not found, returns proto.ENOENT. Users override this by implementing NodeLookuper on their struct.
func (*Inode) Readdir ¶
Readdir returns (nil, proto.ENOSYS). Override by implementing NodeReaddirer.
func (*Inode) Readlink ¶
Readlink returns ("", proto.ENOSYS). Override by implementing NodeReadlinker.
func (*Inode) RemoveChild ¶
RemoveChild removes a child by name.
func (*Inode) RemoveXattr ¶
RemoveXattr returns proto.ENOSYS. Override by implementing NodeXattrRemover.
func (*Inode) StatFS ¶
StatFS returns (proto.FSStat{}, proto.ENOSYS). Override by implementing NodeStatFSer.
func (*Inode) Symlink ¶
Symlink returns (nil, proto.ENOSYS). Override by implementing NodeSymlinker.
type InodeEmbedder ¶
type InodeEmbedder interface {
// EmbeddedInode returns the embedded Inode pointer.
EmbeddedInode() *Inode
}
InodeEmbedder is the base interface for all filesystem nodes that use the Inode tree management. Implement by embedding *Inode in your struct and calling Inode.Init during construction.
type Middleware ¶
Middleware wraps a Handler, adding behavior before and/or after dispatch. Compose by stacking: the first middleware added is outermost (first to execute, last to see the response).
func NewLoggingMiddleware ¶
func NewLoggingMiddleware(logger *slog.Logger) Middleware
NewLoggingMiddleware returns a Middleware that logs each 9P request at Debug level with structured attributes: op (operation type), duration (elapsed time), and error (whether the response was an error). The logger should be wrapped with NewTraceHandler for trace correlation.
type Node ¶
type Node interface {
// QID returns the server's unique identifier for this node.
QID() proto.QID
}
Node is the minimal interface every filesystem node must implement. For most use cases, embed *Inode instead and implement capability interfaces selectively.
type NodeCloser ¶
type NodeCloser interface {
// Close releases resources associated with this node.
Close(ctx context.Context) error
}
NodeCloser is implemented by nodes that need cleanup on clunk.
type NodeCreater ¶
type NodeCreater interface {
// Create creates a new file in this directory.
Create(ctx context.Context, name string, flags uint32, mode proto.FileMode, gid uint32) (Node, FileHandle, uint32, error)
}
NodeCreater is implemented by directory nodes that can create files.
type NodeFsyncer ¶ added in v1.1.0
type NodeFsyncer interface {
// Fsync flushes pending writes to durable storage.
Fsync(ctx context.Context) error
}
NodeFsyncer is implemented by nodes that support flushing node-level state to durable storage. When both FileSyncer (on the open file handle) and NodeFsyncer are available for a given fid, the bridge prefers FileSyncer.
The Tfsync wire message carries a datasync flag which is decoded but not surfaced here -- implementations always perform a full fsync.
type NodeGetattrer ¶
type NodeGetattrer interface {
// Getattr retrieves attributes specified by mask.
Getattr(ctx context.Context, mask proto.AttrMask) (proto.Attr, error)
}
NodeGetattrer is implemented by nodes that return file attributes.
type NodeLinker ¶
type NodeLinker interface {
// Link creates a hard link named name in this directory pointing to target.
Link(ctx context.Context, target Node, name string) error
}
NodeLinker is implemented by directory nodes that can create hard links. The directory receives the request; target is the existing node being linked (resolved from Tlink.Fid). Wire format: dfid[4] fid[4] name[s] -- dfid is this directory, fid is the target.
type NodeLocker ¶
type NodeLocker interface {
// Lock acquires, tests, or releases a POSIX byte-range lock.
Lock(ctx context.Context, lockType proto.LockType, flags proto.LockFlags, start, length uint64, procID uint32, clientID string) (proto.LockStatus, error)
// GetLock tests whether a lock could be placed, returning the conflicting
// lock parameters if one exists.
GetLock(ctx context.Context, lockType proto.LockType, start, length uint64, procID uint32, clientID string) (proto.LockType, uint64, uint64, uint32, string, error)
}
NodeLocker is implemented by nodes that support POSIX byte-range locking. Implementations control blocking behavior; the library does not impose any blocking policy. Implementations should respect context deadlines if blocking.
NodeLocker is the only lock interface -- Lock and GetLock are two halves of the same 9P Tlock/Tgetlock pair, always co-implemented in practice, so splitting them into separate single-method interfaces would force every lock implementer to satisfy two interfaces with no separation benefit.
type NodeLookuper ¶
type NodeLookuper interface {
// Lookup resolves a child by name, returning the child Node or an error.
// Return proto.ENOENT (wrapped) if the name does not exist.
Lookup(ctx context.Context, name string) (Node, error)
}
NodeLookuper is implemented by directory nodes that can resolve child names. Walk calls Lookup for each path element.
type NodeMkdirer ¶
type NodeMkdirer interface {
// Mkdir creates a new subdirectory in this directory.
Mkdir(ctx context.Context, name string, mode proto.FileMode, gid uint32) (Node, error)
}
NodeMkdirer is implemented by directory nodes that can create subdirectories.
type NodeMknoder ¶
type NodeMknoder interface {
// Mknod creates a device node named name with the given mode, major/minor
// numbers, and owning group.
Mknod(ctx context.Context, name string, mode proto.FileMode, major, minor, gid uint32) (Node, error)
}
NodeMknoder is implemented by directory nodes that can create device nodes.
type NodeOpener ¶
type NodeOpener interface {
// Open opens the node with the given flags and returns a FileHandle,
// response flags, and any error.
Open(ctx context.Context, flags uint32) (FileHandle, uint32, error)
}
NodeOpener is implemented by nodes that can be opened.
type NodeRawReaddirer ¶
type NodeRawReaddirer interface {
// RawReaddir reads raw dirent bytes for the given offset into buf
// and returns the number of bytes read. The caller provides a buffer
// sized to the 9P Treaddir count (clamped to msize).
RawReaddir(ctx context.Context, buf []byte, offset uint64) (int, error)
}
NodeRawReaddirer is implemented by directory nodes that manage their own offset tracking and dirent packing.
type NodeReaddirer ¶
type NodeReaddirer interface {
// Readdir returns all directory entries. The server caches and packs
// them into Rreaddir responses using EncodeDirents.
Readdir(ctx context.Context) ([]proto.Dirent, error)
}
NodeReaddirer is implemented by directory nodes that return all entries. The server handles offset tracking and dirent packing per-fid.
type NodeReader ¶
type NodeReader interface {
// Read reads up to len(buf) bytes starting at offset into buf and
// returns the number of bytes read. The caller provides a buffer
// sized to the 9P Tread count (clamped to msize); implementations
// fill it and return n.
Read(ctx context.Context, buf []byte, offset uint64) (int, error)
}
NodeReader is implemented by nodes that support reading.
type NodeReadlinker ¶
type NodeReadlinker interface {
// Readlink returns the target path of this symbolic link.
Readlink(ctx context.Context) (string, error)
}
NodeReadlinker is implemented by symlink nodes that can report their target.
type NodeRenamer ¶
type NodeRenamer interface {
// Rename moves the entry oldName from this directory to newDir with newName.
Rename(ctx context.Context, oldName string, newDir Node, newName string) error
}
NodeRenamer is implemented by directory nodes that support renaming entries. newDir is the target directory Node resolved from the new directory fid.
type NodeSetattrer ¶
type NodeSetattrer interface {
// Setattr modifies attributes specified in attr.
Setattr(ctx context.Context, attr proto.SetAttr) error
}
NodeSetattrer is implemented by nodes that support setting attributes.
type NodeStatFSer ¶
type NodeStatFSer interface {
// StatFS returns filesystem statistics for the filesystem containing this node.
StatFS(ctx context.Context) (proto.FSStat, error)
}
NodeStatFSer is implemented by nodes that can report filesystem statistics.
type NodeSymlinker ¶
type NodeSymlinker interface {
// Symlink creates a symbolic link named name pointing to target in this
// directory. Returns the new symlink Node.
Symlink(ctx context.Context, name, target string, gid uint32) (Node, error)
}
NodeSymlinker is implemented by directory nodes that can create symbolic links.
type NodeUnlinker ¶
type NodeUnlinker interface {
// Unlink removes the entry named name from this directory.
Unlink(ctx context.Context, name string, flags uint32) error
}
NodeUnlinker is implemented by directory nodes that can remove entries. Flags may include AT_REMOVEDIR (0x200) to indicate directory removal.
type NodeWriter ¶
type NodeWriter interface {
// Write writes data at the given offset and returns the count of bytes written.
Write(ctx context.Context, data []byte, offset uint64) (uint32, error)
}
NodeWriter is implemented by nodes that support writing.
type NodeXattrGetter ¶
NodeXattrGetter reads a single extended attribute by name. Called in response to Txattrwalk with a non-empty name on a node that does NOT implement RawXattrer.
type NodeXattrLister ¶
NodeXattrLister returns all extended attribute names set on the node. Called in response to Txattrwalk with an empty name on a node that does NOT implement RawXattrer.
type NodeXattrRemover ¶
NodeXattrRemover removes an extended attribute by name. Called in response to Txattrcreate(size=0) + Tclunk on a node that does NOT implement RawXattrer.
type NodeXattrSetter ¶
type NodeXattrSetter interface {
SetXattr(ctx context.Context, name string, data []byte, flags uint32) error
}
NodeXattrSetter sets (creates or replaces) an extended attribute. Called in response to Txattrcreate followed by Twrite and Tclunk on a node that does NOT implement RawXattrer. The flags argument is the raw Txattrcreate flags field.
type Option ¶
type Option func(*Server)
Option configures a Server. Pass to New.
func WithAnames ¶
WithAnames sets a map of aname strings to root nodes for vhost-style attach dispatch. When set, Tattach uses the aname field to select the root node. An empty aname falls back to the default root.
func WithAttacher ¶
WithAttacher sets a custom Attacher that handles all Tattach requests. When set, it takes precedence over both the default root node and any aname map configured via WithAnames.
func WithIdleTimeout ¶
WithIdleTimeout sets the per-connection idle timeout. When d > 0, the server resets read and write deadlines on the underlying net.Conn before each I/O operation. A connection that sees no activity for the duration is closed. Default: 0 (no timeout -- caller manages via net.Conn wrapping if needed).
func WithLogger ¶
WithLogger sets the structured logger for the server. The handler is automatically wrapped with trace ID correlation (see NewTraceHandler). Default: slog.Default() with trace correlation.
func WithMaxConnections ¶ added in v1.1.0
WithMaxConnections sets the maximum number of concurrent connections the server will serve. When the limit is reached, ServeConn closes the new connection immediately, logs a warning, and increments the ninep.server.connections_rejected OTel counter. Values less than 1 disable the limit. Default: 0 (no limit).
func WithMaxFids ¶ added in v1.1.0
WithMaxFids sets the maximum number of concurrent fids the server will allow per connection. When the cap is reached, fid-creating operations (Tattach, Twalk, Txattrwalk) return EMFILE. The cap check runs inside fidTable.add under the write lock, making enforcement race-free. Values less than 1 disable the limit. Default: 0 (no limit).
func WithMaxInflight ¶
WithMaxInflight sets the maximum number of concurrent in-flight requests per connection. Values less than 1 are clamped to 1. Default: 64.
func WithMaxMsize ¶
WithMaxMsize sets the maximum message size the server will accept during version negotiation. Default: 1048576 (1 MiB, matches the Linux kernel's silent msize cap).
func WithMeter ¶
func WithMeter(mp metric.MeterProvider) Option
WithMeter sets the OpenTelemetry MeterProvider for the server. When set, an OTel middleware is automatically prepended to the middleware chain, recording duration, request/response sizes, and active request counts. If not set, no metrics overhead is incurred.
func WithMiddleware ¶
func WithMiddleware(mw ...Middleware) Option
WithMiddleware adds middleware to the server's dispatch chain. Middleware runs in order: the first added is outermost (first to execute). Multiple calls append to the existing chain.
func WithTracer ¶
func WithTracer(tp trace.TracerProvider) Option
WithTracer sets the OpenTelemetry TracerProvider for the server. When set, an OTel middleware is automatically prepended to the middleware chain, producing a span for every 9P operation. If not set, no tracing overhead is incurred.
type QIDGenerator ¶
type QIDGenerator struct {
// contains filtered or unexported fields
}
QIDGenerator produces QIDs with monotonically increasing Path values. Safe for concurrent use.
type QIDer ¶
QIDer is implemented by nodes that provide their own QID. When present, nodeQID uses this in preference to Inode.QID.
type RawXattrer ¶
type RawXattrer interface {
HandleXattrwalk(ctx context.Context, name string) ([]byte, error)
HandleXattrcreate(ctx context.Context, name string, size uint64, flags uint32) (XattrWriter, error)
}
RawXattrer provides protocol-level control over the xattr two-phase flow. When a node implements RawXattrer, it takes precedence over the simple xattr interfaces (NodeXattrGetter, NodeXattrSetter, NodeXattrLister, NodeXattrRemover) -- a node implementing RawXattrer need not implement any of those.
HandleXattrwalk is called for both get (name != "") and list (name == ""). It returns the full xattr data that will be served to Tread calls.
HandleXattrcreate returns an XattrWriter that accumulates Twrite data and commits the xattr on Tclunk.
RawXattrer is intentionally two-method: HandleXattrwalk and HandleXattrcreate are the two halves of the 9P xattr two-phase protocol (read-side and write-side). A node implementing only one half would be ill-defined. For the high-level alternative (one method per operation), implement NodeXattrGetter/Setter/Lister/Remover instead.
type ReadOnlyDir ¶
type ReadOnlyDir struct {
Inode
}
ReadOnlyDir is a composable node for directories that support Lookup, Readdir, and Getattr but not mutation. Embed in your struct and override Lookup/Readdir/Getattr as needed.
ReadOnlyDir relies on the embedded Inode defaults to return ENOSYS for Create (NodeCreater), Mkdir (NodeMkdirer), Unlink (NodeUnlinker), Rename (NodeRenamer), Link (NodeLinker), Symlink (NodeSymlinker), and Mknod (NodeMknoder). The type is a signal-of-intent: the compile-time surface is identical to embedding Inode directly, but the named type documents the contract.
type ReadOnlyFile ¶
type ReadOnlyFile struct {
Inode
}
ReadOnlyFile is a composable node for files that support Open, Read, and Getattr but not mutation. Embed in your struct and override Open/Read/Getattr as needed.
ReadOnlyFile relies on the embedded Inode defaults to return ENOSYS for Write (FileWriter), Setattr (NodeSetattrer), Create (NodeCreater), Mkdir (NodeMkdirer), Unlink (NodeUnlinker), Rename (NodeRenamer), Link (NodeLinker), and Symlink (NodeSymlinker). The type is a signal-of-intent: the compile-time surface is identical to embedding Inode directly, but the named type documents the contract.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server serves the 9P protocol over network connections. Create with New.
func New ¶
New creates a Server rooted at the given Node. Options configure behavior. The root must implement NodeLookuper for walk resolution.
func (*Server) Serve ¶
Serve accepts connections from ln and serves each one in a new goroutine. It blocks until the context is cancelled or the listener returns an error.
func (*Server) ServeConn ¶
ServeConn serves a single 9P connection. It blocks until the connection is closed or the context is cancelled.
When the server has a WithMaxConnections limit configured and the limit is reached, ServeConn closes nc immediately, logs a warning, increments the ninep.server.connections_rejected counter, and returns without serving.
type StaticFS ¶
StaticFS is a node that returns fixed filesystem statistics. It implements NodeStatFSer. Embed or compose with other node types to add StatFS support.
func StaticStatFS ¶
func StaticStatFS(gen *QIDGenerator, stat proto.FSStat) *StaticFS
StaticStatFS creates a node that returns the given filesystem statistics. The node's QID is assigned from gen with type QTFILE.
type Symlink ¶
Symlink is a node that represents a symbolic link. It implements NodeReadlinker to return the link target. Create with SymlinkTo.
func SymlinkTo ¶
func SymlinkTo(gen *QIDGenerator, target string) *Symlink
SymlinkTo creates a symbolic link node pointing to target. The node's QID is assigned from gen with type QTSYMLINK. The returned node implements NodeReadlinker.
type XattrWriter ¶
type XattrWriter interface {
Write(ctx context.Context, data []byte) (int, error)
Commit(ctx context.Context) error
}
XattrWriter accumulates xattr write data and commits on Close. Returned by RawXattrer.HandleXattrcreate. The library calls Write for each Twrite on the xattr fid, then Commit on Tclunk.
Write and Commit form an accumulator lifecycle (write-phase then commit-phase); they are always used together per the 9P xattr two-phase protocol, so XattrWriter is intentionally two-method.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package fstest provides a protocol-level test harness for validating filesystem implementations against the 9P2000.L contract.
|
Package fstest provides a protocol-level test harness for validating filesystem implementations against the 9P2000.L contract. |
|
Package memfs provides in-memory filesystem node types for use with the ninep server.
|
Package memfs provides in-memory filesystem node types for use with the ninep server. |
|
Package passthrough implements a 9P filesystem that proxies all operations to the host OS filesystem.
|
Package passthrough implements a 9P filesystem that proxies all operations to the host OS filesystem. |