summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 16:50:04 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit16d5415c508fea0b5dc90dd2cc40866beecf216b (patch)
tree6e558181c1371fb2bc3ef805a64e28c269885795 /src/ninep
parent0f632dbd1069d9654d587ebac8e29955a9a31188 (diff)
downloadpardes-16d5415c508fea0b5dc90dd2cc40866beecf216b.tar.gz
pardes-16d5415c508fea0b5dc90dd2cc40866beecf216b.zip
acme's words run as pardes's or are refused, never as shell commands: Put saves
Put, Get, Delete, Snarf, Cut, Paste, Zerox, Sort, Putall, Load, ID and Send written to exec or tagexec each ran as a shell command that exited 127, the write answering 0 -- a Put that looked like a save. Put now runs Save and Delete a Del that does not ask; the rest are refused, EINVAL, invalid: acme's X is not a pardes builtin (with where pardes does it, where it does), in exec and ctl alike. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/ctl.zig43
1 files changed, 38 insertions, 5 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index d2ff4dab..a8d4e8e0 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -251,8 +251,9 @@ pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, i
/// EIO.
fn failureErrno(failure: []const u8) u16 {
if (std.mem.indexOf(u8, failure, "no such") != null or std.mem.indexOf(u8, failure, "not found") != null) return E.NOENT;
- // A pattern refused (Edit's too) is malformed input, as 9ns reads it.
- if (std.mem.indexOf(u8, failure, "bad regular expression") != null) return E.INVAL;
+ // A pattern refused (Edit's too) is malformed input, as 9ns reads it;
+ // so is a word this build has not (`invalid: acme's Put ...`).
+ if (std.mem.indexOf(u8, failure, "bad regular expression") != null or std.mem.startsWith(u8, failure, "invalid")) return E.INVAL;
return E.IO;
}
@@ -387,7 +388,13 @@ pub fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []
/// as on a tag; acme's lowercase verbs are the pane ctl's and alias none.
fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?Reply {
const sp = std.mem.indexOfAny(u8, line, " \t");
- const word = line[0 .. sp orelse line.len];
+ const typed = line[0 .. sp orelse line.len];
+ // acme's words: checked as the pardes word they run as, or refused.
+ const word = for (pardes.exec.acme_words) |w| {
+ if (!std.mem.eql(u8, typed, w.acme)) continue;
+ const now = w.pardes orelse return tree.failText(req.tag, E.INVAL, std.fmt.bufPrint(&p.fs.ename, "invalid: acme's {s} is not a pardes builtin{s}", .{ w.acme, w.hint }) catch "invalid: an acme word pardes has not");
+ break now;
+ } else typed;
const arg = if (sp) |s| std.mem.trim(u8, line[s + 1 ..], " \t") else "";
const b = std.meta.stringToEnum(Builtin, word) orelse {
// A setting this build's frontend cannot show (Lift, GripWidth on a
@@ -1035,11 +1042,12 @@ test "the pane ctl takes acme's verbs and the pane's builtins, and refuses the r
const serial = serialOf(p);
const ctl_node = Node.of(serial, .ctl);
// acme's other ctl words have files of their own here, and a builtin is
- // its own capitalised word: `Get`, `DEL` and `del` are none of them.
+ // its own capitalised word: `GET`, `DEL` and `del` are none of them
+ // (acme's own `Get` is refused as acme's).
for ([_][]const u8{
"menu", "nomenu", "dump echo hi", "font Go Mono", "lock x", "bogus", "DEL",
"put", "del", "delete", "clean", "dirty", "show", "mark",
- "Get", "limit=addr", "get x", "look /tmp", "exec Del",
+ "GET", "limit=addr", "get x", "look /tmp", "exec Del",
}) |bad| {
const refused = wr(p, ctl_node, bad);
try testing.expectEqual(E.INVAL, refused.errno());
@@ -2269,6 +2277,31 @@ test "a look that misses file:/re/ names the file, and is logged as its pane's"
try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "err {d} look: {s}/f.txt: no match for regexp", .{ f_serial, dir })));
}
+test "acme's words run as pardes's (Put is Save, Delete a Del that does not ask) or are refused, never run as commands" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ for ([_][]const u8{ "Snarf\n", "Putall\n", "Zerox\n", "Get\n" }) |line| {
+ for ([_]u64{ Node.of(serial, .ctl), Node.of(serial, .exec) }) |node| {
+ const r = wr(p, node, line);
+ try testing.expectEqual(E.INVAL, r.errno());
+ try testing.expect(std.mem.indexOf(u8, r.reply.ename, "is not a pardes builtin") != null);
+ }
+ }
+ // Put saves, as Save does.
+ _ = wr(p, Node.of(serial, .name), "/tmp/pardes-put.txt\n");
+ const put = wr(p, Node.of(serial, .exec), "Put\n");
+ try testing.expectEqual(Status.ok, put.reply.status);
+ try testing.expect(put.saved);
+ // Delete closes an edited pane without asking.
+ const made = try th.newPane(p);
+ _ = wr(p, Node.of(made, .name), "/tmp/pardes-delete.txt\n");
+ _ = wr(p, Node.of(made, .body), "edited\n");
+ try testing.expectEqual(Status.ok, wr(p, Node.of(made, .ctl), "Delete\n").reply.status);
+ p.sync();
+ try testing.expect(p.paneBySerial(made) == null);
+}
+
test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();