diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-20 01:47:28 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-20 01:47:29 -0300 |
| commit | 66f2e492c348677ab3050f5e378b9eb4c04c98ce (patch) | |
| tree | cf06b32309eace8eb573925e9cf14e3dfc27bd66 /9proc/docs | |
| parent | 65209217b5b68f56bc0bd5bc6c4dce33911ded59 (diff) | |
| download | cloud9-66f2e492c348677ab3050f5e378b9eb4c04c98ce.tar.gz cloud9-66f2e492c348677ab3050f5e378b9eb4c04c98ce.zip | |
9proc core becomes a backend of cloud9.fs; engine gains optional features
9proc's own fid table, walk loop and dir-read engine are replaced by
cloud9.fs.Server; the tree (static, vars, providers) is served through the
engine's Req/Reply contract with node ids that keep the old qid scheme.
Providers may answer later by returning error.Again (parked in the engine,
retried each step, Tflush -> EINTR); no new files are exposed.
Engine (backward compatible, all opt-in via Backend.features / Options):
create, remove, wstat, reference accounting for backends that count
handles, a salted fid index, name_capacity 0 (names from getattr),
Reply.ename for backend-chosen error text, Attr.path/version/atime.
Engine-level error strings and the 217-byte msize floor now apply to 9proc;
tests updated accordingly.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to '9proc/docs')
| -rw-r--r-- | 9proc/docs/LIBRARY.md | 85 |
1 files changed, 66 insertions, 19 deletions
diff --git a/9proc/docs/LIBRARY.md b/9proc/docs/LIBRARY.md index eef7dad..e0152e0 100644 --- a/9proc/docs/LIBRARY.md +++ b/9proc/docs/LIBRARY.md @@ -24,7 +24,7 @@ Design rules (non-negotiable, they mirror cloud9): ``` 9proc/src/root.zig pub const core, vars, scratch, linux (linux only), Server(cfg) - 9proc/src/core.zig Tree/Server engine on cloud9.Server: fids, walks, dir reads, providers + 9proc/src/core.zig the tree (static nodes, vars, providers) as a backend of cloud9.fs.Server 9proc/src/vars.zig comptime value renderers (@typeInfo) for /vars 9proc/src/scratch.zig in-memory read/write tree provider (takes an Allocator) 9proc/src/linux/probe.zig background thread + poll loop + unix/tcp/fd listeners @@ -51,34 +51,51 @@ pub const Config = struct { /// slots so that reads at arbitrary offsets are consistent. snapshot_slots: u8 = 8, snapshot_bytes: u32 = 16 * 1024, + max_parked: u8 = 8, // reads a provider has parked with error.Again, per connection }; pub fn Server(comptime cfg: Config) type { return struct { pub const Storage = struct { // caller places this in static memory - in: [msize]u8, out: [msize]u8, snapshots: [cfg.snapshot_slots][cfg.snapshot_bytes]u8, + in: [msize]u8, out: [msize]u8, data: [msize]u8, snapshots: [cfg.snapshot_slots][cfg.snapshot_bytes]u8, }; pub const Shared = struct { // state common to all connections (providers, vars) pub fn init(name_ctx: *anyopaque) Shared; pub fn addProvider(s: *Shared, p: Provider) error{Full}!void; pub fn expose(s: *Shared, name: []const u8, ptr: anytype) error{Full}!void; // typed value → /vars/<name> }; - pub const Conn = struct { // one 9P connection, push/step/output like cloud9 + pub const Backend = struct { ... }; // what cloud9.fs.Server knows of the tree: Req, Reply, features + pub const Engine = cloud9.fs.Server(Backend, .{ .fid_capacity = cfg.max_fids, .slot_capacity = cfg.max_parked, ... }); + pub const Conn = struct { // one 9P connection: an Engine plus the tree's per-connection state pub fn init(shared: *Shared, storage: *Storage, msize: u32) Conn; pub fn push(c: *Conn, bytes: []const u8) usize; // feed transport bytes - pub fn step(c: *Conn) error{Protocol}!bool; // handle ≤ 1 request; false = nothing to do + pub fn step(c: *Conn) error{Protocol}!bool; // serve ≤ 1 engine request; false = nothing to do pub fn output(c: *const Conn) []const u8; // bytes to send pub fn wrote(c: *Conn, n: usize) void; pub fn hangup(c: *Conn) void; // drop fids, tell providers + pub fn fidCount(c: *const Conn) usize; }; }; } ``` -`step` drives `cloud9.Server.receive/reply/negotiate` and the backend: the -static tree (comptime-generated from `cfg`: `/README`, `/build/*` via a -`build_options`-like struct passed in `cfg.build`, `/comptime/types/*`, -`/comptime/decls`, `/runtime/fn/*`, `/ctl`, `/vars/*`) plus **providers**. +The core is a **backend of `cloud9.fs.Server`**, the library's file-server +engine (cloud9's `docs/design.md`, "File server engine"): one engine for +every 9P server built on cloud9. The engine owns the fid table (indexed, +`Options.fid_index`, so thousands of fids cost O(1) per lookup), walks, +permission checks, directory cursors, Tflush, and the releases a dropped +connection owes; `Conn` wraps one engine instance and answers its +requests (`lookup`, `getattr`, `setattr`, `open`, `read`, `write`, +`release`, `readdir`) from the tree: the static part (comptime-generated +from `cfg`: `/README`, `/build/*` via a `build_options`-like struct passed +in `cfg.build`, `/comptime/types/*`, `/comptime/decls`, `/runtime/fn/*`, +`/ctl`, `/vars/*`) is served directly, **providers** through their vtable. +The backend declares every optional engine feature (`fs.Features`: +create, remove, wstat, references), which is how Tcreate/Tremove/Twstat +and the one-clunk-per-handle contract below reach providers. `step` +answers the engine's `retry()` and `next()` requests synchronously, one +`next()` per call; a provider that cannot answer yet parks instead (see +"Answering later"). A provider is a runtime vtable mounted at a top-level name. It owns a subtree with its own naming (dynamic directories such as `/threads/<tid>` or @@ -106,23 +123,53 @@ pub const Provider = struct { }; ``` -Error → Rerror text mapping lives in one place in the core, using the Plan 9 -strings 9ns's bridge already understands (`file does not exist`, -`permission denied`, `file already exists`, `directory not empty`, +Error → Rerror text: what the tree or a provider refuses is answered in +`ename`'s Plan 9 strings, which 9ns's bridge understands (`file does not +exist`, `permission denied`, `file already exists`, `directory not empty`, `not a directory`, `is a directory`, `bad offset`, `no space`, `i/o error`, -`not supported`). +`not supported`, `bad command`, `bad value`, `too many open dynamic +files`); what the engine refuses on its own carries cloud9.fs's strings, +the ones Linux v9fs maps back to errnos (`fid unknown or out of range`, +`fid already in use`, `Too many open files in system`, `bad use of fid` +for I/O on an unopened fid or a walk or clone from an open one, `file +already open for I/O`, `bad offset in directory read`, `permission denied` +for an open the walked mode forbids (a directory for writing, OEXEC, a +static file for writing), `wstat prohibited` for any of the fields the +engine owns (type, dev, qid, atime, the owner names) or a length on a +directory, `illegal name`, `Invalid argument` for an Rstat that cannot fit +the msize or a directory read whose count holds no whole record). The +smallest msize is the engine's `fs.msize_min` (217: one full Rwalk); a +Tversion below it ends the connection. Directory reads follow the 9P rule (offset 0 or previous offset+count, never -split a record). Dynamic file reads: on open the content is generated once -into a snapshot slot (`open` runs the generator; `read` serves the slot; a -read at offset 0 regenerates); no free slot → Rerror `too many open dynamic +split a record); the engine encodes the entries from the tree's records, +with uid/gid/muid the attach uname, the modes `dirent_dir_perm`/ +`dirent_file_perm` and length 0 (a stat of the entry gives the real +ones). Dynamic file reads: on open the content is generated once into a +snapshot slot (`open` runs the generator; `read` serves the slot; a read +at offset 0 regenerates); no free slot → Rerror `too many open dynamic files`. Stats of dynamic files report length 0. Qids: static nodes get comptime paths; provider nodes get -`(provider index << 56) | handle`. +`(provider index << 56) | handle` (or `NodeStat.path` in place of the +handle). The engine's node ids are the same numbers, except that +provider ids are offset by one in the top byte, since the engine reads +node 0 as "the node asked about". + +**Answering later.** `Provider.read` may return `error.Again`: the engine +parks the request (`Config.max_parked` per connection; a further one fails +with EAGAIN), the connection goes on serving, and every later `Conn.step` +asks the provider again with the same handle and offset until it answers, +whether at once or on a later step. The fid stays open; a Tflush of a +parked read answers it with `Interrupted system call` before the Rflush; +hangup and Tversion drop it and close the file as usual. Nothing wakes a +connection by itself: the platform layer steps a connection when its +transport moves, so a provider that becomes ready has to make that happen +(an event stream's job, not the core's). Writes cannot park. Static memory: `Server(cfg).Storage` per connection, `Shared` once. No heap. -The core has unit tests driven through `cloud9.Client` in memory (like today). +The core has unit tests driven through `cloud9.Client` in memory, +including a provider that parks. ## Value renderers (`vars.zig`) @@ -252,8 +299,8 @@ the demo's Storage is a global. ## Verification -* Unit tests: core (in-memory client drives every op incl. providers and - snapshots), vars (render/set for every category), scratch, debug (capture +* Unit tests: core (in-memory client drives every op incl. providers, + snapshots and a parked read), vars (render/set for every category), scratch, debug (capture own thread and a helper thread; breakpoint pause/continue on a helper thread; panic record path without holding). * `zig build 9proc-check-freestanding`: compiles `core.zig` + `vars.zig` |
