Commit 8e168b6
Valentine Silvansky
·
2026-07-27 09:32:02 -0400 EDT
parent 0266042
fix: attach hang against pre-0.7.0 daemons
2 files changed,
+44,
-8
+41,
-0
| ... | ... | @@ -132,6 +132,23 @@ pub fn appendMessage( | |
| 132 | 132 | } | |
| 133 | 133 | } | |
| 134 | 134 | ||
| 135 | + | /// Pre-0.7.0 daemons expect a 4-byte Init/Resize payload (rows+cols, no | |
| 136 | + | /// pixel size) and silently drop the 8-byte form, hanging `zmx attach` | |
| 137 | + | /// against a running old daemon (#211). Append both encodings: every daemon | |
| 138 | + | /// drops the length it doesn't expect and processes the other exactly once. | |
| 139 | + | pub const LEGACY_RESIZE_LEN = 4; | |
| 140 | + | ||
| 141 | + | pub fn appendSizeMessage( | |
| 142 | + | alloc: std.mem.Allocator, | |
| 143 | + | list: *std.ArrayList(u8), | |
| 144 | + | tag: Tag, | |
| 145 | + | size: Resize, | |
| 146 | + | ) !void { | |
| 147 | + | const bytes = std.mem.asBytes(&size); | |
| 148 | + | try appendMessage(alloc, list, tag, bytes); | |
| 149 | + | try appendMessage(alloc, list, tag, bytes[0..LEGACY_RESIZE_LEN]); | |
| 150 | + | } | |
| 151 | + | ||
| 135 | 152 | fn writeAll(fd: i32, data: []const u8) !void { | |
| 136 | 153 | var index: usize = 0; | |
| 137 | 154 | while (index < data.len) { |
| ... | ... | @@ -362,6 +379,30 @@ pub fn roundTripForTag( | |
| 362 | 379 | return error.Unexpected; | |
| 363 | 380 | } | |
| 364 | 381 | ||
| 382 | + | test "appendSizeMessage emits current and legacy encodings" { | |
| 383 | + | const alloc = std.testing.allocator; | |
| 384 | + | var list = try std.ArrayList(u8).initCapacity(alloc, 64); | |
| 385 | + | defer list.deinit(alloc); | |
| 386 | + | ||
| 387 | + | const size = Resize{ .rows = 45, .cols = 170, .xpixel = 900, .ypixel = 1800 }; | |
| 388 | + | try appendSizeMessage(alloc, &list, .Init, size); | |
| 389 | + | ||
| 390 | + | const h1 = std.mem.bytesToValue(Header, list.items[0..@sizeOf(Header)]); | |
| 391 | + | try std.testing.expectEqual(Tag.Init, h1.tag); | |
| 392 | + | try std.testing.expectEqual(@as(u32, @sizeOf(Resize)), h1.len); | |
| 393 | + | const p1 = list.items[@sizeOf(Header)..][0..@sizeOf(Resize)]; | |
| 394 | + | try std.testing.expectEqual(size, std.mem.bytesToValue(Resize, p1)); | |
| 395 | + | ||
| 396 | + | const off2 = @sizeOf(Header) + @sizeOf(Resize); | |
| 397 | + | const h2 = std.mem.bytesToValue(Header, list.items[off2..][0..@sizeOf(Header)]); | |
| 398 | + | try std.testing.expectEqual(Tag.Init, h2.tag); | |
| 399 | + | try std.testing.expectEqual(@as(u32, LEGACY_RESIZE_LEN), h2.len); | |
| 400 | + | // Legacy payload is the rows+cols prefix of the current encoding. | |
| 401 | + | const p2 = list.items[off2 + @sizeOf(Header) ..][0..LEGACY_RESIZE_LEN]; | |
| 402 | + | try std.testing.expectEqualSlices(u8, p1[0..LEGACY_RESIZE_LEN], p2); | |
| 403 | + | try std.testing.expectEqual(off2 + @sizeOf(Header) + LEGACY_RESIZE_LEN, list.items.len); | |
| 404 | + | } | |
| 405 | + | ||
| 365 | 406 | test "zeroed Info has no stack garbage in wire bytes" { | |
| 366 | 407 | var info = std.mem.zeroes(Info); | |
| 367 | 408 | info.clients_len = 3; |
+3,
-8
| ... | ... | @@ -47,7 +47,7 @@ pub fn clientLoop(client_sock_fd: i32, env_str: []const u8) !ClientResult { | |
| 47 | 47 | ||
| 48 | 48 | // Send init message with terminal size (buffered) | |
| 49 | 49 | const size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO); | |
| 50 | - | try ipc.appendMessage(gpa, &sock_write_buf, .Init, std.mem.asBytes(&size)); | |
| 50 | + | try ipc.appendSizeMessage(gpa, &sock_write_buf, .Init, size); | |
| 51 | 51 | ||
| 52 | 52 | var poll_fds = try std.ArrayList(lib_posix.pollfd).initCapacity(gpa, 4); | |
| 53 | 53 | defer poll_fds.deinit(gpa); |
| ... | ... | @@ -104,7 +104,7 @@ pub fn clientLoop(client_sock_fd: i32, env_str: []const u8) !ClientResult { | |
| 104 | 104 | if (poll_fds.items[2].revents & lib_posix.POLL.IN != 0) { | |
| 105 | 105 | signal.drainSignalPipe(); | |
| 106 | 106 | const next_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO); | |
| 107 | - | try ipc.appendMessage(gpa, &sock_write_buf, .Resize, std.mem.asBytes(&next_size)); | |
| 107 | + | try ipc.appendSizeMessage(gpa, &sock_write_buf, .Resize, next_size); | |
| 108 | 108 | } | |
| 109 | 109 | ||
| 110 | 110 | // Handle stdin -> socket (Input) |
| ... | ... | @@ -160,12 +160,7 @@ pub fn clientLoop(client_sock_fd: i32, env_str: []const u8) !ClientResult { | |
| 160 | 160 | // daemon is asking for the client's window size usually in response | |
| 161 | 161 | // to this client being set as leader. | |
| 162 | 162 | const next_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO); | |
| 163 | - | try ipc.appendMessage( | |
| 164 | - | gpa, | |
| 165 | - | &sock_write_buf, | |
| 166 | - | .Resize, | |
| 167 | - | std.mem.asBytes(&next_size), | |
| 168 | - | ); | |
| 163 | + | try ipc.appendSizeMessage(gpa, &sock_write_buf, .Resize, next_size); | |
| 169 | 164 | }, | |
| 170 | 165 | .Switch => { | |
| 171 | 166 | std.log.info("switch session", .{}); |