a73x

8557c2a6

docs: comments that were lying stop lying

a73x   2026-08-23 10:26

Commit message
docs: comments that were lying stop lying

The tree-wide burn-down made comments shorter. It did not check whether
they were TRUE. Eight reviewers read every surviving doc block against
the code it documents; 31 claims came back wrong, and all 31 were
verified against the source before anything was touched.

The one that matters most: shellint.zig credited `xdg.makePrivateDir`
for the safety of code that calls `makeNewPrivateDir`. xdg.zig's own
header says the cited variant adopts an existing directory and follows
symlinks, and is "wrong anywhere a stranger can create entries". A
reader auditing that hazard was being pointed at the wrong function.

Also false: client.zig called writeCache "write-then-rename" when it
truncates in place; xdg.zig said the log is truncated at each spawn when
only `muxd start` truncates and spawn.zig says so; server.zig's
`inbound` claimed to stay empty for every client while quicOnData fills
it; replica.zig named a `session()` that no longer exists; webhub.zig
described `muxweb -A` in the present tense.

Nine findings were nothing but arithmetic — "the three frames" grown to
seven, "three callers" to four. Those are repaired by stating the rule
that generates the number instead of the number: `.not_mine`, "every
binary", "any reattach quoting a held seq". Three stale line citations
became symbols and an assertion string, which the gate can check and a
line number cannot.

Two findings are code defects, not comment drift, and are NOT fixed
here: quicOnClose skips closeAgentChansOfClient, and stats_text_len's
comptime assert was sized for 8 main-line fields where statsText now
prints 11. The comment that hid the first now names it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

