summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 23:42:32 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitf842a4026348c65370a973e466c2b4f45063bdaf (patch)
treee6258955563103db1ad76b1324f3971e279afa06 /src/ninep/tree.zig
parent8469def61324feb5e278f88bebad36af7a0d997e (diff)
downloadpardes-f842a4026348c65370a973e466c2b4f45063bdaf.tar.gz
pardes-f842a4026348c65370a973e466c2b4f45063bdaf.zip
A write to a closed pane's file says it is gone, and a terminal says why it takes no name
`echo Del > pane/6/exec` just after pane 6 closed failed EACCES: the shell's lookup failed, it tried to create the file, and the engine refused every Tcreate as a permission error. pardes now takes Tcreate itself: refused as before (acme's fsyscreate refuses it too), ENOENT in a pane that has closed. Renaming a terminal was refused with only EPERM; it now says a terminal is named by its shell's directory, worded so a mount still maps it to EPERM. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig22
1 files changed, 15 insertions, 7 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index e5320f61..7279b64a 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -68,9 +68,11 @@ pub const e_shut_down = "window shut down";
/// 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
-/// /pane/new, which needs no feature of the engine's, so Tcreate is refused
-/// everywhere, as it is in acme (editors/acme/fsys.c, fsyscreate).
-pub const features: cloud9.fs.Features = .{ .remove = true };
+/// /pane/new, so Tcreate is refused everywhere, as it is in acme
+/// (editors/acme/fsys.c, fsyscreate) -- here rather than by the engine, so
+/// a create in a pane that has closed says the pane is gone (a shell's `>`
+/// to a closed pane's file creates when its lookup fails).
+pub const features: cloud9.fs.Features = .{ .remove = true, .create = true };
/// Whether a request must wait until no step is out in a syscall
/// (`pardes.turn`): what would change a pane such a step still points into.
@@ -371,6 +373,11 @@ pub fn stagedReply(p: *Pardes, req: Req) Reply {
// ---- dispatch ----
pub fn handle(p: *Pardes, req: Req) Reply {
+ if (req.op == .open and req.create) {
+ if (Node.target(req.node)) |t| if (t == .pane and p.paneBySerial(t.pane.serial) == null)
+ return Reply.fail(req.tag, E.NOENT);
+ return Reply.fail(req.tag, E.PERM);
+ }
const host = req.node == fs.os_root or req.node & fs.os_node != 0;
const archive = req.node & sources.archive_node != 0;
// Only a pane directory is removed. The host tree and the embedded
@@ -1105,10 +1112,11 @@ test "opening /pane/new makes a pane and removing one closes it" {
try testing.expect(p.paneBySerial(serial) != null);
try testing.expectEqualStrings("hi", p.panes[id].?.file.?.content);
- // The tree declares no create, so the engine refuses every Tcreate
- // (cloud9 fs.zig: `.tcreate => if (features.create) ... else fail(e_perm)`),
- // and `new` is the only name in /pane that is not a serial.
- try testing.expect(!features.create);
+ // Every create is refused, a pane's that has closed as gone, and `new`
+ // is the only name in /pane that is not a serial.
+ try testing.expectEqual(E.PERM, call(p, .{ .tag = 6, .op = .open, .node = panes_dir, .create = true, .data = "scratch" }).errno());
+ try testing.expectEqual(E.PERM, call(p, .{ .tag = 6, .op = .open, .node = Node.of(serial, .dir), .create = true, .data = "x" }).errno());
+ try testing.expectEqual(E.NOENT, call(p, .{ .tag = 6, .op = .open, .node = Node.of(999_999, .dir), .create = true, .data = "exec" }).errno());
try testing.expectEqual(E.NOENT, look_up(p, panes_dir, "scratch").errno());
try testing.expectEqual(E.NOENT, look_up(p, root, new_pane).errno());
// ...and it is listed, because the user asked to see it in `ls`.