summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-27 17:41:55 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit542dd489149e33915d19878a277e23b3a9c0b070 (patch)
treeaeb557581446621c366a2d3cd5888d25b37c5916 /src/ninep/tree.zig
parentc11ffe24f022d61cdd479e1ee946033c42e9a08c (diff)
downloadpardes-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.zig46
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");