summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 22:51:17 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit4f65855f372186406f4e48708ec3d5eb9aceb6d3 (patch)
tree0498afe611660274f96fbbe6a69c9c041ec6513d /src
parent244704dabbe279d03f1432f8474e985aeb25f2ad (diff)
downloadpardes-4f65855f372186406f4e48708ec3d5eb9aceb6d3.tar.gz
pardes-4f65855f372186406f4e48708ec3d5eb9aceb6d3.zip
log stats 0, and pardes re-pins cloud9 whose 9ns opens such a file direct and nonseekable: fs.md's follow recipe with cat works through a mount
The log stated the length an open would freeze, a length a mount's kernel could take as its end, and a splicing cat read it from the kernel's offset. log now stats 0, as the other streams and generated views do, and the re-pinned cloud9's 9ns opens any file stating 0 FOPEN_DIRECT_IO | FOPEN_NONSEEKABLE. A look at such a file (the mounted index) reads a regular file that will not seek as a stream rather than refusing it as a pipe. selfmount runs the recipe, exec 3<>log; echo follow >&3; cat <&3, through the mount and hears a record made after it started. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/fs.zig8
-rw-r--r--src/ninep/events.zig12
-rw-r--r--src/ninep/tree.zig12
3 files changed, 18 insertions, 14 deletions
diff --git a/src/fs.zig b/src/fs.zig
index d13e36bd..91edd429 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -1360,7 +1360,13 @@ fn readFileLimit(gpa: std.mem.Allocator, path: []const u8, limit: usize) ![]u8 {
defer _ = libc.close(fd);
const end = libc.lseek(fd, 0, libc.SEEK.END);
- if (end < 0 and libc.errno(end) == .SPIPE) return error.NotAFile;
+ // Not seekable: a pipe or a FIFO is no file; a regular file that is a
+ // stream (a 9ns mount's log or screen, opened nonseekable) is read to
+ // its end, as one with no length is.
+ if (end < 0 and libc.errno(end) == .SPIPE) {
+ const facts = ninep_io.statNoFollow(path_z) orelse return error.NotAFile;
+ if (facts.mode & 0o170000 != 0o100000) return error.NotAFile;
+ }
const size: usize = if (end < 0) 0 else @intCast(end);
if (end >= 0 and libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.ReadFailed;
if (size == 0) {
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index bacf3ad6..9752e287 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -494,18 +494,6 @@ pub fn writeLog(p: *Pardes, req: Req) Reply {
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
-/// The length a stat reports: the text an open would freeze now.
-pub fn logLen(q: *const Queue) u64 {
- var n: u64 = 0;
- var at = q.head;
- while (at + 4 <= q.buf.items.len) {
- const len = std.mem.readInt(u32, q.buf.items[at..][0..4], .little);
- n += len;
- at += 4 + len;
- }
- return n;
-}
-
// ---- per-pane event records ----
pub const max_record_text = 256;
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index be03e8f0..8c536c51 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -646,7 +646,9 @@ fn topSize(p: *Pardes, f: TopFile) u64 {
.README => fs.help.len,
.status => ctl.statusLen(p),
.look, .exec, .tagexec => ctl.resultsLen(p),
- .log => events.logLen(&p.fs.log),
+ // A stream: a length would read as its end to a kernel that caches
+ // one (a 9ns mount's splice), and a follower's reads go past it.
+ .log => 0,
.ctl => ctl.rootLen(p),
.commands => ctl.commandsLen(),
.focus => focus: {
@@ -1335,6 +1337,14 @@ test "filesystem inspection preserves pending and displayed Look hover" {
try testing.expect(!p.raw_hover_intent);
}
+test "log stats 0 however much it holds, a stream as acme's generated files are" {
+ const p = try th.withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ _ = th.wr(p, Node.of(th.serialOf(p), .exec), "Msg a record for the log\n");
+ try testing.expect(th.logHas(p, "a record for the log"));
+ try testing.expectEqual(@as(u64, 0), call(p, .{ .tag = 1, .op = .getattr, .node = @intFromEnum(TopFile.log) }).reply.attr.size);
+}
+
test "filesystem pane creation and truncation cancel Look hover" {
const requests = [_]Req{
// Opening `new` is what makes a pane now; walking to it makes none,