diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 17:41:55 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 542dd489149e33915d19878a277e23b3a9c0b070 (patch) | |
| tree | aeb557581446621c366a2d3cd5888d25b37c5916 /src/ninep/tree.zig | |
| parent | c11ffe24f022d61cdd479e1ee946033c42e9a08c (diff) | |
| download | pardes-542dd489149e33915d19878a277e23b3a9c0b070.tar.gz pardes-542dd489149e33915d19878a277e23b3a9c0b070.zip | |
Record /log whether or not anyone reads it, with the editor's messages in it
/log is now one ring (64 KiB, 4 KiB on the board) that records new, del,
rename and save, and a msg line for everything the editor says. An open
freezes it, so cat log shows what happened lately and ends; writing
follow to that same open makes reads past it wait for newer records, and
a follower the ring outran reads lost N first.
The message log keeps the pane's serial, not its reusable slot.
A test now fails when the tree serves a file /README never mentions; it
caught pty/status, now documented along with typing through pty/data.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
| -rw-r--r-- | src/ninep/tree.zig | 46 |
1 files changed, 37 insertions, 9 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index aca8d109..f98429ab 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -117,8 +117,8 @@ pub const TopFile = enum(u4) { pub fn mode(f: TopFile) u16 { return switch (f) { .root, .pane => 0o755, - .look, .exec => 0o666, - .README, .index, .status, .log, .screen, .listeners, .new => 0o444, + .look, .exec, .log => 0o666, + .README, .index, .status, .screen, .listeners, .new => 0o444, }; } @@ -375,7 +375,7 @@ fn topSize(p: *Pardes, f: TopFile) u64 { .README => fs.help.len, .status => ctl.statusLen(p), .look, .exec => ctl.resultsLen(p), - .log => events.pending(&p.fs.log), + .log => events.logLen(&p.fs.log), .listeners => screen.listenersLen(p), }; } @@ -497,7 +497,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply { .top => |f| switch (f) { .new => return .{ .tag = req.tag, .handle = makePane(p) orelse return Reply.fail(req.tag, E.NFILE) }, .screen => return screen.openSnapshot(p, req, true), - .log => p.fs.log_readers +|= 1, + .log => return events.openLog(p, req), else => {}, }, .pane => |t| { @@ -543,10 +543,7 @@ fn releaseHandle(p: *Pardes, req: Req) Reply { if (screen.releaseSnapshot(p, req)) return .{ .tag = req.tag }; const target = Node.target(req.node) orelse return .{ .tag = req.tag }; switch (target) { - .top => |f| if (f == .log and p.fs.log_readers > 0) { - p.fs.log_readers -= 1; - if (p.fs.log_readers == 0) p.fs.log.clearAndFree(p.gpa); - }, + .top => {}, .pane => |t| { if (t.file != .event and t.file != .pty_data) return .{ .tag = req.tag }; const id = p.paneBySerial(t.serial) orelse return .{ .tag = req.tag }; @@ -597,7 +594,7 @@ fn read(p: *Pardes, req: Req, target: Target) Reply { }, .status => ctl.readStatus(p, req), .look, .exec => ctl.readResults(p, req), - .log => events.readQueue(p, req, &p.fs.log), + .log => events.readLog(p, req), .screen => screen.readSnapshot(p, req, null), .listeners => screen.readListeners(p, req), // The serial the open handed this fid, so that two reads of one @@ -623,6 +620,7 @@ fn write(p: *Pardes, req: Req, target: Target) Reply { switch (target) { .top => |f| return switch (f) { .look, .exec => ctl.command(p, req, null, f == .exec), + .log => events.writeLog(p, req), else => Reply.fail(req.tag, E.PERM), }, .pane => |t| { @@ -730,6 +728,36 @@ test "filesystem pane creation and truncation cancel Look hover" { } } +test "README names every file the tree serves" { + // /README is the whole manual a client gets; a file it never mentions + // is one nobody can find without reading this source. + const words = struct { + fn has(name: []const u8) bool { + var it = std.mem.tokenizeAny(u8, fs.help, " \t\n,.:;()'\"$<>|=/"); + while (it.next()) |w| if (std.mem.eql(u8, w, name)) return true; + return false; + } + }; + inline for (@typeInfo(TopFile).@"enum".fields) |f| { + const t: TopFile = @enumFromInt(f.value); + if (t != .root and !words.has(t.fileName())) { + std.debug.print("README does not mention /{s}\n", .{t.fileName()}); + return error.Undocumented; + } + } + inline for (@typeInfo(PaneFile).@"enum".fields) |f| { + const t: PaneFile = @enumFromInt(f.value); + const ok = if (t == .dir) true else if (t.inPty() and t != .pty) + std.mem.indexOf(u8, fs.help, "pty/" ++ comptime @as(PaneFile, @enumFromInt(f.value)).fileName()) != null + else + words.has(t.fileName()); + if (!ok) { + std.debug.print("README does not mention pane file {s}\n", .{@tagName(t)}); + return error.Undocumented; + } + } +} + test "readdir lists the root and a pane directory without creating anything" { const gpa = testing.allocator; const p = try withFile(gpa, "hello\n"); |
