289e83a4
refactor: an observer's refusal is spelled once, not eight times
a73x 2026-08-29 01:10
Commit message
src/server/server.zig
| Old | New | ||
|---|---|---|---|
| @@ -1962,6 +1962,25 @@ pub const Server = struct { | |||
| 1962 | } | 1962 | } |
| 1963 | } | 1963 | } |
| 1964 | 1964 | ||
| 1965 | /// exit_status 1 is the only "no" this wire has; the close follows it. | ||
| 1966 | fn refuseObserver(self: *Server, i: usize) void { | ||
| 1967 | self.replyTo(.{ .observer = i }, .exit_status, &.{1}); | ||
| 1968 | self.dropObserver(i); | ||
| 1969 | } | ||
| 1970 | |||
| 1971 | /// An upgrade refusal: byte 1, then the reason in words, then the close | ||
| 1972 | /// — a `mux d upgrade` that will not happen has nothing further to say | ||
| 1973 | /// on this socket. Five sites spelled this out, each remembering the | ||
| 1974 | /// drop for itself. | ||
| 1975 | fn refuseUpgrade(self: *Server, i: usize, reason: []const u8) void { | ||
| 1976 | var reply: [256]u8 = undefined; | ||
| 1977 | reply[0] = 1; | ||
| 1978 | const n = @min(reason.len, reply.len - 1); | ||
| 1979 | @memcpy(reply[1..][0..n], reason[0..n]); | ||
| 1980 | self.replyTo(.{ .observer = i }, .upgrade_reply, reply[0 .. 1 + n]); | ||
| 1981 | self.dropObserver(i); | ||
| 1982 | } | ||
| 1983 | |||
| 1965 | fn dropPeer(self: *Server, p: Peer) void { | 1984 | fn dropPeer(self: *Server, p: Peer) void { |
| 1966 | switch (p) { | 1985 | switch (p) { |
| 1967 | .client => |i| self.dropClient(i), | 1986 | .client => |i| self.dropClient(i), |
| @@ -2540,17 +2559,13 @@ pub const Server = struct { | |||
| 2540 | // nonzero rather than hanging on a silent socket. | 2559 | // nonzero rather than hanging on a silent socket. |
| 2541 | // Checked BEFORE the name resolves so a client that | 2560 | // Checked BEFORE the name resolves so a client that |
| 2542 | // cannot be seated never spawns a shell either. | 2561 | // cannot be seated never spawns a shell either. |
| 2543 | proto.writeFrameBounded(fd, .exit_status, &.{1}, proto.reply_budget_ms) catch {}; | 2562 | return self.refuseObserver(i); |
| 2544 | self.dropObserver(i); | ||
| 2545 | return; | ||
| 2546 | }; | 2563 | }; |
| 2547 | // Attach-or-create; null is the refusal (bad name, session | 2564 | // Attach-or-create; null is the refusal (bad name, session |
| 2548 | // table full, or an unknown name with no size to create | 2565 | // table full, or an unknown name with no size to create |
| 2549 | // at), answered exactly like the full-table no above. | 2566 | // at), answered exactly like the full-table no above. |
| 2550 | const si = self.sessions.resolve(self, sz.name, sz.cols, sz.rows) orelse { | 2567 | const si = self.sessions.resolve(self, sz.name, sz.cols, sz.rows) orelse { |
| 2551 | proto.writeFrameBounded(fd, .exit_status, &.{1}, proto.reply_budget_ms) catch {}; | 2568 | return self.refuseObserver(i); |
| 2552 | self.dropObserver(i); | ||
| 2553 | return; | ||
| 2554 | }; | 2569 | }; |
| 2555 | // Past both refusals above, so the counter only ever grows | 2570 | // Past both refusals above, so the counter only ever grows |
| 2556 | // for an attach that seated somebody. See Stats.attaches. | 2571 | // for an attach that seated somebody. See Stats.attaches. |
| @@ -2591,44 +2606,26 @@ pub const Server = struct { | |||
| 2591 | // the frame handler returns cleanly and the close-all + execve | 2606 | // the frame handler returns cleanly and the close-all + execve |
| 2592 | // happen outside the observer's read cycle. | 2607 | // happen outside the observer's read cycle. |
| 2593 | .upgrade_req => { | 2608 | .upgrade_req => { |
| 2594 | const req = proto.parseUpgradeReq(frame.payload) catch { | 2609 | const req = proto.parseUpgradeReq(frame.payload) catch return self.refuseUpgrade(i, "bad frame"); |
| 2595 | proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'b', 'a', 'd', ' ', 'f', 'r', 'a', 'm', 'e' }, proto.reply_budget_ms) catch self.dropObserver(i); | ||
| 2596 | self.dropObserver(i); | ||
| 2597 | return; | ||
| 2598 | }; | ||
| 2599 | if (self.validateUpgrade(req, self.version)) |reason| { | 2610 | if (self.validateUpgrade(req, self.version)) |reason| { |
| 2600 | var reply: [256]u8 = undefined; | 2611 | defer self.alloc.free(reason); |
| 2601 | reply[0] = 1; | 2612 | return self.refuseUpgrade(i, reason); |
| 2602 | const rlen = @min(reason.len, reply.len - 1); | ||
| 2603 | @memcpy(reply[1..][0..rlen], reason[0..rlen]); | ||
| 2604 | proto.writeFrameBounded(fd, .upgrade_reply, reply[0 .. 1 + rlen], proto.reply_budget_ms) catch self.dropObserver(i); | ||
| 2605 | self.alloc.free(reason); | ||
| 2606 | self.dropObserver(i); | ||
| 2607 | return; | ||
| 2608 | } | 2613 | } |
| 2609 | // Accepted: write the manifest to a memfd (no CLOEXEC — the | 2614 | // Accepted: write the manifest to a memfd (no CLOEXEC — the |
| 2610 | // new binary must inherit it), reply, and arm the exec. | 2615 | // new binary must inherit it), reply, and arm the exec. |
| 2611 | const memfd = std.posix.memfd_create("mux-upgrade", 0) catch { | 2616 | const memfd = std.posix.memfd_create("mux-upgrade", 0) catch return self.refuseUpgrade(i, "memfd"); |
| 2612 | proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'm', 'e', 'm', 'f', 'd' }, proto.reply_budget_ms) catch self.dropObserver(i); | ||
| 2613 | self.dropObserver(i); | ||
| 2614 | return; | ||
| 2615 | }; | ||
| 2616 | self.writeManifestTo(memfd, self.version) catch { | 2617 | self.writeManifestTo(memfd, self.version) catch { |
| 2617 | std.posix.close(memfd); | 2618 | std.posix.close(memfd); |
| 2618 | proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'm', 'a', 'n', 'i', 'f', 'e', 's', 't' }, proto.reply_budget_ms) catch self.dropObserver(i); | 2619 | return self.refuseUpgrade(i, "manifest"); |
| 2619 | self.dropObserver(i); | ||
| 2620 | return; | ||
| 2621 | }; | 2620 | }; |
| 2622 | // Owned, because `req.path` points into the frame payload | 2621 | // Owned, because `req.path` points into the frame payload |
| 2623 | // this handler's caller frees on return, and the exec runs a | 2622 | // this handler's caller frees on return, and the exec runs a |
| 2624 | // pump later. execUpgrade frees it if the exec fails. | 2623 | // pump later. execUpgrade frees it if the exec fails. |
| 2625 | const path = self.alloc.dupe(u8, req.path) catch { | 2624 | const path = self.alloc.dupe(u8, req.path) catch { |
| 2626 | std.posix.close(memfd); | 2625 | std.posix.close(memfd); |
| 2627 | proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'o', 'o', 'm' }, proto.reply_budget_ms) catch self.dropObserver(i); | 2626 | return self.refuseUpgrade(i, "oom"); |
| 2628 | self.dropObserver(i); | ||
| 2629 | return; | ||
| 2630 | }; | 2627 | }; |
| 2631 | proto.writeFrameBounded(fd, .upgrade_reply, &.{0}, proto.reply_budget_ms) catch self.dropObserver(i); | 2628 | self.replyTo(.{ .observer = i }, .upgrade_reply, &.{0}); |
| 2632 | self.dropObserver(i); | 2629 | self.dropObserver(i); |
| 2633 | self.pending_upgrade = .{ .path = path, .memfd = memfd }; | 2630 | self.pending_upgrade = .{ .path = path, .memfd = memfd }; |
| 2634 | }, | 2631 | }, |
| @@ -2651,9 +2648,7 @@ pub const Server = struct { | |||
| 2651 | // refusal above: the byte must be gone before the fd is, | 2648 | // refusal above: the byte must be gone before the fd is, |
| 2652 | // but not at the price of the whole pump. | 2649 | // but not at the price of the whole pump. |
| 2653 | std.debug.print("mux d: status_req for unknown session: {s}\n", .{name}); | 2650 | std.debug.print("mux d: status_req for unknown session: {s}\n", .{name}); |
| 2654 | proto.writeFrameBounded(fd, .exit_status, &.{1}, proto.reply_budget_ms) catch {}; | 2651 | return self.refuseObserver(i); |
| 2655 | self.dropObserver(i); | ||
| 2656 | return; | ||
| 2657 | }; | 2652 | }; |
| 2658 | const payload = proto.encodeStatusReply(self.buildStatusReply(si)); | 2653 | const payload = proto.encodeStatusReply(self.buildStatusReply(si)); |
| 2659 | proto.writeFrameBounded(fd, .status_reply, &payload, proto.reply_budget_ms) catch self.dropObserver(i); | 2654 | proto.writeFrameBounded(fd, .status_reply, &payload, proto.reply_budget_ms) catch self.dropObserver(i); |