summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 11:28:48 -0300
committerGabriel Schneider <[email protected]>2026-10-01 11:28:48 -0300
commit9e30f716e73b4b830b0fde7c5142d8f6b8deb511 (patch)
tree59709ca2f1ef996a23322da1865b4e4d41b24950
parent6d8bd80429102cb06947f92c2bde00292cae05d5 (diff)
downloadpardes-9e30f716e73b4b830b0fde7c5142d8f6b8deb511.tar.gz
pardes-9e30f716e73b4b830b0fde7c5142d8f6b8deb511.zip
A name held for its newline that no newline could make good (a terminal's, a component over 255 bytes, a path too long) is refused by the write that held it, not at the close (monkey9p's close_runs_only_line_files; follows sswkwwvv)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/ninep/pane.zig28
-rw-r--r--src/ninep/tree.zig5
2 files changed, 29 insertions, 4 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index c496571f..ae4dcd71 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -931,16 +931,31 @@ fn nameFault(name: []const u8) ?[]const u8 {
return null;
}
+fn unnamed(pane: *const Pane) []const u8 {
+ return if (pane.isTerminal())
+ "invalid rename: a terminal is named by its shell's directory; cd there, or Tty in another"
+ else
+ "invalid rename: an image or PDF is named by the file it shows";
+}
+
+/// Why a name still held for its newline (tree.zig) can never be taken,
+/// whatever follows: said by the write that held it, not at the close.
+pub fn heldNameFault(p: *Pardes, serial: u32, held: []const u8) ?[]const u8 {
+ const pane = p.panes[p.paneBySerial(serial) orelse return null].?;
+ if (fileOf(pane) == null) return unnamed(pane);
+ var parts = std.mem.splitScalar(u8, held, '/');
+ while (parts.next()) |part| if (part.len > 255) return "invalid file name: a component over 255 bytes";
+ if (held.len >= 4096) return "invalid file name: longer than a path may be";
+ return null;
+}
+
fn writeName(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
// One name: its newline ends it, as `echo` writes it, and it is one.
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 tree.failText(req.tag, E.INVAL, e_name_char ++ ": an empty name");
if (nameFault(name)) |why| return tree.failText(req.tag, E.INVAL, why);
// Words 9ns maps to EINVAL, as every refusal of a name here.
- if (fileOf(pane) == null) return tree.failText(req.tag, E.INVAL, if (pane.isTerminal())
- "invalid rename: a terminal is named by its shell's directory; cd there, or Tty in another"
- else
- "invalid rename: an image or PDF is named by the file it shows");
+ if (fileOf(pane) == null) return tree.failText(req.tag, E.INVAL, unnamed(pane));
var home_buf: [4096]u8 = undefined;
const full = std.fs.path.resolvePosix(p.scratch.allocator(), &.{ pardes.Pardes.paneDir(pane), fs.expandHome(name, &home_buf) }) catch
return Reply.fail(req.tag, E.NOMEM);
@@ -1537,6 +1552,11 @@ test "a name with a component over 255 bytes is refused, and a long path's faile
try testing.expectEqual(E.INVAL, refused.errno());
try testing.expectEqualStrings("invalid file name: a component over 255 bytes", refused.reply.ename);
try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .name), "/tmp/" ++ "c" ** 255 ++ "\n").reply.status);
+ // Held with no newline, it is refused by that write, not at the close.
+ const held = call(p, .{ .tag = 1, .op = .open, .node = Node.of(serial, .name), .omode = 1 }).reply.handle;
+ try testing.expectEqual(E.INVAL, call(p, .{ .tag = 2, .op = .write, .node = Node.of(serial, .name), .handle = held, .data = "/tmp/" ++ "e" ** 256 }).errno());
+ _ = call(p, .{ .tag = 3, .op = .release, .node = Node.of(serial, .name), .handle = held, .opened = true });
+ try testing.expectEqual(@as(usize, 0), p.fs.closed_lines.items.len);
// A Save of a 3000-byte path the host refuses: the waiting write's
// reason is at the end, the path giving up its middle.
// (Under /tmp: under / a user's Save is refused permission, not
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index b0ffdd68..1990d09e 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1278,6 +1278,11 @@ fn writeLines(p: *Pardes, req: Req, target: Target) Reply {
// its message is no whole line. An Edit block is whole when it closes.
const end = ctl.completeEnd(p, o.pending.items);
if (end == 0) {
+ if (target == .pane and target.pane.file == .name) if (pane.heldNameFault(p, target.pane.serial, o.pending.items)) |why| {
+ o.pending.clearRetainingCapacity();
+ o.discarding = true;
+ return failText(req.tag, E.INVAL, why);
+ };
if (o.pending.items.len <= pending_cap) return .{ .tag = req.tag, .written = @intCast(req.data.len) };
o.pending.clearRetainingCapacity();
o.discarding = true;