summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/builtins.zig13
-rw-r--r--src/detached/server.zig4
-rw-r--r--src/dump.zig9
-rw-r--r--src/gui/gui.zig4
-rw-r--r--src/macos.zig4
-rw-r--r--src/ninep/ctl.zig27
-rw-r--r--src/pardes.zig14
-rw-r--r--src/tty/tty.zig4
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);
}