Skip to main content

Crate zerofs_client

Crate zerofs_client 

Source
Expand description

Async, path-based client for a ZeroFS server, speaking 9P2000.L (plus the ZeroFS fast-path extensions, used automatically when the server offers them) over TCP or a unix socket.

The primary surface is one-shot path operations on a shared Client ( read, write, stat, rename, mkdir), with File and Dir handles only where statefulness pays (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 need nothing special. The API otherwise stays FFI-friendly (owned buffers, a flat exhaustive error enum, Arc handles, explicit close, cancel-safe futures) for the zerofs-ffi bindings layer built on top.

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

use zerofs_client::{Client, OpenOptions};

let fs = Client::connect("unix:/tmp/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;

§Lifecycles

Close & drop. close() marks the handle closed immediately (later calls return ZeroFsError::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()); the janitor performs the clunk in the background. 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 (in-flight fids are reclaimed in the background). There is deliberately 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 operation in flight at the instant of a disconnect is resent, so a non-idempotent op (rename, create, unlink) can apply twice across a reconnect.

Re-exports§

pub use stream::DirStream;

Modules§

io
tokio-io feature: a FileCursor adapter that turns the positioned File API into a stateful AsyncRead + AsyncWrite + AsyncSeek stream, so tokio::io::copy and friends work against a ZeroFS file. Rust-only; never crosses the FFI boundary.
stream
stream feature: a futures_core::Stream adapter over Dir listing, so a directory can be consumed with StreamExt (.next(), .collect(), try_for_each, …). Rust-only; never crosses the FFI boundary.

Structs§

Bytes
Re-exported so callers need not depend on bytes directly; it is the return type of every read (cheap to clone and slice, derefs to &[u8]). A cheaply cloneable and sliceable chunk of contiguous memory.
Capabilities
Live snapshot of negotiated session properties. msize and the extension level are re-negotiated on every transparent reconnect; treat this as advisory at the instant of the call, not a constant for the life of the client.
Client
One ZeroFS session, one identity. Share via Arc; every method takes &self and is safe to call concurrently. The underlying connection reconnects transparently, blocking calls through outages; bound waits with your async runtime’s timeout facilities (every future is cancel-safe).
ConnectOptions
Options for crate::Client::connect_with. All defaults are literal-expressible so uniffi record defaults can reproduce them in every binding; None identity fields are resolved Rust-side (euid/egid/$USER) inside connect.
Dir
An open directory: a pull-based listing cursor plus at-style child operations taking ONE byte-exact name component (no / or NUL). The *_at suite is the FFI-clean escape hatch for non-UTF-8 names discovered via DirEntry::name_bytes; chain Dir::open_dir_at to reach arbitrary depth.
DirEntry
One directory entry; the library filters out . and ...
File
An open file. All I/O is positioned (pread/pwrite); there is no shared cursor, so an Arc<File> is safe to use from many tasks at once.
Metadata
POSIX-shaped attributes; plain data record everywhere.
OpenOptions
Plain record so it crosses FFI as a dictionary; constructors are Rust sugar. All field defaults are literals (bools false, mode 420 = 0o644), so keyword-style construction in the bindings does the right thing. There is deliberately no append flag: the server ignores open flags on writes, so appending is the explicit crate::Client::append composition.
SetAttrs
Metadata changes; None fields are untouched. All-None is a no-op.
StatFs
Filesystem usage, from 9P statfs.
Timestamp
Nanosecond UNIX timestamp as explicit fields (predictable across all bindings).

Enums§

FileType
File type derived from the mode/dirent type.
NodeKind
Kind of special node for mknod; a tagged enum so callers never pack S_IF* bits or pass meaningless major/minor for fifos and sockets.
SetTime
A time to set: the server’s current clock, or an explicit instant.
ZeroFsError
The single error type: flat and exhaustive (deliberately NOT #[non_exhaustive], so zerofs-ffi can apply uniffi remote derives and match exhaustively; a new variant must break its build, not silently degrade to a catch-all).