diff options
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 2 | ||||
| -rw-r--r-- | docs/fs.md | 20 | ||||
| -rw-r--r-- | src/fs-help.txt | 2 | ||||
| -rw-r--r-- | src/ninep/addr.zig | 4 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 25 |
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 @@ -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); + } +} |
