diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 15:36:47 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | b9eefda75ab9a63fc510de781d02b92fc04ad31d (patch) | |
| tree | 10ad6accba3a767a37fcd334d6316a2fc3f53442 /src | |
| parent | 12c257e624d3f1f80ca3df041b54026ff772d638 (diff) | |
| parent | 31b7bcbae734cd1d0a432ff5acaf9c65bcd33897 (diff) | |
| download | pardes-b9eefda75ab9a63fc510de781d02b92fc04ad31d.tar.gz pardes-b9eefda75ab9a63fc510de781d02b92fc04ad31d.zip | |
Release Pardes 0.8
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/9p_io.zig | 29 | ||||
| -rw-r--r-- | src/CHANGELOG.md | 5 | ||||
| -rw-r--r-- | src/host_io.zig | 4 | ||||
| -rw-r--r-- | src/main.zig | 21 |
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| { |
