502e12d7
refactor: the daemon's foreground is `start`, and `-d` is what forks it
a73x 2026-08-30 07:39
Commit message
README.md
| Old | New | ||
|---|---|---|---|
| @@ -38,7 +38,7 @@ ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < dist/mux-vVERSIO | |||
| 38 | ## Quick start, local | 38 | ## Quick start, local |
| 39 | 39 | ||
| 40 | ```sh | 40 | ```sh |
| 41 | mux d run & # the daemon: hosts your shell and the authoritative screen | 41 | mux d start & # the daemon: hosts your shell and the authoritative screen |
| 42 | mux # attach | 42 | mux # attach |
| 43 | ``` | 43 | ``` |
| 44 | 44 | ||
| @@ -182,18 +182,18 @@ Works anywhere ssh works. On the remote host: put `mux` on PATH (`make | |||
| 182 | install` puts it in `~/.local/bin`), then: | 182 | install` puts it in `~/.local/bin`), then: |
| 183 | 183 | ||
| 184 | ```sh | 184 | ```sh |
| 185 | ssh HOST 'mux d start' | 185 | ssh HOST 'mux d start -d' |
| 186 | mux HOST # attach; Ctrl-\ d detaches, running it again reattaches | 186 | mux HOST # attach; Ctrl-\ d detaches, running it again reattaches |
| 187 | ``` | 187 | ``` |
| 188 | 188 | ||
| 189 | `mux d start` spawns the daemon detached and waits until it answers, so it | 189 | `mux d start -d` spawns the daemon detached and waits until it answers, so it |
| 190 | is safe to run every time — if one is already up it says so and exits 0. | 190 | is safe to run every time — if one is already up it says so and exits 0. |
| 191 | 191 | ||
| 192 | `mux HOST` runs `mux d endpoint --start` on HOST over ssh, ONE run | 192 | `mux HOST` runs `mux d endpoint --start` on HOST over ssh, ONE run |
| 193 | (finding a `mux` in `~/.local/bin` even though ssh's non-login shell leaves | 193 | (finding a `mux` in `~/.local/bin` even though ssh's non-login shell leaves |
| 194 | it off PATH). That verb ensures a daemon and then announces the | 194 | it off PATH). That verb ensures a daemon and then announces the |
| 195 | coordinates on the same stdout, so a cold attach costs one ssh login and | 195 | coordinates on the same stdout, so a cold attach costs one ssh login and |
| 196 | the `ssh HOST 'mux d start'` line above is optional after the first time. | 196 | the `ssh HOST 'mux d start -d'` line above is optional after the first time. |
| 197 | Nothing else starts a daemon, because nothing else spells the flag: the | 197 | Nothing else starts a daemon, because nothing else spells the flag: the |
| 198 | wall's per-host poll, `mux hosts`, every reconnect and `mux --via 'ssh HOST | 198 | wall's per-host poll, `mux hosts`, every reconnect and `mux --via 'ssh HOST |
| 199 | mux d proxy'` run the bare `mux d endpoint`, report the box unreachable and | 199 | mux d proxy'` run the bare `mux d endpoint`, report the box unreachable and |
| @@ -287,7 +287,7 @@ Put `mux` on the host (`scp` a static binary), then: | |||
| 287 | mux d keygen # once | 287 | mux d keygen # once |
| 288 | ssh HOST 'mkdir -p -m 700 ~/.config/mux && cat > ~/.config/mux/key \ | 288 | ssh HOST 'mkdir -p -m 700 ~/.config/mux && cat > ~/.config/mux/key \ |
| 289 | && chmod 600 ~/.config/mux/key' < ~/.config/mux/key # once per host | 289 | && chmod 600 ~/.config/mux/key' < ~/.config/mux/key # once per host |
| 290 | ssh HOST 'mux d start --quic 0.0.0.0' # once per host boot | 290 | ssh HOST 'mux d start -d --quic 0.0.0.0' # once per host boot |
| 291 | mux quic://HOST # every attach | 291 | mux quic://HOST # every attach |
| 292 | ``` | 292 | ``` |
| 293 | 293 | ||
| @@ -532,7 +532,7 @@ recently active client — typing, attaching, or resizing claims it (latest | |||
| 532 | wins). A session survives logout (this assumes systemd-logind's default | 532 | wins). A session survives logout (this assumes systemd-logind's default |
| 533 | `KillUserProcesses=no`; a box configured to kill user processes at logout | 533 | `KillUserProcesses=no`; a box configured to kill user processes at logout |
| 534 | kills the daemon with them), though not a reboot. | 534 | kills the daemon with them), though not a reboot. |
| 535 | `mux d run` refuses a socket another daemon already owns; there is no | 535 | `mux d start` refuses a socket another daemon already owns; there is no |
| 536 | socket-stealing. It runs until something stops it — no session's exit ends | 536 | socket-stealing. It runs until something stops it — no session's exit ends |
| 537 | it — and exits 0 when `mux d stop` or a supervisor's SIGTERM asks; a nonzero | 537 | it — and exits 0 when `mux d stop` or a supervisor's SIGTERM asks; a nonzero |
| 538 | exit means it never got as far as serving. | 538 | exit means it never got as far as serving. |
| @@ -556,7 +556,7 @@ ending, retry` rather than carrying a shell with no master across the exec. | |||
| 556 | 556 | ||
| 557 | Coming from v0.0.1-15 or older is the one upgrade `mux d upgrade` cannot | 557 | Coming from v0.0.1-15 or older is the one upgrade `mux d upgrade` cannot |
| 558 | do: that daemon probes a candidate for `muxd <version>` and this binary | 558 | do: that daemon probes a candidate for `muxd <version>` and this binary |
| 559 | prints `mux <version>`, so it refuses. `mux d stop` then `mux d start`, | 559 | prints `mux <version>`, so it refuses. `mux d stop` then `mux d start -d`, |
| 560 | once per box. `make install` deletes `muxd`, `muxa` and `muxweb` from the | 560 | once per box. `make install` deletes `muxd`, `muxa` and `muxweb` from the |
| 561 | install directory on its way in — they are modes of `mux` now, and a | 561 | install directory on its way in — they are modes of `mux` now, and a |
| 562 | `muxd` left on PATH is a second daemon waiting for someone to start it. | 562 | `muxd` left on PATH is a second daemon waiting for someone to start it. |
src/cli/flags.zig
| Old | New | ||
|---|---|---|---|
| @@ -407,8 +407,8 @@ test "flagName: underscores become dashes" { | |||
| 407 | } | 407 | } |
| 408 | 408 | ||
| 409 | test "documented: a prefix of a longer flag does not count" { | 409 | test "documented: a prefix of a longer flag does not count" { |
| 410 | try std.testing.expect(!documented("--sock", " mux d run [--socket PATH]\n")); | 410 | try std.testing.expect(!documented("--sock", " mux d start [--socket PATH]\n")); |
| 411 | try std.testing.expect(documented("--sock", " mux d run [--sock PATH]\n")); | 411 | try std.testing.expect(documented("--sock", " mux d start [--sock PATH]\n")); |
| 412 | try std.testing.expect(documented("--vt", " mux d dump [--vt]\n")); | 412 | try std.testing.expect(documented("--vt", " mux d dump [--vt]\n")); |
| 413 | try std.testing.expect(documented("--vt", " mux d dump --vt\n")); | 413 | try std.testing.expect(documented("--vt", " mux d dump --vt\n")); |
| 414 | try std.testing.expect(documented("--vt", " mux d dump --vt")); | 414 | try std.testing.expect(documented("--vt", " mux d dump --vt")); |
src/cli/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,4 +1,4 @@ | |||
| 1 | //! `mux d` — the daemon mode. `run` hosts the session; `dump` prints the | 1 | //! `mux d` — the daemon mode. `start` hosts the session; `dump` prints the |
| 2 | //! authoritative grid over the protocol (debug aid, also used by e2e); | 2 | //! authoritative grid over the protocol (debug aid, also used by e2e); |
| 3 | //! `proxy` exposes the session socket over stdio for `mux --via`. | 3 | //! `proxy` exposes the session socket over stdio for `mux --via`. |
| 4 | // folder rule 5 exemption: the daemon's whole job is to spawn the user's | 4 | // folder rule 5 exemption: the daemon's whole job is to spawn the user's |
| @@ -20,15 +20,15 @@ const cliflags = @import("cliflags"); | |||
| 20 | 20 | ||
| 21 | const usage = | 21 | const usage = |
| 22 | \\usage: | 22 | \\usage: |
| 23 | \\ mux d run [--sock PATH] [--shell PATH] [--cols N] [--rows N] | 23 | \\ mux d start [-d] [--sock PATH] [--shell PATH] [--cols N] [--rows N] |
| 24 | \\ [--quic HOST[:PORT] --key FILE] [--quic-idle-ms N] | 24 | \\ [--quic HOST[:PORT] --key FILE] [--quic-idle-ms N] |
| 25 | \\ (-d forks it off and waits; no-op if one is up) | ||
| 25 | \\ mux d dump [--vt] [--session NAME] [--sock PATH] | 26 | \\ mux d dump [--vt] [--session NAME] [--sock PATH] |
| 26 | \\ mux d stats [--sock PATH] | 27 | \\ mux d stats [--sock PATH] |
| 27 | \\ mux d stop [--sock PATH] (ask the daemon on PATH to exit) | 28 | \\ mux d stop [--sock PATH] (ask the daemon on PATH to exit) |
| 28 | \\ mux d proxy [--sock PATH] (byte pump: stdio <-> session socket) | 29 | \\ mux d proxy [--sock PATH] (byte pump: stdio <-> session socket) |
| 29 | \\ mux d endpoint [--sock PATH] [--start] (announce QUIC port+key, then proxy) | 30 | \\ mux d endpoint [--sock PATH] [--start] (announce QUIC port+key, then proxy) |
| 30 | \\ mux d keygen (write a fresh key to ~/.config/mux/key) | 31 | \\ mux d keygen (write a fresh key to ~/.config/mux/key) |
| 31 | \\ mux d start [run's flags] (spawn a daemon detached; no-op if one is up) | ||
| 32 | \\ mux d upgrade [--sock PATH] (exec THIS binary over the daemon; sessions live) | 32 | \\ mux d upgrade [--sock PATH] (exec THIS binary over the daemon; sessions live) |
| 33 | \\ [--allow-same-version] (strictly newer, unless this; the e2e leg's) | 33 | \\ [--allow-same-version] (strictly newer, unless this; the e2e leg's) |
| 34 | \\ mux d --version | 34 | \\ mux d --version |
| @@ -36,7 +36,7 @@ const usage = | |||
| 36 | \\ | 36 | \\ |
| 37 | ; | 37 | ; |
| 38 | 38 | ||
| 39 | const Cmd = enum { run, dump, stats, proxy, endpoint, version, help, keygen, start, stop, upgrade }; | 39 | const Cmd = enum { dump, stats, proxy, endpoint, version, help, keygen, start, stop, upgrade }; |
| 40 | 40 | ||
| 41 | /// One row per verb. Adding a subcommand used to mean editing the usage | 41 | /// One row per verb. Adding a subcommand used to mean editing the usage |
| 42 | /// literal, the Cmd enum, a name→Cmd if/else chain, keygen's hand-rolled | 42 | /// literal, the Cmd enum, a name→Cmd if/else chain, keygen's hand-rolled |
| @@ -74,7 +74,7 @@ const specs = [_]Spec{ | |||
| 74 | // Spelled as a flag for the same reason, and `.ignored` for a second: | 74 | // Spelled as a flag for the same reason, and `.ignored` for a second: |
| 75 | // asking for the usage must never be refused over the words next to it. | 75 | // asking for the usage must never be refused over the words next to it. |
| 76 | .{ .name = "--help", .cmd = .help, .uses_socket = false, .flags = .ignored }, | 76 | .{ .name = "--help", .cmd = .help, .uses_socket = false, .flags = .ignored }, |
| 77 | .{ .name = "run", .cmd = .run, .uses_socket = true, .flags = .all }, | 77 | .{ .name = "start", .cmd = .start, .uses_socket = true, .flags = .all }, |
| 78 | .{ .name = "dump", .cmd = .dump, .uses_socket = true, .flags = .all }, | 78 | .{ .name = "dump", .cmd = .dump, .uses_socket = true, .flags = .all }, |
| 79 | .{ .name = "stats", .cmd = .stats, .uses_socket = true, .flags = .all }, | 79 | .{ .name = "stats", .cmd = .stats, .uses_socket = true, .flags = .all }, |
| 80 | .{ .name = "proxy", .cmd = .proxy, .uses_socket = true, .flags = .all }, | 80 | .{ .name = "proxy", .cmd = .proxy, .uses_socket = true, .flags = .all }, |
| @@ -82,7 +82,6 @@ const specs = [_]Spec{ | |||
| 82 | // keygen configures nothing: its one output is the default path, and a | 82 | // keygen configures nothing: its one output is the default path, and a |
| 83 | // flag here would be a request this command cannot honor. | 83 | // flag here would be a request this command cannot honor. |
| 84 | .{ .name = "keygen", .cmd = .keygen, .uses_socket = false, .flags = .none }, | 84 | .{ .name = "keygen", .cmd = .keygen, .uses_socket = false, .flags = .none }, |
| 85 | .{ .name = "start", .cmd = .start, .uses_socket = true, .flags = .all }, | ||
| 86 | .{ .name = "stop", .cmd = .stop, .uses_socket = true, .flags = .all }, | 85 | .{ .name = "stop", .cmd = .stop, .uses_socket = true, .flags = .all }, |
| 87 | .{ .name = "upgrade", .cmd = .upgrade, .uses_socket = true, .flags = .all }, | 86 | .{ .name = "upgrade", .cmd = .upgrade, .uses_socket = true, .flags = .all }, |
| 88 | }; | 87 | }; |
| @@ -133,7 +132,7 @@ const Opts = struct { | |||
| 133 | vt: bool = false, | 132 | vt: bool = false, |
| 134 | quic: ?[]const u8 = null, | 133 | quic: ?[]const u8 = null, |
| 135 | /// Null does NOT mean "no key": it means the command line named none, | 134 | /// Null does NOT mean "no key": it means the command line named none, |
| 136 | /// and `run` still has MUX_KEY_FILE and the default path to try. Only | 135 | /// and `start` still has MUX_KEY_FILE and the default path to try. Only |
| 137 | /// `--key` without `--quic` is settled here, because that one has no | 136 | /// `--key` without `--quic` is settled here, because that one has no |
| 138 | /// reading that makes it sensible. | 137 | /// reading that makes it sensible. |
| 139 | key: ?[]const u8 = null, | 138 | key: ?[]const u8 = null, |
| @@ -143,9 +142,13 @@ const Opts = struct { | |||
| 143 | /// is offered to the type: `""` is the wire's own default spelling — no | 142 | /// is offered to the type: `""` is the wire's own default spelling — no |
| 144 | /// tail at all — and would fail a rule written for a name a user typed. | 143 | /// tail at all — and would fail a rule written for a name a user typed. |
| 145 | session: ?proto.SessionName = null, | 144 | session: ?proto.SessionName = null, |
| 145 | /// `start`'s alone: fork, hand the child the same line without this | ||
| 146 | /// flag, and wait until the socket answers. Off is the foreground | ||
| 147 | /// daemon — the process that binds is the one that was typed. | ||
| 148 | detach: bool = false, | ||
| 146 | /// The inherited manifest descriptor an upgrade exec'd us with. Not a | 149 | /// The inherited manifest descriptor an upgrade exec'd us with. Not a |
| 147 | /// user flag: the old daemon writes it into our argv. Its presence is | 150 | /// user flag: the old daemon writes it into our argv. Its presence is |
| 148 | /// what makes `run` an ADOPTION rather than a start, and it is also | 151 | /// what makes `start` an ADOPTION rather than a start, and it is also |
| 149 | /// what excuses this process from resolving a socket path — the | 152 | /// what excuses this process from resolving a socket path — the |
| 150 | /// manifest names the socket the inherited listener is already bound to. | 153 | /// manifest names the socket the inherited listener is already bound to. |
| 151 | resume_fd: ?std.posix.fd_t = null, | 154 | resume_fd: ?std.posix.fd_t = null, |
| @@ -164,11 +167,16 @@ const Opts = struct { | |||
| 164 | /// `endpoint`'s alone, and `parseArgs` refuses it on every other verb: | 167 | /// `endpoint`'s alone, and `parseArgs` refuses it on every other verb: |
| 165 | /// ensure a daemon on the socket, then announce as usual. It is what | 168 | /// ensure a daemon on the socket, then announce as usual. It is what |
| 166 | /// makes a cold `mux HOST` ONE ssh run — the client used to read the | 169 | /// makes a cold `mux HOST` ONE ssh run — the client used to read the |
| 167 | /// refusal, run `mux d start` over a second ssh, and ask again over a | 170 | /// refusal, run `mux d start -d` over a second ssh, and ask again over a |
| 168 | /// third. The decision is the REMOTE's now, and "a read never starts a | 171 | /// third. The decision is the REMOTE's now, and "a read never starts a |
| 169 | /// daemon" holds by argv: a poll spells `mux d endpoint` and starts | 172 | /// daemon" holds by argv: a poll spells `mux d endpoint` and starts |
| 170 | /// nothing, an ask spells this. | 173 | /// nothing, an ask spells this. |
| 171 | start: bool = false, | 174 | start: bool = false, |
| 175 | |||
| 176 | /// `-d` is what everyone types and the only spelling the prose offers; | ||
| 177 | /// `--detach` is the field's own name and works because cliflags reads | ||
| 178 | /// the struct. | ||
| 179 | pub const aliases = .{.{ "-d", "detach" }}; | ||
| 172 | }; | 180 | }; |
| 173 | 181 | ||
| 174 | // The three flags left out are written into argv by the OLD daemon on an | 182 | // The three flags left out are written into argv by the OLD daemon on an |
| @@ -316,8 +324,10 @@ pub fn main(args: []const [:0]const u8) !u8 { | |||
| 316 | .version => return cliflags.version("mux", build_options.version), | 324 | .version => return cliflags.version("mux", build_options.version), |
| 317 | .help => return usageExit(.help), | 325 | .help => return usageExit(.help), |
| 318 | .keygen => return keygen(alloc), | 326 | .keygen => return keygen(alloc), |
| 319 | .start => return startCmd(alloc, sock_path, args[2..]), | 327 | .start => return if (o.resume_fd) |fd| |
| 320 | .run => return if (o.resume_fd) |fd| resumeRun(alloc, o, fd) else run(alloc, o, sock_path), | 328 | resumeRun(alloc, o, fd) |
| 329 | else | ||
| 330 | startCmd(alloc, o, sock_path, args[2..]), | ||
| 321 | .dump => return dump(alloc, sock_path, o.vt, if (o.session) |n| n.name else ""), | 331 | .dump => return dump(alloc, sock_path, o.vt, if (o.session) |n| n.name else ""), |
| 322 | .stats => return stats(alloc, sock_path), | 332 | .stats => return stats(alloc, sock_path), |
| 323 | .stop => return stopCmd(alloc, sock_path), | 333 | .stop => return stopCmd(alloc, sock_path), |
| @@ -325,7 +335,7 @@ pub fn main(args: []const [:0]const u8) !u8 { | |||
| 325 | // A pump, and only a pump: a daemon starts when someone asks for | 335 | // A pump, and only a pump: a daemon starts when someone asks for |
| 326 | // one. `proxy.run` names the socket it could not reach, which is | 336 | // one. `proxy.run` names the socket it could not reach, which is |
| 327 | // what `mux --via 'ssh HOST mux d proxy'` shows a user whose remote | 337 | // what `mux --via 'ssh HOST mux d proxy'` shows a user whose remote |
| 328 | // has none — README's `ssh HOST 'mux d start'` is the answer. | 338 | // has none — README's `ssh HOST 'mux d start -d'` is the answer. |
| 329 | .proxy => return proxy.run(sock_path), | 339 | .proxy => return proxy.run(sock_path), |
| 330 | .endpoint => return endpointCmd(alloc, sock_path, std.posix.STDOUT_FILENO, o.start), | 340 | .endpoint => return endpointCmd(alloc, sock_path, std.posix.STDOUT_FILENO, o.start), |
| 331 | } | 341 | } |
| @@ -387,12 +397,12 @@ fn rollback( | |||
| 387 | // every descriptor is still open and still inherited, and the manifest | 397 | // every descriptor is still open and still inherited, and the manifest |
| 388 | // names the binary that opened them. | 398 | // names the binary that opened them. |
| 389 | std.debug.print( | 399 | std.debug.print( |
| 390 | "mux d run: adoption failed at {s}; exec'ing {s} back\n", | 400 | "mux d start: adoption failed at {s}; exec'ing {s} back\n", |
| 391 | .{ section, writer_path }, | 401 | .{ section, writer_path }, |
| 392 | ); | 402 | ); |
| 393 | if (std.posix.getenv(rollback_marker) != null) { | 403 | if (std.posix.getenv(rollback_marker) != null) { |
| 394 | std.debug.print( | 404 | std.debug.print( |
| 395 | "mux d run: this IS the rollback ({s} refused the manifest it wrote); giving up\n", | 405 | "mux d start: this IS the rollback ({s} refused the manifest it wrote); giving up\n", |
| 396 | .{writer_path}, | 406 | .{writer_path}, |
| 397 | ); | 407 | ); |
| 398 | return 1; | 408 | return 1; |
| @@ -405,11 +415,11 @@ fn rollback( | |||
| 405 | 415 | ||
| 406 | var fd_buf: [12]u8 = undefined; | 416 | var fd_buf: [12]u8 = undefined; |
| 407 | const fd_str = std.fmt.bufPrintZ(&fd_buf, "{d}", .{resume_fd}) catch return 1; | 417 | const fd_str = std.fmt.bufPrintZ(&fd_buf, "{d}", .{resume_fd}) catch return 1; |
| 408 | // `d run`, the only spelling there is. The binary being exec'd back is | 418 | // `d start`, the only spelling there is. The binary being exec'd back is |
| 409 | // older than this one, but not by more than the rename: a daemon of | 419 | // older than this one, but not by more than the rename: a daemon of |
| 410 | // v0.0.1-15 or earlier refuses this binary as a candidate at its | 420 | // v0.0.1-15 or earlier refuses this binary as a candidate at its |
| 411 | // version probe, so it can never have been the writer here. | 421 | // version probe, so it can never have been the writer here. |
| 412 | const argv = [_:null]?[*:0]const u8{ "mux", "d", "run", "--resume-fd", fd_str.ptr }; | 422 | const argv = [_:null]?[*:0]const u8{ "mux", "d", "start", "--resume-fd", fd_str.ptr }; |
| 413 | const path_z = alloc.dupeZ(u8, writer_path) catch return 1; | 423 | const path_z = alloc.dupeZ(u8, writer_path) catch return 1; |
| 414 | const envp = rollbackEnvp(alloc) catch return 1; | 424 | const envp = rollbackEnvp(alloc) catch return 1; |
| 415 | 425 | ||
| @@ -420,7 +430,7 @@ fn rollback( | |||
| 420 | alloc.free(path_z); | 430 | alloc.free(path_z); |
| 421 | alloc.free(std.mem.span(envp)); | 431 | alloc.free(std.mem.span(envp)); |
| 422 | std.debug.print( | 432 | std.debug.print( |
| 423 | "mux d run: rollback exec of {s} failed: {s}\n", | 433 | "mux d start: rollback exec of {s} failed: {s}\n", |
| 424 | .{ writer_path, @errorName(exec_err) }, | 434 | .{ writer_path, @errorName(exec_err) }, |
| 425 | ); | 435 | ); |
| 426 | return 1; | 436 | return 1; |
| @@ -447,7 +457,7 @@ fn rollbackEnvp(alloc: std.mem.Allocator) ![*:null]const ?[*:0]const u8 { | |||
| 447 | return envp.ptr; | 457 | return envp.ptr; |
| 448 | } | 458 | } |
| 449 | 459 | ||
| 450 | /// `mux d run --resume-fd N`: the argv an upgrading daemon exec'd this binary | 460 | /// `mux d start --resume-fd N`: the argv an upgrading daemon exec'd this binary |
| 451 | /// with. Same pid, same children, same descriptors — the manifest names | 461 | /// with. Same pid, same children, same descriptors — the manifest names |
| 452 | /// which ones. It is read from the descriptor and never from a path: the | 462 | /// which ones. It is read from the descriptor and never from a path: the |
| 453 | /// memfd is anonymous memory, and the QUIC key inside it must not touch disk. | 463 | /// memfd is anonymous memory, and the QUIC key inside it must not touch disk. |
| @@ -456,17 +466,17 @@ fn resumeRun(alloc: std.mem.Allocator, o: Opts, resume_fd: std.posix.fd_t) !u8 { | |||
| 456 | // shares the file description with it, so the rewind is ours to do. | 466 | // shares the file description with it, so the rewind is ours to do. |
| 457 | var file = std.fs.File{ .handle = resume_fd }; | 467 | var file = std.fs.File{ .handle = resume_fd }; |
| 458 | file.seekTo(0) catch |err| { | 468 | file.seekTo(0) catch |err| { |
| 459 | std.debug.print("mux d run: --resume-fd {d} does not seek ({t})\n", .{ resume_fd, err }); | 469 | std.debug.print("mux d start: --resume-fd {d} does not seek ({t})\n", .{ resume_fd, err }); |
| 460 | return 1; | 470 | return 1; |
| 461 | }; | 471 | }; |
| 462 | const bytes = file.readToEndAlloc(alloc, manifest_read_max) catch |err| { | 472 | const bytes = file.readToEndAlloc(alloc, manifest_read_max) catch |err| { |
| 463 | std.debug.print("mux d run: cannot read the manifest on fd {d} ({t})\n", .{ resume_fd, err }); | 473 | std.debug.print("mux d start: cannot read the manifest on fd {d} ({t})\n", .{ resume_fd, err }); |
| 464 | return 1; | 474 | return 1; |
| 465 | }; | 475 | }; |
| 466 | defer alloc.free(bytes); | 476 | defer alloc.free(bytes); |
| 467 | 477 | ||
| 468 | var parsed = upgrade.parseManifest(alloc, bytes) catch |err| { | 478 | var parsed = upgrade.parseManifest(alloc, bytes) catch |err| { |
| 469 | std.debug.print("mux d run: manifest on fd {d} is not one ({t})\n", .{ resume_fd, err }); | 479 | std.debug.print("mux d start: manifest on fd {d} is not one ({t})\n", .{ resume_fd, err }); |
| 470 | return 1; | 480 | return 1; |
| 471 | }; | 481 | }; |
| 472 | defer parsed.deinit(); | 482 | defer parsed.deinit(); |
| @@ -509,7 +519,7 @@ fn resumeRun(alloc: std.mem.Allocator, o: Opts, resume_fd: std.posix.fd_t) !u8 { | |||
| 509 | return try srv.run(); | 519 | return try srv.run(); |
| 510 | } | 520 | } |
| 511 | 521 | ||
| 512 | /// `mux d run`: the daemon, in the foreground. Every code this function picks | 522 | /// `mux d start`: the daemon, in the foreground. Every code this function picks |
| 513 | /// is a boot failure — an operator mistake caught before anything bound. | 523 | /// is a boot failure — an operator mistake caught before anything bound. |
| 514 | /// Reaching `srv.run()` means the daemon served, and it answers 0 whenever | 524 | /// Reaching `srv.run()` means the daemon served, and it answers 0 whenever |
| 515 | /// something asks it to stop. No session's exit is ever reported here: a | 525 | /// something asks it to stop. No session's exit is ever reported here: a |
| @@ -719,7 +729,7 @@ fn oneShotQuery( | |||
| 719 | ) !u8 { | 729 | ) !u8 { |
| 720 | const stream = std.net.connectUnixSocket(sock_path) catch { | 730 | const stream = std.net.connectUnixSocket(sock_path) catch { |
| 721 | std.debug.print( | 731 | std.debug.print( |
| 722 | "mux d {s}: nothing listening on {s} (`mux d start` starts a daemon)\n", | 732 | "mux d {s}: nothing listening on {s} (`mux d start -d` starts one)\n", |
| 723 | .{ verb, sock_path }, | 733 | .{ verb, sock_path }, |
| 724 | ); | 734 | ); |
| 725 | return 1; | 735 | return 1; |
| @@ -749,7 +759,7 @@ fn stats(alloc: std.mem.Allocator, sock_path: []const u8) !u8 { | |||
| 749 | /// Ask the daemon on `sock_path` to exit, then wait until the PROCESS is | 759 | /// Ask the daemon on `sock_path` to exit, then wait until the PROCESS is |
| 750 | /// gone, not just the path. Exit 0 covers both "stopped" and "nothing | 760 | /// gone, not just the path. Exit 0 covers both "stopped" and "nothing |
| 751 | /// there" — the state the user asked for is the state they got, which is | 761 | /// there" — the state the user asked for is the state they got, which is |
| 752 | /// what makes the verb safe to script (`mux d start`'s re-runnability, | 762 | /// what makes the verb safe to script (`mux d start -d`'s re-runnability, |
| 753 | /// mirrored). | 763 | /// mirrored). |
| 754 | /// | 764 | /// |
| 755 | /// Prefixes split the way `start`'s do: `mux d stop:` for a refusal or a | 765 | /// Prefixes split the way `start`'s do: `mux d stop:` for a refusal or a |
| @@ -814,7 +824,7 @@ fn peerPid(fd: std.posix.socket_t) ?std.posix.pid_t { | |||
| 814 | /// A socket gone quiet is the unlink, and the unlink is the FIRST thing a | 824 | /// A socket gone quiet is the unlink, and the unlink is the FIRST thing a |
| 815 | /// stopping daemon does; reaping its shells (one TERM grace when one | 825 | /// stopping daemon does; reaping its shells (one TERM grace when one |
| 816 | /// ignores it) and deleting its dirs come after. "stopped" said at the | 826 | /// ignores it) and deleting its dirs come after. "stopped" said at the |
| 817 | /// unlink handed a scripted `mux d start`, or a supervisor's "is it down", | 827 | /// unlink handed a scripted `mux d start -d`, or a supervisor's "is it down", |
| 818 | /// a daemon still running. The bound is the reap's own grace with room to | 828 | /// a daemon still running. The bound is the reap's own grace with room to |
| 819 | /// spare — a daemon still here after it is wedged in teardown, and that | 829 | /// spare — a daemon still here after it is wedged in teardown, and that |
| 820 | /// is a report, not a wait. | 830 | /// is a report, not a wait. |
| @@ -908,7 +918,7 @@ fn upgradeCmd(alloc: std.mem.Allocator, sock_path: []const u8, allow_same: bool) | |||
| 908 | "mux d upgrade: if that daemon is v0.0.1-15 or older, it wants a " ++ | 918 | "mux d upgrade: if that daemon is v0.0.1-15 or older, it wants a " ++ |
| 909 | "candidate that prints `muxd <version>` and this one is `mux`. " ++ | 919 | "candidate that prints `muxd <version>` and this one is `mux`. " ++ |
| 910 | "There is no in-place path across that rename: `mux d stop` then " ++ | 920 | "There is no in-place path across that rename: `mux d stop` then " ++ |
| 911 | "`mux d start`, once.\n", | 921 | "`mux d start -d`, once.\n", |
| 912 | .{}, | 922 | .{}, |
| 913 | ); | 923 | ); |
| 914 | return 1; | 924 | return 1; |
| @@ -957,7 +967,7 @@ const log_hint_len = std.fs.max_path_bytes + 64; | |||
| 957 | /// | 967 | /// |
| 958 | /// Only when the path resolves: an absent HOME (a container, a systemd | 968 | /// Only when the path resolves: an absent HOME (a container, a systemd |
| 959 | /// unit) must not replace the finding that matters with an error trace. | 969 | /// unit) must not replace the finding that matters with an error trace. |
| 960 | /// And the hedge stays in the words: a foreground `mux d run` logs to | 970 | /// And the hedge stays in the words: a foreground `mux d start` logs to |
| 961 | /// its own stderr, so naming the xdg path unconditionally would guess. | 971 | /// its own stderr, so naming the xdg path unconditionally would guess. |
| 962 | fn logHint(alloc: std.mem.Allocator, buf: []u8) []const u8 { | 972 | fn logHint(alloc: std.mem.Allocator, buf: []u8) []const u8 { |
| 963 | const log = xdg.logPath(alloc) catch return ""; | 973 | const log = xdg.logPath(alloc) catch return ""; |
| @@ -1021,9 +1031,9 @@ fn endpointCmd(alloc: std.mem.Allocator, sock_path: []const u8, out_fd: std.posi | |||
| 1021 | // — a stray daemon nothing reports. `spawn.ensureForAttach` forwards it | 1031 | // — a stray daemon nothing reports. `spawn.ensureForAttach` forwards it |
| 1022 | // for the same reason. Nothing else is: a bare daemon binds its QUIC | 1032 | // for the same reason. Nothing else is: a bare daemon binds its QUIC |
| 1023 | // listener when the announce below asks it to, which is how a plain | 1033 | // listener when the announce below asks it to, which is how a plain |
| 1024 | // `mux d start` already produces a port. | 1034 | // `mux d start -d` already produces a port. |
| 1025 | // | 1035 | // |
| 1026 | // Truncating, like `mux d start`: the user asked for a daemon, so the | 1036 | // Truncating, like `mux d start -d`: the user asked for a daemon, so the |
| 1027 | // log they go on to read must be about the one they just got. | 1037 | // log they go on to read must be about the one they just got. |
| 1028 | // | 1038 | // |
| 1029 | // A spawn that failed has already said so on stderr, which ssh carries | 1039 | // A spawn that failed has already said so on stderr, which ssh carries |
| @@ -1223,18 +1233,21 @@ fn askEndpointPort(alloc: std.mem.Allocator, sock_path: []const u8) u16 { | |||
| 1223 | return proto.decodeEndpointReply(frame.payload) catch 0; | 1233 | return proto.decodeEndpointReply(frame.payload) catch 0; |
| 1224 | } | 1234 | } |
| 1225 | 1235 | ||
| 1226 | /// `mux d start` = ensureDaemon under an explicit flag. Everything after | 1236 | /// `mux d start`: the daemon, in this process, or — under `-d` — in a |
| 1227 | /// `start` is forwarded to `run` verbatim — no re-serialization, so a flag | 1237 | /// child of it. Everything after `start` is forwarded to the child |
| 1228 | /// that parses here behaves identically there. parseArgs has already | 1238 | /// verbatim minus the `-d` itself, so a flag that parses here behaves |
| 1229 | /// validated the flags in THIS process; what it cannot validate (a bad | 1239 | /// identically there. parseArgs has already validated the flags in THIS |
| 1230 | /// bind address, a missing key file) surfaces in the daemon's log, which | 1240 | /// process; what it cannot validate (a bad bind address, a missing key |
| 1231 | /// the failure path names. | 1241 | /// file) surfaces in the daemon's log, which the failure path names. |
| 1232 | fn startCmd(alloc: std.mem.Allocator, sock_path: []const u8, forwarded: []const [:0]const u8) !u8 { | 1242 | fn startCmd(alloc: std.mem.Allocator, o: Opts, sock_path: []const u8, forwarded: []const [:0]const u8) !u8 { |
| 1243 | if (!o.detach) return run(alloc, o, sock_path); | ||
| 1244 | const child_args = try withoutDetach(alloc, forwarded); | ||
| 1245 | defer alloc.free(child_args); | ||
| 1233 | // Default log path: the xdg one is the whole point for a real daemon. | 1246 | // Default log path: the xdg one is the whole point for a real daemon. |
| 1234 | // Truncating, and this is the only caller that does: `start` is the one | 1247 | // Truncating, and this is the only caller that does: `-d` is the one |
| 1235 | // verb whose user asked for a (re)start, so the log they go on to read | 1248 | // flag whose user asked for a (re)start, so the log they go on to read |
| 1236 | // must be about the daemon they just started. | 1249 | // must be about the daemon they just started. |
| 1237 | const r = spawn.ensure(alloc, forwarded, sock_path, "mux d", .{ .truncate = true }) orelse return 1; | 1250 | const r = spawn.ensure(alloc, child_args, sock_path, "mux d", .{ .truncate = true }) orelse return 1; |
| 1238 | if (r == .already_running) { | 1251 | if (r == .already_running) { |
| 1239 | std.debug.print( | 1252 | std.debug.print( |
| 1240 | "mux d: already running on {s} (stop it first with `mux d stop --sock {s}` if you meant different flags)\n", | 1253 | "mux d: already running on {s} (stop it first with `mux d stop --sock {s}` if you meant different flags)\n", |
| @@ -1244,6 +1257,46 @@ fn startCmd(alloc: std.mem.Allocator, sock_path: []const u8, forwarded: []const | |||
| 1244 | return 0; | 1257 | return 0; |
| 1245 | } | 1258 | } |
| 1246 | 1259 | ||
| 1260 | /// `start`'s own words back, minus the flag that asked for the fork. The | ||
| 1261 | /// child is the FOREGROUND daemon: a `-d` still in its line would fork | ||
| 1262 | /// again, and its child again, for as long as the machine lasted. | ||
| 1263 | /// | ||
| 1264 | /// A word is only a flag where a flag can stand — `--shell -d` names a | ||
| 1265 | /// shell — so the walk skips each value-taking flag's value, reading the | ||
| 1266 | /// arity off `Opts` exactly as cliflags does. | ||
| 1267 | fn withoutDetach(alloc: std.mem.Allocator, args: []const [:0]const u8) ![]const [:0]const u8 { | ||
| 1268 | var kept: std.ArrayList([:0]const u8) = .empty; | ||
| 1269 | errdefer kept.deinit(alloc); | ||
| 1270 | var i: usize = 0; | ||
| 1271 | while (i < args.len) : (i += 1) { | ||
| 1272 | const a = args[i]; | ||
| 1273 | if (std.mem.eql(u8, a, "-d") or std.mem.eql(u8, a, "--detach")) continue; | ||
| 1274 | try kept.append(alloc, a); | ||
| 1275 | if (takesValue(a) and i + 1 < args.len) { | ||
| 1276 | i += 1; | ||
| 1277 | try kept.append(alloc, args[i]); | ||
| 1278 | } | ||
| 1279 | } | ||
| 1280 | return kept.toOwnedSlice(alloc); | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | /// Whether `word` is one of `Opts`' flags that eats the word after it. | ||
| 1284 | /// Read off the struct, so a field added later is covered without a second | ||
| 1285 | /// list to keep in step. | ||
| 1286 | fn takesValue(word: []const u8) bool { | ||
| 1287 | inline for (@typeInfo(Opts).@"struct".fields) |f| { | ||
| 1288 | if (f.name[0] != '_') { | ||
| 1289 | const B = if (@typeInfo(f.type) == .optional) | ||
| 1290 | @typeInfo(f.type).optional.child | ||
| 1291 | else | ||
| 1292 | f.type; | ||
| 1293 | if (B != bool and std.mem.eql(u8, word, comptime cliflags.flagName(f.name))) | ||
| 1294 | return true; | ||
| 1295 | } | ||
| 1296 | } | ||
| 1297 | return false; | ||
| 1298 | } | ||
| 1299 | |||
| 1247 | fn keygen(alloc: std.mem.Allocator) !u8 { | 1300 | fn keygen(alloc: std.mem.Allocator) !u8 { |
| 1248 | const path = try xdg.keyPath(alloc); | 1301 | const path = try xdg.keyPath(alloc); |
| 1249 | defer alloc.free(path); | 1302 | defer alloc.free(path); |
| @@ -1283,9 +1336,9 @@ fn nameZ(comptime name: []const u8) [:0]const u8 { | |||
| 1283 | } | 1336 | } |
| 1284 | 1337 | ||
| 1285 | test "parseArgs: subcommands and their existing flags" { | 1338 | test "parseArgs: subcommands and their existing flags" { |
| 1286 | const r = parse(&.{ "d", "run" }); | 1339 | const r = parse(&.{ "d", "start" }); |
| 1287 | try std.testing.expect(r == .ok); | 1340 | try std.testing.expect(r == .ok); |
| 1288 | try std.testing.expect(r.ok._cmd == .run); | 1341 | try std.testing.expect(r.ok._cmd == .start); |
| 1289 | try std.testing.expect(r.ok.sock == null); | 1342 | try std.testing.expect(r.ok.sock == null); |
| 1290 | try std.testing.expectEqual(@as(u16, 80), r.ok.cols); | 1343 | try std.testing.expectEqual(@as(u16, 80), r.ok.cols); |
| 1291 | try std.testing.expectEqual(@as(u16, 24), r.ok.rows); | 1344 | try std.testing.expectEqual(@as(u16, 24), r.ok.rows); |
| @@ -1298,14 +1351,14 @@ test "parseArgs: subcommands and their existing flags" { | |||
| 1298 | // on the wire as the empty tail. | 1351 | // on the wire as the empty tail. |
| 1299 | try std.testing.expect(d.ok.session == null); | 1352 | try std.testing.expect(d.ok.session == null); |
| 1300 | 1353 | ||
| 1301 | const g = parse(&.{ "d", "run", "--cols", "120", "--rows", "40", "--shell", "/bin/dash" }); | 1354 | const g = parse(&.{ "d", "start", "--cols", "120", "--rows", "40", "--shell", "/bin/dash" }); |
| 1302 | try std.testing.expectEqual(@as(u16, 120), g.ok.cols); | 1355 | try std.testing.expectEqual(@as(u16, 120), g.ok.cols); |
| 1303 | try std.testing.expectEqual(@as(u16, 40), g.ok.rows); | 1356 | try std.testing.expectEqual(@as(u16, 40), g.ok.rows); |
| 1304 | try std.testing.expectEqualStrings("/bin/dash", g.ok.shell.?); | 1357 | try std.testing.expectEqualStrings("/bin/dash", g.ok.shell.?); |
| 1305 | 1358 | ||
| 1306 | try std.testing.expect(parse(&.{"d"}).err == .no_command); | 1359 | try std.testing.expect(parse(&.{"d"}).err == .no_command); |
| 1307 | try std.testing.expect(parse(&.{ "d", "wat" }).err == .unknown_command); | 1360 | try std.testing.expect(parse(&.{ "d", "wat" }).err == .unknown_command); |
| 1308 | try std.testing.expect(parse(&.{ "d", "run", "--wat" }).err == .unknown_arg); | 1361 | try std.testing.expect(parse(&.{ "d", "start", "--wat" }).err == .unknown_arg); |
| 1309 | } | 1362 | } |
| 1310 | 1363 | ||
| 1311 | test "parse: dump --session rides into the payload" { | 1364 | test "parse: dump --session rides into the payload" { |
| @@ -1323,60 +1376,60 @@ test "parse: dump --session rides into the payload" { | |||
| 1323 | } | 1376 | } |
| 1324 | 1377 | ||
| 1325 | test "parseArgs: --key without --quic is refused; --quic alone defers to main" { | 1378 | test "parseArgs: --key without --quic is refused; --quic alone defers to main" { |
| 1326 | const both = parse(&.{ "d", "run", "--quic", "0.0.0.0:4433", "--key", "/k" }); | 1379 | const both = parse(&.{ "d", "start", "--quic", "0.0.0.0:4433", "--key", "/k" }); |
| 1327 | try std.testing.expect(both == .ok); | 1380 | try std.testing.expect(both == .ok); |
| 1328 | try std.testing.expectEqualStrings("0.0.0.0:4433", both.ok.quic.?); | 1381 | try std.testing.expectEqualStrings("0.0.0.0:4433", both.ok.quic.?); |
| 1329 | try std.testing.expectEqualStrings("/k", both.ok.key.?); | 1382 | try std.testing.expectEqualStrings("/k", both.ok.key.?); |
| 1330 | 1383 | ||
| 1331 | // --quic without --key is no longer a parse error: main resolves | 1384 | // --quic without --key is no longer a parse error: main resolves |
| 1332 | // MUX_KEY_FILE and the default path, and parse cannot see either. | 1385 | // MUX_KEY_FILE and the default path, and parse cannot see either. |
| 1333 | const deferred = parse(&.{ "d", "run", "--quic", "0.0.0.0:4433" }); | 1386 | const deferred = parse(&.{ "d", "start", "--quic", "0.0.0.0:4433" }); |
| 1334 | try std.testing.expect(deferred == .ok); | 1387 | try std.testing.expect(deferred == .ok); |
| 1335 | try std.testing.expect(deferred.ok.key == null); | 1388 | try std.testing.expect(deferred.ok.key == null); |
| 1336 | 1389 | ||
| 1337 | // A key with nowhere to listen is still a mistake with no reading that | 1390 | // A key with nowhere to listen is still a mistake with no reading that |
| 1338 | // makes it sensible, and parse can see the whole of it. | 1391 | // makes it sensible, and parse can see the whole of it. |
| 1339 | try std.testing.expect(parse(&.{ "d", "run", "--key", "/k" }).err == .key_without_quic); | 1392 | try std.testing.expect(parse(&.{ "d", "start", "--key", "/k" }).err == .key_without_quic); |
| 1340 | 1393 | ||
| 1341 | // Neither is the ordinary case and must stay silent. | 1394 | // Neither is the ordinary case and must stay silent. |
| 1342 | const neither = parse(&.{ "d", "run" }); | 1395 | const neither = parse(&.{ "d", "start" }); |
| 1343 | try std.testing.expect(neither.ok.quic == null); | 1396 | try std.testing.expect(neither.ok.quic == null); |
| 1344 | try std.testing.expect(neither.ok.key == null); | 1397 | try std.testing.expect(neither.ok.key == null); |
| 1345 | } | 1398 | } |
| 1346 | 1399 | ||
| 1347 | test "parseArgs: --quic-idle-ms defaults, parses, and refuses nonsense" { | 1400 | test "parseArgs: --quic-idle-ms defaults, parses, and refuses nonsense" { |
| 1348 | const dflt = parse(&.{ "d", "run", "--quic", "127.0.0.1:1", "--key", "/k" }); | 1401 | const dflt = parse(&.{ "d", "start", "--quic", "127.0.0.1:1", "--key", "/k" }); |
| 1349 | // Spelled out rather than written `quic.default_idle_ms`: asserting | 1402 | // Spelled out rather than written `quic.default_idle_ms`: asserting |
| 1350 | // against the same constant the parser reads would hold for any value, | 1403 | // against the same constant the parser reads would hold for any value, |
| 1351 | // so it could never catch the number changing. | 1404 | // so it could never catch the number changing. |
| 1352 | try std.testing.expectEqual(@as(u32, 15_000), dflt.ok.quic_idle_ms.ms); | 1405 | try std.testing.expectEqual(@as(u32, 15_000), dflt.ok.quic_idle_ms.ms); |
| 1353 | 1406 | ||
| 1354 | const set = parse(&.{ "d", "run", "--quic", "127.0.0.1:1", "--key", "/k", "--quic-idle-ms", "2500" }); | 1407 | const set = parse(&.{ "d", "start", "--quic", "127.0.0.1:1", "--key", "/k", "--quic-idle-ms", "2500" }); |
| 1355 | try std.testing.expectEqual(@as(u32, 2500), set.ok.quic_idle_ms.ms); | 1408 | try std.testing.expectEqual(@as(u32, 2500), set.ok.quic_idle_ms.ms); |
| 1356 | 1409 | ||
| 1357 | // Zero means "no idle timeout" to ngtcp2 — the opposite of what anyone | 1410 | // Zero means "no idle timeout" to ngtcp2 — the opposite of what anyone |
| 1358 | // typing a timeout of zero is asking for, so it is refused rather than | 1411 | // typing a timeout of zero is asking for, so it is refused rather than |
| 1359 | // silently inverted. | 1412 | // silently inverted. |
| 1360 | try std.testing.expect(parse(&.{ "d", "run", "--quic-idle-ms", "0" }).err == .bad_value); | 1413 | try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "0" }).err == .bad_value); |
| 1361 | try std.testing.expect(parse(&.{ "d", "run", "--quic-idle-ms", "soon" }).err == .bad_value); | 1414 | try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "soon" }).err == .bad_value); |
| 1362 | try std.testing.expect(parse(&.{ "d", "run", "--quic-idle-ms", "-5" }).err == .bad_value); | 1415 | try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "-5" }).err == .bad_value); |
| 1363 | // Wider than u32: refused at the parse rather than overflowing where it | 1416 | // Wider than u32: refused at the parse rather than overflowing where it |
| 1364 | // is multiplied out to nanoseconds. | 1417 | // is multiplied out to nanoseconds. |
| 1365 | try std.testing.expect(parse(&.{ "d", "run", "--quic-idle-ms", "99999999999" }).err == .bad_value); | 1418 | try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "99999999999" }).err == .bad_value); |
| 1366 | // The idle flag alone does not turn QUIC on, and must not smuggle the | 1419 | // The idle flag alone does not turn QUIC on, and must not smuggle the |
| 1367 | // both-or-neither rule past the check. | 1420 | // both-or-neither rule past the check. |
| 1368 | try std.testing.expect(parse(&.{ "d", "run", "--quic-idle-ms", "2500" }) == .ok); | 1421 | try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "2500" }) == .ok); |
| 1369 | 1422 | ||
| 1370 | // Same treatment for the numbers that were already here. | 1423 | // Same treatment for the numbers that were already here. |
| 1371 | try std.testing.expect(parse(&.{ "d", "run", "--cols", "wide" }).err == .bad_value); | 1424 | try std.testing.expect(parse(&.{ "d", "start", "--cols", "wide" }).err == .bad_value); |
| 1372 | try std.testing.expect(parse(&.{ "d", "run", "--rows", "99999" }).err == .bad_value); | 1425 | try std.testing.expect(parse(&.{ "d", "start", "--rows", "99999" }).err == .bad_value); |
| 1373 | } | 1426 | } |
| 1374 | 1427 | ||
| 1375 | test "parseArgs: a value-taking flag at the end of argv names itself" { | 1428 | test "parseArgs: a value-taking flag at the end of argv names itself" { |
| 1376 | // This used to report "unknown argument: --quic", which blames the flag | 1429 | // This used to report "unknown argument: --quic", which blames the flag |
| 1377 | // rather than the missing value. | 1430 | // rather than the missing value. |
| 1378 | inline for (.{ "--sock", "--shell", "--cols", "--rows", "--quic", "--key", "--quic-idle-ms", "--session", "--resume-fd", "--resume-fail-at" }) |flag| { | 1431 | inline for (.{ "--sock", "--shell", "--cols", "--rows", "--quic", "--key", "--quic-idle-ms", "--session", "--resume-fd", "--resume-fail-at" }) |flag| { |
| 1379 | const r = parse(&.{ "d", "run", flag }); | 1432 | const r = parse(&.{ "d", "start", flag }); |
| 1380 | try std.testing.expect(r.err == .missing_value); | 1433 | try std.testing.expect(r.err == .missing_value); |
| 1381 | try std.testing.expectEqualStrings(flag, r.err.missing_value); | 1434 | try std.testing.expectEqualStrings(flag, r.err.missing_value); |
| 1382 | } | 1435 | } |
| @@ -1432,13 +1485,13 @@ test "parseArgs: --version is a command, not a flag on one" { | |||
| 1432 | try std.testing.expect(r == .ok); | 1485 | try std.testing.expect(r == .ok); |
| 1433 | try std.testing.expect(r.ok._cmd == .version); | 1486 | try std.testing.expect(r.ok._cmd == .version); |
| 1434 | 1487 | ||
| 1435 | // Typed onto a verb it becomes that same command, so `mux d run | 1488 | // Typed onto a verb it becomes that same command, so `mux d start |
| 1436 | // --version` answers instead of refusing an unknown flag. Asserted on | 1489 | // --version` answers instead of refusing an unknown flag. Asserted on |
| 1437 | // the parse rather than on `main`, which writes the version to STDOUT | 1490 | // the parse rather than on `main`, which writes the version to STDOUT |
| 1438 | // and would hang the build runner's IPC. | 1491 | // and would hang the build runner's IPC. |
| 1439 | const on_run = parse(&.{ "d", "run", "--sock", "/x", "--version" }); | 1492 | const on_start = parse(&.{ "d", "start", "--sock", "/x", "--version" }); |
| 1440 | try std.testing.expect(on_run == .ok); | 1493 | try std.testing.expect(on_start == .ok); |
| 1441 | try std.testing.expect(on_run.ok._cmd == .version); | 1494 | try std.testing.expect(on_start.ok._cmd == .version); |
| 1442 | } | 1495 | } |
| 1443 | 1496 | ||
| 1444 | test "parseArgs: --help is a command, and a flag on one, and both exit 0 on stdout" { | 1497 | test "parseArgs: --help is a command, and a flag on one, and both exit 0 on stdout" { |
| @@ -1449,9 +1502,9 @@ test "parseArgs: --help is a command, and a flag on one, and both exit 0 on stdo | |||
| 1449 | // On a subcommand it is an outcome of the flag parse rather than a row, | 1502 | // On a subcommand it is an outcome of the flag parse rather than a row, |
| 1450 | // and it must outrank the grammar: `--sock` here is still waiting for a | 1503 | // and it must outrank the grammar: `--sock` here is still waiting for a |
| 1451 | // value, and asking for the usage is not a way to mistype one. | 1504 | // value, and asking for the usage is not a way to mistype one. |
| 1452 | try std.testing.expect(parse(&.{ "d", "run", "--help" }).err == .help); | 1505 | try std.testing.expect(parse(&.{ "d", "start", "--help" }).err == .help); |
| 1453 | try std.testing.expect(parse(&.{ "d", "dump", "-h", "--sock", "/x" }).err == .help); | 1506 | try std.testing.expect(parse(&.{ "d", "dump", "-h", "--sock", "/x" }).err == .help); |
| 1454 | try std.testing.expect(parse(&.{ "d", "run", "--sock", "--help" }).err == .help); | 1507 | try std.testing.expect(parse(&.{ "d", "start", "--sock", "--help" }).err == .help); |
| 1455 | 1508 | ||
| 1456 | // The code, asked of `usageCode` rather than of `usageExit`: the latter | 1509 | // The code, asked of `usageCode` rather than of `usageExit`: the latter |
| 1457 | // writes the usage to STDOUT, which under `zig build test` is the build | 1510 | // writes the usage to STDOUT, which under `zig build test` is the build |
| @@ -1469,12 +1522,39 @@ test "parseArgs: keygen takes no flags" { | |||
| 1469 | try std.testing.expect(parse(&.{ "d", "keygen", "--sock", "/x" }).err == .unknown_arg); | 1522 | try std.testing.expect(parse(&.{ "d", "keygen", "--sock", "/x" }).err == .unknown_arg); |
| 1470 | } | 1523 | } |
| 1471 | 1524 | ||
| 1472 | test "parseArgs: start takes run's flags" { | 1525 | test "parseArgs: -d is start's own flag, and the rest are the daemon's" { |
| 1473 | const r = parse(&.{ "d", "start", "--sock", "/tmp/x.sock", "--cols", "100" }); | 1526 | const r = parse(&.{ "d", "start", "--sock", "/tmp/x.sock", "--cols", "100" }); |
| 1474 | try std.testing.expect(r == .ok); | 1527 | try std.testing.expect(r == .ok); |
| 1475 | try std.testing.expect(r.ok._cmd == .start); | 1528 | try std.testing.expect(r.ok._cmd == .start); |
| 1476 | try std.testing.expectEqualStrings("/tmp/x.sock", r.ok.sock.?); | 1529 | try std.testing.expectEqualStrings("/tmp/x.sock", r.ok.sock.?); |
| 1477 | try std.testing.expectEqual(@as(u16, 100), r.ok.cols); | 1530 | try std.testing.expectEqual(@as(u16, 100), r.ok.cols); |
| 1531 | // Off unless typed: the process that binds is the one that was typed, | ||
| 1532 | // and a `start` that forked by default would put the daemon somewhere | ||
| 1533 | // the user's shell cannot see it fail. | ||
| 1534 | try std.testing.expect(!r.ok.detach); | ||
| 1535 | |||
| 1536 | const d = parse(&.{ "d", "start", "-d", "--sock", "/tmp/x.sock" }); | ||
| 1537 | try std.testing.expect(d.ok.detach); | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | test "withoutDetach: the child is handed the line minus the fork, values and all" { | ||
| 1541 | const alloc = std.testing.allocator; | ||
| 1542 | |||
| 1543 | // The whole point: a `-d` still in the child's argv forks again, and | ||
| 1544 | // its child again, for as long as the machine lasts. | ||
| 1545 | const stripped = try withoutDetach(alloc, &.{ "-d", "--sock", "/s", "--cols", "100" }); | ||
| 1546 | defer alloc.free(stripped); | ||
| 1547 | try std.testing.expectEqual(@as(usize, 4), stripped.len); | ||
| 1548 | try std.testing.expectEqualStrings("--sock", stripped[0]); | ||
| 1549 | try std.testing.expectEqualStrings("/s", stripped[1]); | ||
| 1550 | |||
| 1551 | // A word is only a flag where a flag can stand. `--shell -d` names a | ||
| 1552 | // shell called `-d`, and a blind filter would hand the daemon | ||
| 1553 | // `--shell --cols` instead. | ||
| 1554 | const value = try withoutDetach(alloc, &.{ "--shell", "-d", "--cols", "100" }); | ||
| 1555 | defer alloc.free(value); | ||
| 1556 | try std.testing.expectEqual(@as(usize, 4), value.len); | ||
| 1557 | try std.testing.expectEqualStrings("-d", value[1]); | ||
| 1478 | } | 1558 | } |
| 1479 | 1559 | ||
| 1480 | test "parseArgs: stop is a command and takes --sock" { | 1560 | test "parseArgs: stop is a command and takes --sock" { |
| @@ -1551,24 +1631,24 @@ test "parseArgs: --start belongs to endpoint alone, and every other verb refuses | |||
| 1551 | } | 1631 | } |
| 1552 | } | 1632 | } |
| 1553 | 1633 | ||
| 1554 | test "parseArgs: run --resume-fd N --check is the old daemon's dry run" { | 1634 | test "parseArgs: start --resume-fd N --check is the old daemon's dry run" { |
| 1555 | const r = parse(&.{ "d", "run", "--resume-fd", "7", "--check" }); | 1635 | const r = parse(&.{ "d", "start", "--resume-fd", "7", "--check" }); |
| 1556 | try std.testing.expect(r == .ok); | 1636 | try std.testing.expect(r == .ok); |
| 1557 | try std.testing.expect(r.ok._cmd == .run); | 1637 | try std.testing.expect(r.ok._cmd == .start); |
| 1558 | try std.testing.expectEqual(@as(std.posix.fd_t, 7), r.ok.resume_fd.?); | 1638 | try std.testing.expectEqual(@as(std.posix.fd_t, 7), r.ok.resume_fd.?); |
| 1559 | try std.testing.expect(r.ok.check); | 1639 | try std.testing.expect(r.ok.check); |
| 1560 | 1640 | ||
| 1561 | // A number, like --cols: an fd that is not one would be read as a | 1641 | // A number, like --cols: an fd that is not one would be read as a |
| 1562 | // descriptor the daemon never passed. | 1642 | // descriptor the daemon never passed. |
| 1563 | try std.testing.expect(parse(&.{ "d", "run", "--resume-fd", "x" }).err == .bad_value); | 1643 | try std.testing.expect(parse(&.{ "d", "start", "--resume-fd", "x" }).err == .bad_value); |
| 1564 | 1644 | ||
| 1565 | const f = parse(&.{ "d", "run", "--resume-fd", "3", "--resume-fail-at", "session" }); | 1645 | const f = parse(&.{ "d", "start", "--resume-fd", "3", "--resume-fail-at", "session" }); |
| 1566 | try std.testing.expectEqualStrings("session", f.ok.resume_fail_at.?); | 1646 | try std.testing.expectEqualStrings("session", f.ok.resume_fail_at.?); |
| 1567 | 1647 | ||
| 1568 | // Neither flag is the ordinary start, and both must stay off there — | 1648 | // Neither flag is the ordinary start, and both must stay off there — |
| 1569 | // a `run` that thought it was resuming would adopt nothing and serve | 1649 | // a `start` that thought it was resuming would adopt nothing and serve |
| 1570 | // nothing. | 1650 | // nothing. |
| 1571 | const plain = parse(&.{ "d", "run" }); | 1651 | const plain = parse(&.{ "d", "start" }); |
| 1572 | try std.testing.expect(plain.ok.resume_fd == null); | 1652 | try std.testing.expect(plain.ok.resume_fd == null); |
| 1573 | try std.testing.expect(!plain.ok.check); | 1653 | try std.testing.expect(!plain.ok.check); |
| 1574 | } | 1654 | } |
| @@ -1634,7 +1714,7 @@ test "resumeRun: --check adopts nothing, so --resume-fail-at has nothing to abor | |||
| 1634 | try file.writeAll(buf.items); | 1714 | try file.writeAll(buf.items); |
| 1635 | 1715 | ||
| 1636 | const code = try resumeRun(alloc, .{ | 1716 | const code = try resumeRun(alloc, .{ |
| 1637 | ._cmd = .run, | 1717 | ._cmd = .start, |
| 1638 | .check = true, | 1718 | .check = true, |
| 1639 | .resume_fd = memfd, | 1719 | .resume_fd = memfd, |
| 1640 | .resume_fail_at = "daemon", | 1720 | .resume_fail_at = "daemon", |
src/cli/mux.zig
| Old | New | ||
|---|---|---|---|
| @@ -45,14 +45,14 @@ pub fn main() !u8 { | |||
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | test "modeOf: the three mode words, and nothing else" { | 47 | test "modeOf: the three mode words, and nothing else" { |
| 48 | try std.testing.expectEqual(Mode.daemon, modeOf(&.{ "mux", "d", "run" })); | 48 | try std.testing.expectEqual(Mode.daemon, modeOf(&.{ "mux", "d", "start" })); |
| 49 | try std.testing.expectEqual(Mode.agent, modeOf(&.{ "mux", "a", "status" })); | 49 | try std.testing.expectEqual(Mode.agent, modeOf(&.{ "mux", "a", "status" })); |
| 50 | try std.testing.expectEqual(Mode.hub, modeOf(&.{ "mux", "web" })); | 50 | try std.testing.expectEqual(Mode.hub, modeOf(&.{ "mux", "web" })); |
| 51 | try std.testing.expectEqual(Mode.client, modeOf(&.{"mux"})); | 51 | try std.testing.expectEqual(Mode.client, modeOf(&.{"mux"})); |
| 52 | try std.testing.expectEqual(Mode.client, modeOf(&.{ "mux", "box" })); | 52 | try std.testing.expectEqual(Mode.client, modeOf(&.{ "mux", "box" })); |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | test "modeOf: `run` is a host, not an alias for `d run`" { | 55 | test "modeOf: `run` is a host, and no daemon verb has a top-level alias" { |
| 56 | // There WAS a bare `run` here, as the bridge for a daemon of v0.0.1-15 | 56 | // There WAS a bare `run` here, as the bridge for a daemon of v0.0.1-15 |
| 57 | // or older exec'ing its upgrade candidate as `<binary> run --resume-fd | 57 | // or older exec'ing its upgrade candidate as `<binary> run --resume-fd |
| 58 | // N`. It is gone because it can never be reached: that daemon runs | 58 | // N`. It is gone because it can never be reached: that daemon runs |
src/cli/spawn.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,5 +1,5 @@ | |||
| 1 | //! Get a daemon onto a socket path: probe, spawn detached, poll until it | 1 | //! Get a daemon onto a socket path: probe, spawn detached, poll until it |
| 2 | //! answers. `mux d start` is explicit and spelled out; the LOCAL client's | 2 | //! answers. `mux d start -d` is explicit and spelled out; the LOCAL client's |
| 3 | //! own entry (`mux` with no host) is the one attach that may still start | 3 | //! own entry (`mux` with no host) is the one attach that may still start |
| 4 | //! one, through `ensureForAttach` below. The remote verbs a client reaches | 4 | //! one, through `ensureForAttach` below. The remote verbs a client reaches |
| 5 | //! over ssh do not: reading a box must never create a session there. | 5 | //! over ssh do not: reading a box must never create a session there. |
| @@ -26,7 +26,7 @@ pub fn selfExe(buf: *[std.fs.max_path_bytes]u8) []const u8 { | |||
| 26 | // from the basename of the FILENAME handed to execve, so exec'ing the | 26 | // from the basename of the FILENAME handed to execve, so exec'ing the |
| 27 | // link itself leaves every daemon called `exe` — nothing `pgrep mux`, | 27 | // link itself leaves every daemon called `exe` — nothing `pgrep mux`, |
| 28 | // `killall mux`, `ps -o comm` or systemd's MainPID name can find — | 28 | // `killall mux`, `ps -o comm` or systemd's MainPID name can find — |
| 29 | // while only the args still read `mux d run`. The e2e that reads a | 29 | // while only the args still read `mux d start`. The e2e that reads a |
| 30 | // spawned daemon's `/proc/PID/comm` is what says so. | 30 | // spawned daemon's `/proc/PID/comm` is what says so. |
| 31 | return execOrLink(std.fs.selfExePath(buf) catch return self_exe); | 31 | return execOrLink(std.fs.selfExePath(buf) catch return self_exe); |
| 32 | } | 32 | } |
| @@ -47,7 +47,7 @@ fn execOrLink(resolved: []const u8) []const u8 { | |||
| 47 | pub const Ensured = enum { already_running, started }; | 47 | pub const Ensured = enum { already_running, started }; |
| 48 | 48 | ||
| 49 | /// How long a spawn gets to answer, for every caller. One number because a | 49 | /// How long a spawn gets to answer, for every caller. One number because a |
| 50 | /// user who waited two seconds for `muxd start` must not wait a different | 50 | /// user who waited two seconds for `mux d start -d` must not wait a different |
| 51 | /// two seconds for an attach that starts the same daemon the same way: the | 51 | /// two seconds for an attach that starts the same daemon the same way: the |
| 52 | /// deadline describes how long muxd takes to bind, which is not a fact | 52 | /// deadline describes how long muxd takes to bind, which is not a fact |
| 53 | /// about which verb asked for it. | 53 | /// about which verb asked for it. |
| @@ -63,7 +63,7 @@ pub const LogSpec = struct { | |||
| 63 | /// machine, and writing over it from a unit test would punch a hole in | 63 | /// machine, and writing over it from a unit test would punch a hole in |
| 64 | /// a live daemon's log. | 64 | /// a live daemon's log. |
| 65 | path: ?[]const u8 = null, | 65 | path: ?[]const u8 = null, |
| 66 | /// True for the two verbs whose user asked for a daemon — `mux d start` | 66 | /// True for the two verbs whose user asked for a daemon — `mux d start -d` |
| 67 | /// and `mux d endpoint --start`; both probe first, so a daemon already | 67 | /// and `mux d endpoint --start`; both probe first, so a daemon already |
| 68 | /// writing its log is never truncated under it. Auto-start appends | 68 | /// writing its log is never truncated under it. Auto-start appends |
| 69 | /// instead: an attach is not a restart. | 69 | /// instead: an attach is not a restart. |
| @@ -145,10 +145,10 @@ pub fn ensureDaemon( | |||
| 145 | const devnull = std.fs.cwd().openFile("/dev/null", .{}) catch return error.SpawnFailed; | 145 | const devnull = std.fs.cwd().openFile("/dev/null", .{}) catch return error.SpawnFailed; |
| 146 | defer devnull.close(); | 146 | defer devnull.close(); |
| 147 | 147 | ||
| 148 | // argv for the child: mux d run <forwarded...>, all null-terminated. | 148 | // argv for the child: mux d start <forwarded...>, all null-terminated. |
| 149 | // The mode word is spelled out so `ps` shows a daemon as a daemon — | 149 | // The mode word is spelled out so `ps` shows a daemon as a daemon — |
| 150 | // it is the only thing separating the long-lived process from the | 150 | // it is the only thing separating the long-lived process from the |
| 151 | // `mux d start` that spawned it. | 151 | // `mux d start -d` that spawned it. |
| 152 | const exe_z = alloc.dupeZ(u8, exe_path) catch return error.SpawnFailed; | 152 | const exe_z = alloc.dupeZ(u8, exe_path) catch return error.SpawnFailed; |
| 153 | defer alloc.free(exe_z); | 153 | defer alloc.free(exe_z); |
| 154 | const argv = alloc.allocSentinel(?[*:0]const u8, run_args.len + 3, null) catch | 154 | const argv = alloc.allocSentinel(?[*:0]const u8, run_args.len + 3, null) catch |
| @@ -156,7 +156,7 @@ pub fn ensureDaemon( | |||
| 156 | defer alloc.free(argv); | 156 | defer alloc.free(argv); |
| 157 | argv[0] = "mux"; | 157 | argv[0] = "mux"; |
| 158 | argv[1] = "d"; | 158 | argv[1] = "d"; |
| 159 | argv[2] = "run"; | 159 | argv[2] = "start"; |
| 160 | for (run_args, 0..) |a, i| argv[i + 3] = a.ptr; | 160 | for (run_args, 0..) |a, i| argv[i + 3] = a.ptr; |
| 161 | 161 | ||
| 162 | progress.emitFmt("{s}: starting\u{2026}", .{progress.prefix}); | 162 | progress.emitFmt("{s}: starting\u{2026}", .{progress.prefix}); |
| @@ -495,7 +495,7 @@ test "ensureDaemon: the child execs the path it was HANDED, never a name off PAT | |||
| 495 | // shebang script it is the script, whatever argv[0] the caller wrote. | 495 | // shebang script it is the script, whatever argv[0] the caller wrote. |
| 496 | // Nothing on this box resolves by NAME to a file in a fresh tmp dir, so | 496 | // Nothing on this box resolves by NAME to a file in a fresh tmp dir, so |
| 497 | // a spawn that searched PATH cannot pass this. `$*` pins the other half | 497 | // a spawn that searched PATH cannot pass this. `$*` pins the other half |
| 498 | // — the daemon is asked for `d run`, which is also what makes a daemon | 498 | // — the daemon is asked for `d start`, which is also what makes a daemon |
| 499 | // legible in `ps`. Production hands `selfExe` and nothing else, so the | 499 | // legible in `ps`. Production hands `selfExe` and nothing else, so the |
| 500 | // same mechanism cannot reach a sibling: `mux` used to hunt PATH for a | 500 | // same mechanism cannot reach a sibling: `mux` used to hunt PATH for a |
| 501 | // `muxd`, and an e2e leg graded an installed v0.0.1-10 that way. | 501 | // `muxd`, and an e2e leg graded an installed v0.0.1-10 that way. |
| @@ -533,11 +533,11 @@ test "ensureDaemon: the child execs the path it was HANDED, never a name off PAT | |||
| 533 | // not as a FileNotFound three frames inside std. | 533 | // not as a FileNotFound three frames inside std. |
| 534 | const got = std.fs.cwd().readFile(seen, &got_buf) catch "<the child ran something else>"; | 534 | const got = std.fs.cwd().readFile(seen, &got_buf) catch "<the child ran something else>"; |
| 535 | var want_buf: [200]u8 = undefined; | 535 | var want_buf: [200]u8 = undefined; |
| 536 | const want = try std.fmt.bufPrint(&want_buf, "{s}|d run", .{stub}); | 536 | const want = try std.fmt.bufPrint(&want_buf, "{s}|d start", .{stub}); |
| 537 | try std.testing.expectEqualStrings(want, std.mem.trimRight(u8, got, "\n")); | 537 | try std.testing.expectEqualStrings(want, std.mem.trimRight(u8, got, "\n")); |
| 538 | } | 538 | } |
| 539 | 539 | ||
| 540 | test "ensureDaemon: an attach appends to the log, `muxd start` truncates it" { | 540 | test "ensureDaemon: an attach appends to the log, `mux d start -d` truncates it" { |
| 541 | var tmp = try testtmp.TmpDir.make(); | 541 | var tmp = try testtmp.TmpDir.make(); |
| 542 | defer tmp.cleanup(); | 542 | defer tmp.cleanup(); |
| 543 | var pbuf: [128]u8 = undefined; | 543 | var pbuf: [128]u8 = undefined; |
| @@ -580,7 +580,7 @@ test "ensureDaemon: an attach appends to the log, `muxd start` truncates it" { | |||
| 580 | // that wrote over the front and happened to be shorter still fails. | 580 | // that wrote over the front and happened to be shorter still fails. |
| 581 | try std.testing.expectEqualStrings(seed, try std.fs.cwd().readFile(log, &rbuf)); | 581 | try std.testing.expectEqualStrings(seed, try std.fs.cwd().readFile(log, &rbuf)); |
| 582 | 582 | ||
| 583 | // `muxd start` still truncates, and that half is asserted here rather | 583 | // `mux d start -d` still truncates, and that half is asserted here rather |
| 584 | // than assumed: the two verbs differ only in this flag, so a change that | 584 | // than assumed: the two verbs differ only in this flag, so a change that |
| 585 | // made everything append would otherwise pass unnoticed until an | 585 | // made everything append would otherwise pass unnoticed until an |
| 586 | // operator read a restarted daemon's log and found the old one's. | 586 | // operator read a restarted daemon's log and found the old one's. |
src/client/client.zig
| Old | New | ||
|---|---|---|---|
| @@ -1050,7 +1050,7 @@ pub fn spellingCap(target: Target) usize { | |||
| 1050 | } | 1050 | } |
| 1051 | 1051 | ||
| 1052 | // The grid a birth asks for. It is `main.Opts`'s own default — the size | 1052 | // The grid a birth asks for. It is `main.Opts`'s own default — the size |
| 1053 | // `mux d run` gives session 0 — because a session created for a client | 1053 | // `mux d start` gives session 0 — because a session created for a client |
| 1054 | // that claims no size has to be born at SOMETHING, and the daemon's own | 1054 | // that claims no size has to be born at SOMETHING, and the daemon's own |
| 1055 | // answer to that question is the one nobody has to explain. Wrong, and a | 1055 | // answer to that question is the one nobody has to explain. Wrong, and a |
| 1056 | // restored browser tile comes back at a shape no client ever asked for. | 1056 | // restored browser tile comes back at a shape no client ever asked for. |
src/quic.zig
| Old | New | ||
|---|---|---|---|
| @@ -420,7 +420,7 @@ test "keyRefusalBody: the words three binaries print, byte for byte" { | |||
| 420 | // These bytes ARE the contract. Every key refusal any binary prints is | 420 | // These bytes ARE the contract. Every key refusal any binary prints is |
| 421 | // a prefix, this body, and at most a suffix: | 421 | // a prefix, this body, and at most a suffix: |
| 422 | // | 422 | // |
| 423 | // mux d: <body> (mux d run --quic) | 423 | // mux d: <body> (mux d start --quic) |
| 424 | // mux d endpoint: <body>; staying on ssh | 424 | // mux d endpoint: <body>; staying on ssh |
| 425 | // mux d: endpoint_req: <body> (the daemon's lazy bind) | 425 | // mux d: endpoint_req: <body> (the daemon's lazy bind) |
| 426 | // mux: <body> (the client's dial) | 426 | // mux: <body> (the client's dial) |
src/server/server.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,4 +1,4 @@ | |||
| 1 | //! The daemon core (`mux d run`): up to max_sessions sessions (engine + pty + command | 1 | //! The daemon core (`mux d start`): up to max_sessions sessions (engine + pty + command |
| 2 | //! tracker each); a connection is a session, named at attach. One listener | 2 | //! tracker each); a connection is a session, named at attach. One listener |
| 3 | //! on the unix socket, and a second on UDP when QUIC is configured. | 3 | //! on the unix socket, and a second on UDP when QUIC is configured. |
| 4 | //! Up to max_clients attached interactive clients plus a few one-shot | 4 | //! Up to max_clients attached interactive clients plus a few one-shot |
| @@ -152,7 +152,7 @@ fn onShutdownSignal(_: c_int) callconv(.c) void { | |||
| 152 | shutdown_flag.store(true, .release); | 152 | shutdown_flag.store(true, .release); |
| 153 | } | 153 | } |
| 154 | 154 | ||
| 155 | /// Install SIGINT/SIGTERM handlers so a foreground `mux d run` shuts down | 155 | /// Install SIGINT/SIGTERM handlers so a foreground `mux d start` shuts down |
| 156 | /// cleanly (socket file removed, shell reaped). Called by main; one test | 156 | /// cleanly (socket file removed, shell reaped). Called by main; one test |
| 157 | /// borrows it for the SIGPIPE ignore. | 157 | /// borrows it for the SIGPIPE ignore. |
| 158 | pub fn installSignalHandlers() void { | 158 | pub fn installSignalHandlers() void { |
| @@ -575,7 +575,7 @@ pub const Server = struct { | |||
| 575 | /// default: the shim is not free — under zsh it costs the user their | 575 | /// default: the shim is not free — under zsh it costs the user their |
| 576 | /// `~/.zshenv`, under bash it displaces their DEBUG trap — and what | 576 | /// `~/.zshenv`, under bash it displaces their DEBUG trap — and what |
| 577 | /// it buys, a knowable exit code, is read only by `mux a`. A caller | 577 | /// it buys, a knowable exit code, is read only by `mux a`. A caller |
| 578 | /// that wants marks says so; `mux d run` says so for | 578 | /// that wants marks says so; `mux d start` says so for |
| 579 | /// `MUX_SHELL_INTEGRATION=1`. A shell shellint has no scripts for is | 579 | /// `MUX_SHELL_INTEGRATION=1`. A shell shellint has no scripts for is |
| 580 | /// unaffected either way. | 580 | /// unaffected either way. |
| 581 | shell_integration: bool = false, | 581 | shell_integration: bool = false, |
| @@ -949,7 +949,7 @@ pub const Server = struct { | |||
| 949 | // `term_grace_ms`, not one each — a supervisor's stop timeout does | 949 | // `term_grace_ms`, not one each — a supervisor's stop timeout does |
| 950 | // not grow with `max_sessions`, and a daemon SIGKILLed halfway | 950 | // not grow with `max_sessions`, and a daemon SIGKILLed halfway |
| 951 | // through leaves its socket and every per-session agent socket for | 951 | // through leaves its socket and every per-session agent socket for |
| 952 | // the next `mux d run` to reason about. | 952 | // the next `mux d start` to reason about. |
| 953 | for (&self.sessions.table) |*slot| { | 953 | for (&self.sessions.table) |*slot| { |
| 954 | if (slot.* != null) slot.*.?.pty.requestExit(); | 954 | if (slot.* != null) slot.*.?.pty.requestExit(); |
| 955 | } | 955 | } |
| @@ -1164,7 +1164,7 @@ pub const Server = struct { | |||
| 1164 | } | 1164 | } |
| 1165 | 1165 | ||
| 1166 | /// Pumps until something asks the daemon to stop. The only nonzero exit | 1166 | /// Pumps until something asks the daemon to stop. The only nonzero exit |
| 1167 | /// `mux d run` has is a boot failure in main; getting here means the | 1167 | /// `mux d start` has is a boot failure in main; getting here means the |
| 1168 | /// daemon served, so the answer is 0 whether the ask was `mux d stop` or | 1168 | /// daemon served, so the answer is 0 whether the ask was `mux d stop` or |
| 1169 | /// a SIGTERM from a supervisor — which reads a nonzero exit on a clean | 1169 | /// a SIGTERM from a supervisor — which reads a nonzero exit on a clean |
| 1170 | /// shutdown as a crash. | 1170 | /// shutdown as a crash. |
| @@ -3346,7 +3346,7 @@ pub const Server = struct { | |||
| 3346 | return a.dupe(u8, "check: oom") catch null; | 3346 | return a.dupe(u8, "check: oom") catch null; |
| 3347 | const result = std.process.Child.run(.{ | 3347 | const result = std.process.Child.run(.{ |
| 3348 | .allocator = a, | 3348 | .allocator = a, |
| 3349 | .argv = &.{ path, "d", "run", "--resume-fd", fd_str, "--check" }, | 3349 | .argv = &.{ path, "d", "start", "--resume-fd", fd_str, "--check" }, |
| 3350 | .max_output_bytes = 4096, | 3350 | .max_output_bytes = 4096, |
| 3351 | }) catch return a.dupe(u8, "check: cannot spawn candidate") catch null; | 3351 | }) catch return a.dupe(u8, "check: cannot spawn candidate") catch null; |
| 3352 | defer a.free(result.stdout); | 3352 | defer a.free(result.stdout); |
| @@ -3470,7 +3470,7 @@ pub const Server = struct { | |||
| 3470 | } | 3470 | } |
| 3471 | @memset(&self.observers, null); | 3471 | @memset(&self.observers, null); |
| 3472 | 3472 | ||
| 3473 | // Build argv: {"mux", "d", "run", "--resume-fd", "<n>"}. The | 3473 | // Build argv: {"mux", "d", "start", "--resume-fd", "<n>"}. The |
| 3474 | // candidate is by definition NEWER than this binary, so it reads the | 3474 | // candidate is by definition NEWER than this binary, so it reads the |
| 3475 | // mode word. | 3475 | // mode word. |
| 3476 | var fd_buf: [12]u8 = undefined; | 3476 | var fd_buf: [12]u8 = undefined; |
| @@ -3478,7 +3478,7 @@ pub const Server = struct { | |||
| 3478 | const argv = [_:null]?[*:0]const u8{ | 3478 | const argv = [_:null]?[*:0]const u8{ |
| 3479 | "mux", | 3479 | "mux", |
| 3480 | "d", | 3480 | "d", |
| 3481 | "run", | 3481 | "start", |
| 3482 | "--resume-fd", | 3482 | "--resume-fd", |
| 3483 | fd_str.ptr, | 3483 | fd_str.ptr, |
| 3484 | }; | 3484 | }; |
src/server/server_test_session.zig
| Old | New | ||
|---|---|---|---|
| @@ -851,7 +851,7 @@ test "Server: an attach past max_sessions is refused with exit_status, sessions | |||
| 851 | // session cannot tell "one grace for the table" from "one grace each". A | 851 | // session cannot tell "one grace for the table" from "one grace each". A |
| 852 | // supervisor with a stop timeout kills a daemon that spends the second shape, | 852 | // supervisor with a stop timeout kills a daemon that spends the second shape, |
| 853 | // and a killed daemon leaves its socket and its agent sockets unlinked for | 853 | // and a killed daemon leaves its socket and its agent sockets unlinked for |
| 854 | // the next `mux d run` to reason about. | 854 | // the next `mux d start` to reason about. |
| 855 | test "Server: a table of TERM-ignoring shells costs one grace, not one each" { | 855 | test "Server: a table of TERM-ignoring shells costs one grace, not one each" { |
| 856 | const alloc = std.testing.allocator; | 856 | const alloc = std.testing.allocator; |
| 857 | 857 | ||
src/sockpath.zig
| Old | New | ||
|---|---|---|---|
| @@ -112,7 +112,7 @@ pub fn claim(path: []const u8) !void { | |||
| 112 | // Nobody is listening — but this is NOT yet proof of a stale | 112 | // Nobody is listening — but this is NOT yet proof of a stale |
| 113 | // socket: Linux answers ECONNREFUSED for a regular file at the | 113 | // socket: Linux answers ECONNREFUSED for a regular file at the |
| 114 | // path exactly as it does for a dead socket, so connect alone | 114 | // path exactly as it does for a dead socket, so connect alone |
| 115 | // cannot tell a dead daemon from `mux d run --sock notes.txt`. | 115 | // cannot tell a dead daemon from `mux d start --sock notes.txt`. |
| 116 | // The stat below is what separates them. | 116 | // The stat below is what separates them. |
| 117 | error.ConnectionRefused => {}, | 117 | error.ConnectionRefused => {}, |
| 118 | else => |e| return e, | 118 | else => |e| return e, |
test/agent.sh
| Old | New | ||
|---|---|---|---|
| @@ -106,7 +106,7 @@ cleanup() { | |||
| 106 | "$RELAY_PID" "$SINK_PID" "$CLI_PID"; do | 106 | "$RELAY_PID" "$SINK_PID" "$CLI_PID"; do |
| 107 | [ -n "$p" ] && kill "$p" 2>/dev/null | 107 | [ -n "$p" ] && kill "$p" 2>/dev/null |
| 108 | done | 108 | done |
| 109 | # ...and by socket, for the window between `mux d start`'s fork and the | 109 | # ...and by socket, for the window between `mux d start -d`'s fork and the |
| 110 | # up-line this script reads its pid from. `mux d stop` on a path nobody | 110 | # up-line this script reads its pid from. `mux d stop` on a path nobody |
| 111 | # serves is a no-op. These must precede the rm -rf: unlinking the sockets | 111 | # serves is a no-op. These must precede the rm -rf: unlinking the sockets |
| 112 | # first would leave a live daemon nothing could reach by path. | 112 | # first would leave a live daemon nothing could reach by path. |
| @@ -280,7 +280,7 @@ leakcheck() { | |||
| 280 | # unreadable verdict AND the `mux d stop` that failed to kill anything. | 280 | # unreadable verdict AND the `mux d stop` that failed to kill anything. |
| 281 | kill -0 "$1" 2>/dev/null && { why "daemon still alive ${_i}x50ms after stop — leak verdict unreadable"; return 1; } | 281 | kill -0 "$1" 2>/dev/null && { why "daemon still alive ${_i}x50ms after stop — leak verdict unreadable"; return 1; } |
| 282 | _dl=$(daemon_log "$2") | 282 | _dl=$(daemon_log "$2") |
| 283 | # A missing log is a broken convention, not a clean daemon: `mux d start` | 283 | # A missing log is a broken convention, not a clean daemon: `mux d start -d` |
| 284 | # creates this file before it forks, so every daemon that ever existed has | 284 | # creates this file before it forks, so every daemon that ever existed has |
| 285 | # one. Passing on its absence is how this gate would go quietly vacuous. | 285 | # one. Passing on its absence is how this gate would go quietly vacuous. |
| 286 | [ -f "$_dl" ] || { why "no daemon log at $_dl — the leak verdict was never captured"; return 1; } | 286 | [ -f "$_dl" ] || { why "no daemon log at $_dl — the leak verdict was never captured"; return 1; } |
| @@ -292,10 +292,10 @@ leakcheck() { | |||
| 292 | # script guessed from a process name: the suite kills what it started, and a | 292 | # script guessed from a process name: the suite kills what it started, and a |
| 293 | # name match can only ever name a bystander. | 293 | # name match can only ever name a bystander. |
| 294 | # | 294 | # |
| 295 | # $_log catches what `mux d start` ITSELF prints (the up-line, or the refusal). | 295 | # $_log catches what `mux d start -d` ITSELF prints (the up-line, or the refusal). |
| 296 | # The daemon's own output goes somewhere else entirely: start detaches the | 296 | # The daemon's own output goes somewhere else entirely: start detaches the |
| 297 | # child onto $XDG_STATE_HOME/mux/muxd.log — a path computed from the | 297 | # child onto $XDG_STATE_HOME/mux/muxd.log — a path computed from the |
| 298 | # environment `mux d start` is run with — and TRUNCATES it at every spawn. One | 298 | # environment `mux d start -d` is run with — and TRUNCATES it at every spawn. One |
| 299 | # state home for the whole suite would therefore leave exactly one daemon's | 299 | # state home for the whole suite would therefore leave exactly one daemon's |
| 300 | # allocator verdict readable, the last one's, with every earlier verdict | 300 | # allocator verdict readable, the last one's, with every earlier verdict |
| 301 | # overwritten by the next scenario's spawn. So each daemon gets a state home | 301 | # overwritten by the next scenario's spawn. So each daemon gets a state home |
| @@ -306,7 +306,7 @@ start_daemon() { | |||
| 306 | shift | 306 | shift |
| 307 | _tag=$(basename "$_log" .log) | 307 | _tag=$(basename "$_log" .log) |
| 308 | mkdir -p "$TMP/state/$_tag/mux" || return 1 | 308 | mkdir -p "$TMP/state/$_tag/mux" || return 1 |
| 309 | XDG_STATE_HOME="$TMP/state/$_tag" "$MUX" d start "$@" >"$_log" 2>&1 | 309 | XDG_STATE_HOME="$TMP/state/$_tag" "$MUX" d start -d "$@" >"$_log" 2>&1 |
| 310 | sed -n 's/^up .*pid=\([0-9]*\).*/\1/p' "$_log" | head -1 | 310 | sed -n 's/^up .*pid=\([0-9]*\).*/\1/p' "$_log" | head -1 |
| 311 | } | 311 | } |
| 312 | 312 | ||
| @@ -358,7 +358,7 @@ start_ready() { | |||
| 358 | # moment ago, and between that proof and this bind a concurrent suite or an | 358 | # moment ago, and between that proof and this bind a concurrent suite or an |
| 359 | # outgoing connection can take it — the daemon holds no SO_REUSEADDR and refuses to | 359 | # outgoing connection can take it — the daemon holds no SO_REUSEADDR and refuses to |
| 360 | # share a port rather than split its datagrams, so it prints `a daemon is | 360 | # share a port rather than split its datagrams, so it prints `a daemon is |
| 361 | # already listening on udp ...` into its own log and exits, and `mux d start` | 361 | # already listening on udp ...` into its own log and exits, and `mux d start -d` |
| 362 | # reports no up-line. That refusal is the ONLY failure another port can fix, | 362 | # reports no up-line. That refusal is the ONLY failure another port can fix, |
| 363 | # and it is told apart from the rest by the daemon's own words: anything else | 363 | # and it is told apart from the rest by the daemon's own words: anything else |
| 364 | # stops here and is reported, because a daemon that cannot start is a defect and | 364 | # stops here and is reported, because a daemon that cannot start is a defect and |
| @@ -450,7 +450,7 @@ run_scenario "marks: exit codes and output come back from a bash session" scen_m | |||
| 450 | 450 | ||
| 451 | # --- The ephemeral TUI, which two scenarios share -------------------------- | 451 | # --- The ephemeral TUI, which two scenarios share -------------------------- |
| 452 | # The spec's field specimen: a daemon whose session is not a shell at all but a | 452 | # The spec's field specimen: a daemon whose session is not a shell at all but a |
| 453 | # throwaway full-screen program. `mux d start` has no `--` argv, and --shell | 453 | # throwaway full-screen program. `mux d start -d` has no `--` argv, and --shell |
| 454 | # execs whatever path it is given, so a one-line wrapper carries the argument. | 454 | # execs whatever path it is given, so a one-line wrapper carries the argument. |
| 455 | # HOME points into the tmpdir for e2e.sh's $SHELL reason: ~/.lesskey and | 455 | # HOME points into the tmpdir for e2e.sh's $SHELL reason: ~/.lesskey and |
| 456 | # ~/.vimrc are arbitrary code on the session under test. | 456 | # ~/.vimrc are arbitrary code on the session under test. |
test/bench.sh
| Old | New | ||
|---|---|---|---|
| @@ -10,7 +10,7 @@ XDG_STATE_HOME="${TMPDIR:-/tmp}/muxd-bench-$$-state"; export XDG_STATE_HOME | |||
| 10 | cleanup() { kill "$DPID" 2>/dev/null || true; rm -rf "$SOCK" "$XDG_STATE_HOME"; } | 10 | cleanup() { kill "$DPID" 2>/dev/null || true; rm -rf "$SOCK" "$XDG_STATE_HOME"; } |
| 11 | trap cleanup EXIT INT TERM | 11 | trap cleanup EXIT INT TERM |
| 12 | 12 | ||
| 13 | "$MUX" d run --sock "$SOCK" --shell /bin/sh & | 13 | "$MUX" d start --sock "$SOCK" --shell /bin/sh & |
| 14 | DPID=$! | 14 | DPID=$! |
| 15 | i=0 | 15 | i=0 |
| 16 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | 16 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done |
test/coverage.sh
| Old | New | ||
|---|---|---|---|
| @@ -22,13 +22,13 @@ | |||
| 22 | # the one signal a wrapper cannot forward, so a leg that kill -9s a | 22 | # the one signal a wrapper cannot forward, so a leg that kill -9s a |
| 23 | # daemon would leave the real one alive holding its socket. e2e.sh's | 23 | # daemon would leave the real one alive holding its socket. e2e.sh's |
| 24 | # hardkill answers that; see the comment there. | 24 | # hardkill answers that; see the comment there. |
| 25 | # - kcov waits for the LAST traced descendant, and `mux d start` daemonises: | 25 | # - kcov waits for the LAST traced descendant, and `mux d start -d` daemonises: |
| 26 | # its launcher forks, the parent exits, and the daemon reparents to init. | 26 | # its launcher forks, the parent exits, and the daemon reparents to init. |
| 27 | # Traced, that daemon outlives the scenario, so kcov never exits and the | 27 | # Traced, that daemon outlives the scenario, so kcov never exits and the |
| 28 | # suite's `wait` on it never returns — a hang at the auto-start leg that | 28 | # suite's `wait` on it never returns — a hang at the auto-start leg that |
| 29 | # reads as a slow run, and one that ignores SIGTERM, so `timeout` will not | 29 | # reads as a slow run, and one that ignores SIGTERM, so `timeout` will not |
| 30 | # free it either. The shims therefore run `start` BARE, exporting | 30 | # free it either. The shims therefore run `start` BARE, exporting |
| 31 | # MUX_KCOV_ACTIVE so the `mux d run` it re-execs stays bare too. The cost | 31 | # MUX_KCOV_ACTIVE so the `mux d start -d` it re-execs stays bare too. The cost |
| 32 | # is the auto-started daemons' lines; the alternative was a suite that | 32 | # is the auto-started daemons' lines; the alternative was a suite that |
| 33 | # could not finish. `endpoint` is the same shape one hop out: it spawns | 33 | # could not finish. `endpoint` is the same shape one hop out: it spawns |
| 34 | # the daemon it announces, and the ssh handoff client then kill()s its | 34 | # the daemon it announces, and the ssh handoff client then kill()s its |
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -23,8 +23,8 @@ WSCLIENT="$6" | |||
| 23 | # The ssh recipes a handoff leg drives name `mux` and let the remote's PATH | 23 | # The ssh recipes a handoff leg drives name `mux` and let the remote's PATH |
| 24 | # find it, so the build's own bin directory goes in front of the shims that | 24 | # find it, so the build's own bin directory goes in front of the shims that |
| 25 | # stand in for a remote. The client's LOCAL auto-start no longer reads PATH | 25 | # stand in for a remote. The client's LOCAL auto-start no longer reads PATH |
| 26 | # at all — it execs this same binary with argv `mux d run …` (spawn.zig) — | 26 | # at all — it execs this same binary with argv `mux d start …` (spawn.zig) — |
| 27 | # so a leg that forgets its `mux d run &` can no longer silently grade an | 27 | # so a leg that forgets its `mux d start &` can no longer silently grade an |
| 28 | # installed release, which it did against a v0.0.1-10 with no agent code. | 28 | # installed release, which it did against a v0.0.1-10 with no agent code. |
| 29 | PATH="$(cd "$(dirname "$MUX")" && pwd):$PATH" | 29 | PATH="$(cd "$(dirname "$MUX")" && pwd):$PATH" |
| 30 | export PATH | 30 | export PATH |
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -27,7 +27,7 @@ SOCK10="${TMPDIR:-/tmp}/muxd-e2e-flagwins-$$.sock" | |||
| 27 | defer_sock "$SOCK10" | 27 | defer_sock "$SOCK10" |
| 28 | QPORT2=$(( 26000 + ($$ % 4000) )) | 28 | QPORT2=$(( 26000 + ($$ % 4000) )) |
| 29 | QPORT3=$(( 31000 + ($$ % 4000) )) | 29 | QPORT3=$(( 31000 + ($$ % 4000) )) |
| 30 | # M10 `mux d start`. These daemons are spawned DETACHED, so the suite never | 30 | # M10 `mux d start -d`. These daemons are spawned DETACHED, so the suite never |
| 31 | # holds their pids as shell jobs — it reads them off the up-line and kills | 31 | # holds their pids as shell jobs — it reads them off the up-line and kills |
| 32 | # by that tracked pid, never by name. | 32 | # by that tracked pid, never by name. |
| 33 | SOCK8="${TMPDIR:-/tmp}/muxd-e2e-start-$$.sock" | 33 | SOCK8="${TMPDIR:-/tmp}/muxd-e2e-start-$$.sock" |
| @@ -406,15 +406,15 @@ refuse() { | |||
| 406 | # Timed out rather than trusted to exit: every case here is a refusal, so | 406 | # Timed out rather than trusted to exit: every case here is a refusal, so |
| 407 | # a regression that ACCEPTS one would otherwise run a daemon forever and | 407 | # a regression that ACCEPTS one would otherwise run a daemon forever and |
| 408 | # hang the suite instead of failing it. 124 is a distinguishable answer. | 408 | # hang the suite instead of failing it. 124 is a distinguishable answer. |
| 409 | env XDG_CONFIG_HOME="$_cfg" timeout 10 "$MUX" d run --sock "$SOCK4" \ | 409 | env XDG_CONFIG_HOME="$_cfg" timeout 10 "$MUX" d start --sock "$SOCK4" \ |
| 410 | --shell /bin/sh "$@" > "$OUT.q" 2>&1 | 410 | --shell /bin/sh "$@" > "$OUT.q" 2>&1 |
| 411 | _rc=$? | 411 | _rc=$? |
| 412 | set -e | 412 | set -e |
| 413 | [ "$_rc" -eq "$_want" ] || { | 413 | [ "$_rc" -eq "$_want" ] || { |
| 414 | echo "e2e FAIL: mux d run $* exited $_rc (want $_want)"; cat "$OUT.q"; exit 1; | 414 | echo "e2e FAIL: mux d start $* exited $_rc (want $_want)"; cat "$OUT.q"; exit 1; |
| 415 | } | 415 | } |
| 416 | [ ! -e "$SOCK4" ] || { | 416 | [ ! -e "$SOCK4" ] || { |
| 417 | echo "e2e FAIL: mux d run $* was refused but left $SOCK4 behind"; exit 1; | 417 | echo "e2e FAIL: mux d start $* was refused but left $SOCK4 behind"; exit 1; |
| 418 | } | 418 | } |
| 419 | # One line of complaint plus the usage block, and not a stack trace: a | 419 | # One line of complaint plus the usage block, and not a stack trace: a |
| 420 | # Zig panic runs to dozens of lines and names a source file, which is | 420 | # Zig panic runs to dozens of lines and names a source file, which is |
| @@ -429,7 +429,7 @@ refuse() { | |||
| 429 | _usage_lines=$("$MUX" d 2>&1 | wc -l) | 429 | _usage_lines=$("$MUX" d 2>&1 | wc -l) |
| 430 | _lines=$(wc -l < "$OUT.q") | 430 | _lines=$(wc -l < "$OUT.q") |
| 431 | { [ "$_lines" -le $((_usage_lines + 2)) ] && [ "$_lines" -le 15 ]; } || { | 431 | { [ "$_lines" -le $((_usage_lines + 2)) ] && [ "$_lines" -le 15 ]; } || { |
| 432 | echo "e2e FAIL: mux d run $* answered with more than a message:"; cat "$OUT.q"; exit 1; | 432 | echo "e2e FAIL: mux d start $* answered with more than a message:"; cat "$OUT.q"; exit 1; |
| 433 | } | 433 | } |
| 434 | } | 434 | } |
| 435 | 435 | ||
| @@ -454,7 +454,7 @@ refuse 1 "$XDG_CONFIG_HOME" --quic "127.0.0.1:$QPORT" --key "$QKEY.bad" | |||
| 454 | refuse 1 "$XDG_CONFIG_HOME" --quic "127.0.0.1:$QPORT" --key "$QKEY.missing" | 454 | refuse 1 "$XDG_CONFIG_HOME" --quic "127.0.0.1:$QPORT" --key "$QKEY.missing" |
| 455 | 455 | ||
| 456 | # The key-missing message must name the way out, not just the absence. | 456 | # The key-missing message must name the way out, not just the absence. |
| 457 | env XDG_CONFIG_HOME="$NOKEY_CFG" "$MUX" d run --sock "$SOCK4.nokey" \ | 457 | env XDG_CONFIG_HOME="$NOKEY_CFG" "$MUX" d start --sock "$SOCK4.nokey" \ |
| 458 | --quic "127.0.0.1:$QPORT" 2> "$OUT.nokey" || true | 458 | --quic "127.0.0.1:$QPORT" 2> "$OUT.nokey" || true |
| 459 | grep -q "mux d keygen" "$OUT.nokey" || { | 459 | grep -q "mux d keygen" "$OUT.nokey" || { |
| 460 | echo "e2e FAIL: key-missing message does not name keygen"; cat "$OUT.nokey"; exit 1; } | 460 | echo "e2e FAIL: key-missing message does not name keygen"; cat "$OUT.nokey"; exit 1; } |
| @@ -484,7 +484,7 @@ grep -qi " $QHEX " /proc/net/udp || { | |||
| 484 | set +e | 484 | set +e |
| 485 | # Same reasoning as refuse(): if the second daemon ever succeeds it runs | 485 | # Same reasoning as refuse(): if the second daemon ever succeeds it runs |
| 486 | # until killed, so the failure has to be a timeout rather than a hang. | 486 | # until killed, so the failure has to be a timeout rather than a hang. |
| 487 | timeout 10 "$MUX" d run --sock "$SOCK4.second" --shell /bin/sh \ | 487 | timeout 10 "$MUX" d start --sock "$SOCK4.second" --shell /bin/sh \ |
| 488 | --quic "127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000 > "$OUT.q" 2>&1 | 488 | --quic "127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000 > "$OUT.q" 2>&1 |
| 489 | RC=$? | 489 | RC=$? |
| 490 | set -e | 490 | set -e |
| @@ -764,7 +764,7 @@ D4PID="" | |||
| 764 | # 1. MUX_KEY_FILE with no --key anywhere. The daemon's env support had no | 764 | # 1. MUX_KEY_FILE with no --key anywhere. The daemon's env support had no |
| 765 | # automated coverage at all: envKey() reads the real environment, so | 765 | # automated coverage at all: envKey() reads the real environment, so |
| 766 | # only a daemon started with it set can prove the path works. | 766 | # only a daemon started with it set can prove the path works. |
| 767 | env MUX_KEY_FILE="$QKEY" "$MUX" d run --sock "$SOCK9" --shell /bin/sh \ | 767 | env MUX_KEY_FILE="$QKEY" "$MUX" d start --sock "$SOCK9" --shell /bin/sh \ |
| 768 | --quic "127.0.0.1:$QPORT2" --quic-idle-ms 3000 > "$OUT.d9.d" 2>&1 & | 768 | --quic "127.0.0.1:$QPORT2" --quic-idle-ms 3000 > "$OUT.d9.d" 2>&1 & |
| 769 | D9PID=$! | 769 | D9PID=$! |
| 770 | defer_kill "$D9PID" | 770 | defer_kill "$D9PID" |
| @@ -786,7 +786,7 @@ D9PID="" | |||
| 786 | # This is the shape that catches the two being swapped at the call site | 786 | # This is the shape that catches the two being swapped at the call site |
| 787 | # in run() — pickKey's unit test pins the ordering inside the function | 787 | # in run() — pickKey's unit test pins the ordering inside the function |
| 788 | # and is blind to the order they are handed to it. | 788 | # and is blind to the order they are handed to it. |
| 789 | env MUX_KEY_FILE="$QKEY.wrong" "$MUX" d run --sock "$SOCK10" --shell /bin/sh \ | 789 | env MUX_KEY_FILE="$QKEY.wrong" "$MUX" d start --sock "$SOCK10" --shell /bin/sh \ |
| 790 | --quic "127.0.0.1:$QPORT3" --key "$QKEY" --quic-idle-ms 3000 > "$OUT.d10.d" 2>&1 & | 790 | --quic "127.0.0.1:$QPORT3" --key "$QKEY" --quic-idle-ms 3000 > "$OUT.d10.d" 2>&1 & |
| 791 | D10PID=$! | 791 | D10PID=$! |
| 792 | defer_kill "$D10PID" | 792 | defer_kill "$D10PID" |
| @@ -807,13 +807,13 @@ ok "daemon honours MUX_KEY_FILE, and --key beats it" | |||
| 807 | rm_swept "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKEY.wrong" \ | 807 | rm_swept "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKEY.wrong" \ |
| 808 | "$OUT.qc.err" "$OUT.qr.err" "$OUT.qk.err" | 808 | "$OUT.qc.err" "$OUT.qr.err" "$OUT.qk.err" |
| 809 | 809 | ||
| 810 | # --- M10: mux d start — detached spawn, no-op rerun, race, pinned lines. | 810 | # --- M10: mux d start -d — detached spawn, no-op rerun, race, pinned lines. |
| 811 | # | 811 | # |
| 812 | # Every scenario below asserts a MARKER through the session, never just $?. | 812 | # Every scenario below asserts a MARKER through the session, never just $?. |
| 813 | # A client that fails to authenticate exits 0 by way of "aborted before | 813 | # A client that fails to authenticate exits 0 by way of "aborted before |
| 814 | # attaching", so an exit code cannot distinguish a working session from a | 814 | # attaching", so an exit code cannot distinguish a working session from a |
| 815 | # key or transport regression — only bytes coming back out of the shell can. | 815 | # key or transport regression — only bytes coming back out of the shell can. |
| 816 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.start" | 816 | "$MUX" d start -d --sock "$SOCK8" 2> "$OUT.start" |
| 817 | grep -q '^mux d: starting' "$OUT.start" || { | 817 | grep -q '^mux d: starting' "$OUT.start" || { |
| 818 | echo "e2e FAIL: start printed no starting line"; cat "$OUT.start"; exit 1; } | 818 | echo "e2e FAIL: start printed no starting line"; cat "$OUT.start"; exit 1; } |
| 819 | grep -q '^up (' "$OUT.start" || { | 819 | grep -q '^up (' "$OUT.start" || { |
| @@ -827,14 +827,14 @@ SPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.start") | |||
| 827 | defer_kill "$SPID" | 827 | defer_kill "$SPID" |
| 828 | [ -n "$SPID" ] || { echo "e2e FAIL: up line carries no pid"; exit 1; } | 828 | [ -n "$SPID" ] || { echo "e2e FAIL: up line carries no pid"; exit 1; } |
| 829 | kill -0 "$SPID" || { echo "e2e FAIL: started daemon not alive"; exit 1; } | 829 | kill -0 "$SPID" || { echo "e2e FAIL: started daemon not alive"; exit 1; } |
| 830 | # `mux d start` is the OTHER production spawn, and it wears the name too. | 830 | # `mux d start -d` is the OTHER production spawn, and it wears the name too. |
| 831 | # It shares `spawn.selfExe` with the client's auto-start (pinned the same | 831 | # It shares `spawn.selfExe` with the client's auto-start (pinned the same |
| 832 | # way in 03_side), so this holds today by construction — which is exactly | 832 | # way in 03_side), so this holds today by construction — which is exactly |
| 833 | # why it is asserted here: a later split of the two callers would leave | 833 | # why it is asserted here: a later split of the two callers would leave |
| 834 | # `d start` free to hand execve the /proc link and name its daemon `exe`. | 834 | # `d start` free to hand execve the /proc link and name its daemon `exe`. |
| 835 | SCOMM=$(cat "/proc/$SPID/comm") | 835 | SCOMM=$(cat "/proc/$SPID/comm") |
| 836 | [ "$SCOMM" = "mux" ] || { | 836 | [ "$SCOMM" = "mux" ] || { |
| 837 | echo "e2e FAIL: the daemon mux d start brought up has comm '$SCOMM', want 'mux'" | 837 | echo "e2e FAIL: the daemon mux d start -d brought up has comm '$SCOMM', want 'mux'" |
| 838 | exit 1; } | 838 | exit 1; } |
| 839 | # Non-tty stderr: exactly two lines, no dots. | 839 | # Non-tty stderr: exactly two lines, no dots. |
| 840 | [ "$(wc -l < "$OUT.start")" = "2" ] || { | 840 | [ "$(wc -l < "$OUT.start")" = "2" ] || { |
| @@ -858,7 +858,7 @@ LPERMS=$(stat -c %a "$MUXLOG") | |||
| 858 | [ "$LPERMS" = "600" ] || { echo "e2e FAIL: daemon log perms $LPERMS, want 600"; exit 1; } | 858 | [ "$LPERMS" = "600" ] || { echo "e2e FAIL: daemon log perms $LPERMS, want 600"; exit 1; } |
| 859 | 859 | ||
| 860 | # Rerun: silent no-op beyond the already-running line, exit 0, same daemon. | 860 | # Rerun: silent no-op beyond the already-running line, exit 0, same daemon. |
| 861 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.start2" | 861 | "$MUX" d start -d --sock "$SOCK8" 2> "$OUT.start2" |
| 862 | grep -q "already running on $SOCK8 (stop it first with \`mux d stop --sock $SOCK8\`" "$OUT.start2" || { | 862 | grep -q "already running on $SOCK8 (stop it first with \`mux d stop --sock $SOCK8\`" "$OUT.start2" || { |
| 863 | echo "e2e FAIL: rerun did not say already running"; cat "$OUT.start2"; exit 1; } | 863 | echo "e2e FAIL: rerun did not say already running"; cat "$OUT.start2"; exit 1; } |
| 864 | "$MUX" d dump --sock "$SOCK8" | grep -q "start-works" || { | 864 | "$MUX" d dump --sock "$SOCK8" | grep -q "start-works" || { |
| @@ -869,7 +869,7 @@ grep -q "already running on $SOCK8 (stop it first with \`mux d stop --sock $SOCK | |||
| 869 | # restart. Padded, then a real spawn on a fresh path must shrink it. | 869 | # restart. Padded, then a real spawn on a fresh path must shrink it. |
| 870 | dd if=/dev/zero bs=1024 count=8 2>/dev/null >> "$MUXLOG" | 870 | dd if=/dev/zero bs=1024 count=8 2>/dev/null >> "$MUXLOG" |
| 871 | PADDED=$(stat -c %s "$MUXLOG") | 871 | PADDED=$(stat -c %s "$MUXLOG") |
| 872 | "$MUX" d start --sock "$SOCK8T" 2> "$OUT.start" | 872 | "$MUX" d start -d --sock "$SOCK8T" 2> "$OUT.start" |
| 873 | TPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.start") | 873 | TPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.start") |
| 874 | defer_kill "$TPID" | 874 | defer_kill "$TPID" |
| 875 | [ -n "$TPID" ] || { echo "e2e FAIL: truncation spawn reported no pid"; exit 1; } | 875 | [ -n "$TPID" ] || { echo "e2e FAIL: truncation spawn reported no pid"; exit 1; } |
| @@ -883,8 +883,8 @@ TPID="" | |||
| 883 | # survives — a second daemon on the path would have started a fresh shell). | 883 | # survives — a second daemon on the path would have started a fresh shell). |
| 884 | softkill "$SPID" && wait_gone "$SOCK8" | 884 | softkill "$SPID" && wait_gone "$SOCK8" |
| 885 | SPID="" | 885 | SPID="" |
| 886 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.ra" & RA=$!; defer_kill "$RA" | 886 | "$MUX" d start -d --sock "$SOCK8" 2> "$OUT.ra" & RA=$!; defer_kill "$RA" |
| 887 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.rb" & RB=$!; defer_kill "$RB" | 887 | "$MUX" d start -d --sock "$SOCK8" 2> "$OUT.rb" & RB=$!; defer_kill "$RB" |
| 888 | set +e | 888 | set +e |
| 889 | wait "$RA"; RCA=$? | 889 | wait "$RA"; RCA=$? |
| 890 | wait "$RB"; RCB=$? | 890 | wait "$RB"; RCB=$? |
| @@ -905,26 +905,26 @@ defer_kill "$SPID" | |||
| 905 | # instead of unlinking the winner's socket and binding over it. The race | 905 | # instead of unlinking the winner's socket and binding over it. The race |
| 906 | # above cannot see that — both starts report success either way, and the | 906 | # above cannot see that — both starts report success either way, and the |
| 907 | # marker comes back from whichever daemon owns the path — so it is pinned | 907 | # marker comes back from whichever daemon owns the path — so it is pinned |
| 908 | # head-on: a second `mux d run` against a live socket must refuse. | 908 | # head-on: a second `mux d start` against a live socket must refuse. |
| 909 | set +e | 909 | set +e |
| 910 | timeout 10 "$MUX" d run --sock "$SOCK8" --shell /bin/sh > "$OUT.rb" 2>&1 | 910 | timeout 10 "$MUX" d start --sock "$SOCK8" --shell /bin/sh > "$OUT.rb" 2>&1 |
| 911 | RCS=$? | 911 | RCS=$? |
| 912 | set -e | 912 | set -e |
| 913 | [ "$RCS" -eq 1 ] || { | 913 | [ "$RCS" -eq 1 ] || { |
| 914 | echo "e2e FAIL: second mux d run on a live socket exited $RCS (want 1; 124 means it took the path)" | 914 | echo "e2e FAIL: second mux d start on a live socket exited $RCS (want 1; 124 means it took the path)" |
| 915 | cat "$OUT.rb"; exit 1; } | 915 | cat "$OUT.rb"; exit 1; } |
| 916 | grep -q "already running" "$OUT.rb" || { | 916 | grep -q "already running" "$OUT.rb" || { |
| 917 | echo "e2e FAIL: second mux d run refused, but not as already-running"; cat "$OUT.rb"; exit 1; } | 917 | echo "e2e FAIL: second mux d start refused, but not as already-running"; cat "$OUT.rb"; exit 1; } |
| 918 | # ...and the session the winner was serving is undisturbed. | 918 | # ...and the session the winner was serving is undisturbed. |
| 919 | "$MUX" d dump --sock "$SOCK8" | grep -q "race-one" || { | 919 | "$MUX" d dump --sock "$SOCK8" | grep -q "race-one" || { |
| 920 | echo "e2e FAIL: the refused daemon disturbed the live session"; exit 1; } | 920 | echo "e2e FAIL: the refused daemon disturbed the live session"; exit 1; } |
| 921 | # The race client's own capture, checked here rather than above: the refused | 921 | # The race client's own capture, checked here rather than above: the refused |
| 922 | # `mux d run` between the two touches no session, so the grid has not moved. | 922 | # `mux d start` between the two touches no session, so the grid has not moved. |
| 923 | assert_converged "$OUT.race" "$SOCK8" "start race" | 923 | assert_converged "$OUT.race" "$SOCK8" "start race" |
| 924 | 924 | ||
| 925 | softkill "$SPID" || true | 925 | softkill "$SPID" || true |
| 926 | SPID="" | 926 | SPID="" |
| 927 | ok "mux d start — spawn, no-op rerun, log truncation, race" | 927 | ok "mux d start -d — spawn, no-op rerun, log truncation, race" |
| 928 | 928 | ||
| 929 | # --- `mux d endpoint --start` ensures the daemon ON THE SOCKET IT PROBES. | 929 | # --- `mux d endpoint --start` ensures the daemon ON THE SOCKET IT PROBES. |
| 930 | # | 930 | # |
| @@ -1004,7 +1004,7 @@ ESPID="" | |||
| 1004 | ok "mux d endpoint --start ensures a daemon on the socket it announces, and on no other" | 1004 | ok "mux d endpoint --start ensures a daemon on the socket it announces, and on no other" |
| 1005 | 1005 | ||
| 1006 | # --- M10: a start whose daemon dies young REPORTS it. This is the first-run | 1006 | # --- M10: a start whose daemon dies young REPORTS it. This is the first-run |
| 1007 | # mistake the failure line exists for — `ssh HOST 'mux d start --quic 0.0.0.0'` | 1007 | # mistake the failure line exists for — `ssh HOST 'mux d start -d --quic 0.0.0.0'` |
| 1008 | # before the key was ever scp'd — so it must be a message, not a panic. The | 1008 | # before the key was ever scp'd — so it must be a message, not a panic. The |
| 1009 | # child exits on the missing key, the poll loop reaps it, and polling | 1009 | # child exits on the missing key, the poll loop reaps it, and polling |
| 1010 | # continues to the deadline; a second waitpid there gets ECHILD, which the | 1010 | # continues to the deadline; a second waitpid there gets ECHILD, which the |
| @@ -1013,7 +1013,7 @@ ok "mux d endpoint --start ensures a daemon on the socket it announces, and on n | |||
| 1013 | DEADCFG="${TMPDIR:-/tmp}/mux-e2e-deadchild-$$" | 1013 | DEADCFG="${TMPDIR:-/tmp}/mux-e2e-deadchild-$$" |
| 1014 | defer_rm "$DEADCFG" | 1014 | defer_rm "$DEADCFG" |
| 1015 | set +e | 1015 | set +e |
| 1016 | env XDG_CONFIG_HOME="$DEADCFG" timeout 30 "$MUX" d start --sock "$SOCK8T" \ | 1016 | env XDG_CONFIG_HOME="$DEADCFG" timeout 30 "$MUX" d start -d --sock "$SOCK8T" \ |
| 1017 | --quic "127.0.0.1:1" --key /nonexistent > "$OUT.dead" 2>&1 | 1017 | --quic "127.0.0.1:1" --key /nonexistent > "$OUT.dead" 2>&1 |
| 1018 | DRC=$? | 1018 | DRC=$? |
| 1019 | set -e | 1019 | set -e |
| @@ -1041,7 +1041,7 @@ ok "a start whose daemon dies young says so, with the log path" | |||
| 1041 | # explicit loopback port (4433 on the suite machine is somebody's daemon). | 1041 | # explicit loopback port (4433 on the suite machine is somebody's daemon). |
| 1042 | # No --key on either side — the key is the one `mux d keygen` wrote at the | 1042 | # No --key on either side — the key is the one `mux d keygen` wrote at the |
| 1043 | # hermetic default path near the top of this suite. | 1043 | # hermetic default path near the top of this suite. |
| 1044 | "$MUX" d start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal" | 1044 | "$MUX" d start -d --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal" |
| 1045 | GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal") | 1045 | GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal") |
| 1046 | defer_kill "$GPID" | 1046 | defer_kill "$GPID" |
| 1047 | [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; } | 1047 | [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; } |
test/e2e_03_side.sh
| Old | New | ||
|---|---|---|---|
| @@ -25,7 +25,7 @@ defer_sock "$SOCK15" | |||
| 25 | SOCK24="${TMPDIR:-/tmp}/muxd-e2e-bell-$$.sock" | 25 | SOCK24="${TMPDIR:-/tmp}/muxd-e2e-bell-$$.sock" |
| 26 | defer_sock "$SOCK24" | 26 | defer_sock "$SOCK24" |
| 27 | 27 | ||
| 28 | # --- M13: proxy reaches a daemon, `mux d start` makes one, mux d stop ---- | 28 | # --- M13: proxy reaches a daemon, `mux d start -d` makes one, mux d stop ---- |
| 29 | # | 29 | # |
| 30 | # Nothing is serving SOCK14, and `mux d proxy` no longer answers that by | 30 | # Nothing is serving SOCK14, and `mux d proxy` no longer answers that by |
| 31 | # starting a daemon: it is the verb a client reaches over ssh, and a wall | 31 | # starting a daemon: it is the verb a client reaches over ssh, and a wall |
| @@ -61,12 +61,12 @@ grep -q "mux d proxy: cannot connect to $SOCK14" "$OUT.ascold" || { | |||
| 61 | # zero. Unlike the M10 start block's version of this gap (:1150) it is | 61 | # zero. Unlike the M10 start block's version of this gap (:1150) it is |
| 62 | # covered anyway: the trap also stops SOCK14/SOCK15 by PATH, which needs | 62 | # covered anyway: the trap also stops SOCK14/SOCK15 by PATH, which needs |
| 63 | # no pid. | 63 | # no pid. |
| 64 | "$MUX" d start --sock "$SOCK14" --shell /bin/sh 2> "$OUT.as.start" || { | 64 | "$MUX" d start -d --sock "$SOCK14" --shell /bin/sh 2> "$OUT.as.start" || { |
| 65 | echo "e2e FAIL: mux d start refused $SOCK14:"; cat "$OUT.as.start"; exit 1; } | 65 | echo "e2e FAIL: mux d start -d refused $SOCK14:"; cat "$OUT.as.start"; exit 1; } |
| 66 | APID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.as.start" | head -1) | 66 | APID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.as.start" | head -1) |
| 67 | defer_kill "$APID" | 67 | defer_kill "$APID" |
| 68 | [ -n "$APID" ] || { | 68 | [ -n "$APID" ] || { |
| 69 | echo "e2e FAIL: mux d start's up-line carries no pid"; cat "$OUT.as.start"; exit 1; } | 69 | echo "e2e FAIL: mux d start -d's up-line carries no pid"; cat "$OUT.as.start"; exit 1; } |
| 70 | kill -0 "$APID" || { echo "e2e FAIL: the started daemon is not alive"; exit 1; } | 70 | kill -0 "$APID" || { echo "e2e FAIL: the started daemon is not alive"; exit 1; } |
| 71 | 71 | ||
| 72 | pipe_mux "$OUT.as" "$OUT.as.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUX d proxy --sock $SOCK14" | 72 | pipe_mux "$OUT.as" "$OUT.as.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUX d proxy --sock $SOCK14" |
| @@ -134,7 +134,7 @@ grep -q "nothing listening on $SOCK14" "$OUT.stop2" || { | |||
| 134 | echo "e2e FAIL: stop-when-nothing said the wrong thing"; cat "$OUT.stop2"; exit 1; } | 134 | echo "e2e FAIL: stop-when-nothing said the wrong thing"; cat "$OUT.stop2"; exit 1; } |
| 135 | rm_swept "$OUT.ascold" "$OUT.as.start" "$OUT.as" "$OUT.as.err" "$OUT.as2" \ | 135 | rm_swept "$OUT.ascold" "$OUT.as.start" "$OUT.as" "$OUT.as.err" "$OUT.as2" \ |
| 136 | "$OUT.as2.err" "$OUT.stop" "$OUT.stop2" | 136 | "$OUT.as2.err" "$OUT.stop" "$OUT.stop2" |
| 137 | ok "a cold --via proxy refuses and starts nothing; mux d start makes the daemon and mux d stop tears it down" | 137 | ok "a cold --via proxy refuses and starts nothing; mux d start -d makes the daemon and mux d stop tears it down" |
| 138 | 138 | ||
| 139 | # --- M13: local mux auto-start, under the pty fixture ------------------ | 139 | # --- M13: local mux auto-start, under the pty fixture ------------------ |
| 140 | # | 140 | # |
| @@ -201,7 +201,7 @@ PAWANT=$(readlink -f "$MUX_ELF") | |||
| 201 | # from the basename of the FILENAME handed to execve, so exec'ing the | 201 | # from the basename of the FILENAME handed to execve, so exec'ing the |
| 202 | # /proc/self/exe link itself leaves every daemon on the box called `exe` — | 202 | # /proc/self/exe link itself leaves every daemon on the box called `exe` — |
| 203 | # invisible to `pgrep mux`, `killall mux`, `ps -o comm` and systemd's | 203 | # invisible to `pgrep mux`, `killall mux`, `ps -o comm` and systemd's |
| 204 | # MainPID name, with only the args still saying `mux d run`. | 204 | # MainPID name, with only the args still saying `mux d start`. |
| 205 | PACOMM=$(cat "/proc/$PAPID/comm") | 205 | PACOMM=$(cat "/proc/$PAPID/comm") |
| 206 | [ "$PACOMM" = "mux" ] || { | 206 | [ "$PACOMM" = "mux" ] || { |
| 207 | echo "e2e FAIL: the auto-started daemon's comm is '$PACOMM', want 'mux'" | 207 | echo "e2e FAIL: the auto-started daemon's comm is '$PACOMM', want 'mux'" |
test/e2e_04_handoff.sh
| Old | New | ||
|---|---|---|---|
| @@ -133,7 +133,7 @@ wait_for "$OUT.h1" "cold-one" 25 || { | |||
| 133 | # Exactly ONE ssh run, from the log rather than from ps. Three was the cold | 133 | # Exactly ONE ssh run, from the log rather than from ps. Three was the cold |
| 134 | # shape while the CLIENT decided to start: `mux d endpoint` refused an empty | 134 | # shape while the CLIENT decided to start: `mux d endpoint` refused an empty |
| 135 | # box, the client reaped the exit code to tell that refusal from ssh's own | 135 | # box, the client reaped the exit code to tell that refusal from ssh's own |
| 136 | # 255, ran `mux d start`, and asked again. The remote decides now, so the | 136 | # 255, ran `mux d start -d`, and asked again. The remote decides now, so the |
| 137 | # run that starts the daemon is the run that announces it. | 137 | # run that starts the daemon is the run that announces it. |
| 138 | HSHIMS=$(wc -l < "$SSHIM_PIDLOG") | 138 | HSHIMS=$(wc -l < "$SSHIM_PIDLOG") |
| 139 | [ "$HSHIMS" -eq 1 ] || { | 139 | [ "$HSHIMS" -eq 1 ] || { |
test/e2e_05_session.sh
| Old | New | ||
|---|---|---|---|
| @@ -328,7 +328,7 @@ chmod 600 "$M18KEY" | |||
| 328 | # is reported with the daemon's own words; only the taken-port case walks. | 328 | # is reported with the daemon's own words; only the taken-port case walks. |
| 329 | M18_TRIES=0 | 329 | M18_TRIES=0 |
| 330 | while : ; do | 330 | while : ; do |
| 331 | "$MUX" d run --sock "$SOCK23" --quic "127.0.0.1:$QPORT5" --key "$M18KEY" \ | 331 | "$MUX" d start --sock "$SOCK23" --quic "127.0.0.1:$QPORT5" --key "$M18KEY" \ |
| 332 | --quic-idle-ms 15000 --shell /bin/sh > "$OUT.m18q.d" 2>&1 & | 332 | --quic-idle-ms 15000 --shell /bin/sh > "$OUT.m18q.d" 2>&1 & |
| 333 | D20PID=$! | 333 | D20PID=$! |
| 334 | defer_kill "$D20PID" | 334 | defer_kill "$D20PID" |
test/e2e_09_hosts.sh
| Old | New | ||
|---|---|---|---|
| @@ -918,7 +918,7 @@ ok "the picker births on the host a digit names, forgets a host without ending i | |||
| 918 | # the client does with the answer. | 918 | # the client does with the answer. |
| 919 | # | 919 | # |
| 920 | # The oracle is the OS, never the daemon: the socket INODE on the far side, | 920 | # The oracle is the OS, never the daemon: the socket INODE on the far side, |
| 921 | # and a walk of /proc for a `mux d run` born under that box's runtime dir. A | 921 | # and a walk of /proc for a `mux d start` born under that box's runtime dir. A |
| 922 | # daemon asked whether it exists cannot answer no. The shim log is the | 922 | # daemon asked whether it exists cannot answer no. The shim log is the |
| 923 | # other half — it records the remote command, so "a read started nothing" | 923 | # other half — it records the remote command, so "a read started nothing" |
| 924 | # is checked against the word that ran and not only against the outcome. | 924 | # is checked against the word that ran and not only against the outcome. |
| @@ -962,13 +962,13 @@ chmod +x "$NSHIM/ssh" | |||
| 962 | NPATH="$NSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH" | 962 | NPATH="$NSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH" |
| 963 | printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts" | 963 | printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts" |
| 964 | 964 | ||
| 965 | # The /proc walk. `mux d run` and not any `mux` — the transient `mux d | 965 | # The /proc walk. `mux d start` and not any `mux` — the transient `mux d |
| 966 | # endpoint` processes share the runtime dir and are not daemons — and | 966 | # endpoint` processes share the runtime dir and are not daemons — and |
| 967 | # `environ` rather than the argv's `--sock`: the path the ensure forwards is | 967 | # `environ` rather than the argv's `--sock`: the path the ensure forwards is |
| 968 | # whatever this variable resolved to, so the variable is the box's identity | 968 | # whatever this variable resolved to, so the variable is the box's identity |
| 969 | # and the argv is derived from it. The shim gives each fake host a runtime | 969 | # and the argv is derived from it. The shim gives each fake host a runtime |
| 970 | # dir of its own, which is the whole of what makes them two machines. The | 970 | # dir of its own, which is the whole of what makes them two machines. The |
| 971 | # daemon spells `d run` because that is the argv spawn.zig | 971 | # daemon spells `d start` because that is the argv spawn.zig |
| 972 | # execs it with, which is also what makes `ps` legible. | 972 | # execs it with, which is also what makes `ps` legible. |
| 973 | nostart_daemons() { | 973 | nostart_daemons() { |
| 974 | _nd=0 | 974 | _nd=0 |
| @@ -979,7 +979,7 @@ nostart_daemons() { | |||
| 979 | # on one that exits mid-walk), so the silencing has to wrap the | 979 | # on one that exits mid-walk), so the silencing has to wrap the |
| 980 | # whole command and not just `tr`. | 980 | # whole command and not just `tr`. |
| 981 | { tr '\0' '\n' < "$_ne"; } 2>/dev/null | grep -qxF "XDG_RUNTIME_DIR=$1" || continue | 981 | { tr '\0' '\n' < "$_ne"; } 2>/dev/null | grep -qxF "XDG_RUNTIME_DIR=$1" || continue |
| 982 | { tr '\0' ' ' < "/proc/$_np/cmdline"; } 2>/dev/null | grep -q 'mux d run' || continue | 982 | { tr '\0' ' ' < "/proc/$_np/cmdline"; } 2>/dev/null | grep -q 'mux d start' || continue |
| 983 | _nd=$((_nd + 1)) | 983 | _nd=$((_nd + 1)) |
| 984 | done | 984 | done |
| 985 | echo "$_nd" | 985 | echo "$_nd" |
| @@ -988,7 +988,7 @@ nostart_daemons() { | |||
| 988 | nostart_empty() { # $1 = runtime dir, $2 = socket, $3 = what was just done | 988 | nostart_empty() { # $1 = runtime dir, $2 = socket, $3 = what was just done |
| 989 | _nc=$(nostart_daemons "$1") | 989 | _nc=$(nostart_daemons "$1") |
| 990 | [ "$_nc" -eq 0 ] || { | 990 | [ "$_nc" -eq 0 ] || { |
| 991 | echo "e2e FAIL: no-start: $3 left $_nc mux d run under $1, want 0"; exit 1; } | 991 | echo "e2e FAIL: no-start: $3 left $_nc mux d start under $1, want 0"; exit 1; } |
| 992 | [ ! -e "$2" ] || { | 992 | [ ! -e "$2" ] || { |
| 993 | echo "e2e FAIL: no-start: $3 bound $2 — a read started a daemon"; exit 1; } | 993 | echo "e2e FAIL: no-start: $3 bound $2 — a read started a daemon"; exit 1; } |
| 994 | } | 994 | } |
| @@ -1046,7 +1046,7 @@ nostart_empty "$NRUNA" "$NSOCKA" "a wall polling it" | |||
| 1046 | # every poll cycle, would show as several. | 1046 | # every poll cycle, would show as several. |
| 1047 | NCB=$(nostart_daemons "$NRUNB") | 1047 | NCB=$(nostart_daemons "$NRUNB") |
| 1048 | [ "$NCB" -eq 1 ] || { | 1048 | [ "$NCB" -eq 1 ] || { |
| 1049 | echo "e2e FAIL: no-start: picker Enter left $NCB mux d run under $NRUNB, want 1"; exit 1; } | 1049 | echo "e2e FAIL: no-start: picker Enter left $NCB mux d start under $NRUNB, want 1"; exit 1; } |
| 1050 | [ -S "$NSOCKB" ] || { | 1050 | [ -S "$NSOCKB" ] || { |
| 1051 | echo "e2e FAIL: no-start: picker Enter started nothing on box B ($NSOCKB)"; exit 1; } | 1051 | echo "e2e FAIL: no-start: picker Enter started nothing on box B ($NSOCKB)"; exit 1; } |
| 1052 | # ONE ask in the whole run, and it is box B's. Three seconds of polling | 1052 | # ONE ask in the whole run, and it is box B's. Three seconds of polling |
| @@ -1123,7 +1123,7 @@ await_out "$OUT.nsa" "ns-cold-ok" "no-start: the cold entry never served a sessi | |||
| 1123 | pipe_detach "no-start cold entry client" | 1123 | pipe_detach "no-start cold entry client" |
| 1124 | NCA=$(nostart_daemons "$NRUNA") | 1124 | NCA=$(nostart_daemons "$NRUNA") |
| 1125 | [ "$NCA" -eq 1 ] || { | 1125 | [ "$NCA" -eq 1 ] || { |
| 1126 | echo "e2e FAIL: no-start: a cold mux HOST left $NCA mux d run under $NRUNA, want 1" | 1126 | echo "e2e FAIL: no-start: a cold mux HOST left $NCA mux d start under $NRUNA, want 1" |
| 1127 | cat "$OUT.nsa.err"; exit 1; } | 1127 | cat "$OUT.nsa.err"; exit 1; } |
| 1128 | [ -S "$NSOCKA" ] || { | 1128 | [ -S "$NSOCKA" ] || { |
| 1129 | echo "e2e FAIL: no-start: the cold entry started nothing on box A"; exit 1; } | 1129 | echo "e2e FAIL: no-start: the cold entry started nothing on box A"; exit 1; } |
| @@ -1149,7 +1149,7 @@ NASKS_ALL=$(grep -c -- '--start' "$NSHIMLOG" || true) | |||
| 1149 | # ...and box A goes down by the daemon's own verb, which makes the teardown | 1149 | # ...and box A goes down by the daemon's own verb, which makes the teardown |
| 1150 | # one more assertion rather than a daemon handed to the trap: what a `mux d | 1150 | # one more assertion rather than a daemon handed to the trap: what a `mux d |
| 1151 | # stop` owes on a box somebody started is exactly what an untouched box | 1151 | # stop` owes on a box somebody started is exactly what an untouched box |
| 1152 | # already shows — no `mux d run` under that runtime dir, and no socket. | 1152 | # already shows — no `mux d start` under that runtime dir, and no socket. |
| 1153 | # `assert_stopped` is not usable here: it wants a pid, and this daemon was | 1153 | # `assert_stopped` is not usable here: it wants a pid, and this daemon was |
| 1154 | # forked by an ssh shim on the far side of a client. | 1154 | # forked by an ssh shim on the far side of a client. |
| 1155 | "$MUX" d stop --sock "$NSOCKA" > "$OUT.nsastop" 2>&1 || { | 1155 | "$MUX" d stop --sock "$NSOCKA" > "$OUT.nsastop" 2>&1 || { |
test/e2e_13_birth.sh
| Old | New | ||
|---|---|---|---|
| @@ -191,7 +191,7 @@ ok "the picker's a adds a host by spelling: its sessions become tiles, recorded, | |||
| 191 | # The snapshot tells the two apart — a served attach always sends one first | 191 | # The snapshot tells the two apart — a served attach always sends one first |
| 192 | # — and what this leg pins is the words an agent actually parses. | 192 | # — and what this leg pins is the words an agent actually parses. |
| 193 | mkdir -p "$REFSTATE" | 193 | mkdir -p "$REFSTATE" |
| 194 | XDG_STATE_HOME="$REFSTATE" "$MUX" d run --sock "$SOCK66" --shell /bin/sh > "$OUT.ref.d" 2>&1 & | 194 | XDG_STATE_HOME="$REFSTATE" "$MUX" d start --sock "$SOCK66" --shell /bin/sh > "$OUT.ref.d" 2>&1 & |
| 195 | D66PID=$! | 195 | D66PID=$! |
| 196 | defer_kill "$D66PID" | 196 | defer_kill "$D66PID" |
| 197 | wait_sock "$SOCK66" "$OUT.ref.d" "refused-attach daemon never bound" | 197 | wait_sock "$SOCK66" "$OUT.ref.d" "refused-attach daemon never bound" |
test/e2e_14_upgrade.sh
| Old | New | ||
|---|---|---|---|
| @@ -68,7 +68,7 @@ case "$UPVER" in | |||
| 68 | exit 1;; | 68 | exit 1;; |
| 69 | esac | 69 | esac |
| 70 | mkdir -p "$UPHOME" | 70 | mkdir -p "$UPHOME" |
| 71 | MUX_SHELL_INTEGRATION=1 HOME="$UPHOME" "$MUX" d run --sock "$SOCK69" \ | 71 | MUX_SHELL_INTEGRATION=1 HOME="$UPHOME" "$MUX" d start --sock "$SOCK69" \ |
| 72 | --shell /bin/bash > "$OUT.up.d" 2>&1 & | 72 | --shell /bin/bash > "$OUT.up.d" 2>&1 & |
| 73 | D69PID=$! | 73 | D69PID=$! |
| 74 | defer_kill "$D69PID" | 74 | defer_kill "$D69PID" |
| @@ -578,7 +578,7 @@ ok "a QUIC client is served again within a breath of the exec, not after the idl | |||
| 578 | # | 578 | # |
| 579 | # The unlink is the first thing a stopping daemon does; reaping its shells | 579 | # The unlink is the first thing a stopping daemon does; reaping its shells |
| 580 | # and deleting its dirs come after. A stop that said "stopped" at the | 580 | # and deleting its dirs come after. A stop that said "stopped" at the |
| 581 | # unlink handed a scripted `mux d start`, or a supervisor's "is it down", | 581 | # unlink handed a scripted `mux d start -d`, or a supervisor's "is it down", |
| 582 | # a daemon still running — `wait_pid_gone` after every stop in this suite | 582 | # a daemon still running — `wait_pid_gone` after every stop in this suite |
| 583 | # was that gap, papered. Shells that ignore TERM make the window a real | 583 | # was that gap, papered. Shells that ignore TERM make the window a real |
| 584 | # grace rather than a race the assertion could win by luck, and two | 584 | # grace rather than a race the assertion could win by luck, and two |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -67,7 +67,7 @@ $E2E_KILL" | |||
| 67 | 67 | ||
| 68 | # defer_sock PATH... — a path a daemon may be listening on. The trap asks | 68 | # defer_sock PATH... — a path a daemon may be listening on. The trap asks |
| 69 | # `mux d stop` there before it kills anything and long before it unlinks the | 69 | # `mux d stop` there before it kills anything and long before it unlinks the |
| 70 | # path: a daemon this shell never forked — one a proxy, `mux d start` or the | 70 | # path: a daemon this shell never forked — one a proxy, `mux d start -d` or the |
| 71 | # handoff spawned — has no pid here, and the socket is the only handle | 71 | # handoff spawned — has no pid here, and the socket is the only handle |
| 72 | # there is. | 72 | # there is. |
| 73 | defer_sock() { | 73 | defer_sock() { |
| @@ -152,7 +152,7 @@ export XDG_CONFIG_HOME XDG_STATE_HOME XDG_CACHE_HOME | |||
| 152 | # resolves $SHELL, so without this the suite runs the developer's login | 152 | # resolves $SHELL, so without this the suite runs the developer's login |
| 153 | # shell and its whole rc — arbitrary code, on the session under test. | 153 | # shell and its whole rc — arbitrary code, on the session under test. |
| 154 | # | 154 | # |
| 155 | # Found by soak, not by reasoning. The M10 `mux d start` block inherited zsh, | 155 | # Found by soak, not by reasoning. The M10 `mux d start -d` block inherited zsh, |
| 156 | # whose plugin manager roots itself at $XDG_CACHE_HOME; pointing that at a | 156 | # whose plugin manager roots itself at $XDG_CACHE_HOME; pointing that at a |
| 157 | # fresh directory (the line above) made every session re-clone its plugins | 157 | # fresh directory (the line above) made every session re-clone its plugins |
| 158 | # from the network before the shell would answer, and the scenario's marker | 158 | # from the network before the shell would answer, and the scenario's marker |
| @@ -325,7 +325,7 @@ wait_gone() { | |||
| 325 | } | 325 | } |
| 326 | 326 | ||
| 327 | # wait_sock PATH LOG LABEL — poll until a daemon has bound PATH (5s), then | 327 | # wait_sock PATH LOG LABEL — poll until a daemon has bound PATH (5s), then |
| 328 | # ASSERT it. Every `mux d run &` in this file needs this: the bind happens | 328 | # ASSERT it. Every `mux d start &` in this file needs this: the bind happens |
| 329 | # after the fork, so the very next command would otherwise race it. LOG is | 329 | # after the fork, so the very next command would otherwise race it. LOG is |
| 330 | # the spawn's own capture, printed on failure because a daemon that failed | 330 | # the spawn's own capture, printed on failure because a daemon that failed |
| 331 | # to bind almost always said why; pass "" for the spawns that have none. | 331 | # to bind almost always said why; pass "" for the spawns that have none. |
| @@ -348,7 +348,7 @@ wait_sock() { | |||
| 348 | # own. The long-lived daemon is $D1PID for exactly that reason — it is | 348 | # own. The long-lived daemon is $D1PID for exactly that reason — it is |
| 349 | # read some 5,000 lines after it is started. | 349 | # read some 5,000 lines after it is started. |
| 350 | # | 350 | # |
| 351 | # The registration is the whole reason this exists. A `mux d run &` written | 351 | # The registration is the whole reason this exists. A `mux d start &` written |
| 352 | # out by hand is three statements — the spawn, the pid, the wait — plus a | 352 | # out by hand is three statements — the spawn, the pid, the wait — plus a |
| 353 | # fourth in cleanup() saying how to end it, and the fourth is the one that | 353 | # fourth in cleanup() saying how to end it, and the fourth is the one that |
| 354 | # got forgotten. Here the spawn IS the registration and there is no fourth | 354 | # got forgotten. Here the spawn IS the registration and there is no fourth |
| @@ -361,7 +361,7 @@ wait_sock() { | |||
| 361 | # scenario after it. | 361 | # scenario after it. |
| 362 | start_daemon() { | 362 | start_daemon() { |
| 363 | _sds="$1"; _sdl="$2"; _sdlab="$3"; shift 3 | 363 | _sds="$1"; _sdl="$2"; _sdlab="$3"; shift 3 |
| 364 | "$MUX" d run --sock "$_sds" "$@" > "$_sdl" 2>&1 & | 364 | "$MUX" d start --sock "$_sds" "$@" > "$_sdl" 2>&1 & |
| 365 | DPID=$! | 365 | DPID=$! |
| 366 | defer_kill "$DPID" | 366 | defer_kill "$DPID" |
| 367 | defer_sock "$_sds" | 367 | defer_sock "$_sds" |
| @@ -1022,7 +1022,7 @@ leak_sweep() { | |||
| 1022 | cat "$LEAKBANK" | 1022 | cat "$LEAKBANK" |
| 1023 | _lsbad=1 | 1023 | _lsbad=1 |
| 1024 | fi | 1024 | fi |
| 1025 | # The detached (`mux d start`) daemons log via XDG_STATE_HOME — and every | 1025 | # The detached (`mux d start -d`) daemons log via XDG_STATE_HOME — and every |
| 1026 | # wall on a terminal now runs under a state home of its OWN (hostroom, | 1026 | # wall on a terminal now runs under a state home of its OWN (hostroom, |
| 1027 | # and the per-leg $*STATE dirs), so reading the suite's shared home alone | 1027 | # and the per-leg $*STATE dirs), so reading the suite's shared home alone |
| 1028 | # swept none of the daemons those legs start: the local auto-start, the | 1028 | # swept none of the daemons those legs start: the local auto-start, the |
| @@ -1095,7 +1095,7 @@ cleanup() { | |||
| 1095 | ' | 1095 | ' |
| 1096 | # Sockets first, and by the daemon's own verb. `mux d stop` on a path | 1096 | # Sockets first, and by the daemon's own verb. `mux d stop` on a path |
| 1097 | # nobody serves is a no-op that exits 0, and on a daemon this shell never | 1097 | # nobody serves is a no-op that exits 0, and on a daemon this shell never |
| 1098 | # forked — one a proxy, `mux d start` or the handoff spawned — it is the | 1098 | # forked — one a proxy, `mux d start -d` or the handoff spawned — it is the |
| 1099 | # only handle there is. Before the kills, and long before the unlink | 1099 | # only handle there is. Before the kills, and long before the unlink |
| 1100 | # below: unlinking first would leave a live daemon nothing could reach | 1100 | # below: unlinking first would leave a live daemon nothing could reach |
| 1101 | # by path. | 1101 | # by path. |
test/soak.sh
| Old | New | ||
|---|---|---|---|
| @@ -75,7 +75,7 @@ CYCLES="${SOAK_CYCLES:-20}" | |||
| 75 | WARMUP=3 | 75 | WARMUP=3 |
| 76 | RSS_BOUND_KB=4096 | 76 | RSS_BOUND_KB=4096 |
| 77 | PERSIST_FAILED_BEFORE=$FAILED | 77 | PERSIST_FAILED_BEFORE=$FAILED |
| 78 | "$MUX" d run --sock "$PSOCK" --shell /bin/sh > "$PLOG" 2>&1 & | 78 | "$MUX" d start --sock "$PSOCK" --shell /bin/sh > "$PLOG" 2>&1 & |
| 79 | PDPID=$! | 79 | PDPID=$! |
| 80 | _i=0 | 80 | _i=0 |
| 81 | while [ ! -S "$PSOCK" ] && [ "$_i" -lt 100 ]; do sleep 0.05; _i=$((_i + 1)); done | 81 | while [ ! -S "$PSOCK" ] && [ "$_i" -lt 100 ]; do sleep 0.05; _i=$((_i + 1)); done |
test/throughput.sh
| Old | New | ||
|---|---|---|---|
| @@ -88,7 +88,7 @@ cleanup() { | |||
| 88 | } | 88 | } |
| 89 | trap cleanup EXIT INT TERM | 89 | trap cleanup EXIT INT TERM |
| 90 | 90 | ||
| 91 | "$MUX" d run --sock "$SOCK" --shell /bin/sh --cols 80 --rows 24 & | 91 | "$MUX" d start --sock "$SOCK" --shell /bin/sh --cols 80 --rows 24 & |
| 92 | DPID=$! | 92 | DPID=$! |
| 93 | i=0 | 93 | i=0 |
| 94 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | 94 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done |
test/wan.sh
| Old | New | ||
|---|---|---|---|
| @@ -1133,7 +1133,7 @@ if [ "${MUX_WAN_QUIC:-}" = "1" ]; then | |||
| 1133 | fi | 1133 | fi |
| 1134 | 1134 | ||
| 1135 | say "starting the remote daemon" | 1135 | say "starting the remote daemon" |
| 1136 | $MUX_WAN_SSH "rm -f $RSOCK; nohup $RBIN d run --sock $RSOCK --shell /bin/bash \ | 1136 | $MUX_WAN_SSH "rm -f $RSOCK; nohup $RBIN d start --sock $RSOCK --shell /bin/bash \ |
| 1137 | $QUIC_ARGS >$RLOG 2>&1 </dev/null & sleep 0.2" | 1137 | $QUIC_ARGS >$RLOG 2>&1 </dev/null & sleep 0.2" |
| 1138 | i=0 | 1138 | i=0 |
| 1139 | until $MUX_WAN_SSH "test -S $RSOCK" 2>/dev/null; do | 1139 | until $MUX_WAN_SSH "test -S $RSOCK" 2>/dev/null; do |
test/xversion.sh
| Old | New | ||
|---|---|---|---|
| @@ -129,7 +129,7 @@ trap cleanup EXIT INT TERM | |||
| 129 | # own filesystem and process table, which is what this gate is actually | 129 | # own filesystem and process table, which is what this gate is actually |
| 130 | # asking about. | 130 | # asking about. |
| 131 | # $5 is how THAT side spells its daemon — `muxd run` on the old prefix, | 131 | # $5 is how THAT side spells its daemon — `muxd run` on the old prefix, |
| 132 | # `mux d run` on the new one. The old binary cannot answer `mux d`, and | 132 | # `mux d start` on the new one. The old binary cannot answer `mux d`, and |
| 133 | # asking it to would test this gate's own wording instead of the wire. | 133 | # asking it to would test this gate's own wording instead of the wire. |
| 134 | start_daemon() { | 134 | start_daemon() { |
| 135 | # shellcheck disable=SC2086 # $5 is a command spelling of one or two words | 135 | # shellcheck disable=SC2086 # $5 is a command spelling of one or two words |
| @@ -163,7 +163,7 @@ echo "xver: image=$IMAGE engine=$ENGINE ports=$PORT_OLD/$PORT_NEW" | |||
| 163 | 163 | ||
| 164 | start_daemon "$CN_OLD" "$OLD_BIN" "$RUN_OLD" "$PORT_OLD" "/mux/muxd run" || | 164 | start_daemon "$CN_OLD" "$OLD_BIN" "$RUN_OLD" "$PORT_OLD" "/mux/muxd run" || |
| 165 | { echo "xver FAIL: could not start the old daemon's container"; exit 1; } | 165 | { echo "xver FAIL: could not start the old daemon's container"; exit 1; } |
| 166 | start_daemon "$CN_NEW" "$NEW_BIN" "$RUN_NEW" "$PORT_NEW" "/mux/mux d run" || | 166 | start_daemon "$CN_NEW" "$NEW_BIN" "$RUN_NEW" "$PORT_NEW" "/mux/mux d start" || |
| 167 | { echo "xver FAIL: could not start the new daemon's container"; exit 1; } | 167 | { echo "xver FAIL: could not start the new daemon's container"; exit 1; } |
| 168 | 168 | ||
| 169 | SOCK_OLD="$RUN_OLD/muxd.sock" | 169 | SOCK_OLD="$RUN_OLD/muxd.sock" |