9P File Access
9P is an alternative to NFS for mounting ZeroFS on Linux. ZeroFS serves 9P2000.L, the Linux dialect of the protocol that originated in Plan 9.
Over 9P, fsync returns only after data reaches stable storage. NFS COMMIT semantics allow fsync to return before data reaches stable storage, so applications that depend on fsync durability (databases, transactional systems) should mount over 9P.
Installation
9P support is included in the Linux kernel. You may need to load the module:
# Load 9P modules
sudo modprobe 9p
sudo modprobe 9pnet_tcp
# Verify modules are loaded
lsmod | grep 9p
Basic Usage
Mounting ZeroFS
TCP Mount (default)
# Create mount point
sudo mkdir -p /mnt/zerofs-9p
# Mount via 9P over TCP
sudo mount -t 9p -o trans=tcp,port=5564,version=9p2000.L 127.0.0.1 /mnt/zerofs-9p
Unix Socket Mount (lower latency)
For local mounts, a Unix domain socket avoids the TCP stack:
# Configure Unix socket in zerofs.toml
[servers.ninep]
addresses = ["127.0.0.1:5564"]
unix_socket = "/tmp/zerofs.sock"
# Start ZeroFS
# zerofs run --config zerofs.toml
# Mount via Unix socket
sudo mount -t 9p -o trans=unix,version=9p2000.L /tmp/zerofs.sock /mnt/zerofs-9p
Unmounting
sudo umount /mnt/zerofs-9p
Mounting with zerofs mount
The commands above use the kernel's 9P client. ZeroFS also ships its own client, run as zerofs mount, which connects to the same 9P server but runs in userspace over FUSE. The mount subcommand is compiled into Linux builds only; on macOS, mount over NFS instead.
# TCP
zerofs mount 127.0.0.1:5564 /mnt/zerofs-9p
# HA pair
zerofs mount node-a:5564,node-b:5564 /mnt/zerofs-9p
# Unix socket
zerofs mount /tmp/zerofs.sock /mnt/zerofs-9p
One target is host (the port defaults to 5564), host:port, tcp://host:port, or unix:/path/to.sock. A bare path starting with / or . is treated as a Unix socket; hostnames are resolved through DNS. For high availability, pass both nodes as a comma-separated target set. zerofs mount probes them concurrently and re-probes the set after a disconnect.
Connection-loss behavior differs. The kernel client does not rebuild a session after a server restart or network interruption; operations fail until the filesystem is unmounted and remounted. zerofs mount retries indefinitely with 50–500 ms backoff and rebuilds the session. Undispatched operations block until a server is available.
Session replay rebinds linked open handles by stable inode id and re-acquires recorded byte-range locks. An open file with no remaining namespace link is connection-local and cannot be replayed. Its presence during reconnect makes the logical mount session terminal with ESTALE; recovery requires a new mount.
Reorder-sensitive 9P2000.L.Z mutations retain the same operation envelope across retries. Automatic retry ends at the 120-second protocol horizon. An unresolved result is reported as an ambiguous connection failure.
A few other practical differences:
- It mounts as a regular user through
fusermount3, so nosudoand no kernel module. --msizedefaults to 10 MiB, which is also the server's maximum: the server clamps every client's requested msize to 10 MiB, so the default is the effective ceiling and raising it has no effect. The kernel client defaults to 128 KiB (see the options table below).- Because the client and server ship together, they negotiate private 9P extensions the kernel client doesn't have, including metadata fast paths and atomic fallocate.
- Writeback caching is on by default, so writes are buffered and flushed asynchronously (similar to the kernel client's
cache=mmap); pass--writeback falseto write through synchronously. --read-onlymounts the filesystem read-only.
Like the kernel client's access=user, each operation runs on the server as the local user that issued it: files are owned by whoever created them and the server enforces each user's own permissions, with no uid to configure. (As with access=user over an unauthenticated transport, this trusts the client-supplied uid and assumes a shared uid namespace.) The --access owner|root|all option controls who may reach the mount; root and all require user_allow_other in /etc/fuse.conf unless you mount as root.
Unmount it like any FUSE mount with fusermount3 -u /mnt/zerofs-9p, or stop the zerofs mount process. Run zerofs mount --help for the full set of options.
Mount Options
Required Options
| Option | Description |
|---|---|
trans=tcp or trans=unix | Use TCP or Unix socket transport |
port=5564 | ZeroFS 9P port (TCP only) |
version=9p2000.L | Use Linux-specific 9P version |
Performance Options
| Option | Description | Default |
|---|---|---|
msize=N | Maximum message size in bytes | 128K |
cache=mode | Caching mode (see below) | none |
access=mode | Access check mode | user |
Cache Modes
| Mode | Description | Use Case |
|---|---|---|
none | No caching, direct I/O | Maximum consistency |
loose | Relaxed consistency | Performance over consistency |
fscache | Persistent cache | Cache survives unmount |
mmap | Enable mmap support | Required for memory-mapped file access |
The cache=mmap option doesn't create a cache - it enables support for memory-mapped files. Use this only if your applications use mmap() system calls. For general performance, use cache=loose.
File Locking
ZeroFS supports POSIX byte-range locks, fcntl with F_SETLK, F_SETLKW, and F_GETLK, over 9P, from both the kernel client and zerofs mount. The clients carry lock requests as 9P Tlock and Tgetlock messages. Semantics follow POSIX: unlocking part of a range splits the lock, an overlapping lock from the same owner replaces the old one, read locks share a range, and write locks exclude all others. Locks are advisory: they constrain other lock requests, not reads and writes.
The server arbitrates locks between clients. Processes sharing one mount are arbitrated locally by that client, since the whole mount is a single 9P session. F_GETLK on a range locked by another client reports the holder's pid negated, the v9fs convention for a pid that has no meaning in the local pid namespace.
Blocking acquisition (F_SETLKW) through zerofs mount is polled: the server answers a contended request immediately instead of queueing the waiter, and the client retries every 50 ms until the lock is grantable.
Locks live in server memory, not in the object store. A client's locks are released when its connection closes, and a server restart or HA takeover starts with an empty lock table. zerofs mount records its locks and re-acquires the complete set before publishing a rebuilt session; the kernel client does not, since its mount is wedged after a disconnect. Another session may acquire a lock during the gap, so replay is not continuous ownership and advisory locks are not a distributed fencing primitive.
Configuration
Persistent Mount
Add to /etc/fstab:
# Basic persistent mount
127.0.0.1 /mnt/zerofs-9p 9p trans=tcp,port=5564,version=9p2000.L,_netdev 0 0
# With performance options
127.0.0.1 /mnt/zerofs-9p 9p trans=tcp,port=5564,version=9p2000.L,cache=loose,_netdev 0 0
Systemd Mount Unit
Create /etc/systemd/system/mnt-zerofs-9p.mount:
[Unit]
Description=ZeroFS 9P Mount
After=network.target
[Mount]
What=127.0.0.1
Where=/mnt/zerofs-9p
Type=9p
Options=trans=tcp,port=5564,version=9p2000.L,cache=loose,_netdev
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable mnt-zerofs-9p.mount
sudo systemctl start mnt-zerofs-9p.mount
Performance Tuning
Cache Mode Selection
Choose based on your workload:
# Development - frequent file changes
sudo mount -t 9p -o cache=none ...
# Applications using mmap (databases, etc.)
sudo mount -t 9p -o cache=mmap ...
# Best performance, accept some inconsistency
sudo mount -t 9p -o cache=loose ...
Troubleshooting
Mount Fails
# Check if ZeroFS is running
ps aux | grep zerofs
# Verify 9P port is open
nc -zv 127.0.0.1 5564
# Check kernel modules
lsmod | grep 9p
# Enable debug messages
sudo mount -t 9p -o trans=tcp,port=5564,debug=0x1ff 127.0.0.1 /mnt/test
Performance Issues
# Check current mount options
mount | grep 9p
# Test with different cache modes
for mode in none mmap loose; do
echo "Testing cache=$mode"
sudo umount /mnt/zerofs-9p
sudo mount -t 9p -o cache=$mode,trans=tcp,port=5564 127.0.0.1 /mnt/zerofs-9p
# Run your benchmark
done
Security Considerations
Like the NFS server, ZeroFS's 9P server has no built-in authentication:
- Bind to localhost only (default behavior)
- Use firewall rules for network access
- Consider SSH tunneling for remote access
- No encryption - use VPN or tunnel for sensitive data
Advanced Usage
Multiple Cache Modes
Mount the same filesystem with different cache modes:
# Consistent mount for important operations
sudo mount -t 9p -o cache=none,trans=tcp,port=5564 127.0.0.1 /mnt/zerofs-consistent
# Performance mount for read-heavy operations
sudo mount -t 9p -o cache=loose,trans=tcp,port=5564 127.0.0.1 /mnt/zerofs-fast
9P Limitations
Be aware of these 9P characteristics:
- No Extended Attributes: The server rejects
TxattrwalkwithENOTSUP, so xattrs (and POSIX ACLs, which are stored as xattrs) are unavailable from any client (zerofs mountreturnsENOSYSfor xattr calls without contacting the server).setfaclandgetfattrfail;rsync -Xandcp --preserve=xattrcannot copy xattrs onto the mount. - No Supplementary Groups: A 9P2000.L attach carries a single numeric uid. ZeroFS uses that uid as the gid and builds a one-entry group list, so the server sees each user as a member of exactly one group, numbered the same as their uid. Access granted only through another group (a mode 070 directory owned by a shared group, for example) fails with
EACCES. An unspecified uid (n_uname = -1) maps to 0 for unamerootand to 65534 (nobody) otherwise. - Partially supported operations on
zerofs mount: plainfallocate(mode 0) reserves quota and extends the file when needed. Hole punching (PUNCH_HOLE | KEEP_SIZE) and zero-range with optional keep-size are also supported. Zero-filled ranges are represented as sparse holes while logical growth remains quota-accounted. Standalone keep-size preallocation, collapse-range, insert-range, unshare-range, and write-zeroes returnEOPNOTSUPP. - Unsupported operations on
zerofs mount:copy_file_range,lseekwithSEEK_HOLE/SEEK_DATA,ioctl, andpollreturnENOSYS. Where the kernel has a fallback it uses it (cpcopies with regular reads and writes). - No rename Flags on
zerofs mount:renamewith flags (RENAME_NOREPLACE,RENAME_EXCHANGE,RENAME_WHITEOUT) returnsEINVALbecause 9Prenameatcarries no flag field. Plainrenameworks.