A Plan 9 Filesystem and Namespaces Suite for Zig

A Plan 9 Filesystem Protocol Suite for Zig

I’ve been building a suite of libraries and tools implementing the Plan 9
filesystem protocol (9P2000, 9P2000.u, 9P2000.L) in Zig. The project is modular
— each library handles one concern, and they compose together for different use
cases. Everything targets Zig 0.16-dev with std.Io async support and
basic libraries cross-compiles to all tier 1 and tier 2 platforms.

Here’s a tour of what’s in the box(es).

nine_p — Wire Protocol

The foundation. A zero-copy, allocation-free wire codec for all three 9P
dialects. Messages are parsed as views over the receive buffer — field access
compiles down to a single load from a known offset. No copies, no allocations
at the protocol layer.

The transport layer owns separate recv and encode buffers, with three
implementations: WireTransport for network I/O, using Io.Reader/Io.Writer,
MemoryPipe for synchronous
IPC, and QueueTransport for async IPC via std.Io.Queue. Buffer size is
runtime (negotiated msize).

network transport via TCP is handled by nine_serve, see below.

https://codeberg.org/typedlambda/nine_p

nine_vfs — Virtual Filesystem Server

The equivalent of Plan 9’s lib9p — manages an in-memory file tree, fid
lifecycle, reference counting, and request dispatch. You implement a handler
with attach, open, read and optionally write, create, remove,
lock, etc. The library handles walk, stat, directory reads, dialect
negotiation, and all the mechanical 9P plumbing.

Two variants: SrvT for dynamic trees that change at runtime (files created,
removed, refcounted across connections),

SrvStaticT for compile-time
trees where the namespace lives in the binary as comptime data — zero runtime
allocation beyond the fid table.

Also provides a 9p command / control file parser, for custom filesystems.

https://codeberg.org/typedlambda/nine_vfs

nine_vfs_ram — In-Memory Ramdisk

A complete in-memory filesystem handler for nine_vfs. Files, directories,
symlinks, permissions, and POSIX advisory byte-range locking. Memory quota via
BoundedAllocator (OOM maps to ENOSPC). Per-file size limits. The allocator is
split: metadata uses a general allocator, file content uses the bounded one —
so quota enforcement targets data without starving the tree.

Mainly used to test all of the operations, therfore support for file locking etc.

https://codeberg.org/typedlambda/nine_vfs_ram

nine_fs — Path-Based Server Adapter

A lighter alternative to nine_vfs for wrapping external filesystems. Instead of
managing a tree, nine_fs maintains a fid-to-path mapping and delegates
everything to a duck-typed Fs implementation. Walk becomes path
concatenation, stat becomes a path lookup. Good for host filesystem adapters,
like a NFS bridge, or anything where the tree lives outside the 9P server.

https://codeberg.org/typedlambda/nine_fs

nine_fs_host — Host Filesystem Backend

Implements the nine_fs Fs trait by delegating to std.Io.Dir and
std.Io.File. Serves local OS directories over 9P. Cross-platform file
locking: Linux via OFD locks (fcntl F_OFD_SETLK), Windows via
NtLockFile/NtUnlockFile.

this is the backend to use for a 9p local file server like nineserve (below).

https://codeberg.org/typedlambda/nine_fs_host

nine_serve — Connection Dispatch

Transport-agnostic TCP server and request queue. serveTcp accepts
connections, enforces a configurable connection limit, and dispatches each to a
handler. serveOne for single-connection setups (pipes, serial). An async
ReqQueue with Tflush cancellation support for deferred request processing.

the filesystem servers above are transport agnostic, but if you want tcp this is a dispatch loop for it.

https://codeberg.org/typedlambda/nine_serve

nineserve — Ready-to-Use Server

A CLI server combining everything above. Two modes: serve local directories
(via nine_fs_host) or an in-memory ramdisk (via nine_vfs_ram), over TCP with
dialect negotiation. Configurable exports, memory limits, per-file size caps.

This server can not do both at the same time, as it is not using the namespace client library.
It is an example how to create servers without the ninepea client abstractions following next.

