summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 13:26:10 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit917e9a099f3a5048ac436aafcfabbf3d40d1c24f (patch)
tree334ca7e2cee44cd106533d57562a7a3f2cfb013d
parentf4fdeb4f0f5f08699f77bd2cc6cac5a87d9f5091 (diff)
downloadpardes-917e9a099f3a5048ac436aafcfabbf3d40d1c24f.tar.gz
pardes-917e9a099f3a5048ac436aafcfabbf3d40d1c24f.zip
docs: through a mount a malformed write is EINVAL and one that fails is EIO, and the error words keep it so
The served README said every refused write reads as EINVAL, but a mount's errno is 9ns's reading of the error's words, so a no-match or out-of-range address and a refused Exit came back EIO, as the dogfood run saw. That split is the useful one, malformed against failed, so the docs now state it rather than the words being bent to EINVAL; a test pins each error's words to its side of it. It caught one: 'regular expression search took too long' read as ENAMETOOLONG, and now says it gave up past its step budget. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md2
-rw-r--r--docs/fs.md20
-rw-r--r--src/fs-help.txt2
-rw-r--r--src/ninep/addr.zig4
-rw-r--r--src/ninep/ctl.zig25
5 files changed, 44 insertions, 9 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 49db81b9..9b751a26 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -154,7 +154,7 @@ selection, `/re/` searches on from its end and wraps unless `limit` is set,
`?re?` or `-/re/` searches back, `#100,#50` fails `addresses out of order`,
and a search that backtracks
past a step budget (about 300 ms) fails with `regular expression search
-took too long`. A failed address says
+gave up, ...`. A failed address says
why (`no match for regexp`, `address out of range`) and leaves no address:
`data` refuses until the next good one, so a missed target is never
written at the old one. Moving `dot` scrolls the pane to it. `limit` bounds a
diff --git a/docs/fs.md b/docs/fs.md
index 96d0eb3a..4673e042 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -151,9 +151,9 @@ and `not a session control message "X"` or `not a window control message
"X"` for a word of the other ctl. 9ns maps them all to EINVAL, and a write
refused here has done nothing. A line that then fails as it runs fails the
write with the error the editor reports for it and the line, e.g. `Mount:
-AlreadyMounted "Mount peer /tmp/s"` (EIO); so does `control message needs
-its argument "Save"`, for a builtin that would have asked at a prompt (a
-`Save` on a scratch) rather than open one nobody is there to answer. The
+AlreadyMounted "Mount peer /tmp/s"` (EIO), and `control message needs its
+argument "Save"` (EINVAL), for a builtin that would have asked at a prompt
+(a `Save` on a scratch) rather than open one nobody is there to answer. The
lines before a failing one have taken effect and those after it never run,
which is what acme's ctl loop does (editors/acme/xfid.c:600-790). An error
that only happens as the editor performs what a line asked for -- a `Save`
@@ -314,7 +314,8 @@ pattern that spans lines, `^`, `$` and `[^...]` keep mvzr's own meaning.
mvzr backtracks without bound of its own (`a?` twenty times then twenty
`a`s is 2^20 steps from each place it tries), and a search holds the editor, so pardes patches a
step budget into mvzr's matcher (build.zig): a search that spends it,
-about 300 ms, fails with `regular expression search took too long` rather
+about 300 ms, fails with `regular expression search gave up, backtracking
+past its step budget` rather
than answer a match it is not sure of. Ordinary patterns spend a few
thousand steps; what runs out is exponential backtracking, and a quadratic
pattern over a very long line (`\s*(\w+)\s*=` over 20 KB of letters).
@@ -324,7 +325,7 @@ is the one place both call), so `^` there also means a line's start.
An address that does not evaluate fails the write with why: `bad address
syntax`, `no match for regexp`, `address out of range`, `bad regular
-expression`, `regular expression search took too long`, or sam's
+expression`, `regular expression search gave up, ...`, or sam's
`addresses out of order` for a range that ends before it starts
(`#100,#50`), which acme lets through. A failed write to `addr` leaves no
address at all, where acme
@@ -370,7 +371,14 @@ reply to, usually `Invalid argument`, and this is the reason (`err 3 addr:
no match for regexp`). The same err again, before any follower has read
the first, is that record counted (`err 3 addr: no match for regexp (x4)`),
so a client retrying a failing write does not push the rest out of the
-ring; a follower that read it gets each repeat. There is no per-pane error file to read instead:
+ring; a follower that read it gets each repeat. Through a kernel mount a client sees only an errno, which 9ns reads from the
+error's words (cloud9's 9ns/src/nine.zig, `enameToErrno`): a malformed write
+-- an unknown or ill-formed control message, `bad address syntax`, `bad
+regular expression` -- is EINVAL; a lock another open holds, EBUSY; a pane
+gone, ENOENT; a well-formed write that fails -- `no match for regexp`,
+`address out of range`, `addresses out of order`, a search that gave up,
+`<name>: Modified (Exit again to discard)` -- EIO. The err record has the
+words. There is no per-pane error file to read instead:
acme's `errors` only takes text, and one record stream is simpler to watch
than a file per pane. A `msg` said while a
pane is being made can precede that pane's `new`; panes present at boot are
diff --git a/src/fs-help.txt b/src/fs-help.txt
index 689d8839..4633d91c 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -40,6 +40,6 @@ Pitfalls, one each:
tag reads the path, then the tag's own text; writes append to that text; truncating clears it.
A terminal's body is a history snapshot frozen per open; pty/data is the live stream.
exec: a word no builtin knows is typed into a terminal in the pane's directory, and not an error.
- A refused write reads as EINVAL through a mount: the log's err record says why.
+ Through a mount a malformed write is EINVAL, a well-formed one that fails EIO: log's err says why.
lock/unlock on a pane's ctl need a held fd (exec 3>ctl); a lock someone holds fails: retry.
Closing a column's last pane leaves a +New in its place; Delcol closes the column.
diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig
index 6656c786..3b29f622 100644
--- a/src/ninep/addr.zig
+++ b/src/ninep/addr.zig
@@ -14,7 +14,9 @@ fn clip(n: usize) u32 {
pub const e_no_match = "no match for regexp";
pub const e_range = "address out of range";
pub const e_regexp = "bad regular expression";
-pub const e_slow = "regular expression search took too long";
+/// Not "took too long": 9ns reads errors by their words, and that would be
+/// ENAMETOOLONG.
+pub const e_slow = "regular expression search gave up, backtracking past its step budget";
pub const e_syntax = "bad address syntax";
pub const e_order = "addresses out of order";
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 382cc0e7..386fd9a9 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -926,3 +926,28 @@ test "look and exec read back what the last command touched, a ctl write's too"
try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.ctl), "Verbose off\n").reply.status);
try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), rd(p, root_exec, 0, 64).bytes);
}
+
+test "an error's words give the errno a mount reads: EINVAL for what is malformed, EIO for what failed" {
+ // 9ns (cloud9's 9ns/src/nine.zig) maps an error to an errno by its
+ // words, and EIO when none of them match: these words must not.
+ const addressing = @import("addr.zig");
+ const einval = [_][]const u8{ "control message", "invalid", "bad " };
+ const other = [_][]const u8{ "interrupt", "shut down", "not exist", "not found", "no such", "exists", "not empty", "not a dir", "is a dir", "permission", "denied", "read-only", "read only", "no space", "not allowed", "not permitted", "cannot", "fid", "busy", "in use", "too long", "not supported", "unsupported" };
+ const Case = struct { text: []const u8, malformed: bool };
+ for ([_]Case{
+ .{ .text = addressing.e_syntax, .malformed = true },
+ .{ .text = addressing.e_regexp, .malformed = true },
+ .{ .text = e_prompt, .malformed = true },
+ .{ .text = addressing.e_no_match, .malformed = false },
+ .{ .text = addressing.e_range, .malformed = false },
+ .{ .text = addressing.e_order, .malformed = false },
+ .{ .text = addressing.e_slow, .malformed = false },
+ .{ .text = "/tmp/x.txt: Modified (Exit again to discard)", .malformed = false },
+ }) |c| {
+ const says_einval = for (einval) |w| {
+ if (std.ascii.findIgnoreCase(c.text, w) != null) break true;
+ } else false;
+ try testing.expectEqual(c.malformed, says_einval);
+ for (other) |w| try testing.expect(std.ascii.findIgnoreCase(c.text, w) == null);
+ }
+}