summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/9p_io.zig3
-rw-r--r--src/ninep/events.zig10
-rw-r--r--src/pardes.zig31
3 files changed, 42 insertions, 2 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 72963e08..bda1ad36 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -316,6 +316,9 @@ pub const Listener = struct {
// settle in the same wait is told to this write too.
if (l.core == core and core.fs.late_failure_len != 0) {
const failed = pardes.ctlfs.failText(req.tag, pardes.ctlfs.E.IO, core.fs.late_failure[0..core.fs.late_failure_len]);
+ // Its err record says it (the path in it); the msg the failure
+ // was also said as goes, as a builtin's failing a write does.
+ pardes.ctlfs.events.dropMessage(core, core.fs.late_failure[0..core.fs.late_failure_len]);
pardes.ctlfs.events.noteError(core, req, failed);
return conn.reply(&failed, "");
}
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 7a06bd04..d2beecd1 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -226,6 +226,16 @@ pub fn dropAnnouncement(p: *Pardes, serial: u32, text: []const u8) void {
p.fs.log.buf.shrinkRetainingCapacity(last.at);
}
+/// Drops the newest record when it is a msg saying `text` (of any pane)
+/// that no follower has read: a failure its write's err record will say.
+pub fn dropMessage(p: *Pardes, text: []const u8) void {
+ const last = newest(p) orelse return;
+ if (followerRead(p, last.seq) or !std.mem.startsWith(u8, last.text, "msg ")) return;
+ const rest = last.text["msg ".len..];
+ const sp = std.mem.indexOfScalar(u8, rest, ' ') orelse return;
+ if (std.mem.eql(u8, rest[sp + 1 ..], text)) p.fs.log.buf.shrinkRetainingCapacity(last.at);
+}
+
/// Pushes `record`, or, when it repeats the newest record word for word,
/// counts that one instead: `<record> (x2)`, the count being every time it
/// was said. One a follower has already read is not rewritten: the repeat
diff --git a/src/pardes.zig b/src/pardes.zig
index 5d709679..b488ec1c 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5239,9 +5239,12 @@ pub const Pardes = struct {
p.hostWriteFile(sf.pane, f.path, f.content);
const saved_pane = p.panes[sf.pane] orelse return;
if (saved_pane.serial != serial) return;
- ctlfs.events.noteLog(p, .save, saved_pane);
const saved = if (saved_pane.file) |*file| file else return;
- if (saved.saved_revision != saved.revision or !saved.watch_after_save) return;
+ // A save the host could not do (saveFailed) is its err
+ // record alone, not a `save`.
+ if (saved.saved_revision != saved.revision) return;
+ ctlfs.events.noteLog(p, .save, saved_pane);
+ if (!saved.watch_after_save) return;
saved.watch_after_save = false;
if (filesystem.localPath(saved.path) != null)
p.emit(.{ .watch = .{ .pane = sf.pane, .on = true } });
@@ -7983,3 +7986,27 @@ test "a pane closed while its question stands ends the question in the log" {
var want: [64]u8 = undefined;
try std.testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "answer {d} -\ndel {d}", .{ serial, serial })));
}
+
+test "a Save the host could not do logs no save record" {
+ const th = @import("ninep/testing.zig");
+ const p = try th.withFile(std.testing.allocator, "x\n");
+ defer p.deinit();
+ const Failing = struct {
+ core: *Pardes,
+ const vtable: Host.VTable = .{ .write_file = write };
+ fn write(ctx: ?*anyopaque, pane: u8, path: []const u8, _: []const u8) void {
+ const f: *@This() = @ptrCast(@alignCast(ctx.?));
+ f.core.saveFailed(pane, path, error.AccessDenied);
+ }
+ };
+ var host: Failing = .{ .core = p };
+ p.host = .{ .ctx = &host, .vtable = &Failing.vtable };
+ _ = th.wr(p, ctlfs.Node.of(p.panes[0].?.serial, .body), "more\n");
+ p.perform(.{ .save_file = .{ .pane = 0 } });
+ try std.testing.expect(!th.logHas(p, "\nsave "));
+ try std.testing.expect(p.panes[0].?.file.?.saved_revision != p.panes[0].?.file.?.revision);
+ // With the host's write done, it is logged.
+ p.host = .{};
+ p.perform(.{ .save_file = .{ .pane = 0 } });
+ try std.testing.expect(th.logHas(p, "\nsave "));
+}