diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 17:35:15 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | daf833bccf5aca958593738ecc59fd58dbb697b6 (patch) | |
| tree | 1e149640bbfa66886f3acb6d20f0720a4e6d1266 /src | |
| parent | 39149c051a79e7d5148522b9851768a8e5b2ab54 (diff) | |
| download | pardes-daf833bccf5aca958593738ecc59fd58dbb697b6.tar.gz pardes-daf833bccf5aca958593738ecc59fd58dbb697b6.zip | |
name refuses a directory, EISDIR, when written, not at the Save that could only fail
`name ~`, `name /` or `name foo/` renamed the pane to a directory, and
only its Save failed later. A name ending in `/`, the root, or one naming
a directory that is there is now refused at once, with words a mount
reads as EISDIR (`name: <path> is a directory, not a file`). The ctl word
name and a forwarded `pardes ~` go through the same check. fs.md says so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/ninep/pane.zig | 19 |
1 files changed, 19 insertions, 0 deletions
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(); |
