summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 02:21:57 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit90f5c414a90afec978c8c0add58fd2a7e29acbaa (patch)
tree21a73a5ec603705cf789d39da73cceea425d9420
parenteece0fbe709142f6731b7dd2e84009c3a18a3806 (diff)
downloadpardes-90f5c414a90afec978c8c0add58fd2a7e29acbaa.tar.gz
pardes-90f5c414a90afec978c8c0add58fd2a7e29acbaa.zip
A read on a closed pane's event ends ENOENT, and the pane cap is ENOSPC
9ns reads an error's words, not its errno: "window shut down" came out as EIO where every other file of a gone pane gives ENOENT, and "no pane slots" as EIO. The event and pty/data reads a pane closed under now say "no such pane: its window shut" (ENOENT), and the cap says "no space for a pane: 64 max" (ENOSPC; 9ns has no word for ENFILE). Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md2
-rw-r--r--docs/fs.md6
-rw-r--r--src/Messages.zig3
-rw-r--r--src/ninep/events.zig2
-rw-r--r--src/ninep/tree.zig13
-rw-r--r--src/pardes.zig4
6 files changed, 18 insertions, 12 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 5e79a470..61b117a5 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -94,7 +94,7 @@ $m/pane/new open it to make a pane (a scratch named <dir>/+New), read names it
the ACTIVE column (the one last typed or clicked in, or Newcol's), filling it if
empty, else taking the bottom half of its last pane (ctl `Placement pardes`: the old rules);
a session holds 64 panes (16 on the board): past that, every route that opens one
- fails with `no pane slots: 64 max`; rmdir $m/pane/<n> closes it; a column's last pane leaves the column EMPTY
+ fails with `no space for a pane: 64 max` (ENOSPC); rmdir $m/pane/<n> closes it; a column's last pane leaves the column EMPTY
(focus reads empty, the log says only del), and the session's LAST pane QUITS it
$m/pane/<n>/errors write-only: text appended to the +Errors pane of the pane's directory
$m/os/ the host filesystem
diff --git a/docs/fs.md b/docs/fs.md
index 64f10e21..a93137bb 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -117,7 +117,8 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC.
an empty column (a column with panes is refused, ENOTEMPTY)
/pane/new open it to make a pane; the read answers that pane's serial. A session holds
64 panes (16 on the board); at that, every route that would open one -- this
- open, look, exec, New, Tty -- fails with `no pane slots: 64 max` and an err
+ open, look, exec, New, Tty -- fails with `no space for a pane: 64 max` (ENOSPC through 9ns, which has
+ no word for ENFILE) and an err
record, and look reads back empty
/pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll
errors event look exec, plus pty/{ctl,status,data} on terminals
@@ -674,7 +675,8 @@ A read with nothing to give yet -- a following `log`, `event`, `pty/data`, a
`pty/run` before its answer -- is held, the way factotum holds its log's reads
(security/auth/factotum/log.c) and acme an event read: the core keeps it, and
whoever next has news for it (a record, output, a run's answer, the pane
-closing, which answers acme's "window shut down") answers it on the
+closing, which answers `no such pane: its window shut`, ENOENT as any
+other file of a gone pane gives, where acme says "window shut down") answers it on the
connection it came on as the turn is given up. Nothing else parked is
retried for it. The core keeps the ticket cloud9 gave the park
(`Conn.hold`) and answers only while that very park waits, so a read the
diff --git a/src/Messages.zig b/src/Messages.zig
index 7d284e6b..b197e715 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -446,7 +446,8 @@ pub fn messageLog(m: *const Messages, i: usize) ?*const LoggedMessage {
/// `<operation>: <the error in words>`: FileNotFound reads "file not found".
/// What a route that would open a pane says when every slot is taken.
-pub const no_pane_slots = "no pane slots: {d} max";
+/// "no space": 9ns's word for ENOSPC (it has none for ENFILE).
+pub const no_pane_slots = "no space for a pane: {d} max";
pub fn reportError(p: *Pardes, id: usize, operation: []const u8, err: anyerror) void {
var buf: [256]u8 = undefined;
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 61aadae2..c54d8454 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -765,7 +765,7 @@ test "a pane deleted while its event file is open leaves no suppression behind"
try testing.expectEqual(@as(u16, 0), p.fs.listeners);
// The reader's next read hears what acme says of a window gone under it.
const shut = call(p, .{ .tag = 19, .op = .read, .node = event, .handle = a.reply.handle, .size = 64 });
- try testing.expectEqual(E.IO, shut.errno());
+ try testing.expectEqual(E.NOENT, shut.errno());
try testing.expectEqualStrings(tree.e_shut_down, shut.reply.ename);
_ = call(p, .{ .tag = 20, .op = .release, .node = event, .handle = a.reply.handle });
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 278732b5..aec3cab7 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -66,7 +66,10 @@ pub const e_bad_event = "bad event syntax";
/// A second open reading a file whose reads consume: rio's word for it
/// (windows/rio/xfid.c:25), which 9ns turns into EBUSY.
pub const e_in_use = "file in use";
-pub const e_shut_down = "window shut down";
+/// A read held on a pane's event or pty/data that the pane closed under.
+/// acme says "window shut down" (xfid.c:1005), which 9ns reads as EIO;
+/// these words read as ENOENT, what every other file of a gone pane gives.
+pub const e_shut_down = "no such pane: its window shut";
/// Tremove closes a pane; nothing else in the tree is created or destroyed by
/// the protocol, and wstat stays a truncation. Making a pane is an open of
@@ -709,7 +712,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply {
// pane it made, so reading it twice answers the same one and
// closing it leaves the pane alone.
.new => {
- const slot = p.freeSlot() orelse return failText(req.tag, E.NFILE, std.fmt.bufPrint(&p.fs.ename, "pane/new: " ++ pardes.Messages.no_pane_slots, .{pardes.MAX_PANES}) catch "no pane slots");
+ const slot = p.freeSlot() orelse return failText(req.tag, E.NOSPC, std.fmt.bufPrint(&p.fs.ename, "pane/new: " ++ pardes.Messages.no_pane_slots, .{pardes.MAX_PANES}) catch "no space for a pane");
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 };
@@ -939,7 +942,7 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
// An event or pty/data read the pane closed under: acme's
// answer to the same (editors/acme/xfid.c:1005).
if (openOf(p, req)) |o| if (o.what == .event or o.what == .pty_data)
- return failText(req.tag, E.IO, e_shut_down);
+ return failText(req.tag, E.NOENT, e_shut_down);
return Reply.fail(req.tag, E.NOENT);
};
const pn = p.panes[id].?;
@@ -1427,9 +1430,9 @@ test "at the pane cap, pane/new, look and New each say so, and look reads back e
const new = @intFromEnum(TopFile.new);
while (p.freeSlot() != null) try testing.expectEqual(Status.ok, call(p, .{ .tag = 1, .op = .open, .node = new }).reply.status);
var want: [64]u8 = undefined;
- const said = try std.fmt.bufPrint(&want, "no pane slots: {d} max", .{pardes.MAX_PANES});
+ const said = try std.fmt.bufPrint(&want, "no space for a pane: {d} max", .{pardes.MAX_PANES});
const refused = call(p, .{ .tag = 2, .op = .open, .node = new });
- try testing.expectEqual(E.NFILE, refused.errno());
+ try testing.expectEqual(E.NOSPC, refused.errno());
try testing.expect(std.mem.endsWith(u8, refused.reply.ename, said));
try testing.expect(th.logHas(p, said));
// A look that would open a pane fails its write, and says the same.
diff --git a/src/pardes.zig b/src/pardes.zig
index f55654b6..a0b11566 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -2679,7 +2679,7 @@ test "a builtin that needs a pane reports capacity failure when every slot is fu
};
const help = std.meta.stringToEnum(Builtin, "Help") orelse return error.MissingHelpBuiltin;
exec.runBuiltin(p, help, 0, "", null);
- try std.testing.expect(std.mem.indexOf(u8, p.panes[0].?.msg[0..p.panes[0].?.msg_len], "help: no pane slots") != null);
+ try std.testing.expect(std.mem.indexOf(u8, p.panes[0].?.msg[0..p.panes[0].?.msg_len], "help: no space for a pane") != null);
}
test "Font keeps requested and acknowledged faces as separate plain state" {
@@ -5316,7 +5316,7 @@ pub const Pardes = struct {
p.raw_hover_intent = false;
look.cancelLookHover(p);
const reply = ctlfs.handle(p, req);
- if (reply.status == .err and (req.op == .write or req.op == .setattr or (req.op == .open and reply.errno == ctlfs.E.NFILE) or (req.op == .release and req.remove))) ctlfs.events.noteError(p, req, reply);
+ if (reply.status == .err and (req.op == .write or req.op == .setattr or (req.op == .open and reply.errno == ctlfs.E.NOSPC) or (req.op == .release and req.remove))) ctlfs.events.noteError(p, req, reply);
// The request was a whole step of its own, so it settles the way a
// step does: the cursor and scroll reconciled, the scripted panes
// told, and the panes it made announced to /log now rather than at