src/client.zig
Old New
@@ -233,10 +233,10 @@ pub const HandoffTarget = struct {
233 /// tests can shrink it; production passes `handoff.deadline_ms`. 233 /// tests can shrink it; production passes `handoff.deadline_ms`.
234 deadline_ms: u32 = handoff.deadline_ms, 234 deadline_ms: u32 = handoff.deadline_ms,
235 idle_ms: u32 = quic_idle_ms_default, 235 idle_ms: u32 = quic_idle_ms_default,
236 /// Whether a fallback to ssh may say so. True on the initial attach, 236 /// Whether a fallback to ssh may say so. True on the ENTRY dial, where
237 /// where the line reports a choice the user has not seen made; false 237 /// the line reports a choice the user has not seen made; false for
238 /// from the reconnect loop, which re-runs this whole recipe on every 238 /// every pump-side dial — a stripe's bar narrates state, and a
239 /// retry — forever, against a network that has started dropping UDP. 239 /// reconnect re-runs this recipe forever against dropped UDP.
240 /// One line per retry would scroll a live session's stderr into the 240 /// One line per retry would scroll a live session's stderr into the
241 /// alternate screen and corrupt the paint, to say what the 241 /// alternate screen and corrupt the paint, to say what the
242 /// [reconnecting] banner is already saying. 242 /// [reconnecting] banner is already saying.
@@ -427,10 +427,12 @@ pub const Transport = struct {
427 // 427 //
428 // Serialized because openHandoff is no longer single-threaded: the 428 // Serialized because openHandoff is no longer single-threaded: the
429 // web hub runs one of these per tile, and two tiles naming the same 429 // web hub runs one of these per tile, and two tiles naming the same
430 // host share a cache path. writeCache is write-then-rename, so the 430 // host share a cache path. writeCache truncates in place — there is
431 // worst interleaving loses a write rather than tearing a file — but 431 // no rename — so unserialized writers can tear the line. A torn one
432 // "the loser wrote the STALER endpoint" is a cold attach that looks 432 // costs an attach, not a host: readCache refuses it and the cold
433 // like a bug, and a mutex is cheaper than the afternoon. 433 // path below rewrites it. The likelier miss is the loser writing the
434 // STALER endpoint, a cold attach that looks like a bug — and a mutex
435 // is cheaper than the afternoon.
434 if (h.cache_path) |cp| { 436 if (h.cache_path) |cp| {
435 cache_write_mu.lock(); 437 cache_write_mu.lock();
436 defer cache_write_mu.unlock(); 438 defer cache_write_mu.unlock();
@@ -722,12 +724,12 @@ fn readAnnounceAbortable(
722 if (got > 0) { 724 if (got > 0) {
723 if (std.mem.indexOfScalar(u8, in[0..got], interact.detach_key) != null) 725 if (std.mem.indexOfScalar(u8, in[0..got], interact.detach_key) != null)
724 return error.UserAbort; 726 return error.UserAbort;
725 // waitReady's contract, and for waitReady's reason: on a 727 // `openHandoff` always passes `carry` null: on a first
726 // first attach these are the user's first keystrokes and are 728 // attach this fd is -1 and never read — the keystrokes
727 // owed to the shell, so they are kept; during a reconnect 729 // wait in the kernel's tty buffer for ssh's prompt, then
728 // `carry` is null and input is dropped by long-standing 730 // the shell — and on a reconnect input is dropped by
729 // policy, because replaying a burst of stale keystrokes on 731 // policy: replaying stale keystrokes on resume is worse
730 // resume is worse than losing them. 732 // than losing them.
731 if (carry) |q| q.appendSlice(alloc, in[0..got]) catch {}; 733 if (carry) |q| q.appendSlice(alloc, in[0..got]) catch {};
732 } 734 }
733 } 735 }
@@ -1150,7 +1152,7 @@ test "handoff: endpoint-none rides the open pipe with no deadline paid" {
1150 var carry: std.ArrayList(u8) = .empty; 1152 var carry: std.ArrayList(u8) = .empty;
1151 defer carry.deinit(alloc); 1153 defer carry.deinit(alloc);
1152 1154
1153 // Inert, but ours: the announce wait polls the real fd 0, and a 1155 // Inert, but ours: this open is handed fd 0 as its abort fd, and a
1154 // developer's terminal is not a fixture. 1156 // developer's terminal is not a fixture.
1155 var stdin = try FakeStdin.install(""); 1157 var stdin = try FakeStdin.install("");
1156 defer stdin.deinit(); 1158 defer stdin.deinit();
@@ -1196,8 +1198,8 @@ test "handoff: dead coordinates are a fast no, and the pipe is the fallback" {
1196 var carry: std.ArrayList(u8) = .empty; 1198 var carry: std.ArrayList(u8) = .empty;
1197 defer carry.deinit(alloc); 1199 defer carry.deinit(alloc);
1198 1200
1199 // Inert, but ours — and this test needs it twice over, since waitReady 1201 // Inert, but ours — and this test really needs it: waitReady watches
1200 // watches fd 0 for the whole dial as well. 1202 // fd 0 for the whole QUIC dial.
1201 var stdin = try FakeStdin.install(""); 1203 var stdin = try FakeStdin.install("");
1202 defer stdin.deinit(); 1204 defer stdin.deinit();
1203 1205
@@ -1231,9 +1233,10 @@ test "handoff: dead coordinates are a fast no, and the pipe is the fallback" {
1231 /// 1233 ///
1232 /// The CLI passes STDIN_FILENO as the abort fd — that is where the abort 1234 /// The CLI passes STDIN_FILENO as the abort fd — that is where the abort
1233 /// key arrives in production — so testing that spelling means briefly 1235 /// key arrives in production — so testing that spelling means briefly
1234 /// owning fd 0. (The fd is injectable; the -1 test 1236 /// owning fd 0. (The fd is injectable; the -1 test leans on this same
1235 /// leans on this same harness to prove fd 0 is NOT read in that case.) Zig runs a file's tests one at a time in one 1237 /// harness to prove fd 0 is NOT read in that case.) Zig runs a file's
1236 /// process, so this is safe as long as every caller restores; `deinit` 1238 /// tests one at a time in one process, so this is safe as long as every
1239 /// caller restores; `deinit`
1237 /// exists to make that a defer rather than a discipline. 1240 /// exists to make that a defer rather than a discipline.
1238 const FakeStdin = struct { 1241 const FakeStdin = struct {
1239 saved: std.posix.fd_t, 1242 saved: std.posix.fd_t,
src/delta.zig
Old New
@@ -306,8 +306,9 @@ test "DeltaTracker: blind output is answerable on reattach without rendering a r
306 // row but forgot to refresh those two would repaint the right text with 306 // row but forgot to refresh those two would repaint the right text with
307 // the cursor parked where the gap began. Deleting either line in 307 // the cursor parked where the gap began. Deleting either line in
308 // noteBlind left the entire unit suite green until this assertion 308 // noteBlind left the entire unit suite green until this assertion
309 // existed, and the client that would wear it is a browser reconnect — 309 // existed, and any reattach quoting a held seq wears it — a browser
310 // the CLI opens at seq 0 and takes the snapshot arm instead. 310 // reconnect, or the CLI's redial after transport death
311 // (wallview.sendAttach); only a fresh attach takes the snapshot arm.
311 const hdr = try proto.readDeltaHeader(payload); 312 const hdr = try proto.readDeltaHeader(payload);
312 const cur = eng.cursorPos(); 313 const cur = eng.cursorPos();
313 try std.testing.expectEqual(cur.x, hdr.cursor_x); 314 try std.testing.expectEqual(cur.x, hdr.cursor_x);
src/handoff.zig
Old New
@@ -119,9 +119,9 @@ pub fn formatAnnounce(buf: []u8, ep: Endpoint) ![]const u8 {
119 /// the key is hex in either case, as `quic.Key.load` also accepts. Nothing 119 /// the key is hex in either case, as `quic.Key.load` also accepts. Nothing
120 /// but this module's own writer produces these lines, a looser reader 120 /// but this module's own writer produces these lines, a looser reader
121 /// cannot admit anything a dial would not immediately reject, and the 121 /// cannot admit anything a dial would not immediately reject, and the
122 /// input is length-bounded by `announce_max_len` at both call sites 122 /// input is length-bounded by `announce_max_len` at every call site:
123 /// (`readAnnounce`'s buffer, `readCache`'s size check). Tightening it 123 /// each reader sizes its buffer — or, for `readCache`, its size check —
124 /// would only add rules to get wrong. 124 /// from that constant. Tightening it would only add rules to get wrong.
125 pub fn parseAnnounce(line: []const u8) ParseError!?Endpoint { 125 pub fn parseAnnounce(line: []const u8) ParseError!?Endpoint {
126 const prefix = "endpoint "; 126 const prefix = "endpoint ";
127 127
@@ -223,9 +223,7 @@ pub fn readLine(fd: std.posix.fd_t, buf: []u8) ![]const u8 {
223 } 223 }
224 224
225 /// Null is `endpoint none`: no coordinates, session stays on ssh. 225 /// Null is `endpoint none`: no coordinates, session stays on ssh.
226 /// 226 /// The interactive client's reader is `readAnnounceAbortable`.
227 /// One call, so no caller has to know the buffer size or the missing
228 /// newline.
229 pub fn readAnnounce(fd: std.posix.fd_t) !?Endpoint { 227 pub fn readAnnounce(fd: std.posix.fd_t) !?Endpoint {
230 var buf: [announce_max_len]u8 = undefined; 228 var buf: [announce_max_len]u8 = undefined;
231 return parseAnnounce(try readLine(fd, &buf)); 229 return parseAnnounce(try readLine(fd, &buf));
@@ -440,7 +438,7 @@ test "readLine: consumes the newline and NOT the byte after it" {
440 try std.testing.expectEqual(@as(u8, 'X'), one[0]); 438 try std.testing.expectEqual(@as(u8, 'X'), one[0]);
441 } 439 }
442 440
443 test "readAnnounce: one call for the cold path, and it leaves the frames alone" { 441 test "readAnnounce: one call reads the line, and it leaves the frames alone" {
444 const fds = try std.posix.pipe(); 442 const fds = try std.posix.pipe();
445 defer std.posix.close(fds[0]); 443 defer std.posix.close(fds[0]);
446 444
src/interact.zig
Old New
@@ -165,12 +165,12 @@ const wheel_rows: u32 = 3;
165 /// Pulls SGR mouse reports out of the stdin stream and turns the wheel ones 165 /// Pulls SGR mouse reports out of the stdin stream and turns the wheel ones
166 /// into scrollback movement. 166 /// into scrollback movement.
167 /// 167 ///
168 /// TWO instances exist, and they are not alternatives. A tile's `Core` owns 168 /// TWO ROLES, and they are not alternatives. Every tile's `Core` owns a
169 /// one for the bytes a ZOOMED session's terminal delivers; it runs only 169 /// filter for the bytes a ZOOMED session's terminal delivers; it runs only
170 /// while no application in that session has asked for the mouse, because 170 /// while no application in that session has asked for the mouse, because
171 /// when one has, the bytes are its own and the filter is bypassed entirely 171 /// when one has, the bytes are its own and the filter is bypassed entirely
172 /// (and reset, so a report split across that transition cannot be 172 /// (and reset, so a report split across that transition cannot be
173 /// half-eaten). The CLI wall owns the other, in front of its UNZOOMED key 173 /// half-eaten). The CLI wall owns one more, in front of its UNZOOMED key
174 /// loop, where there is no session to bypass it for — the modes are the 174 /// loop, where there is no session to bypass it for — the modes are the
175 /// wall's own (`wall_mouse_claim`) and every report is the wall's. 175 /// wall's own (`wall_mouse_claim`) and every report is the wall's.
176 /// 176 ///
@@ -1623,7 +1623,7 @@ pub const Core = struct {
1623 /// 1623 ///
1624 /// What comes back is only what is LEFT (see `Routed`): the driver's 1624 /// What comes back is only what is LEFT (see `Routed`): the driver's
1625 /// own bookkeeping on first state, its re-attach spelling on a resync, 1625 /// own bookkeeping on first state, its re-attach spelling on a resync,
1626 /// and the three frames that are nobody's but the driver's. 1626 /// and the frames that are nobody's but the driver's (`.not_mine`).
1627 /// 1627 ///
1628 /// Errors are the replica's, unchanged: a snapshot too short to read is 1628 /// Errors are the replica's, unchanged: a snapshot too short to read is
1629 /// `.skip` because `readSnapshotPrefix` left everything untouched, and 1629 /// `.skip` because `readSnapshotPrefix` left everything untouched, and
@@ -1911,7 +1911,7 @@ pub const Core = struct {
1911 /// 1911 ///
1912 /// The correlation is `client_core`'s, unchanged: a reply that answers 1912 /// The correlation is `client_core`'s, unchanged: a reply that answers
1913 /// no pending request, or answers one this drag already replaced, is 1913 /// no pending request, or answers one this drag already replaced, is
1914 /// `.ignored` there and never reaches the three tests below. 1914 /// `.ignored` there and never reaches the tests below.
1915 pub fn selectionCopy(self: *Core, payload: []const u8, held: ?select.Range) Copy { 1915 pub fn selectionCopy(self: *Core, payload: []const u8, held: ?select.Range) Copy {
1916 const reply = switch (self.semantic.receive(.selection_reply, payload)) { 1916 const reply = switch (self.semantic.receive(.selection_reply, payload)) {
1917 .reply => |r| r.selection, 1917 .reply => |r| r.selection,
src/keymap.zig
Old New
@@ -1,8 +1,8 @@
1 //! Normalized key events → VT byte sequences. The portable half of input: 1 //! Normalized key events → VT byte sequences. The portable half of input:
2 //! each shell (browser KeyboardEvent, later xkb) produces the normalized 2 //! each shell (browser KeyboardEvent, later xkb) produces the normalized
3 //! form; this module owns every byte that reaches the PTY, so the tables 3 //! form; this module owns every byte a key event turns into, so the
4 //! are unit-tested natively with no browser in the loop — they arrived 4 //! tables are unit-tested natively with no browser in the loop — they
5 //! for the browser client and were never allowed to depend on it. 5 //! arrived for the browser client and were never allowed to depend on it.
6 //! 6 //!
7 //! v1 scope (spec): printable input, control characters, arrows and nav 7 //! v1 scope (spec): printable input, control characters, arrows and nav
8 //! keys, function keys, the xterm modifier-encoded CSI variants, and 8 //! keys, function keys, the xterm modifier-encoded CSI variants, and
@@ -70,8 +70,9 @@ pub const Event = struct {
70 mods: Mods = .{}, 70 mods: Mods = .{},
71 }; 71 };
72 72
73 /// Every sequence this module can emit fits here with room to spare (the 73 /// Every sequence this module can emit fits here with room to spare —
74 /// longest is ESC + 4 UTF-8 bytes, or an 8-byte modified CSI). 74 /// the widest today is the 7-byte modified tilde CSI; the slack is
75 /// headroom for forms not in the table yet.
75 pub const max_seq_len = 16; 76 pub const max_seq_len = 16;
76 77
77 /// Encode one event into `buf` (at least max_seq_len bytes), returning the 78 /// Encode one event into `buf` (at least max_seq_len bytes), returning the
src/main.zig
Old New
@@ -68,9 +68,10 @@ const Spec = struct {
68 /// returns before the loop, so nothing after the name is read. 68 /// returns before the loop, so nothing after the name is read.
69 /// 69 ///
70 /// `ignored` is a contract, not an oversight: trailing arguments are 70 /// `ignored` is a contract, not an oversight: trailing arguments are
71 /// ACCEPTED and vanish silently. `muxd --version --sock <110 bytes>` 71 /// ACCEPTED and vanish silently. `muxd --version` with an over-long
72 /// exits 0 today, pinned at e2e.sh:637. Narrowing it to `none` would 72 /// `--sock` exits 0 today, pinned in e2e.sh ("--version refused over
73 /// turn that into a refusal. 73 /// sock length"). Narrowing it to `none` would turn that into a
74 /// refusal.
74 flags: enum { none, all, ignored }, 75 flags: enum { none, all, ignored },
75 }; 76 };
76 77
src/mux_main.zig
Old New
@@ -456,8 +456,9 @@ pub fn main() !u8 {
456 } 456 }
457 } 457 }
458 458
459 /// `mux wall`: gather spellings (argv, or the muxweb wall file when argv 459 /// `mux wall`: gather spellings (argv, or with none the saved wall — the
460 /// names none), resolve each through the one grammar, hand the lot to 460 /// attach history mux itself writes, muxweb's too via argv and POST
461 /// /tiles), resolve each through the one grammar, hand the lot to
461 /// wallview.run. Resolution allocates into an arena because run() never 462 /// wallview.run. Resolution allocates into an arena because run() never
462 /// returns on the success path (it exits the process — see wallview.run); 463 /// returns on the success path (it exits the process — see wallview.run);
463 /// only the early usage-error paths come back through the defers here. 464 /// only the early usage-error paths come back through the defers here.
src/muxa.zig
Old New
@@ -43,7 +43,7 @@ const Opts = struct {
43 /// `--key PATH`, the highest-priority spelling of the QUIC key. Null 43 /// `--key PATH`, the highest-priority spelling of the QUIC key. Null
44 /// does NOT mean "no key": `$MUX_KEY_FILE` and the XDG default are 44 /// does NOT mean "no key": `$MUX_KEY_FILE` and the XDG default are
45 /// still to be tried, and neither is parse's to look at (xdg.pickKey 45 /// still to be tried, and neither is parse's to look at (xdg.pickKey
46 /// and xdg.resolveKeyPath own that order, as they do for muxd and mux). 46 /// and xdg.resolveKeyPath own that order here as they do for mux).
47 key: ?[]const u8 = null, 47 key: ?[]const u8 = null,
48 settle_ms: u32 = 0, 48 settle_ms: u32 = 0,
49 // Never 0 by default: the daemon reads a 0 timeout on await_req as "no 49 // Never 0 by default: the daemon reads a 0 timeout on await_req as "no
@@ -1017,9 +1017,10 @@ fn openQuicConn(
1017 deadline: i64, 1017 deadline: i64,
1018 ) Opened { 1018 ) Opened {
1019 // `--key`, then `$MUX_KEY_FILE`, then the XDG default if it exists. 1019 // `--key`, then `$MUX_KEY_FILE`, then the XDG default if it exists.
1020 // The order is not spelled here on purpose: xdg owns it, muxd and mux 1020 // The order is not spelled here on purpose: xdg owns it, mux reads the
1021 // read it from the same two functions, and a third copy is how two 1021 // same two functions, and a copy that drifted is how two binaries end
1022 // binaries end up authenticating with different keys. 1022 // up authenticating with different keys. (muxd's pickKey stands apart
1023 // deliberately: it takes the default only when the file exists.)
1023 const res = xdg.resolveKeyPath(alloc, xdg.pickKey(o.key, std.posix.getenv("MUX_KEY_FILE"))) catch |e| 1024 const res = xdg.resolveKeyPath(alloc, xdg.pickKey(o.key, std.posix.getenv("MUX_KEY_FILE"))) catch |e|
1024 return .{ .exit = fail("quic: cannot resolve a key path", @errorName(e)) }; 1025 return .{ .exit = fail("quic: cannot resolve a key path", @errorName(e)) };
1025 const key_path = switch (res) { 1026 const key_path = switch (res) {
src/quic.zig
Old New
@@ -352,7 +352,7 @@ test "Key.load: refuses a permissive mode, a missing file, and a bad length" {
352 ); 352 );
353 } 353 }
354 354
355 test "keyRefusalBody: the words four binaries print, byte for byte" { 355 test "keyRefusalBody: the words three binaries print, byte for byte" {
356 // These bytes ARE the contract. Every key refusal any binary prints is 356 // These bytes ARE the contract. Every key refusal any binary prints is
357 // a prefix, this body, and at most a suffix: 357 // a prefix, this body, and at most a suffix:
358 // 358 //
@@ -360,10 +360,11 @@ test "keyRefusalBody: the words four binaries print, byte for byte" {
360 // muxd endpoint: <body>; staying on ssh 360 // muxd endpoint: <body>; staying on ssh
361 // muxd: endpoint_req: <body> (the daemon's lazy bind) 361 // muxd: endpoint_req: <body> (the daemon's lazy bind)
362 // mux: <body> (the client's dial) 362 // mux: <body> (the client's dial)
363 // muxa: the JSON detail after "quic: unusable key"
363 // 364 //
364 // so a change here is a change to all four at once — which is what the 365 // so a change here is a change to every caller at once — which is what
365 // four literal copies this replaced could never guarantee, and did not: 366 // the four literal copies this replaced could never guarantee, and did
366 // one of their catch-alls had drifted to a different verb. 367 // not: one of their catch-alls had drifted to a different verb.
367 var buf: [key_refusal_len]u8 = undefined; 368 var buf: [key_refusal_len]u8 = undefined;
368 try std.testing.expectEqualStrings( 369 try std.testing.expectEqualStrings(
369 "no such key file: /etc/mux/key", 370 "no such key file: /etc/mux/key",
@@ -471,8 +472,9 @@ pub const Egress = struct {
471 /// the only thing that ever frees space. 472 /// the only thing that ever frees space.
472 pub fn ack(self: *Egress, n: usize) void { 473 pub fn ack(self: *Egress, n: usize) void {
473 // A deinit'd ring has a zero-length buffer, and the modulo below 474 // A deinit'd ring has a zero-length buffer, and the modulo below
474 // would divide by zero. Reachable because an ack can arrive for a 475 // would divide by zero. Unreachable while owners delete the conn
475 // connection whose egress has already been torn down. 476 // before freeing the ring; the guard keeps a teardown reordering
477 // from turning into one.
476 if (self.buf.len == 0) return; 478 if (self.buf.len == 0) return;
477 const taken = @min(n, self.held - self.unsent); 479 const taken = @min(n, self.held - self.unsent);
478 self.head = (self.head + taken) % self.buf.len; 480 self.head = (self.head + taken) % self.buf.len;
@@ -580,11 +582,11 @@ test "Egress: an ack against a torn-down ring is ignored, not a division by zero
580 _ = e.push("abcd"); 582 _ = e.push("abcd");
581 e.took(4); 583 e.took(4);
582 584
583 // Teardown leaves a zero-length buffer behind, and an ack can still 585 // Teardown leaves a zero-length buffer behind. No live path acks it
584 // arrive for it: ngtcp2 delivers acked_stream_data during its own 586 // today — both owners delete the ngtcp2 conn before freeing the ring,
585 // shutdown, after the owner has freed the ring. The modulo in `ack` 587 // and ngtcp2 fires this callback only on ACK receipt — but the modulo
586 // divides by buf.len, so without the guard this is a division by zero 588 // in `ack` divides by buf.len, so a teardown reordering would be a
587 // on a path nobody would think to look at. 589 // division by zero on a path nobody would think to look at.
588 e.deinit(alloc); 590 e.deinit(alloc);
589 try std.testing.expectEqual(@as(usize, 0), e.buf.len); 591 try std.testing.expectEqual(@as(usize, 0), e.buf.len);
590 e.ack(4); 592 e.ack(4);
src/quic_server.zig
Old New
@@ -115,7 +115,7 @@ const Conn = struct {
115 ncids: usize = 0, 115 ncids: usize = 0,
116 cids_dirty: bool = true, 116 cids_dirty: bool = true,
117 /// Set when this connection must go but cannot be freed yet, because 117 /// Set when this connection must go but cannot be freed yet, because
118 /// ngtcp2 is inside a call on it. See Listener.in_ngtcp2. 118 /// ngtcp2 is inside a call on it. See `inNgtcp2` and `ngtcp2_depth`.
119 close_state: enum { open, closing_quiet, closing_notify } = .open, 119 close_state: enum { open, closing_quiet, closing_notify } = .open,
120 stream_id: i64 = -1, 120 stream_id: i64 = -1,
121 /// Bytes owed to this peer. See Egress: they do not move until acked. 121 /// Bytes owed to this peer. See Egress: they do not move until acked.
src/replica.zig
Old New
@@ -10,7 +10,7 @@
10 //! a timestamp, or a transport belongs to the caller. 10 //! a timestamp, or a transport belongs to the caller.
11 //! 11 //!
12 //! The Replica BORROWS its engine. The caller owns the Engine's lifetime 12 //! The Replica BORROWS its engine. The caller owns the Engine's lifetime
13 //! (the CLI's session() and the server's tests already do); a Replica is 13 //! (interact's Core and the server's tests already do); a Replica is
14 //! bookkeeping around it, not a second owner. 14 //! bookkeeping around it, not a second owner.
15 15
16 const std = @import("std"); 16 const std = @import("std");
src/server.zig
Old New
@@ -1,10 +1,11 @@
1 //! muxd's daemon core: up to max_sessions sessions (engine + pty + command 1 //! muxd's daemon core: 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 dump-only 4 //! Up to max_clients attached interactive clients plus a few one-shot
5 //! observer connections. Every state update is broadcast to all attached 5 //! observer connections (dump, stats, status, endpoint, stop). Every state
6 //! clients; the grid follows the most recently active client — typing, 6 //! update is broadcast to that session's attached clients; each grid follows
7 //! attaching or resizing claims it (latest wins). 7 //! its most recently active client — typing, attaching or resizing claims
8 //! it (latest wins).
8 //! Single-threaded; pumpOnce is one poll iteration so tests can drive the 9 //! Single-threaded; pumpOnce is one poll iteration so tests can drive the
9 //! loop. 10 //! loop.
10 const std = @import("std"); 11 const std = @import("std");
@@ -115,7 +116,8 @@ fn onShutdownSignal(_: c_int) callconv(.c) void {
115 } 116 }
116 117
117 /// Install SIGINT/SIGTERM handlers so a foreground `muxd run` shuts down 118 /// Install SIGINT/SIGTERM handlers so a foreground `muxd run` shuts down
118 /// cleanly (socket file removed, shell reaped). Called by main, not tests. 119 /// cleanly (socket file removed, shell reaped). Called by main; one test
120 /// borrows it for the SIGPIPE ignore.
119 pub fn installSignalHandlers() void { 121 pub fn installSignalHandlers() void {
120 var sa: std.posix.Sigaction = .{ 122 var sa: std.posix.Sigaction = .{
121 .handler = .{ .handler = onShutdownSignal }, 123 .handler = .{ .handler = onShutdownSignal },
@@ -196,9 +198,9 @@ const Sink = union(enum) {
196 const ClientSlot = struct { 198 const ClientSlot = struct {
197 sink: Sink, 199 sink: Sink,
198 /// Bytes that arrived for this client but do not yet form a whole 200 /// Bytes that arrived for this client but do not yet form a whole
199 /// frame. Only the injection path fills this (see `pushInbound`); a 201 /// frame. Only the injection path fills this (see `pushInbound`): a
200 /// socket client reads whole frames straight off its fd, so for every 202 /// socket client reads whole frames straight off its fd and never
201 /// client that exists today this stays empty and costs one pointer. 203 /// buffers here; a QUIC client's stream chunks land and wait here.
202 inbound: std.ArrayList(u8) = .empty, 204 inbound: std.ArrayList(u8) = .empty,
203 /// The grid size this client last asked for *and got*: written only 205 /// The grid size this client last asked for *and got*: written only
204 /// after an applySize that succeeded, so it is never some other 206 /// after an applySize that succeeded, so it is never some other
@@ -501,7 +503,8 @@ pub const Server = struct {
501 /// init cannot return without one, and an absent-means-false arm is the 503 /// init cannot return without one, and an absent-means-false arm is the
502 /// silent no-unlink 6090604 fixed. 504 /// silent no-unlink 6090604 fixed.
503 path_id: sockpath.PathId, 505 path_id: sockpath.PathId,
504 /// The attached interactive clients. All of them see every update. 506 /// The attached interactive clients, across every session; each sees
507 /// every update of the one session it is attached to.
505 clients: [max_clients]?ClientSlot = @splat(null), 508 clients: [max_clients]?ClientSlot = @splat(null),
506 /// Ticks once per activity verb, stamping ClientSlot.activity. Monotonic 509 /// Ticks once per activity verb, stamping ClientSlot.activity. Monotonic
507 /// and not a timestamp: two clients acting inside the same millisecond 510 /// and not a timestamp: two clients acting inside the same millisecond
@@ -1460,8 +1463,8 @@ pub const Server = struct {
1460 } 1463 }
1461 1464
1462 /// Close every channel this client owns, silently: the peer that would 1465 /// Close every channel this client owns, silently: the peer that would
1463 /// be told is the one that has gone. Called from dropClient, which is 1466 /// be told is the one that has gone. Runs from dropClient — not from
1464 /// every way a client can leave. 1467 /// quicOnClose, which nulls its slot without this sweep.
1465 fn closeAgentChansOfClient(self: *Server, i: usize) void { 1468 fn closeAgentChansOfClient(self: *Server, i: usize) void {
1466 for (0..max_agent_chans) |s| { 1469 for (0..max_agent_chans) |s| {
1467 const ch = self.agent_chans[s] orelse continue; 1470 const ch = self.agent_chans[s] orelse continue;
@@ -1543,13 +1546,13 @@ pub const Server = struct {
1543 } 1546 }
1544 1547
1545 /// Wait, up to `budget_ms` in total, for clients to accept what they are 1548 /// Wait, up to `budget_ms` in total, for clients to accept what they are
1546 /// still owed. Only paths after which a client gets no next pump use 1549 /// still owed. Only the one path after which a client gets no next pump
1547 /// this — daemon shutdown, and a session's death for the clients about 1550 /// uses this — a session's death, for the clients about to be dropped:
1548 /// to be dropped: mid-session the pump's own POLLOUT handling drains 1551 /// mid-session the pump's own POLLOUT handling drains queues without
1549 /// queues without anyone waiting, and that is the property the rest of 1552 /// anyone waiting, and that is the property the rest of this change
1550 /// this change exists to protect. Here there is no next pump, so the 1553 /// exists to protect. Here there is no next pump, so the alternative to
1551 /// alternative to a bounded wait is losing the frame. A peer that never 1554 /// a bounded wait is losing the frame. A peer that never reads costs
1552 /// reads costs the daemon the budget and no more. 1555 /// the daemon the budget and no more.
1553 fn drainPending(self: *Server, budget_ms: i64) void { 1556 fn drainPending(self: *Server, budget_ms: i64) void {
1554 const deadline = std.time.milliTimestamp() + budget_ms; 1557 const deadline = std.time.milliTimestamp() + budget_ms;
1555 var stalls: usize = 0; 1558 var stalls: usize = 0;
src/shellint.zig
Old New
@@ -267,8 +267,9 @@ pub fn prepare(
267 } 267 }
268 } 268 }
269 269
270 /// 0600 here; the directory's 0700 (`xdg.makePrivateDir`) is what keeps 270 /// 0600 here; the exclusively-created 0700 directory
271 /// this from publishing that the daemon exists and what it named its files. 271 /// (`xdg.makeNewPrivateDir`, never the adopting variant) keeps a
272 /// stranger in the shared parent out.
272 fn writeFilePrivate(path: []const u8, contents: []const u8) !void { 273 fn writeFilePrivate(path: []const u8, contents: []const u8) !void {
273 const f = try std.fs.cwd().createFile(path, .{ .mode = 0o600 }); 274 const f = try std.fs.cwd().createFile(path, .{ .mode = 0o600 });
274 defer f.close(); 275 defer f.close();
@@ -290,10 +291,10 @@ test "detect goes by basename" {
290 try std.testing.expectEqual(Kind.other, detect("/opt/bash/bin/dash")); 291 try std.testing.expectEqual(Kind.other, detect("/opt/bash/bin/dash"));
291 } 292 }
292 293
293 /// The three prepare tests all want a real, writable, disposable directory 294 /// Every test that touches the filesystem wants a real, writable,
294 /// and the string naming it. No socket is bound here, so std's tmpDir (and 295 /// disposable directory and the string naming it. No socket is bound
295 /// its long .zig-cache path) is fine — testtmp exists for sun_path, which 296 /// here, so std's tmpDir (and its long .zig-cache path) is fine —
296 /// this module never touches. 297 /// testtmp exists for sun_path, which this module never touches.
297 const TmpPath = struct { 298 const TmpPath = struct {
298 tmp: std.testing.TmpDir, 299 tmp: std.testing.TmpDir,
299 dir: []const u8, 300 dir: []const u8,
src/sockpath.zig
Old New
@@ -10,9 +10,9 @@
10 const std = @import("std"); 10 const std = @import("std");
11 11
12 /// The usable bytes of `sockaddr_un.sun_path`: the field is 108 and the 12 /// The usable bytes of `sockaddr_un.sun_path`: the field is 108 and the
13 /// last one belongs to the NUL. All three binaries refuse a longer path 13 /// last one belongs to the NUL. Every binary refuses a longer path by
14 /// by name before acting on it, each in its own words — the number is 14 /// name before acting on it, each in its own words — the number is the
15 /// the kernel's and belongs in one place, the wording is theirs. 15 /// kernel's and belongs in one place, the wording is theirs.
16 pub const max_sun_path = 107; 16 pub const max_sun_path = 107;
17 17
18 /// No fallback when `$XDG_RUNTIME_DIR` is unset: a guess cannot make two 18 /// No fallback when `$XDG_RUNTIME_DIR` is unset: a guess cannot make two
src/spawn.zig
Old New
@@ -1,7 +1,7 @@
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. Three callers: `muxd start`, which is explicit and spelled 2 //! answers. `muxd start` is explicit and spelled out; the attach
3 //! out, and the two attach auto-starts (`muxd proxy` and `mux`), which 3 //! auto-starts (`muxd proxy`, `muxd endpoint`, `mux`) share
4 //! share `ensureForAttach` below. The prediction made when the QUIC 4 //! `ensureForAttach` below. The prediction made when the QUIC
5 //! ergonomics landed held — auto-start arrived as call sites, not as a 5 //! ergonomics landed held — auto-start arrived as call sites, not as a
6 //! rewrite. 6 //! rewrite.
7 const std = @import("std"); 7 const std = @import("std");
@@ -35,15 +35,15 @@ pub const LogSpec = struct {
35 truncate: bool, 35 truncate: bool,
36 }; 36 };
37 37
38 /// Test hook: the pid of the most recent spawn. Tests use it to reap the 38 /// Test hook: the pid of the most recent spawn; this file's tests are its
39 /// deliberately-orphaned stub; muxd start reads it for the up-line. Not 39 /// only readers, reaping the deliberately-orphaned stub by it. Not
40 /// synchronized — single-threaded callers only, which all three are. 40 /// synchronized — every caller is single-threaded.
41 pub var last_spawned_pid: std.posix.pid_t = 0; 41 pub var last_spawned_pid: std.posix.pid_t = 0;
42 42
43 /// All of ensureDaemon's stderr output belongs to this struct: the caller 43 /// All of ensureDaemon's stderr output belongs to this struct: the caller
44 /// decides the prefix ("muxd", "muxd proxy", "mux") and whether dots 44 /// decides the prefix (the verb the user typed) and whether dots animate.
45 /// animate. Silence on the already-running path is part of the contract — 45 /// Silence on the already-running path is part of the contract — any
46 /// any output at all means something unusual happened. 46 /// output at all means something unusual happened.
47 pub const Progress = struct { 47 pub const Progress = struct {
48 fd: std.posix.fd_t, 48 fd: std.posix.fd_t,
49 prefix: []const u8, 49 prefix: []const u8,
src/wall.zig
Old New
@@ -1,7 +1,8 @@
1 //! The wall: an ordered list of TARGET spellings — attach history, written 1 //! The wall: an ordered list of TARGET spellings — attach history. mux
2 //! by every grid-claiming attach (muxweb and mux alike) — one owner for the 2 //! records a grid-claiming attach on its first state; muxweb records its
3 //! spelling grammar, the session split, and the persisted file, so the wall 3 //! wall mutations (argv tiles, POST /tiles), never an attach. One owner
4 //! built in a browser is the wall the CLI sees. 4 //! for the spelling grammar, the session split, and the persisted file,
5 //! so the wall built in a browser is the wall the CLI sees.
5 //! 6 //!
6 //! Spelling grammar (one string; also the line format of the state file 7 //! Spelling grammar (one string; also the line format of the state file
7 //! and the body of the hub's POST /tiles): 8 //! and the body of the hub's POST /tiles):
src/wallview.zig
Old New
@@ -608,9 +608,9 @@ fn wallMouse(
608 /// because the other five are reachable from an already-zoomed wall. 608 /// because the other five are reachable from an already-zoomed wall.
609 const WallInput = struct { 609 const WallInput = struct {
610 prefix: interact.PrefixFilter = .{}, 610 prefix: interact.PrefixFilter = .{},
611 /// The wall's own mouse filter — the second in the process, and not an 611 /// The wall's own mouse filter — the second ROLE, not an alternative
612 /// alternative to the first: a promoted tile's `Core` runs one over the 612 /// to a Core's: a promoted tile's `Core` runs its own over the bytes
613 /// bytes it is handed zoomed, this one runs over the bytes that arrive 613 /// it is handed zoomed, this one runs over the bytes that arrive
614 /// UNZOOMED, which the wall's own modes are what produce. 614 /// UNZOOMED, which the wall's own modes are what produce.
615 mouse: interact.MouseFilter = .{}, 615 mouse: interact.MouseFilter = .{},
616 616
@@ -1145,9 +1145,9 @@ fn redial(
1145 target: client.Target, 1145 target: client.Target,
1146 state: *State, 1146 state: *State,
1147 /// This tile's agent channels, which the dying connection owned. Dropped 1147 /// This tile's agent channels, which the dying connection owned. Dropped
1148 /// HERE, and here only, for the reason this function exists at all: five 1148 /// HERE, and here only, for the reason this function exists at all:
1149 /// call sites that each had to remember would be five chances to strand 1149 /// every call site that had to remember would be one more chance to
1150 /// a channel on a connection that cannot close it. 1150 /// strand a channel on a connection that cannot close it.
1151 agents: []?AgentLocal, 1151 agents: []?AgentLocal,
1152 ) bool { 1152 ) bool {
1153 // Before the cold-dial refusal below, which returns without reconnecting 1153 // Before the cold-dial refusal below, which returns without reconnecting
src/wasm_core.zig
Old New
@@ -1,5 +1,5 @@
1 //! The browser replica core: Engine + Replica + keymap compiled to 1 //! The browser replica core: Engine + Replica + ClientCore + keymap
2 //! wasm32-freestanding. The JS shell is glue; every 2 //! compiled to wasm32-freestanding. The JS shell is glue; every
3 //! decision lives on this side of the boundary. 3 //! decision lives on this side of the boundary.
4 //! 4 //!
5 //! FRAME-driven, not byte-driven: the host stages one mux protocol frame 5 //! FRAME-driven, not byte-driven: the host stages one mux protocol frame
src/webhub.zig
Old New
@@ -459,9 +459,9 @@ pub const FrameMsgError = error{
459 LengthMismatch, 459 LengthMismatch,
460 Oversize, 460 Oversize,
461 /// An agent frame from the browser. Those are the hub's own to author 461 /// An agent frame from the browser. Those are the hub's own to author
462 /// (`muxweb -A` offers the hub machine's agent), and a tab has no key 462 /// (it offers no agent today), and a tab has no key to speak for — so
463 /// to speak for — so the frame is a bug or a spoof, and either way the 463 /// the frame is a bug or a spoof, and either way the silent offer
464 /// silent offer that wedges ssh. 464 /// that wedges ssh.
465 HubOwned, 465 HubOwned,
466 }; 466 };
467 467
src/webhub_main.zig
Old New
@@ -289,9 +289,9 @@ pub fn main() !u8 {
289 289
290 /// mux_main.key_env's value, spelled again rather than imported: pulling 290 /// mux_main.key_env's value, spelled again rather than imported: pulling
291 /// an exe root into the module graph for one string constant is not 291 /// an exe root into the module graph for one string constant is not
292 /// worth the tangle. mux_main.zig:49 is the origin; a drift between the 292 /// worth the tangle. mux_main.key_env is the origin; a drift between
293 /// two spellings would make the two binaries read different variables, 293 /// the two spellings would make the two binaries read different
294 /// so change both or neither. 294 /// variables, so change both or neither.
295 const client_key_env = "MUX_KEY_FILE"; 295 const client_key_env = "MUX_KEY_FILE";
296 296
297 test "parse: three spellings become three tiles in argv order, port and key bind" { 297 test "parse: three spellings become three tiles in argv order, port and key bind" {
src/xdg.zig
Old New
@@ -8,7 +8,7 @@
8 const std = @import("std"); 8 const std = @import("std");
9 9
10 /// The one place the default key location is spelled; muxd keygen writes 10 /// The one place the default key location is spelled; muxd keygen writes
11 /// it and both binaries' key resolution reads it. 11 /// it and every binary's key resolution reads it.
12 pub fn keyPath(alloc: std.mem.Allocator) ![]const u8 { 12 pub fn keyPath(alloc: std.mem.Allocator) ![]const u8 {
13 return keyPathFrom(alloc, std.posix.getenv("XDG_CONFIG_HOME"), std.posix.getenv("HOME")); 13 return keyPathFrom(alloc, std.posix.getenv("XDG_CONFIG_HOME"), std.posix.getenv("HOME"));
14 } 14 }
@@ -24,8 +24,9 @@ pub const KeyResolution = union(enum) {
24 /// The XDG default, which exists. Owned by the caller. 24 /// The XDG default, which exists. Owned by the caller.
25 default: []const u8, 25 default: []const u8,
26 /// The XDG default, which does not. Owned by the caller, and carried 26 /// The XDG default, which does not. Owned by the caller, and carried
27 /// out rather than printed: `mux` and `muxweb` word this differently 27 /// out rather than printed: `mux` and `muxa` word this differently
28 /// (the hub names the tile that wanted it) and both need the path. 28 /// (the agent answers in JSON) and both need the path; muxweb folds
29 /// it into a bare MissingKey.
29 missing: []const u8, 30 missing: []const u8,
30 }; 31 };
31 32
@@ -56,8 +57,8 @@ pub fn keyPathFrom(
56 return std.fmt.allocPrint(alloc, "{s}/.config/mux/key", .{h}); 57 return std.fmt.allocPrint(alloc, "{s}/.config/mux/key", .{h});
57 } 58 }
58 59
59 /// Truncated at each spawn by the spawner: it holds the current daemon's 60 /// The daemon's stdout+stderr. Truncated only by `muxd start`; the attach
60 /// stdout+stderr, not history. 61 /// auto-starts append (spawn.zig owns why an attach is not a restart).
61 pub fn logPath(alloc: std.mem.Allocator) ![]const u8 { 62 pub fn logPath(alloc: std.mem.Allocator) ![]const u8 {
62 return logPathFrom(alloc, std.posix.getenv("XDG_STATE_HOME"), std.posix.getenv("HOME")); 63 return logPathFrom(alloc, std.posix.getenv("XDG_STATE_HOME"), std.posix.getenv("HOME"));
63 } 64 }
tools/docscheck.zig
Old New
@@ -40,7 +40,7 @@ fn err(comptime fmt: []const u8, args: anytype) void {
40 } 40 }
41 41
42 fn usage() u8 { 42 fn usage() u8 {
43 err("usage: docscheck [--report] --check FILE... --index FILE...\n", .{}); 43 err("usage: docscheck [--report] --check FILE... --index FILE... --budget FILE\n", .{});
44 return 2; 44 return 2;
45 } 45 }
46 46
@@ -438,8 +438,7 @@ pub fn main() !u8 {
438 return 0; 438 return 0;
439 } 439 }
440 440
441 /// Best-effort name for the report's third column. Cosmetic: the file:line is 441 /// The cap a `//!` module header answers to, having no decl to outweigh.
442 /// what a reader navigates by.
443 const header_max = 1024; 442 const header_max = 1024;
444 443
445 const Tally = struct { name: []const u8, n: usize }; 444 const Tally = struct { name: []const u8, n: usize };
@@ -528,8 +527,8 @@ fn heavyBlocks(
528 // is already a gate: indentation is canonical, so a decl's closing 527 // is already a gate: indentation is canonical, so a decl's closing
529 // brace sits at the decl's own indent and nowhere else. 528 // brace sits at the decl's own indent and nowhere else.
530 529
531 // A `//!` header answers to no decl, so it answers to the cap: server.zig 530 // A `//!` header answers to no decl, so it answers to the cap — sized so
532 // states the contract of the largest module in this tree in 532 bytes. 531 // the largest module in this tree states its contract in about half of it.
533 var header: usize = 0; 532 var header: usize = 0;
534 var hi: usize = 0; 533 var hi: usize = 0;
535 while (hi < lines.len and 534 while (hi < lines.len and
@@ -587,6 +586,8 @@ fn heavyBlocks(
587 return flagged; 586 return flagged;
588 } 587 }
589 588
589 /// Best-effort name for the report's third column. Cosmetic: the file:line is
590 /// what a reader navigates by.
590 fn declName(dt: []const u8) []const u8 { 591 fn declName(dt: []const u8) []const u8 {
591 var it = std.mem.tokenizeAny(u8, dt, " \t(:="); 592 var it = std.mem.tokenizeAny(u8, dt, " \t(:=");
592 while (it.next()) |w| { 593 while (it.next()) |w| {