summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Messages.zig10
-rw-r--r--src/fs.zig6
-rw-r--r--src/ninep/pane.zig23
-rw-r--r--src/pardes.zig22
4 files changed, 46 insertions, 15 deletions
diff --git a/src/Messages.zig b/src/Messages.zig
index d1478ac7..3eeb8c92 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -463,7 +463,10 @@ pub fn dialReason(err: anyerror) ?[]const u8 {
}
pub fn reportError(p: *Pardes, id: usize, operation: []const u8, err: anyerror) void {
- var buf: [256]u8 = undefined;
+ // Room for an operation naming the longest path and the reason after
+ // it: a shorter one cut the reason off (`Save /long…`), and the reason
+ // is what a failure is for. Where it must be shorter, the path gives.
+ var buf: [@max(256, limits.host_path_cap + 128)]u8 = undefined;
var w = std.Io.Writer.fixed(&buf);
w.print("{s}: ", .{operation}) catch {};
// A peer out of reach says so, not the error's name (`dial`).
@@ -503,6 +506,11 @@ pub fn clip(text: []const u8, max: usize) []const u8 {
/// reportError with the words already chosen.
pub fn reportFailure(p: *Pardes, id: usize, text: []const u8) void {
p.fs.failures +%= 1;
+ var fitted: @TypeOf(p.fs.said) = undefined;
+ const said = pardes.ctlfs.fitErr(text, &fitted);
+ @memcpy(p.fs.said[0..said.len], said);
+ p.fs.said_len = @intCast(said.len);
+ p.fs.said_at = p.fs.failures;
// A builtin a ctl write runs: its first error is also the write's, its
// path shortened in the middle if it must be, never its reason.
const failing_write = p.fs.no_prompt or p.fs.capturing or p.fs.write_waits;
diff --git a/src/fs.zig b/src/fs.zig
index e66a6a49..30ad9c43 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -1343,6 +1343,12 @@ pub const Namespace = struct {
no_pane_slot: bool = false,
no_pane_slot_len: u16 = 0,
header_held: bool = false,
+ /// The newest failure said (reportFailure), fitted as an err is: its
+ /// path cut in the middle, never its reason, which the message row's
+ /// first 256 bytes lose. `said_at` is the `failures` count it was.
+ said: [256]u8 = undefined,
+ said_len: u16 = 0,
+ said_at: u32 = 0,
late_failure: [256]u8 = undefined,
late_failure_len: u16 = 0,
/// A 9P write asked a language server something: the answer count it
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();
diff --git a/src/pardes.zig b/src/pardes.zig
index ef713e0c..7fddf19f 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5341,10 +5341,7 @@ pub const Pardes = struct {
}
// A 9P write that asked for the shell (pty/ctl's exec), waiting on
// it, fails with what was said, as a failed Save's does.
- const said = pane.msg[0..pane.msg_len];
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.noteLateFailure(id);
}
pub fn saveFailed(p: *Pardes, id: u8, path: []const u8, err: anyerror) void {
@@ -5362,10 +5359,7 @@ pub const Pardes = struct {
p.reportFailure(id, std.fmt.bufPrint(&what, "Save {s}: no such directory", .{path}) catch "Save: no such directory")
else
p.reportError(id, std.fmt.bufPrint(&what, "Save {s}", .{path}) catch "Save", err);
- const said = if (p.panes[id]) |pane| pane.msg[0..pane.msg_len] else "Save failed";
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.noteLateFailure(id);
}
/// A Dump the host could not write: said on the message row and in the
@@ -5388,10 +5382,7 @@ pub const Pardes = struct {
} else p.reportError(p.active, operation, err);
// In the words the message row has, so the write's err is the one
// record of it (its msg goes: 9p_io, dropMessage).
- const said = if (p.panes[p.active]) |pane| pane.msg[0..pane.msg_len] else "Dump failed";
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.noteLateFailure(p.active);
}
/// The path a watch is about: a real file's, or a PDF's.
@@ -6460,8 +6451,11 @@ pub const Pardes = struct {
/// What was said of a failure just now on pane `id` is also the waiting
/// 9P write's (9p_io, `fs.late_failure`).
pub fn noteLateFailure(p: *Pardes, id: usize) void {
- const pane = p.panes[id] orelse return;
- const said = pane.msg[0..pane.msg_len];
+ // Said by reportFailure just now: its words fitted whole, the
+ // reason kept; else what the message row has.
+ const said = if (p.fs.said_at == p.fs.failures and p.fs.said_len > 0)
+ p.fs.said[0..p.fs.said_len]
+ else if (p.panes[id]) |pane| pane.msg[0..pane.msg_len] else return;
const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
@memcpy(p.fs.late_failure[0..kept.len], kept);
p.fs.late_failure_len = @intCast(kept.len);