summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/9p_io.zig8
-rw-r--r--src/detached/server.zig2
-rw-r--r--src/fs.zig5
-rw-r--r--src/gui/gui.zig2
-rw-r--r--src/macos.zig2
-rw-r--r--src/pardes.zig16
-rw-r--r--src/tty/tty.zig2
7 files changed, 29 insertions, 8 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 2ca11a87..6bd03049 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -296,7 +296,15 @@ pub const Listener = struct {
// Set with the turn still held, so `reset` sees it and waits.
if (restoring) l.restore_writer.store(true, .release);
defer if (restoring) l.restore_writer.store(false, .release);
+ core.fs.late_failure_len = 0;
if (core.effects_len != 0) pardes.turn.awaitSettled(epoch);
+ // ponytail: one slot, so a failure of another client's effects that
+ // 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]);
+ pardes.ctlfs.events.noteError(core, req, failed);
+ return conn.reply(&failed, "");
+ }
// The Restore's own write is answered once it is done, and before
// `reset` hangs every connection up, this one too, so the writer
// hears that it happened rather than a cut; a failed one leaves the
diff --git a/src/detached/server.zig b/src/detached/server.zig
index 5374bf73..3921d8d9 100644
--- a/src/detached/server.zig
+++ b/src/detached/server.zig
@@ -555,7 +555,7 @@ pub const Session = struct {
fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) void {
const s = of(ctx);
filesystem.write(s.core, path, bytes) catch |err|
- return s.core.saveFailed(pane, "save", err);
+ return s.core.saveFailed(pane, path, err);
if (s.core.panes[pane]) |pn| if (pn.file) |f| if (std.mem.eql(u8, f.path, path)) {
if (s.watches[pane]) |*w| if (w.serial == pn.serial) switch (w.generation) {
.text => w.generation = .{ .text = std.hash.Wyhash.hash(0, bytes) },
diff --git a/src/fs.zig b/src/fs.zig
index b2e2751d..2dc3394b 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -1263,6 +1263,11 @@ pub const Namespace = struct {
refused: bool = false,
failure: [256]u8 = undefined,
failure_len: u16 = 0,
+ /// What went wrong performing a write's effects after it was answered
+ /// by the core (a save the host could not do): the 9P write, which
+ /// waits for them, fails with it (src/9p_io.zig).
+ late_failure: [256]u8 = undefined,
+ late_failure_len: u16 = 0,
/// A refusal that quotes the message it refuses, as Plan 9's cmderror
/// does (kernel/misc/parse.c:82); answered at once (src/9p_io.zig).
ename: [320]u8 = undefined,
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 5bc1ea6d..84ec1c5c 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -4422,7 +4422,7 @@ fn killJob(ctx: ?*anyopaque, pane: u8) bool {
fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) void {
const s = shellOf(ctx);
filesystem.write(s.core, path, bytes) catch |err|
- return s.core.saveFailed(pane, "save", err);
+ return s.core.saveFailed(pane, path, err);
if (s.core.panes[pane]) |pane_state| if (pane_state.file) |f| {
if (std.mem.eql(u8, f.path, path))
if (s.watches[pane]) |*w| if (w.serial == pane_state.serial) switch (w.generation) {
diff --git a/src/macos.zig b/src/macos.zig
index b472e6cc..b180d7ba 100644
--- a/src/macos.zig
+++ b/src/macos.zig
@@ -2487,7 +2487,7 @@ fn killJob(ctx: ?*anyopaque, pane: u8) bool {
fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) void {
const st = hostState(ctx);
filesystem.write(st.core, path, bytes) catch |err|
- return st.core.saveFailed(pane, "save", err);
+ return st.core.saveFailed(pane, path, err);
st.file_watches.restampText(pane, path, std.hash.Wyhash.hash(0, bytes));
var mbuf: [256]u8 = undefined;
st.core.setMessage(pane, message.stamp(&mbuf, "saved", path));
diff --git a/src/pardes.zig b/src/pardes.zig
index f559b125..581f616e 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -2261,7 +2261,7 @@ test "a save the host could not do leaves the pane dirty" {
const Refusing = struct {
fn writeFile(ctx: ?*anyopaque, pane: u8, _: []const u8, _: []const u8) void {
const core: *Pardes = @ptrCast(@alignCast(ctx.?));
- core.saveFailed(pane, "save", error.PermissionDenied);
+ core.saveFailed(pane, "/x/y.txt", error.PermissionDenied);
}
};
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 100, .rows = 30 });
@@ -4906,15 +4906,23 @@ pub const Pardes = struct {
if (p.host.vtable.write_file) |f| return f(p.host.ctx, pane, path, bytes);
// The in-process filesystem reports the same way a real host does, so
// an OOM here leaves the pane dirty rather than looking saved.
- if (!p.fallback.writeFile(path, bytes)) p.saveFailed(pane, "save", error.OutOfMemory);
+ if (!p.fallback.writeFile(path, bytes)) p.saveFailed(pane, path, error.OutOfMemory);
}
- pub fn saveFailed(p: *Pardes, id: u8, what: []const u8, err: anyerror) void {
+ /// A write of `path` the host could not do: the pane stays dirty, the
+ /// message row says `Save <path>: <why>`, and the 9P write that asked
+ /// for it, waiting on the save, fails with that (`fs.late_failure`).
+ pub fn saveFailed(p: *Pardes, id: u8, path: []const u8, err: anyerror) void {
if (p.panes[id]) |pane| if (pane.file) |*f| {
// The `-%` spelling fs.zig already uses for "make this dirty".
f.saved_revision = f.revision -% 1;
};
- p.reportError(id, what, err);
+ var what: [limits.host_path_cap + 8]u8 = undefined;
+ 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);
}
/// The path a watch is about: a real file's, or a PDF's.
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index 822c2d7f..d17316e8 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -1314,7 +1314,7 @@ const Shell = struct {
fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) void {
const s = of(ctx);
filesystem.write(s.core, path, bytes) catch |err|
- return s.core.saveFailed(pane, "save", err);
+ return s.core.saveFailed(pane, path, err);
if (s.core.panes[pane]) |pn| if (pn.file) |f| if (std.mem.eql(u8, f.path, path)) {
if (s.watches[pane]) |*w| if (w.serial == pn.serial) switch (w.generation) {
.text => w.generation = .{ .text = std.hash.Wyhash.hash(0, bytes) },