8557c2a6
docs: comments that were lying stop lying
a73x 2026-08-23 10:26
Commit message
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| { |