//! The harness file tree served over 9P2000: a unified, read-only, //! fresh-from-disk view of every AI-agent harness's state on the machine. //! //! /pid /uptime daemon facts, one line each //! /claude/ projects/ (mirror of ~/.claude/projects), //! history (~/.claude/history.jsonl), //! skills/ (mirror of ~/.claude/skills) //! /codex/ sessions/ (~/.codex/sessions), //! session-index (~/.codex/session_index.jsonl), //! history (~/.codex/history.jsonl) //! /omp/ mirror of ~/.omp/agent, raw blobs //! /hermes/ mirror of ~/.hermes, raw //! /dsh/ mirror of ~/.dsh //! /skills/{claude,codex,omp}/ the same skills trees the harnesses own //! //! Everything below the named mount points is a lazy mirror: a lookup, //! getattr or readdir walks the real filesystem at request time, so a //! session transcript grows as its harness writes it and a new session //! appears as soon as its file lands. No cache, no invalidation. //! //! Security boundary — the exclusion rule is absolute and unit-tested: //! no name that looks like a credential, key, token or auth store is ever //! answered, at any depth (`excluded`); symlinks are never served (they //! are an escape hatch around the pinned roots). Every path is resolved //! from its pinned root one component at a time with `O_NOFOLLOW` //! (`openIn`), so no name below a root — swapped mid-session or not — //! can point the daemon at a file outside it. The daemon must never //! become a credential reader for anything that mounts it. Writes, //! creates and setattrs answer EPERM. //! //! This module is the backend of `cloud9.fs.Server` (main.zig hands it to //! `serve.Runner`). It declares `features = .{ .references = true }`: every //! lookup result is a reference, so table entries below (the mirrored //! files) are refcounted and freed when the last fid lets go. Node ids //! pack (kind, root index, serial) in a u64, zmx-style: a mirrored file's //! serial is its table slot and generation, so two walks of the same file //! answer the same qid path while it is held. const std = @import("std"); const cloud9 = @import("cloud9"); const fs = cloud9.fs; const E = fs.E; const Io = std.Io; const linux = std.os.linux; pub const active = @import("active.zig"); // ---- comptime bounds --------------------------------------------------------- /// Longest relative path a mirrored file may carry (bytes below a root). pub const rel_capacity: usize = 640; /// Mirrored files remembered at once; each holds one reference per fid. pub const path_capacity: usize = 4096; comptime { if (path_capacity > 1 << 12) @compileError("path table slots must fit the 12 serial bits"); } /// Largest frame the daemon negotiates; read and readdir staging buffers /// are this big, so a single Rread can carry one full frame of bytes. pub const msize: u32 = 32 * 1024; /// Names one directory listing may stage (sorted for cursor stability). pub const list_capacity: usize = 1024; /// Bytes of name storage one listing may use (255 per name, packed). pub const list_name_bytes: usize = 128 * 1024; /// Longest base path a pinned root may carry. pub const base_capacity: usize = 512; pub const opts: fs.Options = .{ .fid_capacity = 512, .slot_capacity = 8, .name_capacity = 255, .username_capacity = 28, .fid_index = true, }; // ---- the tree skeleton ------------------------------------------------------ pub const Root = enum(u8) { claude, codex, omp, hermes, dsh }; /// Static nodes: the facts, the harness dirs and the union skills dir. pub const Top = enum(u8) { root = 1, pid, uptime, claude, codex, omp, hermes, dsh, skills, active, /// Directory entries of the root, in listing order. pub const listed = [_]Top{ .pid, .uptime, .claude, .codex, .omp, .hermes, .dsh, .skills, .active }; pub fn fileName(t: Top) []const u8 { return @tagName(t); } pub fn dir(t: Top) bool { return switch (t) { .root, .claude, .codex, .omp, .hermes, .dsh, .skills, .active => true, .pid, .uptime => false, }; } fn parent(t: Top) Top { _ = t; return .root; // the root is its own parent; every top hangs off it } /// The mirror a harness dir serves: its root and the relative path /// below it. The claude and codex dirs are *virtual* (their children /// are named mounts); omp, hermes and dsh mirror one subtree each. fn mirror(t: Top) ?Mount { return switch (t) { .omp => .{ .root = .omp, .rel = "agent" }, .hermes => .{ .root = .hermes, .rel = "" }, .dsh => .{ .root = .dsh, .rel = "" }, else => null, }; } }; /// Where a mirrored subtree sits: `rel` below the root's pinned base path. pub const Mount = struct { root: Root, rel: []const u8, }; const NamedMount = struct { /// The name as it appears in its virtual directory. name: []const u8, owner: Top, mount: Mount, }; /// The children of the virtual directories (claude, codex, skills). The /// same target may appear twice (/claude/skills and /skills/claude): a /// lookup of either hands out the same table entry, so both paths share /// qid paths and identity. const named_mounts = [_]NamedMount{ .{ .name = "projects", .owner = .claude, .mount = .{ .root = .claude, .rel = "projects" } }, .{ .name = "history", .owner = .claude, .mount = .{ .root = .claude, .rel = "history.jsonl" } }, .{ .name = "skills", .owner = .claude, .mount = .{ .root = .claude, .rel = "skills" } }, .{ .name = "sessions", .owner = .codex, .mount = .{ .root = .codex, .rel = "sessions" } }, .{ .name = "session-index", .owner = .codex, .mount = .{ .root = .codex, .rel = "session_index.jsonl" } }, .{ .name = "history", .owner = .codex, .mount = .{ .root = .codex, .rel = "history.jsonl" } }, .{ .name = "claude", .owner = .skills, .mount = .{ .root = .claude, .rel = "skills" } }, .{ .name = "codex", .owner = .skills, .mount = .{ .root = .codex, .rel = "skills" } }, .{ .name = "omp", .owner = .skills, .mount = .{ .root = .omp, .rel = "skills" } }, }; fn mountNamed(owner: Top, name: []const u8) ?Mount { for (named_mounts) |m| { if (m.owner == owner and std.mem.eql(u8, m.name, name)) return m.mount; } return null; } /// The Top that owns the mount whose target is exactly (root, rel) — the /// canonical parent a ".." walk answers. Falls back to the root's harness /// dir for targets no named mount covers (deep paths, mirrors). fn mountOwner(r: Root, rel_path: []const u8) Top { for (named_mounts) |m| { if (m.mount.root == r and std.mem.eql(u8, m.mount.rel, rel_path)) return m.owner; } return switch (r) { .claude => .claude, .codex => .codex, .omp => .omp, .hermes => .hermes, .dsh => .dsh, }; } // ---- node ids and the path table --------------------------------------------- pub const Kind = enum(u8) { top = 0, path = 1, active = 2 }; comptime { // The derived view indexes the same pinned roots the mirror does. for (std.enums.values(Root)) |r| { if (!std.mem.eql(u8, @tagName(r), @tagName(@as(active.Kind, @enumFromInt(@intFromEnum(r)))))) @compileError("tree.Root and active.Kind must agree, index by index"); } } /// A node under `/active`. Everything but a harness directory names a /// slot of the live table and the generation it had when the id was /// minted, so an id outlives its process only long enough to answer /// ENOENT. pub const AFile = enum(u8) { /// `/active/` harness_dir, /// `/active//` entry, pid, ppid, started, cwd, name, title, session, model, via, zmx, status, transcript, /// `/active///agents` agents, /// `/active///agents/` agent, agent_model, agent_transcript, /// The fields of one entry, in listing order. A field with no value /// is not listed and does not resolve. pub const entry_files = [_]AFile{ .pid, .ppid, .started, .cwd, .name, .title, .session, .model, .via, .zmx, .status, .transcript, }; pub const agent_files = [_]AFile{ .agent_model, .agent_transcript }; pub fn fileName(f: AFile) []const u8 { return switch (f) { .agent_model => "model", .agent_transcript => "transcript", else => @tagName(f), }; } }; /// A resolved `/active` node. pub const Act = struct { file: AFile, /// Only for `harness_dir`. kind: active.Kind = .claude, slot: u8 = 0, gen: u8 = 0, agent: u8 = 0, }; pub fn activeNode(a: Act) u64 { const serial: u48 = switch (a.file) { .harness_dir => @intFromEnum(a.kind), else => @as(u48, a.slot) | (@as(u48, a.gen) << 8) | (@as(u48, a.agent) << 16), }; return @bitCast(Node{ .idx = @intFromEnum(a.file), .kind = @intFromEnum(Kind.active), .serial = serial, }); } pub const Node = packed struct(u64) { /// A Top index (kind == .top) or a Root index (kind == .path). idx: u8 = 0, kind: u8 = 0, serial: u48 = 0, }; pub fn topNode(t: Top) u64 { return @bitCast(Node{ .idx = @intFromEnum(t), .kind = @intFromEnum(Kind.top) }); } pub const root: u64 = topNode(Top.root); /// One remembered mirrored file: its root, its relative path, the hash /// that short-circuits dedupe lookups, and its reference count (one per /// fid holding it, handed out by lookup, paid back by release). const Entry = struct { used: bool = false, root: Root = .claude, rel_buf: [rel_capacity]u8 = undefined, rel_len: u16 = 0, hash: u64 = 0, refs: u32 = 0, gen: u32 = 0, fn rel(e: *const Entry) []const u8 { return e.rel_buf[0..e.rel_len]; } }; fn serialOf(slot: u12, gen: u32) u48 { return (@as(u48, gen) << 12) | slot; } fn nodeOf(e: *const Entry, slot: u12) u64 { return @bitCast(Node{ .idx = @intFromEnum(e.root), .kind = @intFromEnum(Kind.path), .serial = serialOf(slot, e.gen), }); } // ---- the exclusions (the security boundary) ----------------------------------- fn containsFold(name: []const u8, needle: []const u8) bool { if (name.len < needle.len) return false; var i: usize = 0; while (i + needle.len <= name.len) : (i += 1) { if (std.ascii.eqlIgnoreCase(name[i .. i + needle.len], needle)) return true; } return false; } fn endsWithFold(name: []const u8, suffix: []const u8) bool { return name.len >= suffix.len and std.ascii.eqlIgnoreCase(name[name.len - suffix.len ..], suffix); } fn isOneOf(name: []const u8, comptime names: []const []const u8) bool { inline for (names) |n| if (std.ascii.eqlIgnoreCase(name, n)) return true; return false; } /// Absolute rule: a name that may hold a credential, key, token or auth /// material is never served, at any depth, by lookup or readdir. The /// substrings are folded (case-insensitive) and deliberately broad — /// "auth" also hides "author-notes", the price of never guessing wrong. /// When unsure, exclude and document (docs/DESIGN.md). pub fn excluded(name: []const u8) bool { @setEvalBranchQuota(20000); if (name.len == 0) return true; for ([_][]const u8{ "credentials", "token", "auth", "secret" }) |needle| { if (containsFold(name, needle)) return true; } if (endsWithFold(name, ".key")) return true; if (endsWithFold(name, ".pem")) return true; if (endsWithFold(name, ".env")) return true; // Config and settings files of the other harnesses may embed API keys // (hermes config.yaml, omp config.yml, dsh settings.yaml). Claude // Code's settings.json is not in the tree at all: /claude serves only // projects/, skills/ and history.jsonl. if (isOneOf(name, &.{ "settings.json", "settings.yaml", "settings.local.json", "config.yml", "config.yaml", "config.toml", "models.yml", "models.yaml", ".ssh" })) return true; for ([_][]const u8{ "id_rsa", "id_dsa", "id_ecdsa", "id_ed25519" }) |prefix| { if (name.len >= prefix.len and std.ascii.eqlIgnoreCase(name[0..prefix.len], prefix)) return true; } return false; } /// Every component of a relative path must pass `excluded`; used on the /// comptime mount targets and the unit-test fixtures alike. pub fn excludedPath(rel_path: []const u8) bool { @setEvalBranchQuota(4000); var it = std.mem.splitScalar(u8, rel_path, '/'); while (it.next()) |comp| { if (excluded(comp)) return true; } return false; } // ---- the backend -------------------------------------------------------------- pub const Answer = struct { reply: fs.Reply, bytes: []const u8 = "", }; fn fail(tag: u64, e: u16) Answer { return .{ .reply = fs.Reply.fail(tag, e) }; } /// The daemon's state. One instance serves every connection; `handle` is /// called with `mutex` held (the serve handler in main.zig holds it across /// handle and reply, because `bytes` point into the shared buffers). pub const Harness = struct { pub const Req = fs.Req; pub const Reply = fs.Reply; pub const features: fs.Features = .{ .references = true }; io: Io, pid: u32 = 0, started_sec: i64 = 0, /// The five pinned roots; a root without a base is unreachable. base_buf: [5][base_capacity]u8 = @splat(@splat(0)), base_len: [5]u16 = @splat(0), base_set: [5]bool = @splat(false), table: [path_capacity]Entry = @splat(.{}), mutex: Io.Mutex = .init, // Shared staging (guarded by `mutex`). data_buf: [msize]u8 = undefined, stage_buf: [msize]u8 = undefined, list_names: [list_name_bytes]u8 = undefined, list_dirs: [list_capacity]bool = undefined, list_offs: [list_capacity]u32 = undefined, // The derived view (`/active`). An empty `proc` base turns it off, // which is the default: a test must opt in to reading a process // tree, and never the live one. live: active.Table = .{}, proc_buf: [base_capacity]u8 = @splat(0), proc_len: u16 = 0, home_buf: [base_capacity]u8 = @splat(0), home_len: u16 = 0, zmx_buf: [base_capacity]u8 = @splat(0), zmx_len: u16 = 0, runtime_buf: [base_capacity]u8 = @splat(0), runtime_len: u16 = 0, envp: ?cloud9.post.Env = null, self_pid: u32 = 0, btime: i64 = 0, allow_move: bool = false, act_text: [1024]u8 = undefined, act_name: [64]u8 = undefined, act_path: [active.path_capacity]u8 = undefined, pub const InitOptions = struct { io: Io, pid: u32, /// Base path of each root, or "" when the root is not pinned. bases: [5][]const u8, /// The proc filesystem `/active` reads. Empty — the default — /// leaves the derived view out of the tree entirely. proc: []const u8 = "", /// $HOME, for the harnesses' store-slug spellings. home: []const u8 = "", /// The zmx binary a move execs, resolved to an absolute path by /// the caller. Never client bytes. zmx: []const u8 = "zmx", /// $XDG_RUNTIME_DIR, where a move looks for the zmx session it /// is about to create. runtime: []const u8 = "", /// The daemon's own environment block, handed to the harness a /// move re-execs. Null leaves a moved agent with an empty one. envp: ?cloud9.post.Env = null, /// Whether writing `/active///zmx` may move a session. allow_move: bool = false, }; pub fn init(h: *Harness, o: InitOptions) void { h.* = .{ .io = o.io, .pid = o.pid, .started_sec = Io.Timestamp.now(o.io, .real).toSeconds() }; if (o.proc.len > 0 and o.proc.len <= base_capacity) { @memcpy(h.proc_buf[0..o.proc.len], o.proc); h.proc_len = @intCast(o.proc.len); h.self_pid = o.pid; h.btime = active.bootTime(o.io, o.proc); } if (o.home.len <= base_capacity) { @memcpy(h.home_buf[0..o.home.len], o.home); h.home_len = @intCast(o.home.len); } if (o.zmx.len > 0 and o.zmx.len <= base_capacity) { @memcpy(h.zmx_buf[0..o.zmx.len], o.zmx); h.zmx_len = @intCast(o.zmx.len); } if (o.runtime.len <= base_capacity) { @memcpy(h.runtime_buf[0..o.runtime.len], o.runtime); h.runtime_len = @intCast(o.runtime.len); } h.allow_move = o.allow_move; h.envp = o.envp; inline for (0..5) |i| { const path = o.bases[i]; h.base_set[i] = path.len > 0 and path.len <= base_capacity; if (h.base_set[i]) { @memcpy(h.base_buf[i][0..path.len], path); h.base_len[i] = @intCast(path.len); } } } pub fn base(h: *const Harness, r: Root) ?[]const u8 { if (!h.base_set[@intFromEnum(r)]) return null; return h.base_buf[@intFromEnum(r)][0..h.base_len[@intFromEnum(r)]]; } // -- path table -- fn hashOf(r: Root, rel_path: []const u8) u64 { var hh = std.hash.Wyhash.init(0x6861_726e_6573_7300); // "harness" hh.update(&.{@intFromEnum(r)}); hh.update(rel_path); return hh.final(); } /// Finds or creates the entry for (root, rel). `ref` says whether the /// caller hands out a reference (a lookup) or only names the file (a /// readdir record). Two walks of the same file find the same entry, /// so their node ids are equal; a freed entry's generation changes. fn entryFor(h: *Harness, r: Root, rel_path: []const u8, ref: bool) ?*Entry { if (rel_path.len == 0 or rel_path.len > rel_capacity) return null; const hash = hashOf(r, rel_path); var free: ?*Entry = null; for (&h.table) |*e| { if (!e.used) { if (free == null) free = e; continue; } if (e.hash == hash and e.root == r and e.rel_len == rel_path.len and std.mem.eql(u8, e.rel(), rel_path)) { if (ref) e.refs += 1; return e; } } const e = free orelse return null; e.used = true; e.root = r; e.rel_len = @intCast(rel_path.len); @memcpy(e.rel_buf[0..rel_path.len], rel_path); e.hash = hash; e.refs = @intFromBool(ref); e.gen +%= 1; return e; } fn slotOf(h: *Harness, e: *Entry) u12 { const off = (@intFromPtr(e) - @intFromPtr(&h.table)) / @sizeOf(Entry); return @intCast(off); } /// The target a node names; a freed or forged serial answers null. fn resolve(h: *Harness, node: u64) ?Target { const n: Node = @bitCast(node); switch (std.enums.fromInt(Kind, n.kind) orelse return null) { .top => return .{ .top = std.enums.fromInt(Top, n.idx) orelse return null }, .path => { const r = std.enums.fromInt(Root, n.idx) orelse return null; const slot: u12 = @truncate(n.serial); const e = &h.table[slot]; if (!e.used or e.root != r) return null; if (serialOf(slot, e.gen) != n.serial) return null; return .{ .path = e }; }, .active => { const f = std.enums.fromInt(AFile, n.idx) orelse return null; if (f == .harness_dir) { const k = std.enums.fromInt(active.Kind, @as(u8, @truncate(n.serial))) orelse return null; return .{ .act = .{ .file = f, .kind = k } }; } const slot: u8 = @truncate(n.serial); const gen: u8 = @truncate(n.serial >> 8); const agent: u8 = @truncate(n.serial >> 16); const l = h.live.at(slot, gen) orelse return null; return .{ .act = .{ .file = f, .kind = l.kind, .slot = slot, .gen = gen, .agent = agent } }; }, } } fn entryNode(h: *Harness, e: *Entry) u64 { return nodeOf(e, h.slotOf(e)); } }; pub const Target = union(enum) { top: Top, path: *Entry, act: Act, }; // ---- attributes --------------------------------------------------------------- /// Opens `/` without following a symlink at any step /// below the base: every component is opened with `O_NOFOLLOW`, so a /// name swapped for a symlink between the walk and the read fails /// instead of reaching out of the root. Composing the whole path and /// opening it in one call cannot do that — only the last component's /// symlink is refused then, and every directory above it is followed /// wherever it points. The base itself is configuration (`--root` or /// $HOME), not client bytes, so it resolves normally. The caller owns /// the descriptor. fn openIn(h: *Harness, r: Root, rel_path: []const u8, final: linux.O) ?i32 { const b = h.base(r) orelse return null; if (rel_path.len > rel_capacity or b.len > base_capacity) return null; const walk: linux.O = .{ .PATH = true, .DIRECTORY = true, .CLOEXEC = true, .NOFOLLOW = true }; var base_z: [base_capacity + 1]u8 = @splat(0); @memcpy(base_z[0..b.len], b); var top_flags = if (rel_path.len == 0) final else walk; top_flags.NOFOLLOW = false; // the pinned root may legitimately be a link top_flags.CLOEXEC = true; var fd = fdOf(linux.open(@ptrCast(&base_z), top_flags, 0)) orelse return null; if (rel_path.len == 0) return fd; var rest = rel_path; while (true) { const slash = std.mem.indexOfScalar(u8, rest, '/'); const comp = if (slash) |i| rest[0..i] else rest; const last = slash == null; // Nothing below composes these; a component that could re-enter // the walk is refused rather than opened. if (comp.len == 0 or comp.len > 255 or std.mem.eql(u8, comp, ".") or std.mem.eql(u8, comp, "..")) { _ = linux.close(fd); return null; } var name_z: [256]u8 = @splat(0); @memcpy(name_z[0..comp.len], comp); var flags = if (last) final else walk; flags.NOFOLLOW = true; flags.CLOEXEC = true; const next = fdOf(linux.openat(fd, @ptrCast(&name_z), flags, 0)); _ = linux.close(fd); fd = next orelse return null; if (last) return fd; rest = rest[comp.len + 1 ..]; } } fn fdOf(rc: usize) ?i32 { if (linux.errno(rc) != .SUCCESS) return null; return @intCast(rc); } /// Stat of `/`, resolved the no-follow way. A symlink /// is never served: it is the one escape hatch around the pinned roots. /// `O_PATH` opens the name itself, so a fifo or device never blocks and /// never has its open side effects run. fn statIn(h: *Harness, r: Root, rel_path: []const u8) ?Io.Dir.Stat { const fd = openIn(h, r, rel_path, .{ .PATH = true, .CLOEXEC = true }) orelse return null; const f: Io.File = .{ .handle = fd, .flags = .{ .nonblocking = false } }; defer f.close(h.io); const st = f.stat(h.io) catch return null; if (st.kind == .sym_link) return null; // never served: an escape hatch return st; } fn statAttr(h: *Harness, e: *Entry) ?fs.Attr { const st = statIn(h, e.root, e.rel()) orelse return null; return .{ .name = basename(e.rel()), .node = h.entryNode(e), .dir = st.kind == .directory, .size = if (st.kind == .directory) 0 else st.size, .mode = if (st.kind == .directory) 0o555 else 0o444, .mtime = @truncate(@as(u64, @bitCast(st.mtime.toSeconds()))), }; } fn basename(rel_path: []const u8) []const u8 { if (std.mem.lastIndexOfScalar(u8, rel_path, '/')) |i| return rel_path[i + 1 ..]; return rel_path; } /// A fact's text, rendered on demand. `buf` should be a small stack buffer. fn factText(h: *Harness, fact: Top, buf: []u8) []const u8 { var w = Io.Writer.fixed(buf); switch (fact) { .pid => w.print("{d}\n", .{h.pid}) catch {}, .uptime => { const now = Io.Timestamp.now(h.io, .real).toSeconds(); const up: u64 = if (now > h.started_sec) @intCast(now - h.started_sec) else 0; w.print("{d}\n", .{up}) catch {}; }, else => {}, } return w.buffered(); } var fact_buf: [64]u8 = undefined; fn attrFor(h: *Harness, t: Target) ?fs.Attr { switch (t) { .top => |top| { switch (top) { .root => return .{ .name = "/", .node = root, .dir = true, .mode = 0o555 }, .active => return .{ .name = "active", .node = topNode(.active), .dir = true, .mode = 0o555 }, .pid, .uptime => return .{ .name = top.fileName(), .node = topNode(top), .size = factText(h, top, &fact_buf).len, .mode = 0o444, }, .claude, .codex, .skills => return .{ .name = top.fileName(), .node = topNode(top), .dir = true, .mode = 0o555, }, .omp, .hermes, .dsh => { // The harness dir IS its mirror: stat the target. const m = top.mirror().?; const st = statIn(h, m.root, m.rel) orelse return null; if (st.kind != .directory) return null; return .{ .name = top.fileName(), .node = topNode(top), .dir = true, .mode = 0o555 }; }, } }, .path => |e| return statAttr(h, e), .act => |a| return activeAttr(h, a), } } /// Attr of the mount target as a table entry (fresh from disk, or null /// when the target is missing, a symlink, or excluded by name). fn attrOfMount(h: *Harness, r: Root, rel_path: []const u8) ?fs.Attr { const e = h.entryFor(r, rel_path, false) orelse return null; defer if (e.refs == 0) { e.used = false; e.gen +%= 1; }; return statAttr(h, e); } // ---- /active: the derived view ------------------------------------------------- fn sourcesOf(h: *Harness) active.Sources { var roots: [5][]const u8 = @splat(""); for (0..5) |i| { if (h.base_set[i]) roots[i] = h.base_buf[i][0..h.base_len[i]]; } return .{ .io = h.io, .proc = h.proc_buf[0..h.proc_len], .home = h.home_buf[0..h.home_len], .roots = roots, .self_pid = h.self_pid, .btime = h.btime, }; } /// Rescans the process tree. Done on a listing of `/active` and on a /// lookup that misses, which is what makes an agent visible the moment /// it starts and gone the moment it exits. fn rescan(h: *Harness) void { if (h.proc_len == 0) return; h.live.scan(sourcesOf(h)); } fn hasLive(h: *Harness, k: active.Kind) bool { for (&h.live.slots) |*sl| { if (sl.used and sl.live.kind == k) return true; } return false; } /// `\n` staged where an answer can point at it. fn line(h: *Harness, text: []const u8) ?[]const u8 { return std.fmt.bufPrint(&h.act_text, "{s}\n", .{text}) catch null; } /// The value of a field file, or null when this agent has none — an /// absent field is not listed and does not resolve, so the tree never /// answers a blank where it does not know. fn activeText(h: *Harness, a: Act) ?[]const u8 { const l = h.live.at(a.slot, a.gen) orelse return null; const src = sourcesOf(h); var scratch: [active.text_capacity]u8 = undefined; return switch (a.file) { .pid => std.fmt.bufPrint(&h.act_text, "{d}\n", .{l.pid}) catch null, .ppid => if (l.ppid == 0) null else std.fmt.bufPrint(&h.act_text, "{d}\n", .{l.ppid}) catch null, .started => if (l.started == 0) null else std.fmt.bufPrint(&h.act_text, "{d}\n", .{l.started}) catch null, .cwd => if (l.cwd.len == 0) null else line(h, l.cwd.slice()), .session => if (l.session.len == 0) null else line(h, l.session.slice()), .via => line(h, l.via.text()), .name => line(h, active.registryField(src, l, "name", &scratch) orelse return null), .status => line(h, active.registryField(src, l, "status", &scratch) orelse return null), .title => line(h, active.headField(h.io, l.kind, l.transcript.slice(), .title, &scratch) orelse return null), .model => line(h, active.headField(h.io, l.kind, l.transcript.slice(), .model, &scratch) orelse return null), .agent_model => blk: { const path = activePath(h, a) orelse break :blk null; break :blk line(h, active.headField(h.io, l.kind, path, .model, &scratch) orelse return null); }, // `zmx` is always there, so it can always be written to; empty // means the agent runs outside zmx. .zmx => if (active.zmxOf(src, l.pid, &scratch)) |v| line(h, v) else h.act_text[0..0], else => null, }; } /// The absolute path behind a transcript-shaped file. fn activePath(h: *Harness, a: Act) ?[]const u8 { const l = h.live.at(a.slot, a.gen) orelse return null; switch (a.file) { .transcript => return if (l.transcript.len == 0) null else l.transcript.slice(), .agent_transcript, .agent_model => { var ag: active.Agents = .{}; active.agentsOf(h.io, l, &ag); return active.agentPath(l, &ag, a.agent, &h.act_path); }, else => return null, } } /// Opens an absolute path without following a symlink at the last /// component. These paths are derived from a pinned root or from the /// fd the harness itself holds, never from client bytes, but a name /// swapped underneath must still fail rather than redirect. fn openAbsNoFollow(path: []const u8) ?i32 { if (path.len == 0 or path.len >= active.path_capacity) return null; var z: [active.path_capacity]u8 = @splat(0); @memcpy(z[0..path.len], path); const rc = linux.open(@ptrCast(&z), .{ .ACCMODE = .RDONLY, .NOFOLLOW = true, .CLOEXEC = true, .NONBLOCK = true, }, 0); if (linux.errno(rc) != .SUCCESS) return null; return @intCast(rc); } fn activeAttr(h: *Harness, a: Act) ?fs.Attr { switch (a.file) { .harness_dir => { if (!hasLive(h, a.kind)) return null; return .{ .name = a.kind.text(), .node = activeNode(a), .dir = true, .mode = 0o555 }; }, .entry => { const l = h.live.at(a.slot, a.gen) orelse return null; const nm = std.fmt.bufPrint(&h.act_name, "{d}", .{l.pid}) catch return null; return .{ .name = nm, .node = activeNode(a), .dir = true, .mode = 0o555 }; }, .agents => { const l = h.live.at(a.slot, a.gen) orelse return null; if (l.agent_dir.len == 0) return null; return .{ .name = "agents", .node = activeNode(a), .dir = true, .mode = 0o555 }; }, .agent => { const l = h.live.at(a.slot, a.gen) orelse return null; var ag: active.Agents = .{}; active.agentsOf(h.io, l, &ag); if (a.agent >= ag.count) return null; const nm = ag.name(a.agent); @memcpy(h.act_name[0..nm.len], nm); return .{ .name = h.act_name[0..nm.len], .node = activeNode(a), .dir = true, .mode = 0o555 }; }, .transcript, .agent_transcript => { const path = activePath(h, a) orelse return null; const st = Io.Dir.statFile(.cwd(), h.io, path, .{ .follow_symlinks = false }) catch return null; if (st.kind != .file) return null; return .{ .name = a.file.fileName(), .node = activeNode(a), .size = st.size, .mode = 0o444, .mtime = @truncate(@as(u64, @bitCast(st.mtime.toSeconds()))), }; }, else => { const text = activeText(h, a) orelse return null; // Only `zmx` is ever writable, and only when a move is allowed. const mode: u16 = if (a.file == .zmx and h.allow_move) 0o644 else 0o444; return .{ .name = a.file.fileName(), .node = activeNode(a), .size = text.len, .mode = mode }; }, } } /// The slot holding `pid` for this harness, if any. fn slotOfPid(h: *Harness, k: active.Kind, pid: u32) ?Act { for (&h.live.slots, 0..) |*sl, i| { if (!sl.used or sl.live.kind != k or sl.live.pid != pid) continue; return .{ .file = .entry, .kind = k, .slot = @intCast(i), .gen = sl.gen }; } return null; } fn activeLookup(h: *Harness, req: fs.Req, a: Act, name: []const u8) Answer { switch (a.file) { .harness_dir => { const pid = std.fmt.parseInt(u32, name, 10) catch return fail(req.tag, E.NOENT); var found = slotOfPid(h, a.kind, pid); if (found == null) { rescan(h); found = slotOfPid(h, a.kind, pid); } const act = found orelse return fail(req.tag, E.NOENT); return activeReply(h, req, act, name); }, .entry => { if (std.mem.eql(u8, name, "agents")) { return activeReply(h, req, .{ .file = .agents, .kind = a.kind, .slot = a.slot, .gen = a.gen }, name); } for (AFile.entry_files) |f| { if (!std.mem.eql(u8, f.fileName(), name)) continue; return activeReply(h, req, .{ .file = f, .kind = a.kind, .slot = a.slot, .gen = a.gen }, name); } return fail(req.tag, E.NOENT); }, .agents => { const l = h.live.at(a.slot, a.gen) orelse return fail(req.tag, E.NOENT); var ag: active.Agents = .{}; active.agentsOf(h.io, l, &ag); const i = ag.indexOf(name) orelse return fail(req.tag, E.NOENT); return activeReply(h, req, .{ .file = .agent, .kind = a.kind, .slot = a.slot, .gen = a.gen, .agent = @intCast(i), }, name); }, .agent => { for (AFile.agent_files) |f| { if (!std.mem.eql(u8, f.fileName(), name)) continue; return activeReply(h, req, .{ .file = f, .kind = a.kind, .slot = a.slot, .gen = a.gen, .agent = a.agent, }, name); } return fail(req.tag, E.NOENT); }, else => return fail(req.tag, E.NOTDIR), } } fn activeReply(h: *Harness, req: fs.Req, a: Act, name: []const u8) Answer { const attr = activeAttr(h, a) orelse return fail(req.tag, E.NOENT); var with_name = attr; with_name.name = name; return .{ .reply = .{ .tag = req.tag, .attr = with_name } }; } fn activeReaddir(h: *Harness, req: fs.Req, a: Act) Answer { var st: Staging = .{ .buf = &h.stage_buf, .skip = req.off }; switch (a.file) { .harness_dir => { for (&h.live.slots, 0..) |*sl, i| { if (!sl.used or sl.live.kind != a.kind) continue; var num: [24]u8 = undefined; const nm = std.fmt.bufPrint(&num, "{d}", .{sl.live.pid}) catch continue; st.add(activeNode(.{ .file = .entry, .kind = a.kind, .slot = @intCast(i), .gen = sl.gen, }), true, nm); } }, .entry => { const l = h.live.at(a.slot, a.gen) orelse return fail(req.tag, E.NOENT); for (AFile.entry_files) |f| { const child: Act = .{ .file = f, .kind = a.kind, .slot = a.slot, .gen = a.gen }; if (activeAttr(h, child) == null) continue; // no value: not listed st.add(activeNode(child), false, f.fileName()); } if (l.agent_dir.len > 0) { st.add(activeNode(.{ .file = .agents, .kind = a.kind, .slot = a.slot, .gen = a.gen }), true, "agents"); } }, .agents => { const l = h.live.at(a.slot, a.gen) orelse return fail(req.tag, E.NOENT); var ag: active.Agents = .{}; active.agentsOf(h.io, l, &ag); for (0..ag.count) |i| { st.add(activeNode(.{ .file = .agent, .kind = a.kind, .slot = a.slot, .gen = a.gen, .agent = @intCast(i), }), true, ag.name(i)); } }, .agent => { for (AFile.agent_files) |f| { const child: Act = .{ .file = f, .kind = a.kind, .slot = a.slot, .gen = a.gen, .agent = a.agent }; if (activeAttr(h, child) == null) continue; st.add(activeNode(child), false, f.fileName()); } }, else => return fail(req.tag, E.NOTDIR), } return .{ .reply = .{ .tag = req.tag }, .bytes = h.stage_buf[0..st.len] }; } fn activeRead(h: *Harness, req: fs.Req, a: Act) Answer { switch (a.file) { .harness_dir, .entry, .agents, .agent => return fail(req.tag, E.ISDIR), .transcript, .agent_transcript => { const path = activePath(h, a) orelse return fail(req.tag, E.NOENT); const fd = openAbsNoFollow(path) orelse return fail(req.tag, E.NOENT); const file: Io.File = .{ .handle = fd, .flags = .{ .nonblocking = false } }; defer file.close(h.io); const st = file.stat(h.io) catch return fail(req.tag, E.IO); if (st.kind == .directory) return fail(req.tag, E.ISDIR); if (st.kind != .file) return fail(req.tag, E.PERM); _ = linux.fcntl(fd, linux.F.SETFL, 0); const want = @min(req.size, h.data_buf.len); const n = file.readPositionalAll(h.io, h.data_buf[0..want], req.off) catch return fail(req.tag, E.IO); return .{ .reply = .{ .tag = req.tag }, .bytes = h.data_buf[0..n] }; }, else => { const text = activeText(h, a) orelse return fail(req.tag, E.NOENT); return window(req, text); }, } } // ---- the move: writing a zmx session name into an agent's `zmx` ---------------- /// How long a move waits, in 100ms ticks: for the agent to take the /// hangup, then to die outright, then for the new zmx session to post. const term_ticks: usize = 50; const kill_ticks: usize = 30; const post_ticks: usize = 100; fn napOneTick(h: *Harness) void { h.io.sleep(.fromMilliseconds(100), .awake) catch {}; } /// Is this name already a live zmx session? zmx posts each session into /// the registry, so the registry is the answer — no process scanning. fn zmxPosted(h: *Harness, name: []const u8) bool { if (h.runtime_len == 0) return false; var buf: [active.path_capacity]u8 = undefined; const path = std.fmt.bufPrint(&buf, "{s}/9p/zmx/{s}", .{ h.runtime_buf[0..h.runtime_len], name }) catch return true; return Io.Dir.statFile(.cwd(), h.io, path, .{ .follow_symlinks = true }) != error.FileNotFound; } fn procGone(h: *Harness, pid: u32) bool { var buf: [active.path_capacity]u8 = undefined; const path = std.fmt.bufPrint(&buf, "{s}/{d}", .{ h.proc_buf[0..h.proc_len], pid }) catch return false; _ = Io.Dir.statFile(.cwd(), h.io, path, .{ .follow_symlinks = true }) catch return true; return false; } /// SIGTERM, then SIGKILL, then give up. The agent's session was /// resolved before this ran, so whatever happens it has somewhere to /// come back to. fn killAndWait(h: *Harness, pid: u32) bool { _ = linux.kill(@intCast(pid), .TERM); for (0..term_ticks) |_| { if (procGone(h, pid)) return true; napOneTick(h); } _ = linux.kill(@intCast(pid), .KILL); for (0..kill_ticks) |_| { if (procGone(h, pid)) return true; napOneTick(h); } return false; } /// `zmx run -d `, in the /// agent's own directory. Every word but `` is fixed by the /// harness; `` was checked against zmx's label charset before /// anything was killed. No byte a client wrote reaches `exec` as a /// command. fn spawnZmx(h: *Harness, l: *const active.Live, name: []const u8, resume_value: []const u8) bool { if (h.zmx_len == 0) return false; const harness_argv0: []const u8 = @tagName(l.kind); const resume_flag: []const u8 = switch (l.kind) { .codex => "resume", else => "--resume", }; // One buffer holds every NUL-terminated word; `argv` points into it. var words: [8 * active.path_capacity]u8 = undefined; var used: usize = 0; var argv: [9]?[*:0]const u8 = @splat(null); var n: usize = 0; const parts = [_][]const u8{ h.zmx_buf[0..h.zmx_len], "run", name, "-d", harness_argv0, resume_flag, resume_value, }; for (parts) |w| { if (used + w.len + 1 > words.len or n + 1 >= argv.len) return false; @memcpy(words[used..][0..w.len], w); words[used + w.len] = 0; argv[n] = @ptrCast(&words[used]); n += 1; used += w.len + 1; } var cwd_z: [active.cwd_capacity + 1]u8 = @splat(0); if (l.cwd.len >= cwd_z.len) return false; @memcpy(cwd_z[0..l.cwd.len], l.cwd.slice()); const rc = linux.fork(); if (linux.errno(rc) != .SUCCESS) return false; if (rc == 0) { // The child: only async-signal-safe calls from here. _ = linux.chdir(@ptrCast(&cwd_z)); const empty = [_:null]?[*:0]const u8{}; const envp: cloud9.post.Env = h.envp orelse ∅ _ = linux.execve(argv[0].?, @ptrCast(&argv), envp); linux.exit(127); } // Reap the forked `zmx`, which returns as soon as the session is up. const child: i32 = @intCast(rc); var status: u32 = 0; for (0..post_ticks) |_| { const w = linux.waitpid(child, &status, 1); // WNOHANG if (w == @as(usize, @intCast(child))) break; napOneTick(h); } return true; } /// A move: kill the agent and bring it back inside a zmx session of the /// name written. Refusals come before anything is destroyed. fn moveToZmx(h: *Harness, req: fs.Req, a: Act) Answer { if (!h.allow_move) return fail(req.tag, E.PERM); const name = std.mem.trim(u8, req.data, " \t\r\n"); if (!active.legalZmxName(name)) return fail(req.tag, E.INVAL); const l = h.live.at(a.slot, a.gen) orelse return fail(req.tag, E.NOENT); // Nothing is killed that has nowhere to come back to. if (l.via == .none or l.session.len == 0) return fail(req.tag, E.PERM); if (l.cwd.len == 0) return fail(req.tag, E.PERM); const resume_value: []const u8 = switch (l.kind) { // omp resumes by the transcript it wrote, the others by id. .omp => l.transcript.slice(), .claude, .codex, .hermes => l.session.slice(), // dsh has no resume form worth guessing at. .dsh => return fail(req.tag, E.PERM), }; if (resume_value.len == 0) return fail(req.tag, E.PERM); // Already there: setting a value it already has changes nothing. var have: [active.text_capacity]u8 = undefined; if (active.zmxOf(sourcesOf(h), l.pid, &have)) |current| { if (std.mem.eql(u8, current, name)) { return .{ .reply = .{ .tag = req.tag, .written = @intCast(req.data.len) } }; } } if (zmxPosted(h, name)) return fail(req.tag, E.EXIST); // The slot could have gone stale between the scan and this write. if (!active.stillAlive(sourcesOf(h), l)) return fail(req.tag, E.NOENT); // Everything below this line destroys something. var snapshot = l.*; if (!killAndWait(h, snapshot.pid)) return fail(req.tag, E.IO); if (!spawnZmx(h, &snapshot, name, resume_value)) return fail(req.tag, E.IO); for (0..post_ticks) |_| { if (zmxPosted(h, name)) { rescan(h); return .{ .reply = .{ .tag = req.tag, .written = @intCast(req.data.len) } }; } napOneTick(h); } rescan(h); return fail(req.tag, E.IO); } // ---- dispatch ------------------------------------------------------------------ /// Answers one engine request. The caller holds `h.mutex` and replies /// before letting go of it (answer bytes point into `h`). pub fn handle(h: *Harness, req: fs.Req) Answer { const t = h.resolve(req.node) orelse return fail(req.tag, E.NOENT); return switch (req.op) { .lookup => lookup(h, req, t), .getattr => attrReply(h, req.tag, t), .setattr => setattrReq(h, req, t), .open => open(h, req, t), .release => release(h, req, t), .readdir => readdir(h, req, t), .read => read(h, req, t), .write => writeReq(h, req, t), }; } /// Truncation is how a shell's `>` opens a file before writing it. The /// `zmx` file has no length of its own — a write replaces the value — /// so on the one writable file a zero-length truncate is a no-op /// rather than a refusal. Everything else still answers EPERM. fn setattrReq(h: *Harness, req: fs.Req, t: Target) Answer { switch (t) { .act => |a| if (a.file == .zmx and h.allow_move and req.truncate) { return .{ .reply = .{ .tag = req.tag } }; }, else => {}, } return fail(req.tag, E.PERM); } /// The tree answers EPERM to every write but one: a zmx session name /// into a live agent's `zmx`, which moves it there. fn writeReq(h: *Harness, req: fs.Req, t: Target) Answer { switch (t) { .act => |a| if (a.file == .zmx) return moveToZmx(h, req, a), else => {}, } return fail(req.tag, E.PERM); } fn attrReply(h: *Harness, tag: u64, t: Target) Answer { const a = attrFor(h, t) orelse return fail(tag, E.NOENT); return .{ .reply = .{ .tag = tag, .attr = a } }; } /// A lookup's attr names the file as the client asked for it (the engine /// keeps that name for the fid); refs for path targets were already taken /// by `entryFor`. fn lookupAttr(h: *Harness, req: fs.Req, t: Target, name: []const u8) Answer { const a = attrFor(h, t) orelse return fail(req.tag, E.NOENT); var with_name = a; with_name.name = name; return .{ .reply = .{ .tag = req.tag, .attr = with_name } }; } fn lookup(h: *Harness, req: fs.Req, t: Target) Answer { const name = req.data; if (name.len == 0 or name.len > 255) return fail(req.tag, E.NOENT); if (std.mem.indexOfAny(u8, name, "/\x00") != null) return fail(req.tag, E.NOENT); if (std.mem.eql(u8, name, ".")) { // The engine asks for "." when cloning a fid; the reference is // paid for path targets here like any other lookup result. switch (t) { .top, .act => return attrReply(h, req.tag, t), .path => |e| { e.refs += 1; return lookupAttr(h, req, t, name); }, } } if (std.mem.eql(u8, name, "..")) return lookupParent(h, req, t); switch (t) { .top => |top| switch (top) { .root => { const found: ?Top = blk: for (Top.listed) |e| { if (std.mem.eql(u8, e.fileName(), name)) break :blk e; } else break :blk null; return attrReply(h, req.tag, .{ .top = found orelse return fail(req.tag, E.NOENT) }); }, .claude, .codex, .skills => { const m = mountNamed(top, name) orelse return fail(req.tag, E.NOENT); const e = h.entryFor(m.root, m.rel, true) orelse return fail(req.tag, E.NFILE); const a = statAttr(h, e) orelse { unref(e); return fail(req.tag, E.NOENT); }; var with_name = a; with_name.name = name; return .{ .reply = .{ .tag = req.tag, .attr = with_name } }; }, .omp, .hermes, .dsh => { const m = top.mirror().?; return lookupBelow(h, req, m.root, m.rel, name); }, .active => { const k = blk: for (std.enums.values(active.Kind)) |k| { if (std.mem.eql(u8, k.text(), name)) break :blk k; } else return fail(req.tag, E.NOENT); if (!hasLive(h, k)) { rescan(h); if (!hasLive(h, k)) return fail(req.tag, E.NOENT); } return activeReply(h, req, .{ .file = .harness_dir, .kind = k }, name); }, .pid, .uptime => return fail(req.tag, E.NOTDIR), }, .path => |e| { const rel_path = e.rel(); if (attrFor(h, t)) |a| { if (!a.dir) return fail(req.tag, E.NOTDIR); } else return fail(req.tag, E.NOENT); return lookupBelow(h, req, e.root, rel_path, name); }, .act => |a| return activeLookup(h, req, a, name), } } /// A lookup of `name` in the directory (root, dir_rel): the child's rel /// path is composed only from the remembered pair and the (single-segment, /// engine-vetted) name. fn lookupBelow(h: *Harness, req: fs.Req, r: Root, dir_rel: []const u8, name: []const u8) Answer { if (excluded(name)) return fail(req.tag, E.NOENT); var scratch: [rel_capacity + 256]u8 = undefined; const child_rel = joinRel(&scratch, dir_rel, name) orelse return fail(req.tag, E.NOENT); const e = h.entryFor(r, child_rel, true) orelse return fail(req.tag, E.NFILE); const a = statAttr(h, e) orelse { unref(e); return fail(req.tag, E.NOENT); }; var with_name = a; with_name.name = name; return .{ .reply = .{ .tag = req.tag, .attr = with_name } }; } fn joinRel(scratch: []u8, dir_rel: []const u8, name: []const u8) ?[]const u8 { if (dir_rel.len == 0) { if (name.len > scratch.len) return null; @memcpy(scratch[0..name.len], name); return scratch[0..name.len]; } const total = dir_rel.len + 1 + name.len; if (total > scratch.len) return null; @memcpy(scratch[0..dir_rel.len], dir_rel); scratch[dir_rel.len] = '/'; @memcpy(scratch[dir_rel.len + 1 .. total], name); return scratch[0..total]; } fn lookupParent(h: *Harness, req: fs.Req, t: Target) Answer { const parent: Target = switch (t) { .top => |top| .{ .top = top.parent() }, .path => |e| blk: { const rel_path = e.rel(); if (std.mem.lastIndexOfScalar(u8, rel_path, '/')) |i| { const up = rel_path[0..i]; const pe = h.entryFor(e.root, up, true) orelse return fail(req.tag, E.NFILE); break :blk .{ .path = pe }; } break :blk .{ .top = mountOwner(e.root, rel_path) }; }, .act => |a| switch (a.file) { .harness_dir => .{ .top = .active }, .entry => .{ .act = .{ .file = .harness_dir, .kind = a.kind } }, .agents => .{ .act = .{ .file = .entry, .kind = a.kind, .slot = a.slot, .gen = a.gen } }, .agent => .{ .act = .{ .file = .agents, .kind = a.kind, .slot = a.slot, .gen = a.gen } }, .agent_model, .agent_transcript => .{ .act = .{ .file = .agent, .kind = a.kind, .slot = a.slot, .gen = a.gen, .agent = a.agent, } }, // Every other file hangs directly off its entry. else => .{ .act = .{ .file = .entry, .kind = a.kind, .slot = a.slot, .gen = a.gen } }, }, }; return attrReply(h, req.tag, parent); } fn unref(e: *Entry) void { if (e.refs > 0) e.refs -= 1; if (e.refs == 0) { e.used = false; e.gen +%= 1; } } fn open(h: *Harness, req: fs.Req, t: Target) Answer { _ = attrFor(h, t) orelse return fail(req.tag, E.NOENT); // still there? // Read-only tree, with exactly one exception: the `zmx` file of a // live agent, and only when the daemon was started to allow moves. // Truncation is meaningless there (a write replaces the value), so // it is accepted rather than refused, which is what `>` needs. const writable = switch (t) { .act => |a| a.file == .zmx and h.allow_move, else => false, }; const rw = req.omode & 3; if (!writable) { if (rw == cloud9.owrite or rw == cloud9.ordwr) return fail(req.tag, E.PERM); if (req.omode & cloud9.otrunc != 0) return fail(req.tag, E.PERM); } return .{ .reply = .{ .tag = req.tag, .handle = 1 } }; } fn release(h: *Harness, req: fs.Req, t: Target) Answer { _ = h; switch (t) { .top, .act => {}, .path => |e| unref(e), } return .{ .reply = .{ .tag = req.tag } }; } // ---- reads ---------------------------------------------------------------------- fn read(h: *Harness, req: fs.Req, t: Target) Answer { switch (t) { .top => |top| switch (top) { .pid, .uptime => { const text = factText(h, top, &fact_buf); return window(req, text); }, else => return fail(req.tag, E.ISDIR), }, .act => |a| return activeRead(h, req, a), .path => |e| { // `O_NONBLOCK` so a fifo left in a harness root cannot park the // daemon in `open`; the kind check below refuses it anyway. const fd = openIn(h, e.root, e.rel(), .{ .ACCMODE = .RDONLY, .NONBLOCK = true, .CLOEXEC = true, }) orelse return fail(req.tag, E.NOENT); const file: Io.File = .{ .handle = fd, .flags = .{ .nonblocking = false } }; defer file.close(h.io); const st = file.stat(h.io) catch return fail(req.tag, E.IO); if (st.kind == .directory) return fail(req.tag, E.ISDIR); // Only regular files have bytes this tree promises to serve. if (st.kind != .file) return fail(req.tag, E.PERM); _ = linux.fcntl(fd, linux.F.SETFL, 0); // pread wants no O_NONBLOCK const want = @min(req.size, h.data_buf.len); const n = file.readPositionalAll(h.io, h.data_buf[0..want], req.off) catch return fail(req.tag, E.IO); return .{ .reply = .{ .tag = req.tag }, .bytes = h.data_buf[0..n] }; }, } } /// `text` windowed by the request's offset and size. fn window(req: fs.Req, text: []const u8) Answer { const off: usize = @intCast(@min(req.off, text.len)); const n = @min(text.len - off, req.size); return .{ .reply = .{ .tag = req.tag }, .bytes = text[off..][0..n] }; } // ---- readdir -------------------------------------------------------------------- /// Directory records in the engine's shape: `node:u64le dir:u8 len:u8 name`. const Staging = struct { buf: []u8, len: usize = 0, skip: u64, /// Set once a record did not fit. Everything after it is left for the /// next read: dropping one record and staging a shorter one behind it /// would lose that entry, because the client's next offset counts the /// records it received. full: bool = false, fn add(s: *Staging, node: u64, dir: bool, name: []const u8) void { if (s.full) return; if (s.skip > 0) { s.skip -= 1; return; } if (name.len == 0 or name.len > 255) return; if (s.len + 10 + name.len > s.buf.len) { s.full = true; return; } std.mem.writeInt(u64, s.buf[s.len..][0..8], node, .little); s.buf[s.len + 8] = @intFromBool(dir); s.buf[s.len + 9] = @intCast(name.len); @memcpy(s.buf[s.len + 10 ..][0..name.len], name); s.len += 10 + name.len; } }; fn readdir(h: *Harness, req: fs.Req, t: Target) Answer { var st: Staging = .{ .buf = &h.stage_buf, .skip = req.off }; switch (t) { .top => |top| switch (top) { .root => for (Top.listed) |e| st.add(topNode(e), e.dir(), e.fileName()), .claude, .codex, .skills => { for (named_mounts) |m| { if (m.owner != top) continue; // Only mounts whose target exists are listed (a harness // without skills simply has no skills/ entry). const a = attrOfMount(h, m.mount.root, m.mount.rel) orelse continue; const e = h.entryFor(m.mount.root, m.mount.rel, false) orelse continue; st.add(h.entryNode(e), a.dir, m.name); } }, .pid, .uptime => return fail(req.tag, E.NOTDIR), .active => { rescan(h); for (std.enums.values(active.Kind)) |k| { if (!hasLive(h, k)) continue; st.add(activeNode(.{ .file = .harness_dir, .kind = k }), true, k.text()); } }, .omp, .hermes, .dsh => { const m = top.mirror().?; if (h.base(m.root) == null) return fail(req.tag, E.NOENT); switch (listDir(h, &st, m.root, m.rel)) { .ok => {}, .gone => return fail(req.tag, E.NOENT), .failed => return fail(req.tag, E.IO), .overflow => return fail(req.tag, E.NFILE), } }, }, .path => |e| { if (attrFor(h, t)) |a| { if (!a.dir) return fail(req.tag, E.NOTDIR); } else return fail(req.tag, E.NOENT); switch (listDir(h, &st, e.root, e.rel())) { .ok => {}, .gone => return fail(req.tag, E.NOENT), .failed => return fail(req.tag, E.IO), .overflow => return fail(req.tag, E.NFILE), } }, .act => |a| return activeReaddir(h, req, a), } return .{ .reply = .{ .tag = req.tag }, .bytes = h.stage_buf[0..st.len] }; } /// How a listing attempt ended. `overflow` is deliberate: a comptime cap /// that would silently drop entries answers an error instead, because a /// short listing is indistinguishable from a small directory. const Listing = enum { ok, gone, failed, overflow }; /// Stages the contents of the directory (root, dir_rel): every name the /// walker yields that survives the exclusions, sorted so a listing that /// spans several reads stays consistent. fn listDir(h: *Harness, st: *Staging, r: Root, dir_rel: []const u8) Listing { const fd = openIn(h, r, dir_rel, .{ .ACCMODE = .RDONLY, .DIRECTORY = true, .CLOEXEC = true, }) orelse return .gone; const dir: Io.Dir = .{ .handle = fd }; defer Io.Dir.close(dir, h.io); var read_buf: [Io.Dir.Iterator.reader_buffer_len]u8 align(@alignOf(usize)) = undefined; var reader = Io.Dir.Reader.init(dir, &read_buf); var names_len: usize = 0; var count: usize = 0; while (true) { const entry = (reader.next(h.io) catch return .failed) orelse break; if (excluded(entry.name)) continue; var kind = entry.kind; if (kind == .unknown or kind == .sym_link) { // The walker's word is not proof: stat without following. var child_buf: [rel_capacity + 256]u8 = undefined; const child_rel = joinRel(&child_buf, dir_rel, entry.name) orelse continue; const cst = statIn(h, r, child_rel) orelse continue; kind = cst.kind; } if (kind == .sym_link) continue; // never served // A cap reached is an error, never a short listing: a directory // that quietly loses entries is a wrong answer, and a caller // cannot tell it from a small directory. if (count == list_capacity or names_len + entry.name.len > h.list_names.len) return .overflow; @memcpy(h.list_names[names_len..][0..entry.name.len], entry.name); h.list_offs[count] = @intCast(names_len); h.list_dirs[count] = kind == .directory; names_len += entry.name.len; count += 1; } // Sort the (offset, length) pairs by name for cursor stability. const SortCtx = struct { names: []const u8, offs: []const u32, lens: [list_capacity]u32, fn lessThan(ctx: @This(), a: usize, b: usize) bool { return std.mem.order(u8, ctx.nameAt(a), ctx.nameAt(b)) == .lt; } fn nameAt(ctx: @This(), i: usize) []const u8 { const start = ctx.offs[i]; return ctx.names[start..][0..ctx.lens[i]]; } }; var lens: [list_capacity]u32 = @splat(0); var order: [list_capacity]usize = @splat(0); { var end: usize = 0; for (0..count) |i| { end = if (i + 1 < count) h.list_offs[i + 1] else names_len; lens[i] = @intCast(end - h.list_offs[i]); order[i] = i; } } const ctx: SortCtx = .{ .names = h.list_names[0..names_len], .offs = &h.list_offs, .lens = lens }; std.mem.sort(usize, order[0..count], ctx, SortCtx.lessThan); for (order[0..count]) |i| { const name = ctx.nameAt(i); var child_buf: [rel_capacity + 256]u8 = undefined; const child_rel = joinRel(&child_buf, dir_rel, name) orelse continue; const e = h.entryFor(r, child_rel, false) orelse return .overflow; // path table full st.add(h.entryNode(e), h.list_dirs[i], name); } return .ok; } // ---- unit tests ------------------------------------------------------------------- const testing = std.testing; /// A rig with a fake HOME: every root under one temp dir, never the real /// ~/.claude or any other live harness root. const Rig = struct { dir: testing.TmpDir, /// Build a fixture process tree under /proc and point /// `/active` at it. Off by default: a unit test must opt in to /// reading a process tree, and it is never the live one. with_proc: bool = false, /// The pid the daemon believes it is, for the ancestry exclusion. self_pid: u32 = 4242, zmx: []const u8 = "zmx", allow_move: bool = false, path_buf: [std.fs.max_path_bytes]u8 = undefined, home: []const u8 = undefined, h: *Harness = undefined, harness_mem: Harness = undefined, fn start(rig: *Rig) !void { const io = testing.io; rig.dir = testing.tmpDir(.{}); errdefer rig.dir.cleanup(); const len = try rig.dir.dir.realPath(io, &rig.path_buf); rig.home = try testing.allocator.dupe(u8, rig.path_buf[0..len]); var mk: [std.fs.max_path_bytes]u8 = undefined; // The five roots, pinned to the fake home. inline for ([_][]const u8{ ".claude/projects/p1", ".claude/skills/revu", ".codex/sessions/2026/09/21", ".omp/agent", ".hermes/logs", ".dsh/profiles", }) |sub| { try Io.Dir.cwd().createDirPath(io, try std.fmt.bufPrint(&mk, "{s}/{s}", .{ rig.home, sub })); } var base_buf: [5][std.fs.max_path_bytes]u8 = @splat(@splat(0)); var bases: [5][]const u8 = @splat(""); inline for (0..5) |i| { bases[i] = try std.fmt.bufPrint(&base_buf[i], "{s}/{s}", .{ rig.home, home_dirs[i] }); } rig.harness_mem = undefined; var proc_buf: [std.fs.max_path_bytes]u8 = undefined; const proc_root: []const u8 = if (rig.with_proc) try std.fmt.bufPrint(&proc_buf, "{s}/proc", .{rig.home}) else ""; if (rig.with_proc) { try Io.Dir.cwd().createDirPath(io, proc_root); try rig.put("proc/stat", "cpu 1 2 3\nbtime 1000000\nprocesses 7\n"); } rig.harness_mem.init(.{ .io = io, .pid = rig.self_pid, .bases = bases, .proc = proc_root, .home = rig.home, .zmx = rig.zmx, .allow_move = rig.allow_move, }); rig.h = &rig.harness_mem; } fn end(rig: *Rig) void { rig.dir.cleanup(); testing.allocator.free(rig.home); } fn put(rig: *Rig, rel_path: []const u8, bytes: []const u8) !void { var buf: [std.fs.max_path_bytes]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "{s}/{s}", .{ rig.home, rel_path }); if (std.mem.lastIndexOfScalar(u8, rel_path, '/')) |i| { var dbuf: [std.fs.max_path_bytes]u8 = undefined; const dir_path = try std.fmt.bufPrint(&dbuf, "{s}/{s}", .{ rig.home, rel_path[0..i] }); try Io.Dir.cwd().createDirPath(testing.io, dir_path); } var file = try Io.Dir.createFileAbsolute(testing.io, path, .{}); defer file.close(testing.io); try file.writeStreamingAll(testing.io, bytes); } fn del(rig: *Rig, rel_path: []const u8) !void { var buf: [std.fs.max_path_bytes]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "{s}/{s}", .{ rig.home, rel_path }); try Io.Dir.deleteFileAbsolute(testing.io, path); } /// lookup of `name` in `dir_node`, answering the child's attr. fn lookupName(rig: *Rig, tag: u64, dir_node: u64, name: []const u8) Answer { return handle(rig.h, .{ .tag = tag, .op = .lookup, .node = dir_node, .data = name }); } /// Writes one fake process into the fixture `/proc`: the `stat` /// line (with the start time in field 22, after a name that holds /// the spaces and parentheses a real one can), the NUL-separated /// `cmdline`, and a `cwd` symlink. fn fakeProc(rig: *Rig, o: struct { pid: u32, ppid: u32 = 1, comm: []const u8 = "x", starttime: u64 = 5000, argv: []const u8, cwd: []const u8 = "", }) !void { var rel: [128]u8 = undefined; var stat_text: [512]u8 = undefined; var w = Io.Writer.fixed(&stat_text); try w.print("{d} ({s}) S {d}", .{ o.pid, o.comm, o.ppid }); for (5..22) |i| try w.print(" {d}", .{i}); try w.print(" {d} 0 0\n", .{o.starttime}); try rig.put(try std.fmt.bufPrint(&rel, "proc/{d}/stat", .{o.pid}), w.buffered()); try rig.put(try std.fmt.bufPrint(&rel, "proc/{d}/cmdline", .{o.pid}), o.argv); const target = if (o.cwd.len > 0) o.cwd else rig.home; var link_buf: [std.fs.max_path_bytes]u8 = undefined; const link = try std.fmt.bufPrintZ(&link_buf, "{s}/proc/{d}/cwd", .{ rig.home, o.pid }); Io.Dir.cwd().symLink(testing.io, target, link, .{}) catch {}; } /// Removes a fake process, as an exit would. fn reapProc(rig: *Rig, pid: u32) !void { var buf: [std.fs.max_path_bytes]u8 = undefined; const dir_path = try std.fmt.bufPrint(&buf, "{s}/proc/{d}", .{ rig.home, pid }); const d = try Io.Dir.openDirAbsolute(testing.io, dir_path, .{ .iterate = true }); var rb: [Io.Dir.Iterator.reader_buffer_len]u8 align(@alignOf(usize)) = undefined; var it = Io.Dir.Reader.init(d, &rb); var names: [8][64]u8 = undefined; var lens: [8]usize = @splat(0); var n: usize = 0; while (n < names.len) { const e = (it.next(testing.io) catch break) orelse break; @memcpy(names[n][0..e.name.len], e.name); lens[n] = e.name.len; n += 1; } Io.Dir.close(d, testing.io); for (0..n) |i| { var one: [std.fs.max_path_bytes]u8 = undefined; const path = try std.fmt.bufPrint(&one, "{s}/{s}", .{ dir_path, names[i][0..lens[i]] }); Io.Dir.deleteFileAbsolute(testing.io, path) catch {}; } try Io.Dir.cwd().deleteDir(testing.io, dir_path); } }; const home_dirs = [5][]const u8{ ".claude", ".codex", ".omp", ".hermes", ".dsh" }; fn expectNoent(a: Answer) !void { try testing.expect(a.reply.status == .err); try testing.expectEqual(E.NOENT, a.reply.errno); } fn expectEperm(a: Answer) !void { try testing.expect(a.reply.status == .err); try testing.expectEqual(E.PERM, a.reply.errno); } test "tree: facts at the root" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); const a = rig.lookupName(1, root, "pid"); try testing.expect(a.reply.status == .ok); try testing.expect(a.reply.attr.dir == false); var read_a = handle(rig.h, .{ .tag = 2, .op = .read, .node = a.reply.attr.node, .off = 0, .size = 64 }); try testing.expectEqualStrings("4242\n", read_a.bytes); read_a = handle(rig.h, .{ .tag = 3, .op = .read, .node = a.reply.attr.node, .off = 0, .size = 2 }); try testing.expectEqualStrings("42", read_a.bytes); const up = rig.lookupName(4, root, "uptime"); try testing.expect(up.reply.status == .ok); const up_read = handle(rig.h, .{ .tag = 5, .op = .read, .node = up.reply.attr.node, .off = 0, .size = 64 }); try testing.expect(up_read.bytes.len > 0 and up_read.bytes[up_read.bytes.len - 1] == '\n'); // The root lists the facts and the five harness dirs. const listing = handle(rig.h, .{ .tag = 6, .op = .readdir, .node = root, .off = 0, .size = msize }); try testing.expect(listing.reply.status == .ok); for ([_][]const u8{ "pid", "uptime", "claude", "codex", "omp", "hermes", "dsh", "skills" }) |name| { try testing.expect(stageHas(listing.bytes, name)); } } fn stageHas(staged: []const u8, name: []const u8) bool { var i: usize = 0; while (i + 10 <= staged.len) { const len: usize = staged[i + 9]; const entry = staged[i + 10 ..][0..len]; if (std.mem.eql(u8, entry, name)) return true; i += 10 + len; } return false; } test "tree: exclusions never serve a credentials-shaped name" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); // The fixture: a real transcript and a pile of credentials-shaped names // at several depths, including inside the mirrored subtrees. try rig.put(".claude/projects/p1/session-x.jsonl", "{\"a\":1}\n"); try rig.put(".claude/projects/p1/.credentials.json", "STAY OUT\n"); try rig.put(".claude/projects/p1/auth.json", "STAY OUT\n"); try rig.put(".claude/projects/p1/settings.json", "STAY OUT\n"); try rig.put(".claude/projects/p1/token.txt", "STAY OUT\n"); try rig.put(".claude/projects/p1/api.key", "STAY OUT\n"); try rig.put(".claude/projects/p1/models.yml", "STAY OUT\n"); try rig.put(".claude/projects/p1/deep/.env", "STAY OUT\n"); try rig.put(".claude/history.jsonl", "{\"h\":1}\n"); try rig.put(".hermes/auth.json", "STAY OUT\n"); try rig.put(".hermes/.env", "STAY OUT\n"); try rig.put(".hermes/config.yaml", "STAY OUT\n"); try rig.put(".hermes/logs/app.log", "log line\n"); try rig.put(".dsh/.credentials.yaml", "STAY OUT\n"); try rig.put(".dsh/settings.yaml", "STAY OUT\n"); // /claude lists only its mounts; the credentials at ~/.claude root are // not in the tree at all (no mount serves them). const claude = handle(rig.h, .{ .tag = 1, .op = .readdir, .node = topNode(.claude), .off = 0, .size = msize }); try testing.expect(claude.reply.status == .ok); for ([_][]const u8{ "projects", "history", "skills" }) |name| try testing.expect(stageHas(claude.bytes, name)); try testing.expect(!stageHas(claude.bytes, "credentials")); // A project's listing shows the transcript and nothing else. const projects = rig.lookupName(2, topNode(.claude), "projects"); try testing.expect(projects.reply.status == .ok); const p1 = rig.lookupName(3, projects.reply.attr.node, "p1"); try testing.expect(p1.reply.status == .ok); const listed = handle(rig.h, .{ .tag = 4, .op = .readdir, .node = p1.reply.attr.node, .off = 0, .size = msize }); try testing.expect(listed.reply.status == .ok); try testing.expect(stageHas(listed.bytes, "session-x.jsonl")); for ([_][]const u8{ ".credentials.json", "auth.json", "settings.json", "token.txt", "api.key", "models.yml" }) |name| { try testing.expect(!stageHas(listed.bytes, name)); } // The plain directory that holds a .env is served; the .env is not. try testing.expect(stageHas(listed.bytes, "deep")); // Every credentials-shaped name is unreachable by lookup too. for ([_][]const u8{ ".credentials.json", "auth.json", "settings.json", "token.txt", "api.key", "models.yml" }) |name| { try expectNoent(rig.lookupName(5, p1.reply.attr.node, name)); } const deep = rig.lookupName(6, p1.reply.attr.node, "deep"); try testing.expect(deep.reply.status == .ok); try expectNoent(rig.lookupName(7, deep.reply.attr.node, ".env")); // The fully mirrored roots hide theirs as well. const hermes = rig.lookupName(8, root, "hermes"); try testing.expect(hermes.reply.status == .ok); const hlist = handle(rig.h, .{ .tag = 9, .op = .readdir, .node = hermes.reply.attr.node, .off = 0, .size = msize }); try testing.expect(stageHas(hlist.bytes, "logs")); for ([_][]const u8{ "auth.json", ".env", "config.yaml" }) |name| { try testing.expect(!stageHas(hlist.bytes, name)); try expectNoent(rig.lookupName(10, hermes.reply.attr.node, name)); } const dsh = rig.lookupName(11, root, "dsh"); try testing.expect(dsh.reply.status == .ok); const dlist = handle(rig.h, .{ .tag = 12, .op = .readdir, .node = dsh.reply.attr.node, .off = 0, .size = msize }); try testing.expect(!stageHas(dlist.bytes, ".credentials.yaml")); try testing.expect(!stageHas(dlist.bytes, "settings.yaml")); try testing.expect(stageHas(dlist.bytes, "profiles")); // The exclusion predicate itself, spelled out. try testing.expect(excluded(".credentials.json")); try testing.expect(excluded("AUTH.JSON")); try testing.expect(excluded("session-token.bin")); try testing.expect(excluded("id_rsa_backup")); try testing.expect(excluded("prod.pem")); try testing.expect(excluded("config.yaml")); try testing.expect(!excluded("session-x.jsonl")); try testing.expect(!excluded("SKILL.md")); try testing.expect(!excluded("logs")); } test "tree: paths compose only from the pinned roots" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put(".claude/projects/p1/session-x.jsonl", "{}\n"); // A slash, a NUL or an overlong name never reaches the filesystem. try expectNoent(rig.lookupName(1, topNode(.claude), "projects/../p1")); try expectNoent(rig.lookupName(2, topNode(.claude), "projects/\x00")); // A symlink inside a mirror is not served, whatever it points at. var buf: [std.fs.max_path_bytes]u8 = undefined; const link = try std.fmt.bufPrintZ(&buf, "{s}/.claude/projects/p1/escape", .{rig.home}); try Io.Dir.cwd().symLink(testing.io, rig.home, link, .{}); const projects = rig.lookupName(3, topNode(.claude), "projects"); const p1 = rig.lookupName(4, projects.reply.attr.node, "p1"); try expectNoent(rig.lookupName(5, p1.reply.attr.node, "escape")); // ".." from a mirrored file lands on its canonical parent, and walking // ".." repeatedly terminates at the root. const session = rig.lookupName(6, p1.reply.attr.node, "session-x.jsonl"); try testing.expect(session.reply.status == .ok); var cur = session.reply.attr.node; var hops: usize = 0; while (hops < 8) : (hops += 1) { const up = rig.lookupName(7, cur, ".."); try testing.expect(up.reply.status == .ok); if (up.reply.attr.node == root) break; cur = up.reply.attr.node; } try testing.expect(cur == root or hops < 8); // The facts refuse lookups with NOTDIR. try testing.expect(rig.lookupName(8, topNode(.pid), "x").reply.errno == E.NOTDIR); } test "tree: node ids — same file equal, distinct files differ, ids recycle" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put(".claude/projects/p1/session-x.jsonl", "{}\n"); try rig.put(".claude/projects/p1/session-y.jsonl", "{}\n"); const projects = rig.lookupName(1, topNode(.claude), "projects"); const a1 = rig.lookupName(2, projects.reply.attr.node, "p1"); // Two walks of the same file: the same node id (the dedupe table). const x1 = rig.lookupName(3, a1.reply.attr.node, "session-x.jsonl"); const x2 = rig.lookupName(4, a1.reply.attr.node, "session-x.jsonl"); try testing.expect(x1.reply.status == .ok); try testing.expectEqual(x1.reply.attr.node, x2.reply.attr.node); const y1 = rig.lookupName(5, a1.reply.attr.node, "session-y.jsonl"); try testing.expect(y1.reply.attr.node != x1.reply.attr.node); // The union skills view and the harness's own skills share identity. const via_harness = rig.lookupName(6, topNode(.claude), "skills"); const via_union = rig.lookupName(7, topNode(.skills), "claude"); try testing.expectEqual(via_harness.reply.attr.node, via_union.reply.attr.node); // References are paid back by release: both slots go, ids recycle. for ([_]u64{ x1.reply.attr.node, x2.reply.attr.node, y1.reply.attr.node, a1.reply.attr.node }) |n| { const rel = handle(rig.h, .{ .tag = 8, .op = .release, .node = n }); try testing.expect(rel.reply.status == .ok); } const x3 = rig.lookupName(9, projects.reply.attr.node, "p1"); const x4 = rig.lookupName(10, x3.reply.attr.node, "session-x.jsonl"); // The entry was freed and re-created: a fresh generation, a new id. try testing.expect(x4.reply.attr.node != x1.reply.attr.node); // Node packing round-trips through the struct. const n: Node = .{ .idx = 3, .kind = 1, .serial = 0x1234_5678_9abc }; const bits: u64 = @bitCast(n); const back: Node = @bitCast(bits); try testing.expect(back.idx == 3 and back.kind == 1 and back.serial == 0x1234_5678_9abc); } test "tree: a file that vanishes between lookup and read answers ENOENT" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put(".claude/projects/p1/session-x.jsonl", "{\"a\":1}\n"); const projects = rig.lookupName(1, topNode(.claude), "projects"); const p1 = rig.lookupName(2, projects.reply.attr.node, "p1"); const session = rig.lookupName(3, p1.reply.attr.node, "session-x.jsonl"); try testing.expect(session.reply.status == .ok); // The transcript reads back whole, fresh from disk. const whole = handle(rig.h, .{ .tag = 4, .op = .read, .node = session.reply.attr.node, .off = 0, .size = 1024 }); try testing.expectEqualStrings("{\"a\":1}\n", whole.bytes); // It vanishes: lookup, getattr and read all answer ENOENT cleanly. try rig.del(".claude/projects/p1/session-x.jsonl"); try expectNoent(rig.lookupName(5, p1.reply.attr.node, "session-x.jsonl")); const gone = handle(rig.h, .{ .tag = 6, .op = .getattr, .node = session.reply.attr.node }); try expectNoent(gone); const read_gone = handle(rig.h, .{ .tag = 7, .op = .read, .node = session.reply.attr.node, .off = 0, .size = 64 }); try expectNoent(read_gone); } test "tree: writes and setattrs answer EPERM" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put(".claude/projects/p1/session-x.jsonl", "{}\n"); const projects = rig.lookupName(1, topNode(.claude), "projects"); const p1 = rig.lookupName(2, projects.reply.attr.node, "p1"); const session = rig.lookupName(3, p1.reply.attr.node, "session-x.jsonl"); try testing.expect(session.reply.status == .ok); const write = handle(rig.h, .{ .tag = 4, .op = .write, .node = session.reply.attr.node, .data = "x" }); try expectEperm(write); const set = handle(rig.h, .{ .tag = 5, .op = .setattr, .node = session.reply.attr.node, .set = .{ .mtime = true }, .mtime = 1 }); try expectEperm(set); // An open for write is refused at open time. const wopen = handle(rig.h, .{ .tag = 6, .op = .open, .node = session.reply.attr.node, .omode = cloud9.owrite }); try expectEperm(wopen); const ropen = handle(rig.h, .{ .tag = 7, .op = .open, .node = session.reply.attr.node, .omode = cloud9.oread }); try testing.expect(ropen.reply.status == .ok); } test "tree: a transcript written while serving is visible at once" { var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put(".claude/projects/p1/session-x.jsonl", "{\"n\":1}\n"); const projects = rig.lookupName(1, topNode(.claude), "projects"); const p1 = rig.lookupName(2, projects.reply.attr.node, "p1"); // The harness appends; the next read sees it — no cache in between. try rig.put(".claude/projects/p1/session-x.jsonl", "{\"n\":1}\n{\"n\":2}\n"); const session = rig.lookupName(3, p1.reply.attr.node, "session-x.jsonl"); const fresh = handle(rig.h, .{ .tag = 4, .op = .read, .node = session.reply.attr.node, .off = 0, .size = 1024 }); try testing.expectEqualStrings("{\"n\":1}\n{\"n\":2}\n", fresh.bytes); // A brand-new session file appears on the next listing. try rig.put(".claude/projects/p1/session-new.jsonl", "{\"n\":9}\n"); const listed = handle(rig.h, .{ .tag = 5, .op = .readdir, .node = p1.reply.attr.node, .off = 0, .size = msize }); try testing.expect(stageHas(listed.bytes, "session-new.jsonl")); } comptime { // Every static mount target must survive the exclusion rules; the // daemon's own skeleton may never be filtered out from under it. for (named_mounts) |m| { if (excludedPath(m.mount.rel)) @compileError("a 9harness mount target is excluded by name"); } } fn stageCount(staged: []const u8) usize { var i: usize = 0; var n: usize = 0; while (i + 10 <= staged.len) : (n += 1) i += 10 + @as(usize, staged[i + 9]); return n; } test "tree: a file directly under a mirror root reads back" { // Regression: joining a child onto an empty relative path returned an // uncopied scratch slice, so every file at the top of a mirror root // (/hermes/, /dsh/) listed but resolved to garbage bytes. var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put(".hermes/note.txt", "hello-hermes\n"); const listing = handle(rig.h, .{ .tag = 1, .op = .readdir, .node = topNode(.hermes), .off = 0, .size = msize }); try testing.expect(stageHas(listing.bytes, "note.txt")); const note = rig.lookupName(2, topNode(.hermes), "note.txt"); try testing.expect(note.reply.status == .ok); const bytes = handle(rig.h, .{ .tag = 3, .op = .read, .node = note.reply.attr.node, .off = 0, .size = 64 }); try testing.expectEqualStrings("hello-hermes\n", bytes.bytes); } test "tree: a name swapped for a symlink under an open handle serves nothing" { // Regression: the read path composed the whole path and opened it in // one call, following symlinks. A name replaced between the walk and // the read handed the client bytes from outside every pinned root. var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); try rig.put("outside.txt", "OUTSIDE-THE-ROOTS\n"); // beside the roots, not in one try rig.put(".claude/projects/p1/swap.txt", "safe\n"); const projects = rig.lookupName(1, topNode(.claude), "projects"); const p1 = rig.lookupName(2, projects.reply.attr.node, "p1"); const swap = rig.lookupName(3, p1.reply.attr.node, "swap.txt"); try testing.expect(swap.reply.status == .ok); const opened = handle(rig.h, .{ .tag = 4, .op = .open, .node = swap.reply.attr.node, .omode = cloud9.oread }); try testing.expect(opened.reply.status == .ok); // The file becomes a symlink out of the tree while the handle is open. var link_buf: [std.fs.max_path_bytes]u8 = undefined; var target_buf: [std.fs.max_path_bytes]u8 = undefined; const link = try std.fmt.bufPrintZ(&link_buf, "{s}/.claude/projects/p1/swap.txt", .{rig.home}); const target = try std.fmt.bufPrint(&target_buf, "{s}/outside.txt", .{rig.home}); try rig.del(".claude/projects/p1/swap.txt"); try Io.Dir.cwd().symLink(testing.io, target, link, .{}); const after = handle(rig.h, .{ .tag = 5, .op = .read, .node = swap.reply.attr.node, .off = 0, .size = 64 }); try expectNoent(after); try testing.expectEqual(@as(usize, 0), after.bytes.len); } test "tree: a listing past the cap fails loudly instead of truncating" { // Regression: a directory with more entries (or longer names) than the // comptime caps was served short, and a short listing is // indistinguishable from a small directory. var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); var name_buf: [64]u8 = undefined; for (0..list_capacity + 1) |i| { try rig.put(try std.fmt.bufPrint(&name_buf, ".dsh/f{d:0>5}", .{i}), ""); } const listed = handle(rig.h, .{ .tag = 1, .op = .readdir, .node = topNode(.dsh), .off = 0, .size = msize }); try testing.expect(listed.reply.status == .err); try testing.expectEqual(E.NFILE, listed.reply.errno); } test "tree: a listing spanning several reads loses no entry" { // The staging buffer fills long before a big directory ends. Staging // must stop at the first record that does not fit: dropping it and // packing a shorter one behind it would lose that entry, because the // next read's offset counts the records already delivered. var rig: Rig = .{ .dir = undefined }; try rig.start(); defer rig.end(); const count = 200; var name_buf: [320]u8 = undefined; for (0..count) |i| { const name = try std.fmt.bufPrint(&name_buf, ".dsh/{d:0>3}{s}", .{ i, "n" ** 200 }); try rig.put(name, ""); } var seen: [count]bool = @splat(false); var off: u64 = 0; var reads: usize = 0; while (reads < 16) : (reads += 1) { const page = handle(rig.h, .{ .tag = 1, .op = .readdir, .node = topNode(.dsh), .off = off, .size = msize }); try testing.expect(page.reply.status == .ok); if (page.bytes.len == 0) break; var i: usize = 0; while (i + 10 <= page.bytes.len) { const len: usize = page.bytes[i + 9]; const name = page.bytes[i + 10 ..][0..len]; i += 10 + len; // The fixture root also holds `profiles`, created by the rig. const idx = std.fmt.parseInt(usize, name[0..@min(3, name.len)], 10) catch continue; try testing.expect(!seen[idx]); // never served twice seen[idx] = true; } off += stageCount(page.bytes); } try testing.expect(reads > 1); // the listing really did span several reads for (seen) |s| try testing.expect(s); } test { _ = active; // the derived view's own tests run with the tree's } // ---- /active: the derived view ------------------------------------------------ /// Walks `/active` down to one agent's directory, answering its node. fn activeEntryNode(rig: *Rig, harness: []const u8, pid: []const u8) !u64 { const act = rig.lookupName(90, root, "active"); try testing.expect(act.reply.status == .ok); // A listing is what rescans, so it comes before the walk. _ = handle(rig.h, .{ .tag = 91, .op = .readdir, .node = act.reply.attr.node, .off = 0, .size = msize }); const h_dir = rig.lookupName(92, act.reply.attr.node, harness); try testing.expect(h_dir.reply.status == .ok); const entry = rig.lookupName(93, h_dir.reply.attr.node, pid); try testing.expect(entry.reply.status == .ok); return entry.reply.attr.node; } fn activeField(rig: *Rig, entry: u64, name: []const u8) ![]const u8 { const f = rig.lookupName(94, entry, name); try testing.expect(f.reply.status == .ok); const r = handle(rig.h, .{ .tag = 95, .op = .read, .node = f.reply.attr.node, .off = 0, .size = msize }); try testing.expect(r.reply.status == .ok); return r.bytes; } test "active: a fixture process tree lists by harness and by pid" { var rig: Rig = .{ .dir = undefined, .with_proc = true }; try rig.start(); defer rig.end(); // A Claude Code session, resolved the way the harness publishes it. try rig.fakeProc(.{ .pid = 1001, .comm = "claude", .starttime = 5000, .argv = "claude\x00--print\x00" }); var rec: [512]u8 = undefined; try rig.put(".claude/sessions/1001.json", try std.fmt.bufPrint(&rec, \\{{"pid":1001,"sessionId":"sess-abc","cwd":"{s}", \\ "name":"fixture-one","status":"busy","procStart":"5000"}} , .{rig.home})); var slug_buf: [512]u8 = undefined; const slug = active.slugOf(.claude, rig.home, rig.home, &slug_buf).?; var tr: [640]u8 = undefined; try rig.put( try std.fmt.bufPrint(&tr, ".claude/projects/{s}/sess-abc.jsonl", .{slug}), "{\"type\":\"summary\",\"aiTitle\":\"Fixture Title\"}\n", ); const entry = try activeEntryNode(&rig, "claude", "1001"); try testing.expectEqualStrings("1001\n", try activeField(&rig, entry, "pid")); try testing.expectEqualStrings("sess-abc\n", try activeField(&rig, entry, "session")); try testing.expectEqualStrings("registry\n", try activeField(&rig, entry, "via")); try testing.expectEqualStrings("fixture-one\n", try activeField(&rig, entry, "name")); try testing.expectEqualStrings("busy\n", try activeField(&rig, entry, "status")); try testing.expectEqualStrings("Fixture Title\n", try activeField(&rig, entry, "title")); // started is the boot time plus the process's own, in seconds. try testing.expectEqualStrings("1000050\n", try activeField(&rig, entry, "started")); // The transcript is served as the file it is, not as a field. const t = rig.lookupName(96, entry, "transcript"); try testing.expect(t.reply.status == .ok); const bytes = handle(rig.h, .{ .tag = 97, .op = .read, .node = t.reply.attr.node, .off = 0, .size = msize }); try testing.expect(std.mem.indexOf(u8, bytes.bytes, "Fixture Title") != null); // A field this harness does not publish is absent, not blank. try expectNoent(rig.lookupName(98, entry, "model")); } test "active: a daemon or helper is never listed as an agent" { var rig: Rig = .{ .dir = undefined, .with_proc = true }; try rig.start(); defer rig.end(); // The shapes actually running on this machine. try rig.fakeProc(.{ .pid = 1010, .comm = "python3", .argv = "python3\x00-m\x00hermes_cli.main\x00gateway\x00run\x00" }); try rig.fakeProc(.{ .pid = 1011, .comm = "omp", .argv = "omp\x00__omp_worker_daemon_broker\x00" }); try rig.fakeProc(.{ .pid = 1012, .comm = "claude", .argv = "claude\x00mcp-server\x00" }); // ...and one real session, so an empty answer cannot pass by default. try rig.fakeProc(.{ .pid = 1013, .comm = "claude", .argv = "claude\x00" }); const act = rig.lookupName(1, root, "active"); const listing = handle(rig.h, .{ .tag = 2, .op = .readdir, .node = act.reply.attr.node, .off = 0, .size = msize }); try testing.expect(stageHas(listing.bytes, "claude")); try testing.expect(!stageHas(listing.bytes, "hermes")); try testing.expect(!stageHas(listing.bytes, "omp")); const claude = rig.lookupName(3, act.reply.attr.node, "claude"); const pids = handle(rig.h, .{ .tag = 4, .op = .readdir, .node = claude.reply.attr.node, .off = 0, .size = msize }); try testing.expect(stageHas(pids.bytes, "1013")); try testing.expect(!stageHas(pids.bytes, "1012")); // the mcp server } test "active: a pid reused by another process stops resolving" { var rig: Rig = .{ .dir = undefined, .with_proc = true }; try rig.start(); defer rig.end(); try rig.fakeProc(.{ .pid = 1020, .comm = "claude", .starttime = 5000, .argv = "claude\x00" }); const entry = try activeEntryNode(&rig, "claude", "1020"); try testing.expect(handle(rig.h, .{ .tag = 5, .op = .getattr, .node = entry }).reply.status == .ok); // The same pid, a different process: only the start time says so. try rig.fakeProc(.{ .pid = 1020, .comm = "claude", .starttime = 9999, .argv = "claude\x00" }); const act = rig.lookupName(6, root, "active"); _ = handle(rig.h, .{ .tag = 7, .op = .readdir, .node = act.reply.attr.node, .off = 0, .size = msize }); try expectNoent(handle(rig.h, .{ .tag = 8, .op = .getattr, .node = entry })); // The pid is still there — as the new process, under a new node. const fresh = try activeEntryNode(&rig, "claude", "1020"); try testing.expect(fresh != entry); } test "active: a process that exits leaves the tree" { var rig: Rig = .{ .dir = undefined, .with_proc = true }; try rig.start(); defer rig.end(); try rig.fakeProc(.{ .pid = 1030, .comm = "claude", .argv = "claude\x00" }); const entry = try activeEntryNode(&rig, "claude", "1030"); try testing.expect(handle(rig.h, .{ .tag = 9, .op = .getattr, .node = entry }).reply.status == .ok); try rig.reapProc(1030); const act = rig.lookupName(10, root, "active"); const listing = handle(rig.h, .{ .tag = 11, .op = .readdir, .node = act.reply.attr.node, .off = 0, .size = msize }); try testing.expect(!stageHas(listing.bytes, "claude")); try expectNoent(handle(rig.h, .{ .tag = 12, .op = .getattr, .node = entry })); try expectNoent(rig.lookupName(13, act.reply.attr.node, "claude")); } test "active: the daemon never lists the process tree it lives in" { // 1041 is the daemon, its parent 1040 is a harness: showing it would // let a client act on the tree serving it. var rig: Rig = .{ .dir = undefined, .with_proc = true, .self_pid = 1041 }; try rig.start(); defer rig.end(); try rig.fakeProc(.{ .pid = 1040, .comm = "claude", .argv = "claude\x00" }); try rig.fakeProc(.{ .pid = 1041, .comm = "9harness", .ppid = 1040, .argv = "9harness\x00" }); try rig.fakeProc(.{ .pid = 1042, .comm = "claude", .argv = "claude\x00" }); const act = rig.lookupName(14, root, "active"); const claude = rig.lookupName(15, act.reply.attr.node, "claude"); try testing.expect(claude.reply.status == .ok); const pids = handle(rig.h, .{ .tag = 16, .op = .readdir, .node = claude.reply.attr.node, .off = 0, .size = msize }); try testing.expect(stageHas(pids.bytes, "1042")); // an unrelated session lists try testing.expect(!stageHas(pids.bytes, "1040")); // its own parent does not }