1bacdb57
refactor: one owner for the probe-or-spawn wrapper
a73x 2026-08-29 10:01
Commit message
src/cli/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -1165,28 +1165,11 @@ fn askEndpointPort(alloc: std.mem.Allocator, sock_path: []const u8) u16 { | |||
| 1165 | /// bind address, a missing key file) surfaces in the daemon's log, which | 1165 | /// bind address, a missing key file) surfaces in the daemon's log, which |
| 1166 | /// the failure path names. | 1166 | /// the failure path names. |
| 1167 | fn startCmd(alloc: std.mem.Allocator, sock_path: []const u8, forwarded: []const [:0]const u8) !u8 { | 1167 | fn startCmd(alloc: std.mem.Allocator, sock_path: []const u8, forwarded: []const [:0]const u8) !u8 { |
| 1168 | const progress: spawn.Progress = .{ | ||
| 1169 | .fd = std.posix.STDERR_FILENO, | ||
| 1170 | .prefix = "mux d", | ||
| 1171 | .tty = std.posix.isatty(std.posix.STDERR_FILENO), | ||
| 1172 | }; | ||
| 1173 | // Default log path: the xdg one is the whole point for a real daemon. | 1168 | // Default log path: the xdg one is the whole point for a real daemon. |
| 1174 | // Truncating, and this is the only caller that does: `start` is the one | 1169 | // Truncating, and this is the only caller that does: `start` is the one |
| 1175 | // verb whose user asked for a (re)start, so the log they go on to read | 1170 | // verb whose user asked for a (re)start, so the log they go on to read |
| 1176 | // must be about the daemon they just started. | 1171 | // must be about the daemon they just started. |
| 1177 | var exe_buf: [std.fs.max_path_bytes]u8 = undefined; | 1172 | const r = spawn.ensure(alloc, forwarded, sock_path, "mux d", .{ .truncate = true }) orelse return 1; |
| 1178 | const exe = spawn.selfExe(&exe_buf); | ||
| 1179 | const r = spawn.ensureDaemon(alloc, exe, forwarded, sock_path, progress, spawn.start_deadline_ms, .{ | ||
| 1180 | .truncate = true, | ||
| 1181 | }) catch |err| switch (err) { | ||
| 1182 | // The failure line, with the log path, was already printed by | ||
| 1183 | // Progress — a second line here would say the same thing worse. | ||
| 1184 | error.NeverAnswered => return 1, | ||
| 1185 | error.SpawnFailed => { | ||
| 1186 | std.debug.print("mux d: could not spawn {s}: {s}\n", .{ exe, @errorName(err) }); | ||
| 1187 | return 1; | ||
| 1188 | }, | ||
| 1189 | }; | ||
| 1190 | if (r == .already_running) { | 1173 | if (r == .already_running) { |
| 1191 | std.debug.print( | 1174 | std.debug.print( |
| 1192 | "mux d: already running on {s} (stop it first with `mux d stop --sock {s}` if you meant different flags)\n", | 1175 | "mux d: already running on {s} (stop it first with `mux d stop --sock {s}` if you meant different flags)\n", |
src/cli/mux_main.zig
| Old | New | ||
|---|---|---|---|
| @@ -365,12 +365,6 @@ pub fn main(args: []const [:0]const u8) !u8 { | |||
| 365 | } | 365 | } |
| 366 | } | 366 | } |
| 367 | 367 | ||
| 368 | /// The ONLY auto-start left, and it starts this image by its own /proc | ||
| 369 | /// link — no name to look up, so none to get wrong. | ||
| 370 | fn ensureLocalDaemon(alloc: std.mem.Allocator, sock_path: []const u8) !bool { | ||
| 371 | return spawn.ensureForAttach(alloc, sock_path, "mux"); | ||
| 372 | } | ||
| 373 | |||
| 374 | /// Whether the wall has to start the local daemon itself. Asked of the OS, | 368 | /// Whether the wall has to start the local daemon itself. Asked of the OS, |
| 375 | /// not of the file: the daemon dies on every reboot while its line lives on. | 369 | /// not of the file: the daemon dies on every reboot while its line lives on. |
| 376 | fn localNeedsStart(h: *const hosts.Hosts, sock: []const u8) bool { | 370 | fn localNeedsStart(h: *const hosts.Hosts, sock: []const u8) bool { |
| @@ -412,7 +406,7 @@ fn attachLocal( | |||
| 412 | // was doomed at parse. | 406 | // was doomed at parse. |
| 413 | if (sockpath.tooLong("mux", sock_path)) return 1; | 407 | if (sockpath.tooLong("mux", sock_path)) return 1; |
| 414 | 408 | ||
| 415 | if (!try ensureLocalDaemon(alloc, sock_path)) return 1; | 409 | if (!try spawn.ensureForAttach(alloc, sock_path, "mux")) return 1; |
| 416 | return wallview.runAttach( | 410 | return wallview.runAttach( |
| 417 | alloc, | 411 | alloc, |
| 418 | .{ .sock = sock_path }, | 412 | .{ .sock = sock_path }, |
| @@ -471,7 +465,7 @@ fn wallOfHosts(alloc: std.mem.Allocator) !u8 { | |||
| 471 | // Failure is not a refusal: the wall still opens and that host's | 465 | // Failure is not a refusal: the wall still opens and that host's |
| 472 | // poller keeps redialling, so a daemon started elsewhere shows up. | 466 | // poller keeps redialling, so a daemon started elsewhere shows up. |
| 473 | if (sockpath.defaultSockPath(arena) catch null) |sock| { | 467 | if (sockpath.defaultSockPath(arena) catch null) |sock| { |
| 474 | if (localNeedsStart(&h, sock)) _ = try ensureLocalDaemon(alloc, sock); | 468 | if (localNeedsStart(&h, sock)) _ = try spawn.ensureForAttach(alloc, sock, "mux"); |
| 475 | } | 469 | } |
| 476 | 470 | ||
| 477 | const key = std.posix.getenv(xdg.key_env); | 471 | const key = std.posix.getenv(xdg.key_env); |
src/cli/spawn.zig
| Old | New | ||
|---|---|---|---|
| @@ -262,47 +262,49 @@ pub fn probe(sock_path: []const u8) bool { | |||
| 262 | return true; | 262 | return true; |
| 263 | } | 263 | } |
| 264 | 264 | ||
| 265 | /// The local entry's auto-start alone, naming no binary: there is one, | 265 | /// Both starters, one wrapper: this image, the one deadline, a failure |
| 266 | /// and it is this. | 266 | /// already narrated — null needs only an exit code. |
| 267 | pub fn ensureForAttach( | 267 | pub fn ensure( |
| 268 | alloc: std.mem.Allocator, | 268 | alloc: std.mem.Allocator, |
| 269 | run_args: []const [:0]const u8, | ||
| 269 | sock_path: []const u8, | 270 | sock_path: []const u8, |
| 270 | prefix: []const u8, | 271 | prefix: []const u8, |
| 271 | ) error{OutOfMemory}!bool { | 272 | log_spec: LogSpec, |
| 272 | const sock_z = try alloc.dupeZ(u8, sock_path); | 273 | ) ?Ensured { |
| 273 | defer alloc.free(sock_z); | ||
| 274 | var exe_buf: [std.fs.max_path_bytes]u8 = undefined; | 274 | var exe_buf: [std.fs.max_path_bytes]u8 = undefined; |
| 275 | const exe = selfExe(&exe_buf); | 275 | const exe = selfExe(&exe_buf); |
| 276 | // Bare, and the reason is the whole of why this is not `start`: a QUIC | ||
| 277 | // listener must be asked for, never appear because someone attached. | ||
| 278 | const run_args = [_][:0]const u8{ "--sock", sock_z }; | ||
| 279 | const progress: Progress = .{ | 276 | const progress: Progress = .{ |
| 280 | .fd = std.posix.STDERR_FILENO, | 277 | .fd = std.posix.STDERR_FILENO, |
| 281 | .prefix = prefix, | 278 | .prefix = prefix, |
| 282 | .tty = std.posix.isatty(std.posix.STDERR_FILENO), | 279 | .tty = std.posix.isatty(std.posix.STDERR_FILENO), |
| 283 | }; | 280 | }; |
| 284 | _ = ensureDaemon( | 281 | return ensureDaemon(alloc, exe, run_args, sock_path, progress, start_deadline_ms, log_spec) catch |err| { |
| 285 | alloc, | 282 | // NeverAnswered's failure line, with the log path, was already |
| 286 | exe, | 283 | // printed by Progress — a second line would say the same thing |
| 287 | &run_args, | 284 | // worse. A spawn that never happened has nothing printed yet, and |
| 288 | sock_path, | 285 | // std.debug.print rather than progress.emitFmt because an exe path |
| 289 | progress, | 286 | // can run to max_path_bytes and emitFmt's fixed buffer would drop |
| 290 | start_deadline_ms, | 287 | // the whole line rather than shorten it. The RESOLVED path, so the |
| 291 | .{ .truncate = false }, | 288 | // line names a file an operator can stat. |
| 292 | ) catch |err| switch (err) { | 289 | if (err == error.SpawnFailed) |
| 293 | // The failure line, with the log path, was already printed by | ||
| 294 | // Progress — a second line would say the same thing worse. | ||
| 295 | error.NeverAnswered => return false, | ||
| 296 | error.SpawnFailed => { | ||
| 297 | // std.debug.print rather than progress.emitFmt: an exe path can | ||
| 298 | // run to max_path_bytes, and emitFmt's fixed buffer would drop | ||
| 299 | // the whole line rather than shorten it. The RESOLVED path, so | ||
| 300 | // the line names a file an operator can stat. | ||
| 301 | std.debug.print("{s}: could not spawn {s}: {s}\n", .{ prefix, exe, @errorName(err) }); | 290 | std.debug.print("{s}: could not spawn {s}: {s}\n", .{ prefix, exe, @errorName(err) }); |
| 302 | return false; | 291 | return null; |
| 303 | }, | ||
| 304 | }; | 292 | }; |
| 305 | return true; | 293 | } |
| 294 | |||
| 295 | /// The local entry's auto-start alone, naming no binary: there is one, | ||
| 296 | /// and it is this. | ||
| 297 | pub fn ensureForAttach( | ||
| 298 | alloc: std.mem.Allocator, | ||
| 299 | sock_path: []const u8, | ||
| 300 | prefix: []const u8, | ||
| 301 | ) error{OutOfMemory}!bool { | ||
| 302 | const sock_z = try alloc.dupeZ(u8, sock_path); | ||
| 303 | defer alloc.free(sock_z); | ||
| 304 | // Bare, and the reason is the whole of why this is not `start`: a QUIC | ||
| 305 | // listener must be asked for, never appear because someone attached. | ||
| 306 | const run_args = [_][:0]const u8{ "--sock", sock_z }; | ||
| 307 | return ensure(alloc, &run_args, sock_path, prefix, .{ .truncate = false }) != null; | ||
| 306 | } | 308 | } |
| 307 | 309 | ||
| 308 | // --------------------------------------------------------------------------- | 310 | // --------------------------------------------------------------------------- |