diff options
| -rw-r--r-- | docs/fs.md | 3 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 19 |
2 files changed, 21 insertions, 1 deletions
@@ -397,7 +397,8 @@ buffer (relative to the pane's directory) and marks nothing dirty; `Save` then writes under the new name. A name is one line; refused (EINVAL) are a second line, a blank at either end, control bytes and non-UTF-8 (`bad character in file name: a blank at its start`, ...). Up to 255 bytes a -component. +component. A directory (`/`, `~`, `foo/`) is no file name: refused, EISDIR +(`name: /home/u is a directory, not a file`). **`body`** reads the text; a write appends; `>` (OTRUNC) replaces it all. A terminal's body is its history as plain text in logical lines (wrapped diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index d3c1b907..78bec09f 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -883,6 +883,11 @@ fn writeName(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { 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); if (!std.fs.path.isAbsolute(full) or full.len >= 4096) return tree.failText(req.tag, E.INVAL, "invalid file name: longer than a path may be"); + // A directory (`~`, `/`, `foo/`) is no file to name a pane after: a + // Save could only fail. Said now, EISDIR. + const is_dir = name[name.len - 1] == '/' or std.mem.eql(u8, full, "/") or + (if (comptime pardes.hosted) (if (fs.localPath(full)) |local| exec.isDirectory(local) else false) else false); + if (is_dir) return tree.failText(req.tag, E.ISDIR, std.fmt.bufPrint(&p.fs.ename, "name: {s} is a directory, not a file", .{full[0..@min(full.len, 256)]}) catch "name: is a directory, not a file"); nameBuffer(p, id, full, false) catch |err| return Reply.fail(req.tag, switch (err) { error.OutOfMemory => E.NOMEM, else => E.INVAL, @@ -1415,6 +1420,20 @@ test "an addr pair out of order or out of range is refused as the address form i try testing.expectEqual(State.Range{ .q0 = 2, .q1 = 4 }, p.panes[p.paneBySerial(serial).?].?.fs.addr); } +test "name refuses a directory, EISDIR, and leaves the name as it was" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const serial = serialOf(p); + const node = Node.of(serial, .name); + for ([_][]const u8{ "/\n", "/tmp/\n", "sub/\n", "/tmp\n" }) |dir| { + if (comptime !pardes.hosted) if (std.mem.eql(u8, dir, "/tmp\n")) continue; + const r = wr(p, node, dir); + try testing.expectEqual(E.ISDIR, r.errno()); + try testing.expect(std.mem.indexOf(u8, r.reply.ename, "is a directory") != null); + } + try testing.expectEqualStrings("/test.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?)); +} + test "a write of two lines to name is refused EINVAL, on a held open or not" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); |
