diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 20:20:27 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 7dc3891ed574d6e96548f592b09b0f8abc87ae2a (patch) | |
| tree | 70cddb9ce4a7e9039f8167e806b7e996f94eefb0 /src/ninep/events.zig | |
| parent | acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb (diff) | |
| download | pardes-7dc3891ed574d6e96548f592b09b0f8abc87ae2a.tar.gz pardes-7dc3891ed574d6e96548f592b09b0f8abc87ae2a.zip | |
Keep what each open holds in one table of open records
Snapshots (and /log's cursor), runs, and the reader_handle constants for
event and pty/data each reused the open handle and each validated
handle and node on its own. Now p.fs.opens is one table of 64 records,
each the node it was opened on and a tagged union of what it holds, like
lib9p's per-fid aux and acme's Fid (editors/acme/dat.h:373-385): one
lookup (openOf), one release, ENFILE when full. A held read lives in
its open's record, so it goes with the release. Opens that hold nothing
answer handle 0 and take no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/events.zig')
| -rw-r--r-- | src/ninep/events.zig | 67 |
1 files changed, 35 insertions, 32 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 78945c5c..8f66cf3a 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -169,37 +169,42 @@ fn pushLog(p: *Pardes, record: []u8) void { p.fs.news = true; } +/// What an open of /log holds. +pub const LogOpen = struct { + /// The ring's text when it was opened. + bytes: []const u8 = &.{}, + /// This open waits for records after `bytes`, from `next`. + follow: bool = false, + next: u64 = 0, + /// How much of record `next` a short read already took. + part: u32 = 0, + /// How much of `bytes` this open has read. Reads walk it as a stream once + /// following and ignore their offset, since `exec 3<>log; echo follow + /// >&3` leaves the shared offset past the word it wrote. + pos: u64 = 0, +}; + /// An open freezes the ring's text, so `cat log` answers what happened lately /// and ends; `next` is where a follower picks up after it. -pub fn openLog(p: *Pardes, req: Req) Reply { - for (&p.fs.snapshots, 0..) |*slot, i| { - if (slot.node != 0) continue; - var text: std.ArrayList(u8) = .empty; - var at = p.fs.log.head; - const items = p.fs.log.buf.items; - var n: u64 = 0; - while (at + 4 <= items.len) : (n += 1) { - const len = std.mem.readInt(u32, items[at..][0..4], .little); - text.appendSlice(p.gpa, items[at + 4 ..][0..len]) catch { - text.deinit(p.gpa); - return Reply.fail(req.tag, E.NOMEM); - }; - at += 4 + len; - } - const bytes = text.toOwnedSlice(p.gpa) catch return Reply.fail(req.tag, E.NOMEM); - slot.* = .{ .node = req.node, .bytes = bytes, .next = p.fs.log.dropped + n }; - return .{ .tag = req.tag, .handle = @intCast(i + 1) }; +pub fn freezeLog(p: *Pardes) !LogOpen { + var text: std.ArrayList(u8) = .empty; + errdefer text.deinit(p.gpa); + var at = p.fs.log.head; + const items = p.fs.log.buf.items; + var n: u64 = 0; + while (at + 4 <= items.len) : (n += 1) { + const len = std.mem.readInt(u32, items[at..][0..4], .little); + try text.appendSlice(p.gpa, items[at + 4 ..][0..len]); + at += 4 + len; } - return Reply.fail(req.tag, E.NFILE); + return .{ .bytes = try text.toOwnedSlice(p.gpa), .next = p.fs.log.dropped + n }; } /// Past the frozen text, a following open reads newer records one per read, -/// parked until one arrives; one that fell behind the ring hears `lost N`. +/// held until one arrives; one that fell behind the ring hears `lost N`. pub fn readLog(p: *Pardes, req: Req) Reply { - if (req.handle == 0 or req.handle > p.fs.snapshots.len) return Reply.fail(req.tag, E.INVAL); - const slot = &p.fs.snapshots[req.handle - 1]; - if (slot.node != req.node) return Reply.fail(req.tag, E.INVAL); - const frozen = slot.bytes orelse &.{}; + const slot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.log; + const frozen = slot.bytes; const out = p.fs.stage(p.gpa); if (!slot.follow or slot.pos < frozen.len) { // Until `follow`, the frozen text is a file like any other: `tail -n` @@ -243,9 +248,7 @@ pub fn readLog(p: *Pardes, req: Req) Reply { /// `follow`, written to an open of the log, makes that open wait for newer /// records instead of ending; closing it is the way back. pub fn writeLog(p: *Pardes, req: Req) Reply { - if (req.handle == 0 or req.handle > p.fs.snapshots.len) return Reply.fail(req.tag, E.INVAL); - const slot = &p.fs.snapshots[req.handle - 1]; - if (slot.node != req.node) return Reply.fail(req.tag, E.INVAL); + const slot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.log; if (!std.mem.eql(u8, std.mem.trim(u8, req.data, " \t\r\n"), "follow")) return Reply.fail(req.tag, E.INVAL); slot.follow = true; return .{ .tag = req.tag, .written = @intCast(req.data.len) }; @@ -386,7 +389,7 @@ pub fn noteAction( pub fn notePtyOutput(p: *Pardes, id: usize, bytes: []const u8) void { if (id >= MAX_PANES or bytes.len == 0) return; const pf = &(p.panes[id] orelse return).fs; - if (pf.pty_readers == 0) return; + if (!pf.pty_reader) return; var off: usize = 0; while (off < bytes.len) { const n = @min(bytes.len - off, queue_cap / 2); @@ -745,7 +748,7 @@ test "the log records whether or not anyone reads, and an open that follows wait try testing.expect(std.mem.startsWith(u8, lost, "lost ")); try testing.expect(std.mem.startsWith(u8, rdf.next(p, log, fh, frozen).bytes, "msg ")); _ = call(p, .{ .tag = 10, .op = .release, .node = log, .handle = fh }); - for (p.fs.snapshots) |slot| try testing.expect(slot.node == 0); + for (p.fs.opens) |o| try testing.expect(o.node == 0); // A record bigger than the whole ring is cut to fit, not dropped with // everything else pushed out ahead of it. @@ -759,12 +762,12 @@ test "the log records whether or not anyone reads, and an open that follows wait _ = call(p, .{ .tag = 13, .op = .release, .node = log, .handle = cut.reply.handle }); } -test "opens of the log share the snapshot slots, and a closed one frees its slot" { +test "opens of the log share the open records, and a closed one frees its record" { const gpa = testing.allocator; const p = try withFile(gpa, "x\n"); defer p.deinit(); const log = @intFromEnum(tree.TopFile.log); - var handles: [tree.screen.snapshot_slots]u32 = undefined; + var handles: [tree.open_slots]u32 = undefined; for (&handles) |*h| h.* = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle; try testing.expectEqual(E.NFILE, call(p, .{ .tag = 2, .op = .open, .node = log }).errno()); _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = handles[5] }); @@ -772,5 +775,5 @@ test "opens of the log share the snapshot slots, and a closed one frees its slot try testing.expectEqual(Status.ok, again.reply.status); handles[5] = again.reply.handle; for (handles) |h| _ = call(p, .{ .tag = 5, .op = .release, .node = log, .handle = h }); - for (p.fs.snapshots) |slot| try testing.expect(slot.node == 0); + for (p.fs.opens) |o| try testing.expect(o.node == 0); } |