nineserve -r doc -w pub=public nineserve --ram -m 128M -f 16M

https://codeberg.org/typedlambda/nineserve

ninecp — Interactive Client

An sftp-like 9P client with ls, cd, get, put, mget, mput, rm,
mkdir, chmod, stat, df, glob patterns, a built-in line editor (ed),
batch mode, and local shell command execution (!). Supports all three
dialects with automatic negotiation.

Inspired by the classic sftp interface, but enhanced.

This uses nine_p directly to act as a client, use this as a exampe of writing a simple client,
if you do not want or need the full ninepea client abstractions.

https://codeberg.org/typedlambda/ninecp

ninepea — Client Library with Namespaces

This is where Plan 9 influence is strongest. ninepea implements Plan 9
namespace semantics: mount, bind, union directories.

You build a namespace by
mounting remote 9P servers and local directories, then access files through the
unified namespace.

NinepeaIo wraps std.Io to intercept file operations and
route them through the namespace — so standard Zig I/O calls transparently go
through 9P mounts.

The Ns type manages the namespace tree, handling mount ordering
(before/after/replace), path resolution across mount boundaries, and fid
caching. LocalMount serves local directories, MountPoint wraps remote 9P
connections.

Inspired directly by Plan 9’s per-process namespace and the bind/mount model
that makes everything composable.

https://codeberg.org/typedlambda/ninepea

ninepea_namespace_fs — Namespace-to-Server Bridge

The inverse of ninepea: instead of mounting remote servers into a local
namespace, this takes a ninepea namespace and serves it back over 9P. A remote
client connects and sees the assembled namespace — local mounts, remote mounts,
union directories — as a single coherent file tree.

This is how you build composite servers: mount several 9P servers and local
directories into a namespace, then re-export the whole thing as one server.
Clients don’t need to know about the internal mount structure.

you can use this to create a realy flexiple server supporting arbitrary mounts.
but for now i did not have the need for it. But the demo application uses it, to let you play with the namespace.

https://codeberg.org/typedlambda/ninepea_namespace_fs

demo_ninepea_server — Platform Directory Server

A demo that ties it all together. Discovers platform-specific directories (home,
documents, downloads, pictures, etc. via known-folders) and serves them over
9P through a ninepea namespace with union mounts for the configuration folders.
Also mounts a nine_vfs_ram ramdisk at /tmp. Shows how ninepea, ninepea_namespace_fs, and nine_vfs_ram
compose into a single server with mixed local and in-memory backends.

This is currently the best application example, if you remove ninepea_namespace_fs you have a
basic application template.

https://codeberg.org/typedlambda/demo_ninepea_server

nine_fusefs3 — FUSE3 Bridge

Mount a remote 9P server as a local filesystem using FUSE3. Proof of concept /
alpha quality. Tested on Windows (WinFSP). Server response values are
bounds-checked to prevent panics from a malicious server.

https://codeberg.org/typedlambda/nine_fusefs3

Design Choices

Modular by default. Each library is a separate package with its own
build.zig. You can use nine_p alone for a custom protocol
handler, or compose nine_vfs + nine_vfs_ram + nine_serve for a full server, or
just use nineserve out of the box.

Zero-copy for the protocol layer. Message parsing, string fields, directory
entries — all alias the wire buffer. Copies happen only at boundaries
(transport recv, response encode).

Comptime generics, runtime buffers. Server types are comptime-generic over
transport and handler. Buffer sizes are runtime, determined by negotiated
msize. Optional handler methods are detected via @hasDecl — absent methods
generate no code.

Cross-platform. CI builds for x86_64/aarch64/x86/arm/riscv64/ppc64le across
Linux, macOS, Windows, FreeBSD, NetBSD, and WASM. QEMU integration tests for
foreign architectures at least for the basic libraries.

The filesystems have been tested using ninecp, with the fusefs adapter (mainly on windows) and on Linux using the kernel 9p filesystem client.

The NinepeaIo wrapper for std.Io is not yet well tested. For now a proof of concept.

4 Likes

(post deleted by author)