From 74db0ccf8162e8c15f21d73168b13fdc86b12ee3 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 07:31:54 -0300 Subject: A name written with a blank at either end is refused, not quietly trimmed `name` trimmed blanks off both ends, so `trailing.zig ` named another file than the one written without a word. acme refuses any blank in a name, `bad character in file name` (xfid.c:650); pardes takes one inside (it names files with spaces) and now refuses one at an end, and a control character, in acme's words. Co-Authored-By: Claude Opus 5.5 --- src/ninep/pane.zig | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 61642999..d8329ccc 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -591,10 +591,18 @@ fn writeFlag(p: *Pardes, req: Req, pane: *Pane, file: PaneFile) Reply { /// Writing `name` renames the buffer: a relative name resolves against the /// pane's directory, as the tag's name field does. +/// acme's refusal of a name with a blank or a control character in it +/// (editors/acme/xfid.c:650-652). A blank inside a name is taken here, as +/// pardes names files with spaces; one at either end is refused, not +/// quietly cut off, so the name a script wrote is the name it gets. +const e_name_char = "bad character in file name"; + fn writeName(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { - const name = std.mem.trim(u8, req.data, " \t\r\n"); + // One line: its newline ends it, as `echo` writes it. + const name = if (std.mem.endsWith(u8, req.data, "\n")) req.data[0 .. req.data.len - 1] else req.data; if (name.len == 0) return Reply.fail(req.tag, E.INVAL); - for (name) |c| if (c < ' ') return Reply.fail(req.tag, E.INVAL); + for (name) |c| if (c < ' ') return tree.failText(req.tag, E.INVAL, e_name_char); + if (name[0] == ' ' or name[name.len - 1] == ' ') return tree.failText(req.tag, E.INVAL, e_name_char); if (fileOf(pane) == null) return tree.failText(req.tag, E.PERM, if (pane.isTerminal()) "rename not allowed: a terminal is named by its shell's directory; cd there, or Tty in another" else @@ -1095,6 +1103,9 @@ test "name reads the file name and writing it promotes a scratch without touchin try testing.expectEqualStrings("existing target\n", target); for ([_][]const u8{ "", "\n", "bad\x01name\n" }) |bad| try testing.expectEqual(E.INVAL, wr(p, name, bad).errno()); + // A blank at either end is refused as acme refuses one, not cut off. + for ([_][]const u8{ "trailing.zig \n", " leading.zig\n", "tab\t.zig\n" }) |bad| + try testing.expectEqualStrings("bad character in file name", wr(p, name, bad).reply.ename); try testing.expectEqualStrings(path, pane.file.?.path); try testing.expectEqual(Status.ok, wr(p, name, "two words.zig\n").reply.status); const spaced = try std.fmt.bufPrint(&path_buffer, "{s}/two words.zig", .{directory}); -- cgit v1.3