diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Messages.zig | 10 | ||||
| -rw-r--r-- | src/fs.zig | 6 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 23 | ||||
| -rw-r--r-- | src/pardes.zig | 22 |
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; @@ -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); |
