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
# Unix socket
zerofs mount /tmp/zerofs.sock /mnt/zerofs-9p
The 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.
The main difference is what happens when the connection drops. The kernel client leaves the mount wedged after a server restart or a network interruption — it returns errors until you unmount and remount. zerofs mount reconnects by itself: it retries indefinitely (50–500 ms backoff) and rebuilds the session in the background. Operations issued during the outage block until the server is reachable again — the mount hangs rather than returns errors, which matters for health checks. On the new session, open file handles are rebound to their stable inode ids and byte-range locks are re-acquired, so handles survive a server restart. One caveat: a non-idempotent operation (mkdir, create, rename, unlink) in flight at the instant the connection drops may be applied twice, because the client cannot know whether the server processed it before the disconnect.
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 a private fast path the kernel client doesn't have: a path lookup is a single round trip (a combined walk + getattr), and directory listings return each entry's attributes inline (readdirplus) instead of a stat per entry. That cuts the round trips on metadata-heavy work.
- 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. A server restart drops all locks: zerofs mount re-acquires the locks it held when it replays its session on reconnect (best-effort); the kernel client does not, since its mount is wedged after a disconnect.
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. - Unsupported Operations on
zerofs mount:fallocate,copy_file_range,lseekwithSEEK_HOLE/SEEK_DATA,ioctl, andpollreturnENOSYS. Where the kernel has a fallback it uses it (cpcopies with regular reads and writes); where it has none, the call fails (fallocate -l). - No rename Flags on
zerofs mount:renamewith flags (RENAME_NOREPLACE,RENAME_EXCHANGE,RENAME_WHITEOUT) returnsEINVALbecause 9Prenameatcarries no flag field. Plainrenameworks.