diff options
Diffstat (limited to 'src/ninep/tree.zig')
| -rw-r--r-- | src/ninep/tree.zig | 56 |
1 files changed, 54 insertions, 2 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index f118e4b4..ad95f6bf 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -130,6 +130,13 @@ pub const Open = struct { /// place of the older. The listener fills it in and answers it /// (src/9p_io.zig, `answerHeld`). held: ?Held = null, + /// The last answer a stream open (a followed log, event, pty/data) gave, + /// and the offset it was read at. A read at an offset inside it is + /// answered from it again: bash's `read` takes a chunk, keeps a line and + /// lseeks back to just past it, and a stream has already moved on. + replay: ?Replay = null, + + pub const Replay = struct { off: u64, bytes: []u8 }; /// `asker` is the connection the read came on, which only the listener /// knows. @@ -145,6 +152,7 @@ pub const Open = struct { }, .event, .pty_data, .ctl => {}, } + if (o.replay) |r| gpa.free(r.bytes); o.* = .{}; } }; @@ -377,7 +385,7 @@ pub fn handle(p: *Pardes, req: Req) Reply { }; return switch (req.op) { .lookup => lookup(p, req, target), - .getattr => .{ .tag = req.tag, .attr = attrOf(p, target) orelse return Reply.fail(req.tag, E.NOENT) }, + .getattr => .{ .tag = req.tag, .attr = attrOf(p, target) orelse runAttr(p, req, target) orelse return Reply.fail(req.tag, E.NOENT) }, .setattr => setattr(p, req, target), .open => open(p, req, target), .release => release(p, req), @@ -387,6 +395,24 @@ pub fn handle(p: *Pardes, req: Req) Reply { }; } +/// A run's answer outlives its pane, so its file still stats while a run +/// is open on it (a reader's fstat, cat's), as acme's fid keeps its window. +/// The stat names the open when it came on one; a mount's comes on the +/// file's walked name, and serials are never reused, so any run open on +/// this node is the reader's. +fn runAttr(p: *Pardes, req: Req, target: Target) ?Reply.Attr { + const t = switch (target) { + .pane => |t| t, + .top => return null, + }; + if (t.file != .pty_run) return null; + const run = if (req.handle != 0) openOf(p, req) else for (&p.fs.opens) |*o| { + if (o.node == req.node and o.what == .run) break o; + } else null; + if ((run orelse return null).what != .run) return null; + return .{ .name = t.file.fileName(), .node = Node.of(t.serial, t.file), .mode = t.file.mode(), .mtime = p.fs.started }; +} + fn attrOf(p: *Pardes, target: Target) ?Reply.Attr { switch (target) { .top => |f| return .{ @@ -569,7 +595,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply { // closing it leaves the pane alone. .new => { const slot = p.freeSlot() orelse return Reply.fail(req.tag, E.NFILE); - p.newScratchBelow(p.active); + p.newScratchFor(p.active, .fs); const made = p.panes[slot] orelse return Reply.fail(req.tag, E.NFILE); return .{ .tag = req.tag, .handle = made.serial }; }, @@ -692,6 +718,32 @@ fn setattr(p: *Pardes, req: Req, target: Target) Reply { } fn read(p: *Pardes, req: Req, target: Target) Reply { + const stream = if (openOf(p, req)) |o| switch (o.what) { + .log => o, + .event => |reader| if (reader) o else null, + .pty_data => o, + else => null, + } else null; + const s = stream orelse return readFile(p, req, target); + // Strictly inside: a client that reads every record at one offset (the + // offset is ignored on a stream, as in Plan 9) always gets new ones. + if (s.replay) |r| if (req.off > r.off and req.off < r.off + r.bytes.len) { + const from = r.bytes[@intCast(req.off - r.off)..]; + const n = @min(from.len, req.size); + p.fs.stage(p.gpa).appendSlice(p.gpa, from[0..n]) catch return Reply.fail(req.tag, E.NOMEM); + return .{ .tag = req.tag, .payload = .{ .staged = @intCast(n) } }; + }; + const reply = readFile(p, req, target); + if (reply.status != .ok) return reply; + const bytes = p.fsPayload(reply); + if (bytes.len == 0) return reply; + const kept = p.gpa.dupe(u8, bytes) catch return reply; + if (s.replay) |r| p.gpa.free(r.bytes); + s.replay = .{ .off = req.off, .bytes = kept }; + return reply; +} + +fn readFile(p: *Pardes, req: Req, target: Target) Reply { switch (target) { .top => |f| return switch (f) { .README => help: { |
