diff options
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/cols.zig | 5 | ||||
| -rw-r--r-- | src/ninep/events.zig | 4 | ||||
| -rw-r--r-- | src/ninep/tree.zig | 56 |
3 files changed, 59 insertions, 6 deletions
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig index 6b59f9e5..cc583c93 100644 --- a/src/ninep/cols.zig +++ b/src/ninep/cols.zig @@ -368,10 +368,11 @@ test "a column's ctl and exec act on it as its tag would, and rmdir closes it on try testing.expectEqual(E.INVAL, th.wr(p, Node.ofCol(empty, .ctl), "Exit\n").errno()); var rec: [64]u8 = undefined; try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}/ctl: unknown control message \"Exit\"", .{empty}))); - // rmdir refuses a column with a pane, and says why. + // rmdir refuses a column with a pane, and says why, in its error alone. const full = th.call(p, .{ .tag = 1, .op = .release, .node = Node.ofCol(empty, .dir), .remove = true }); try testing.expectEqual(E.NOTEMPTY, full.errno()); - try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}: column not empty", .{empty}))); + try testing.expect(std.mem.startsWith(u8, full.reply.ename, "column not empty")); + try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}: column not empty", .{empty}))); // exec runs a word as a click in that column's tag: Delcol closes it. try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(empty, .exec), "Delcol\n").reply.status); try testing.expectEqual(@as(?usize, null), layout.columnBySerial(p, empty)); diff --git a/src/ninep/events.zig b/src/ninep/events.zig index f76a5212..c0bf4a9a 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -174,8 +174,8 @@ pub fn noteMessage(p: *Pardes, serial: u32, said: []const u8) void { std.fmt.bufPrint(&buf, "msg {d} {s}{s}\n", .{ serial, kept, cut })) catch return, said); } -/// Records `err <serial|-> <file>: <why>` for a write or truncation that was -/// refused or failed: through a mount a shell sees only the errno the +/// Records `err <serial|-> <file>: <why>` for a write that was refused or +/// failed: through a mount a shell sees only the errno the /// kernel mapped the reply to (`Invalid argument`), and here is the reason. /// The log is the one place for it, as acme's `errors` file takes text and /// answers nothing: a per-pane readable error file would be a second. diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 94fa5d1e..30bf073d 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -454,6 +454,33 @@ pub fn stagedReply(p: *Pardes, req: Req) Reply { // ---- dispatch ---- pub fn handle(p: *Pardes, req: Req) Reply { + var reply = serve(p, req); + if (reply.status == .err and reply.ename.len == 0) reply.ename = errWords(reply.errno); + return reply; +} + +/// A refusal with no reason of its own said in Plan 9's words, not the C +/// library's (`Operation not permitted`); each maps back to its errno in +/// 9ns (enameToErrno), EPERM's to EACCES as Plan 9's does. +pub fn errWords(errno: u16) []const u8 { + return switch (errno) { + E.PERM => "permission denied", + E.NOENT => "file does not exist", + E.NOMEM => "out of memory", + E.BUSY => "file in use", + E.EXIST => "file already exists", + E.NOTDIR => "not a directory", + E.ISDIR => "is a directory", + E.INVAL => "bad argument", + E.NFILE => "too many open files", + E.NOSPC => "no space left", + E.NOSYS => "not supported", + E.NOTEMPTY => "directory not empty", + else => "i/o error", + }; +} + +fn serve(p: *Pardes, req: Req) Reply { // Held writes go in before anything but the next write of their open. if (p.fs.batch.bytes.items.len > 0 and !(req.op == .write and req.node == p.fs.batch.node and req.handle == p.fs.batch.handle)) pane.flushBatch(p); @@ -1564,8 +1591,8 @@ test "at the pane cap, pane/new, look and New each say so, and look reads back e const refused = call(p, .{ .tag = 2, .op = .open, .node = new }); 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)); - // Its err alone says it: no msg besides (the one rule). + // A refused open is its error alone: no err, and no msg besides. + try testing.expect(!th.logHas(p, said)); try testing.expect(!th.logHas(p, "New: no space")); // A look that would open a pane fails its write, and says the same. var tmp = testing.tmpDir(.{}); @@ -1614,6 +1641,31 @@ test "a command line cut across writes runs once whole, and the last runs at rel try testing.expectEqualStrings("x\none\ntwo\n", p.panes[0].?.file.?.content); } +test "a refused open, create or remove says why in words and logs no err; a refused write logs one" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const serial = serialOf(p); + // pane/new is read, never written: a write is refused, and is no + // failure the log tells of. + const new = th.wr(p, @intFromEnum(TopFile.new), "x\n"); + try testing.expectEqual(Status.err, new.reply.status); + try testing.expectEqualStrings("permission denied", new.reply.ename); + const made = call(p, .{ .tag = 2, .op = .open, .node = Node.of(serial, .body), .create = true }); + try testing.expectEqualStrings("permission denied", made.reply.ename); + const gone = th.rmdir(p, @intFromEnum(TopFile.index)); + try testing.expectEqual(Status.err, gone.reply.status); + try testing.expect(gone.reply.ename.len > 0 and !std.ascii.isUpper(gone.reply.ename[0])); + // An OTRUNC open's truncation of data after a failed addr: refused, no err. + _ = th.wr(p, Node.of(serial, .addr), "/nothere/"); + const errs = th.logCount(p, "\nerr "); + try testing.expectEqual(Status.err, call(p, .{ .tag = 3, .op = .setattr, .node = Node.of(serial, .data), .truncate = true }).reply.status); + try testing.expectEqual(errs, th.logCount(p, "\nerr ")); + try testing.expectEqual(@as(usize, 1), errs); // the addr write's own + // A write is the rule's: refused, it logs its one err, in words too. + try testing.expectEqual(Status.err, th.wr(p, Node.of(serial, .ctl), "bogus\n").reply.status); + try testing.expectEqual(@as(usize, 2), th.logCount(p, "\nerr ")); +} + test "a write with no newline, whole in its Twrite, runs then and fails the write; one needing more waits" { const p = try th.withFile(testing.allocator, "abc\n"); defer p.deinit(); |
