diff options
| -rw-r--r-- | src/ninep/pane.zig | 28 | ||||
| -rw-r--r-- | src/ninep/tree.zig | 5 |
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; |
