diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 11:30:18 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 5e8d2b7ac7d7880ac36ec45484b2711669ac7ae1 (patch) | |
| tree | d8c84364f51e3d9e136d5ad4e196235e12d37f66 /src/ninep | |
| parent | fda2f546f66d0bb1b30da0ef6aae77fe82803b1c (diff) | |
| download | pardes-5e8d2b7ac7d7880ac36ec45484b2711669ac7ae1.tar.gz pardes-5e8d2b7ac7d7880ac36ec45484b2711669ac7ae1.zip | |
A failure naming a long path keeps its reason, and name refuses a component over 255 bytes
A Save of a 3000-byte path failed with the record "err 2 ctl: Saveā¦", which
lost the reason. The first cause was reportError, which formatted into 256
bytes, so the operation's path filled the buffer before the reason was
written. The second was the waiting write's late failure, which took the
first 256 bytes of the message row, never its end. Now reportError has room
for the longest path. reportFailure also keeps the words fitted as an err is
(fitErr: the path gives up its middle, the reason stays), and the late
failure of a Save, a Dump, a shell or a ThemeFile takes those words.
fs.md already said a name holds up to 255 bytes a component, but a longer
one was taken and only failed later at Save. Now it is refused when written.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/pane.zig | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 30530c3b..de7f7cd4 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -845,6 +845,9 @@ fn nameFault(name: []const u8) ?[]const u8 { } if (name[0] == ' ') return e_name_char ++ ": a blank at its start"; if (name[name.len - 1] == ' ') return e_name_char ++ ": a blank at its end"; + // No file system takes a longer one (NAME_MAX): a Save would only fail. + var parts = std.mem.splitScalar(u8, name, '/'); + while (parts.next()) |part| if (part.len > 255) return "invalid file name: a component over 255 bytes"; return null; } @@ -1364,6 +1367,26 @@ test "a write of two lines to name is refused EINVAL, on a held open or not" { try testing.expectEqualStrings("/tmp/pardes-a", nameOf(p, p.panes[p.paneBySerial(serial).?].?)); } +test "a name with a component over 255 bytes is refused, and a long path's failed Save keeps its reason" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const serial = serialOf(p); + const refused = wr(p, Node.of(serial, .name), "/tmp/" ++ "c" ** 256 ++ "/f.txt\n"); + 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); + // 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. + const long = "/nonexistent-pardes-root/" ++ ("d" ** 200 ++ "/") ** 15 ++ "f.txt"; + const id = p.paneBySerial(serial).?; + try nameBuffer(p, id, long, false); + p.fs.late_failure_len = 0; + p.saveFailed(@intCast(id), long, error.AccessDenied); + const late = p.fs.late_failure[0..p.fs.late_failure_len]; + try testing.expect(std.mem.startsWith(u8, late, "Save /nonexistent-pardes-root/")); + try testing.expect(std.mem.endsWith(u8, late, "/f.txt: no such directory")); +} + test "a name cut across writes is one name, applied once at its newline or its close" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); |
