summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/cols.zig5
-rw-r--r--src/ninep/events.zig4
-rw-r--r--src/ninep/tree.zig56
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();