summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ninep/events.zig26
-rw-r--r--src/ninep/tree.zig34
2 files changed, 60 insertions, 0 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index ffdea95b..1c66c18f 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -565,6 +565,32 @@ const serialOf = th.serialOf;
const Node = tree.Node;
const Status = tree.Status;
+test "a stream read at an offset inside its last answer gets the rest of it again, as bash's read seeks back" {
+ const p = try withFile(testing.allocator, "one\n");
+ defer p.deinit();
+ p.update(.tick);
+ while (p.nextEffect()) |_| {}
+ const log = @intFromEnum(tree.TopFile.log);
+ const f = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle;
+ const frozen: u64 = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = f, .size = 8192 }).bytes.len;
+ _ = call(p, .{ .tag = 3, .op = .write, .node = log, .handle = f, .data = "follow" });
+ notePath(p, "dump", "/a");
+ notePath(p, "dump", "/b");
+ const Read = struct {
+ fn at(pp: *Pardes, h: u32, off: u64) []const u8 {
+ return call(pp, .{ .tag = 4, .op = .read, .node = @intFromEnum(tree.TopFile.log), .handle = h, .off = off, .size = 128 }).bytes;
+ }
+ };
+ try testing.expectEqualStrings("dump /a\n", Read.at(p, f, frozen));
+ // bash kept "dump" and seeked back to just past it.
+ try testing.expectEqualStrings(" /a\n", Read.at(p, f, frozen + 4));
+ // At the end of what it was given: the next record.
+ try testing.expectEqualStrings("dump /b\n", Read.at(p, f, frozen + 8));
+ // A client that reads every record at one offset still gets new ones.
+ notePath(p, "dump", "/c");
+ try testing.expectEqualStrings("dump /c\n", Read.at(p, f, frozen + 8));
+}
+
test "event records are acme's bytes, one per read, and .again when empty" {
const gpa = testing.allocator;
const p = try withFile(gpa, "Msg fs-ran\n");
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: {