summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/9p_io.zig29
-rw-r--r--src/CHANGELOG.md5
-rw-r--r--src/host_io.zig4
-rw-r--r--src/main.zig21
4 files changed, 51 insertions, 8 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 0eaa2d7e..e475416e 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -874,7 +874,29 @@ pub fn listen(io: std.Io, gpa: std.mem.Allocator, core: *pardes.Pardes, named: [
.greet_timeout_ms = Listener.greet_deadline_ms,
});
const p = socketPath(&l.path_buf, dir, entry_name).?;
- _ = l.runner.listen(.{ .unix = p }, max_conns) catch |err| retry: {
+ // Listened on under a name of its own, then renamed into place: a client
+ // that waits for the socket to appear finds it listening already. Bound
+ // at its own name, it was there a moment before listen(2), and a connect
+ // then was refused. (No room for the longer name: bound in place.)
+ var tmp_buf: [sun_path_len]u8 = undefined;
+ if (std.fmt.bufPrintSentinel(&tmp_buf, "{s}.{d}", .{ p, libc.getpid() }, 0) catch null) |tmp| {
+ const existing = @import("fs.zig").statPath(io, p, .{ .follow_symlinks = false }) catch null;
+ if (existing != null and (existing.?.kind != .unix_domain_socket or alive(p))) {
+ log.warn("something is already listening on {s}", .{p});
+ l.deinit(gpa);
+ return null;
+ }
+ _ = libc.unlink(tmp);
+ _ = l.runner.listen(.{ .unix = tmp }, max_conns) catch {
+ l.deinit(gpa);
+ return null;
+ };
+ if (libc.chmod(tmp, 0o600) != 0 or libc.rename(tmp, p) != 0) {
+ _ = libc.unlink(tmp);
+ l.deinit(gpa);
+ return null;
+ }
+ } else _ = l.runner.listen(.{ .unix = p }, max_conns) catch |err| retry: {
const existing = @import("fs.zig").statPath(io, p, .{ .follow_symlinks = false }) catch null;
if (err != error.AddressInUse or existing == null or existing.?.kind != .unix_domain_socket or alive(p)) {
log.warn("something is already listening on {s}", .{p});
@@ -1338,6 +1360,11 @@ test "a listening editor posts itself into the 9P registry and unposts on stop"
// The socket stays exactly where pardes has always bound it:
// adopting the registry moves nothing, it only advertises.
try testing.expect(statNoFollow(sock) != null);
+ // It appeared already listening, renamed into place from a name of
+ // its own, which is gone.
+ try testing.expect(alive(sock));
+ var tmp_buf: [sun_path_len:0]u8 = undefined;
+ try testing.expect(statNoFollow(try std.fmt.bufPrintSentinel(&tmp_buf, "{s}.{d}", .{ sock, libc.getpid() }, 0)) == null);
// And the registry holds a symlink to it one directory down, so
// several editors group under /mnt/9p/pardes/ instead of
// crowding the registry root — the layout zmx posts its
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index baff96b0..25740d16 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -2,6 +2,11 @@
## 0.0.8
+- `~` and `~/` mean the home directory wherever a path is taken: look
+ (`~/x:12`, `~/x:/re/`), `name`, `Save`, `ThemeFile`, DumpDir, Restore.
+- `pardes --wait` on a new file waits on the pane it made, however fast
+ that pane is saved and closed. A session's socket appears only once it is
+ listening, so an early client is never refused.
- A wheel over a terminal whose program asked for the mouse goes to the
program, as a report in the format it chose, or as arrow keys on the
alternate screen with alternate scroll: codex, htop, less and vim scroll.
diff --git a/src/host_io.zig b/src/host_io.zig
index c8c537b9..8cbdd2d4 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -1605,6 +1605,10 @@ pub fn ttyTaken(shell_pid: libc.pid_t, master_fd: c_int) bool {
const exe = procExe(node.pid, &probe.exe) orelse {
if (offTty(node.pid, &probe.blob)) continue;
+ // Exiting (its exe gone, not a zombie yet), or not ours
+ // to read: outside the foreground group it holds
+ // nothing, as a killed background job does for a moment.
+ if (pgrp) |g| if (g != fg) continue;
return true;
};
if (!std.mem.eql(u8, exe, self_exe)) {
diff --git a/src/main.zig b/src/main.zig
index dfeed52d..101a5be3 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -102,13 +102,18 @@ const help_text =
/// pane `path` landed in (the one it already had, if open), then /log
/// followed on one connection until that pane's `del` -- exit 0 -- or the
/// connection ends with the session -- exit 1.
-fn waitForDel(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, path: []const u8) noreturn {
- const first = ninep_io.Client.read(gpa, dial, "/index", "/index") catch std.process.exit(1);
- const serial = paneShowing(first, path) orelse {
- std.Io.File.stderr().writeStreamingAll(io, "pardes: --wait: no pane shows that file\n") catch {};
- std.process.exit(1);
+fn waitForDel(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, path: []const u8, known: ?u32) noreturn {
+ _ = io;
+ // The pane this launch made (a name not there yet) is known by its
+ // serial; one a look opened or went to is found by its file. Found by
+ // none after the look went there, it was closed already: that is the
+ // end being waited for, not a failure (a pane closed between the look
+ // and this read made `--wait` exit 1 before).
+ const serial = known orelse found: {
+ const first = ninep_io.Client.read(gpa, dial, "/index", "/index") catch std.process.exit(1);
+ defer gpa.free(first);
+ break :found paneShowing(first, path) orelse std.process.exit(0);
};
- gpa.free(first);
const Wait = struct {
gpa: std.mem.Allocator,
dial: []const u8,
@@ -439,6 +444,7 @@ fn nativeMain(init: std.process.Init) !void {
};
break :named std.fmt.bufPrint(&newbuf, "{s}/{s}", .{ std.mem.trimEnd(u8, dir.path, "/"), base }) catch |err| Refuse.with(init.io, word, err);
};
+ var made_serial: ?u32 = null;
if (found_path != null) {
var command_buf: [8192]u8 = undefined;
const command = std.fmt.bufPrint(&command_buf, "{s}{s}\n", .{ path, word[target.path.len..] }) catch |err| Refuse.with(init.io, word, err);
@@ -446,6 +452,7 @@ fn nativeMain(init: std.process.Init) !void {
} else {
const made = ninep_io.Client.read(arena, parent.dial, "/pane/new", "/pane/new") catch |err| Refuse.with(init.io, word, err);
const serial = std.fmt.parseInt(u32, std.mem.trim(u8, made, " \n"), 10) catch |err| Refuse.with(init.io, word, err);
+ made_serial = serial;
var name_buf: [64]u8 = undefined;
const name = try std.fmt.bufPrint(&name_buf, "/pane/{d}/name", .{serial});
var line_buf: [4200]u8 = undefined;
@@ -469,7 +476,7 @@ fn nativeMain(init: std.process.Init) !void {
Refuse.with(init.io, word, err);
};
}
- if (wait) waitForDel(init.io, arena, parent.dial, path);
+ if (wait) waitForDel(init.io, arena, parent.dial, path, made_serial);
return;
}
if (positional) |a| {