summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig34
1 files changed, 34 insertions, 0 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index f118e4b4..b3f3dde6 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.* = .{};
}
};
@@ -692,6 +700,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: {