diff options
| -rw-r--r-- | src/builtins.zig | 13 | ||||
| -rw-r--r-- | src/detached/server.zig | 4 | ||||
| -rw-r--r-- | src/dump.zig | 9 | ||||
| -rw-r--r-- | src/gui/gui.zig | 4 | ||||
| -rw-r--r-- | src/macos.zig | 4 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 27 | ||||
| -rw-r--r-- | src/pardes.zig | 14 | ||||
| -rw-r--r-- | src/tty/tty.zig | 4 |
8 files changed, 69 insertions, 10 deletions
diff --git a/src/builtins.zig b/src/builtins.zig index 07cdf741..be674692 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -267,7 +267,11 @@ test "Exit over a ctl write names every pane it asks about, and the next Exit di /// A dump file that exists, for a Restore to get past reading it. fn testDump(buf: []u8) ![]const u8 { var tmp = std.testing.tmpDir(.{}); - try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "d.zon", .data = ".{}" }); + // A real dump: a Restore reads it through before it asks anything. + const source = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer source.deinit(); + try pardes.dump.dumpState(source); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "d.zon", .data = source.dump_out.? }); var dir: [4096]u8 = undefined; const at = dir[0..try tmp.dir.realPath(std.testing.io, &dir)]; return std.fmt.bufPrint(buf, "Restore {s}/d.zon", .{at}); @@ -558,7 +562,12 @@ pub const Restore = struct { }; return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: {s}", .{ path, why }) catch "Restore: cannot read the dump"); }; - c.p.gpa.free(bytes); + defer c.p.gpa.free(bytes); + // Nor does a file that is not a dump, before any warning. + pardes.dump.check(c.p.gpa, bytes) catch |err| { + var buf: [limits.host_path_cap + 64]u8 = undefined; + return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: not a pardes dump ({s})", .{ path, @errorName(err) }) catch "Restore: not a pardes dump"); + }; } // acme's Load adds a dump's windows to the ones there; a Restore // replaces them all, so it asks what Exit asks first. diff --git a/src/detached/server.zig b/src/detached/server.zig index 857591d4..8f45135a 100644 --- a/src/detached/server.zig +++ b/src/detached/server.zig @@ -569,8 +569,8 @@ pub const Session = struct { fn writeDump(ctx: ?*anyopaque, bytes: []const u8) void { const s = of(ctx); var pbuf: [1024:0]u8 = undefined; - const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return; - filesystem.write(s.core, path, bytes) catch |err| return s.core.reportError(0, "dump", err); + const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return s.core.dumpFailed(s.core.settings.dump_dir.get(), error.NoDumpDirectory); + filesystem.write(s.core, path, bytes) catch |err| return s.core.dumpFailed(path, err); s.core.setLastDump(path); } diff --git a/src/dump.zig b/src/dump.zig index ef442ddb..5ebb3c8d 100644 --- a/src/dump.zig +++ b/src/dump.zig @@ -305,6 +305,15 @@ pub const Parsed = struct { } }; +/// Whether `bytes` are a dump a Restore could load, saying nothing: a +/// Restore asks before it warns about unsaved text or replaces anything. +pub fn check(gpa: std.mem.Allocator, bytes: []const u8) !void { + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const source = try arena.allocator().dupeZ(u8, bytes); + try validate(try std.zon.parse.fromSliceAlloc(State, arena.allocator(), source, null, .{ .free_on_error = false })); +} + pub fn readZon(gpa: std.mem.Allocator, bytes: []const u8, label: []const u8) !Parsed { var parsed: Parsed = .{ .value = undefined, .arena = .init(gpa) }; errdefer parsed.deinit(); diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 5dfacee9..141a802a 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -4485,8 +4485,8 @@ fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) vo fn writeDump(ctx: ?*anyopaque, bytes: []const u8) void { const s = shellOf(ctx); var pbuf: [1024:0]u8 = undefined; - const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return; - filesystem.write(s.core, path, bytes) catch |err| return s.core.reportError(0, "dump", err); + const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return s.core.dumpFailed(s.core.settings.dump_dir.get(), error.NoDumpDirectory); + filesystem.write(s.core, path, bytes) catch |err| return s.core.dumpFailed(path, err); s.core.setLastDump(path); } diff --git a/src/macos.zig b/src/macos.zig index 678b155d..d6daf944 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -2496,8 +2496,8 @@ fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) vo fn writeDump(ctx: ?*anyopaque, bytes: []const u8) void { const st = hostState(ctx); var pbuf: [1024:0]u8 = undefined; - const path = pardes.dump.outPath(&pbuf, st.core.settings.dump_dir.get()) orelse return; - filesystem.write(st.core, path, bytes) catch |err| return st.core.reportError(0, "dump", err); + const path = pardes.dump.outPath(&pbuf, st.core.settings.dump_dir.get()) orelse return st.core.dumpFailed(st.core.settings.dump_dir.get(), error.NoDumpDirectory); + filesystem.write(st.core, path, bytes) catch |err| return st.core.dumpFailed(path, err); st.core.setLastDump(path); } diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index e62ff605..caf8914f 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -1237,3 +1237,30 @@ test "a look after Newcol fills the empty column the keyboard is on" { try testing.expectEqual(@as(usize, 1), p.col_n[p.ncol - 1]); try testing.expectEqual(p.active, p.col_panes[p.ncol - 1][0]); } + +test "a Restore of a file that is no dump fails the write before any warning, and a failed Dump fails its write" { + if (comptime !pardes.hosted) return error.SkipZigTest; + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const f = pane_files.fileOf(p.panes[0].?).?; + f.content = try p.gpa.realloc(f.content, 3); + @memcpy(f.content, "yy\n"); + f.revision +%= 1; // unsaved: a Restore would warn about it first + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + try tmp.dir.writeFile(testing.io, .{ .sub_path = "notes.txt", .data = "just notes\n" }); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)]; + var line_buf: [4200]u8 = undefined; + const line = try std.fmt.bufPrint(&line_buf, "Restore {s}/notes.txt\n", .{dir}); + const root_ctl = @intFromEnum(tree.TopFile.ctl); + const refused = wr(p, root_ctl, line); + try testing.expectEqual(E.IO, refused.errno()); + try testing.expect(std.mem.indexOf(u8, refused.reply.ename, "not a pardes dump") != null); + try testing.expect(p.restore_req == null); + try testing.expect(p.panes[0].?.discard_warned == null); + + p.fs.late_failure_len = 0; + p.dumpFailed("/nowhere/pardes.dump.zon", error.FileNotFound); + try testing.expectEqualStrings("Dump /nowhere/pardes.dump.zon: FileNotFound", p.fs.late_failure[0..p.fs.late_failure_len]); +} diff --git a/src/pardes.zig b/src/pardes.zig index 452f6174..c2bde804 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5105,6 +5105,20 @@ pub const Pardes = struct { p.fs.late_failure_len = @intCast(kept.len); } + /// A Dump the host could not write: said on the message row and in the + /// log, and the 9P write that asked for it fails with it, as a failed + /// Save's does. + pub fn dumpFailed(p: *Pardes, path: []const u8, err: anyerror) void { + var what: [limits.host_path_cap + 8]u8 = undefined; + const operation = std.fmt.bufPrint(&what, "Dump {s}", .{path}) catch "Dump"; + p.reportError(p.active, operation, err); + var buf: [256]u8 = undefined; + const said = std.fmt.bufPrint(&buf, "{s}: {s}", .{ operation, @errorName(err) }) catch "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); + } + /// The path a watch is about: a real file's, or a PDF's. fn watchPath(p: *const Pardes, id: u8) ?[]const u8 { const pane = p.panes[id] orelse return null; diff --git a/src/tty/tty.zig b/src/tty/tty.zig index 96aec9a9..754b4b96 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -1329,8 +1329,8 @@ const Shell = struct { fn writeDump(ctx: ?*anyopaque, bytes: []const u8) void { const s = of(ctx); var pbuf: [1024:0]u8 = undefined; - const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return; - filesystem.write(s.core, path, bytes) catch |err| return s.core.reportError(0, "dump", err); + const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return s.core.dumpFailed(s.core.settings.dump_dir.get(), error.NoDumpDirectory); + filesystem.write(s.core, path, bytes) catch |err| return s.core.dumpFailed(path, err); s.core.setLastDump(path); } |
