zerofs-client 0.1.0

Async, path-based client library for ZeroFS over 9P
Documentation
zerofs-client-0.1.0 has been yanked.

zerofs-client

An async, path-based Rust client for a ZeroFS server. It speaks 9P2000.L over TCP or a unix socket, and uses the ZeroFS fast-path extensions automatically when the server offers them.

The primary surface is one-shot path operations on a shared Client (read, write, stat, rename, mkdir), with File and Dir handles where statefulness pays off (chunked I/O, incremental listing, openat-style child operations). Paths are bytes, as on POSIX and the 9P wire: every path parameter is impl AsRef<Path>, so non-UTF-8 names work with no separate API.

This is a trusted-network client: you assert your uid at connect time, NFS-style.

Usage

use zerofs_client::{Client, OpenOptions};

#[tokio::main]
async fn main() -> Result<(), zerofs_client::ZeroFsError> {
    let fs = Client::connect("unix:/run/zerofs/9p.sock").await?;

    fs.create_dir_all("/projects/demo", 0o755).await?;
    fs.write("/projects/demo/hello.txt", b"hello from zerofs").await?;

    let data = fs.read("/projects/demo/hello.txt").await?;
    assert_eq!(&data[..], b"hello from zerofs");

    for entry in fs.read_dir("/projects/demo").await? {
        let size = entry.metadata.as_ref().map_or(0, |m| m.size);
        println!("{size:>8}  {}", entry.name);
    }

    let file = fs
        .open("/projects/demo/big.bin", OpenOptions::read_write().create(true))
        .await?;
    file.write_at(0, &vec![0u8; 4 << 20]).await?;
    file.sync_all().await?;
    file.close().await;
    Ok(())
}

Connection targets

Client::connect accepts:

  • unix:/path/to/sock: unix socket
  • tcp://host:port: TCP
  • host:port / host: TCP (default port 5564)
  • a bare filesystem path (/run/zerofs/9p.sock, ./sock): unix socket

Use Client::connect_with for explicit identity (uid/gid/uname), the attach subtree (aname), msize, and connect_timeout_ms.

Lifecycles

  • Close & drop. close() marks the handle closed immediately (later calls return Closed); it always succeeds, is idempotent, and never hangs. A handle's server-side fid is released and its number recycled when the handle is dropped (for scope-bound use, right after close()); dropping a handle you never closed does the same. Fid numbers are always reused, so a long-running client never exhausts them.
  • Cancellation. Every public future is cancel-safe: dropping it at any await point leaks nothing. There is no per-operation timeout parameter; bound waits with tokio::time::timeout.
  • Connection loss. The underlying session reconnects forever with backoff and replays its state; while the server is unreachable, calls block rather than fail. An op in flight at a disconnect is resent, so a non-idempotent op (rename, create, unlink) can apply twice across a reconnect.

Reads return Bytes

read, read_range, and File::read_at return [bytes::Bytes] (re-exported as zerofs_client::Bytes): cheap to clone and slice, derefs to &[u8], and a read served by a single round trip comes back with no copy. ZeroFsError converts to std::io::Error (impl From), so client calls propagate with ? in functions returning io::Result.

Features

All feature-gated additions are Rust-only and never cross the FFI boundary.

  • tokio-io (off by default): File::cursor() returning an io::FileCursor that implements AsyncRead + AsyncWrite + AsyncSeek over the positioned API, so tokio::io::copy and friends work.
  • stream (off by default): Dir::entries() returning a futures::Stream of DirEntry, for StreamExt consumers.
  • serde (off by default): Serialize/Deserialize on the plain data records (Metadata, DirEntry, StatFs, Timestamp, ...).

SystemTime converts to/from Timestamp and into SetTime, so the standard time types work directly with set_times and the metadata accessors.

License

AGPL-3.0