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.

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 no sudo and no kernel module.
  • --msize defaults 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 false to write through synchronously.
  • --read-only mounts 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.

Mount Options

Required Options

OptionDescription
trans=tcp or trans=unixUse TCP or Unix socket transport
port=5564ZeroFS 9P port (TCP only)
version=9p2000.LUse Linux-specific 9P version

Performance Options

OptionDescriptionDefault
msize=NMaximum message size in bytes128K
cache=modeCaching mode (see below)none
access=modeAccess check modeuser

Cache Modes

ModeDescriptionUse Case
noneNo caching, direct I/OMaximum consistency
looseRelaxed consistencyPerformance over consistency
fscachePersistent cacheCache survives unmount
mmapEnable mmap supportRequired for memory-mapped file access

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

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 Txattrwalk with ENOTSUP, so xattrs (and POSIX ACLs, which are stored as xattrs) are unavailable from any client (zerofs mount returns ENOSYS for xattr calls without contacting the server). setfacl and getfattr fail; rsync -X and cp --preserve=xattr cannot 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 uname root and to 65534 (nobody) otherwise.
  • Partially supported operations on zerofs mount: plain fallocate (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 return EOPNOTSUPP.
  • Unsupported operations on zerofs mount: copy_file_range, lseek with SEEK_HOLE/SEEK_DATA, ioctl, and poll return ENOSYS. Where the kernel has a fallback it uses it (cp copies with regular reads and writes).
  • No rename Flags on zerofs mount: rename with flags (RENAME_NOREPLACE, RENAME_EXCHANGE, RENAME_WHITEOUT) returns EINVAL because 9P renameat carries no flag field. Plain rename works.

Next Steps

Was this page helpful?