a73x

808d5586

refactor: every remaining std.os.linux spelling goes through an os row or std.c

a73x   2026-09-03 15:10

Commit message
refactor: every remaining std.os.linux spelling goes through an os row or std.c

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

build.zig
Old New
@@ -252,7 +252,7 @@ const mod_table = [_]ModSpec{
252 // loop's alone, which the root never spells, and the painter takes 252 // loop's alone, which the root never spells, and the painter takes
253 // `term.engine` and `term.protocol`; the decoder and the key table they 253 // `term.engine` and `term.protocol`; the decoder and the key table they
254 // also want reach them through `client`'s seams. 254 // also want reach them through `client`'s seams.
255 .{ .name = "wall", .path = "src/tui/wallview.zig", .link_libc = true, .imports = &.{ "term", "client", "proxy", "spawn" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 255 .{ .name = "wall", .path = "src/tui/wallview.zig", .link_libc = true, .imports = &.{ "term", "client", "proxy", "spawn", "client_os" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
256 // ---- the one binary ---- 256 // ---- the one binary ----
257 // Four words, one image — and one row: the daemon's entrypoint, the 257 // Four words, one image — and one row: the daemon's entrypoint, the
258 // client's and the hub's are CHILD FILES of the dispatcher, so a second 258 // client's and the hub's are CHILD FILES of the dispatcher, so a second
@@ -275,7 +275,7 @@ const mod_table = [_]ModSpec{
275 // fourth copy of connect-write-poll-read here. `testtmp` is the keygen 275 // fourth copy of connect-write-poll-read here. `testtmp` is the keygen
276 // round-trip's: it needs a directory to generate into, which the daemon 276 // round-trip's: it needs a directory to generate into, which the daemon
277 // never touches. 277 // never touches.
278 .{ .name = "mux", .path = "src/cli/mux.zig", .link_libc = true, .imports = &.{ "daemon", "client", "wall", "agent", "webhub", "term", "proxy", "quic", "xdg", "spawn", "sockpath", "cliflags", "dial", "server_os" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 278 .{ .name = "mux", .path = "src/cli/mux.zig", .link_libc = true, .imports = &.{ "daemon", "client", "wall", "agent", "webhub", "term", "proxy", "quic", "xdg", "spawn", "sockpath", "cliflags", "dial", "server_os", "client_os" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
279 }; 279 };
280 280
281 /// Comptime row lookup. Every hand-written module name in this file goes 281 /// Comptime row lookup. Every hand-written module name in this file goes
src/cli/main.zig
Old New
@@ -2085,7 +2085,7 @@ test "start -d: an answering socket is already_running, nothing spawned" {
2085 test "start -d: a socket this process may not reach is not `already running`" { 2085 test "start -d: a socket this process may not reach is not `already running`" {
2086 // chmod does not bite root; as root the connect succeeds and the 2086 // chmod does not bite root; as root the connect succeeds and the
2087 // premise of the test is gone. 2087 // premise of the test is gone.
2088 if (std.os.linux.geteuid() == 0) return error.SkipZigTest; 2088 if (std.posix.geteuid() == 0) return error.SkipZigTest;
2089 2089
2090 const testtmp = @import("testtmp"); 2090 const testtmp = @import("testtmp");
2091 var tmp = try testtmp.TmpDir.make(); 2091 var tmp = try testtmp.TmpDir.make();
src/cli/muxa.zig
Old New
@@ -648,11 +648,11 @@ test "a re-attach forgets the snapshot it saw, so a refused reconnect is not an
648 const alloc = std.testing.allocator; 648 const alloc = std.testing.allocator;
649 // A socketpair, not a pipe: this conn has to WRITE the attach as well as 649 // A socketpair, not a pipe: this conn has to WRITE the attach as well as
650 // read down the ONE fd it holds, which is the shape under test. Through 650 // read down the ONE fd it holds, which is the shape under test. Through
651 // `std.os.linux` because `std.posix` has no socketpair on 0.15.2. 651 // `std.c` because `std.posix` has no socketpair on 0.15.2.
652 var sp: [2]i32 = undefined; 652 var sp: [2]std.posix.fd_t = undefined;
653 try std.testing.expectEqual( 653 try std.testing.expectEqual(
654 @as(usize, 0), 654 @as(c_int, 0),
655 std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp), 655 std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp),
656 ); 656 );
657 defer std.posix.close(sp[0]); 657 defer std.posix.close(sp[0]);
658 defer std.posix.close(sp[1]); 658 defer std.posix.close(sp[1]);
@@ -682,10 +682,10 @@ test "a refusal that closes the socket before the input write is still reported
682 // Reproduce the race where the daemon's rejection frame and close arrive 682 // Reproduce the race where the daemon's rejection frame and close arrive
683 // before the next write, causing BrokenPipe unless the pending frame is 683 // before the next write, causing BrokenPipe unless the pending frame is
684 // drained and classified. 684 // drained and classified.
685 var sp: [2]i32 = undefined; 685 var sp: [2]std.posix.fd_t = undefined;
686 try std.testing.expectEqual( 686 try std.testing.expectEqual(
687 @as(usize, 0), 687 @as(c_int, 0),
688 std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp), 688 std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp),
689 ); 689 );
690 defer std.posix.close(sp[0]); 690 defer std.posix.close(sp[0]);
691 691
@@ -699,10 +699,10 @@ test "a refusal that closes the socket before the input write is still reported
699 try std.testing.expectError(error.AttachRefused, conn.sendFrame(.input, "x", deadline)); 699 try std.testing.expectError(error.AttachRefused, conn.sendFrame(.input, "x", deadline));
700 700
701 // And the verb's answer is the refusal's JSON, not the write's. 701 // And the verb's answer is the refusal's JSON, not the write's.
702 var sp2: [2]i32 = undefined; 702 var sp2: [2]std.posix.fd_t = undefined;
703 try std.testing.expectEqual( 703 try std.testing.expectEqual(
704 @as(usize, 0), 704 @as(c_int, 0),
705 std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp2), 705 std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp2),
706 ); 706 );
707 defer std.posix.close(sp2[0]); 707 defer std.posix.close(sp2[0]);
708 try proto.writeFrame(sp2[1], .exit_status, &[_]u8{1}); 708 try proto.writeFrame(sp2[1], .exit_status, &[_]u8{1});
src/cli/webhub_main.zig
Old New
@@ -10,6 +10,7 @@
10 10
11 const std = @import("std"); 11 const std = @import("std");
12 const client = @import("client"); 12 const client = @import("client");
13 const client_os = @import("client_os");
13 const webhub = @import("webhub"); 14 const webhub = @import("webhub");
14 const hosts = @import("client").hosts; 15 const hosts = @import("client").hosts;
15 const build_options = @import("build_options"); 16 const build_options = @import("build_options");
@@ -173,7 +174,7 @@ pub fn main(args: []const [:0]const u8) !u8 {
173 // `Hub.birth` above, one per pane, as the layout was read. 174 // `Hub.birth` above, one per pane, as the layout was read.
174 std.debug.print("mux web: serving http://127.0.0.1:{d} pid={d}\n", .{ 175 std.debug.print("mux web: serving http://127.0.0.1:{d} pid={d}\n", .{
175 parsed.port, 176 parsed.port,
176 std.os.linux.getpid(), 177 client_os.getpid(),
177 }); 178 });
178 hub.start(); 179 hub.start();
179 180
src/dial.zig
Old New
@@ -135,8 +135,8 @@ test "detach writes one empty frame and waits for nothing" {
135 // Pinned on a socketpair because the goodbye has no reply to wait for: 135 // Pinned on a socketpair because the goodbye has no reply to wait for:
136 // what is checkable is that exactly one empty `detach` reaches the peer 136 // what is checkable is that exactly one empty `detach` reaches the peer
137 // and the call returns without reading anything back. 137 // and the call returns without reading anything back.
138 var pair: [2]i32 = undefined; 138 var pair: [2]std.posix.fd_t = undefined;
139 try std.testing.expectEqual(@as(usize, 0), std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &pair)); 139 try std.testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &pair));
140 defer std.posix.close(pair[1]); 140 defer std.posix.close(pair[1]);
141 141
142 try detach(pair[0]); 142 try detach(pair[0]);
src/engine/protocol.zig
Old New
@@ -2569,9 +2569,9 @@ test "agentDataOversize draws the line at one full frame" {
2569 /// std.posix has no socketpair on the pinned 0.15.2, and these two tests 2569 /// std.posix has no socketpair on the pinned 0.15.2, and these two tests
2570 /// need a socket whose peer they can refuse to read. 2570 /// need a socket whose peer they can refuse to read.
2571 fn testSocketPair() ![2]std.posix.fd_t { 2571 fn testSocketPair() ![2]std.posix.fd_t {
2572 var fds: [2]i32 = undefined; 2572 var fds: [2]std.posix.fd_t = undefined;
2573 const rc = std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds); 2573 const rc = std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds);
2574 if (std.posix.errno(rc) != .SUCCESS) return error.SocketPairFailed; 2574 if (rc != 0) return error.SocketPairFailed;
2575 return fds; 2575 return fds;
2576 } 2576 }
2577 2577
src/link.zig
Old New
@@ -304,8 +304,8 @@ pub const Link = union(enum) {
304 const testing = std.testing; 304 const testing = std.testing;
305 305
306 fn mkPair() ![2]std.posix.fd_t { 306 fn mkPair() ![2]std.posix.fd_t {
307 var pair: [2]i32 = undefined; 307 var pair: [2]std.posix.fd_t = undefined;
308 try testing.expectEqual(@as(usize, 0), std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &pair)); 308 try testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &pair));
309 return .{ pair[0], pair[1] }; 309 return .{ pair[0], pair[1] };
310 } 310 }
311 311
src/os/client_os.zig
Old New
@@ -39,6 +39,28 @@ pub fn geteuid() std.posix.uid_t {
39 return impl.geteuid(); 39 return impl.geteuid();
40 } 40 }
41 41
42 /// This terminal's size, or null when `fd` is not a terminal. The 0x0 case
43 /// and the daemon's floor are the caller's to judge (`interact.ttySize`).
44 pub fn winSize(fd: std.posix.fd_t) ?std.posix.winsize {
45 return impl.winSize(fd);
46 }
47
48 /// Size a pty. Test-only in practice, but a contract because the wall's
49 /// own `ttySize` is judged against it.
50 pub fn setWinSize(fd: std.posix.fd_t, ws: std.posix.winsize) error{Unsupported}!void {
51 return impl.setWinSize(fd, ws);
52 }
53
54 /// A real master/slave pty pair, the OS answering about the OS. Test-only:
55 /// the wall never opens a pty, it lives in one. Named here rather than
56 /// returned anonymously because an anonymous struct in the root and one in
57 /// an arm are two distinct types, and the arm could then never satisfy the
58 /// contract.
59 pub const PtyPair = struct { master: std.posix.fd_t, slave: std.posix.fd_t };
60 pub fn openPtyPair() error{Unsupported}!PtyPair {
61 return impl.openPtyPair();
62 }
63
42 test "client_os: the arm compiles and answers for the process it is in" { 64 test "client_os: the arm compiles and answers for the process it is in" {
43 try std.testing.expect(getpid() > 0); 65 try std.testing.expect(getpid() > 0);
44 } 66 }
@@ -59,6 +81,16 @@ test "client_os.peerCred and parentOf: asked of the OS, not a fixture" {
59 try std.testing.expectEqual(@as(std.posix.pid_t, 0), parentOf(0)); 81 try std.testing.expectEqual(@as(std.posix.pid_t, 0), parentOf(0));
60 } 82 }
61 83
84 test "client_os.winSize reads what setWinSize wrote, off a real pty" {
85 const p = try openPtyPair();
86 defer std.posix.close(p.master);
87 defer std.posix.close(p.slave);
88 try setWinSize(p.master, .{ .row = 17, .col = 91, .xpixel = 0, .ypixel = 0 });
89 const ws = winSize(p.slave) orelse return error.NoSize;
90 try std.testing.expectEqual(@as(u16, 91), ws.col);
91 try std.testing.expectEqual(@as(u16, 17), ws.row);
92 }
93
62 test { 94 test {
63 std.testing.refAllDeclsRecursive(@This()); 95 std.testing.refAllDeclsRecursive(@This());
64 } 96 }
src/os/client_os_linux.zig
Old New
@@ -35,3 +35,26 @@ pub fn parentOf(pid: std.posix.pid_t) std.posix.pid_t {
35 pub fn geteuid() std.posix.uid_t { 35 pub fn geteuid() std.posix.uid_t {
36 return std.os.linux.geteuid(); 36 return std.os.linux.geteuid();
37 } 37 }
38
39 pub fn winSize(fd: std.posix.fd_t) ?std.posix.winsize {
40 var ws: std.posix.winsize = undefined;
41 if (std.os.linux.ioctl(fd, std.os.linux.T.IOCGWINSZ, @intFromPtr(&ws)) != 0) return null;
42 return ws;
43 }
44
45 pub fn setWinSize(fd: std.posix.fd_t, ws: std.posix.winsize) error{Unsupported}!void {
46 if (std.os.linux.ioctl(fd, std.os.linux.T.IOCSWINSZ, @intFromPtr(&ws)) != 0) return error.Unsupported;
47 }
48
49 pub fn openPtyPair() error{Unsupported}!root.PtyPair {
50 const master = std.posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR }, 0) catch return error.Unsupported;
51 errdefer std.posix.close(master);
52 var unlock: c_int = 0;
53 if (std.os.linux.ioctl(master, std.os.linux.T.IOCSPTLCK, @intFromPtr(&unlock)) != 0) return error.Unsupported;
54 var idx: c_uint = 0;
55 if (std.os.linux.ioctl(master, std.os.linux.T.IOCGPTN, @intFromPtr(&idx)) != 0) return error.Unsupported;
56 var name_buf: [32]u8 = undefined;
57 const name = std.fmt.bufPrint(&name_buf, "/dev/pts/{d}", .{idx}) catch return error.Unsupported;
58 const slave = std.posix.open(name, .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0) catch return error.Unsupported;
59 return .{ .master = master, .slave = slave };
60 }
src/os/server_os.zig
Old New
@@ -34,6 +34,21 @@ pub fn peerCred(fd: std.posix.socket_t) ?PeerCred {
34 return cred; 34 return cred;
35 } 35 }
36 36
37 /// A non-blocking send that cannot raise SIGPIPE: a client that hung up
38 /// mid-frame is an error the pump handles, never a signal that ends the
39 /// daemon. The daemon also ignores SIGPIPE process-wide; this is the half
40 /// that does not depend on the order of that ignore against a fork.
41 pub fn sendNoSig(fd: std.posix.socket_t, bytes: []const u8) std.posix.SendError!usize {
42 return impl.sendNoSig(fd, bytes);
43 }
44
45 /// The socket type of an fd, for refusing to adopt a stream fd as the
46 /// QUIC listener across an upgrade: a stream fd would accept a handshake
47 /// and then lose every packet to recvfrom.
48 pub fn sockType(fd: std.posix.fd_t) error{NotASocket}!u32 {
49 return impl.sockType(fd);
50 }
51
37 pub const Winsize = std.posix.winsize; 52 pub const Winsize = std.posix.winsize;
38 pub const ForkedPty = struct { pid: std.posix.pid_t, master: std.posix.fd_t }; 53 pub const ForkedPty = struct { pid: std.posix.pid_t, master: std.posix.fd_t };
39 54
@@ -342,6 +357,44 @@ test "server_os.anonFd: no path names it, and it is not CLOEXEC" {
342 try std.testing.expectEqualStrings("abc", &buf); 357 try std.testing.expectEqualStrings("abc", &buf);
343 } 358 }
344 359
360 test "server_os.sendNoSig: a closed peer is an error, not a signal" {
361 // Judged in a CHILD, because this process cannot be asked. Zig's own
362 // startup code installs a no-op SIGPIPE handler in every binary it
363 // starts, the test runner included, so a plain send with no
364 // MSG_NOSIGNAL also returns BrokenPipe here — a test written in this
365 // process stays green with the flag deleted, which is the one mistake
366 // it exists to catch. The child puts SIGPIPE back at SIG_DFL first, so
367 // a send that raises the signal DIES and the parent reads a status that
368 // never exited. `forkPty` rather than a raw fork: this file is scanned
369 // by build.zig's folder rule 6, whose one exemption is the Linux arm.
370 const f = try forkPty(.{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 });
371 if (f.pid == 0) {
372 var dfl: std.posix.Sigaction = .{
373 .handler = .{ .handler = std.posix.SIG.DFL },
374 .mask = std.posix.sigemptyset(),
375 .flags = 0,
376 };
377 std.posix.sigaction(std.posix.SIG.PIPE, &dfl, null);
378 var csp: [2]std.posix.fd_t = undefined;
379 if (std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &csp) != 0) exitNow(2);
380 std.posix.close(csp[1]);
381 _ = sendNoSig(csp[0], "x") catch |e| exitNow(if (e == error.BrokenPipe) 0 else 2);
382 // A send that SUCCEEDED to a closed peer is as wrong as one that
383 // signalled, and neither is 0.
384 exitNow(2);
385 }
386 defer std.posix.close(f.master);
387 const status = std.posix.waitpid(f.pid, 0).status;
388 try std.testing.expect(std.posix.W.IFEXITED(status));
389 try std.testing.expectEqual(@as(u32, 0), std.posix.W.EXITSTATUS(status));
390
391 var sp: [2]std.posix.fd_t = undefined;
392 try std.testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp));
393 defer std.posix.close(sp[0]);
394 defer std.posix.close(sp[1]);
395 try std.testing.expectEqual(@as(u32, std.posix.SOCK.STREAM), try sockType(sp[0]));
396 }
397
345 // Forces semantic analysis of every pub decl under `zig build test`, so an 398 // Forces semantic analysis of every pub decl under `zig build test`, so an
346 // unreferenced operation must at least compile for this OS. 399 // unreferenced operation must at least compile for this OS.
347 test { 400 test {
src/os/server_os_linux.zig
Old New
@@ -17,6 +17,18 @@ pub fn peerCred(fd: std.posix.socket_t) ?root.PeerCred {
17 return .{ .uid = cred.uid, .pid = cred.pid }; 17 return .{ .uid = cred.uid, .pid = cred.pid };
18 } 18 }
19 19
20 pub fn sendNoSig(fd: std.posix.socket_t, bytes: []const u8) std.posix.SendError!usize {
21 return std.posix.send(fd, bytes, std.posix.MSG.DONTWAIT | std.posix.MSG.NOSIGNAL);
22 }
23
24 pub fn sockType(fd: std.posix.fd_t) error{NotASocket}!u32 {
25 var t: i32 = undefined;
26 var len: std.posix.socklen_t = @sizeOf(@TypeOf(t));
27 const rc = std.os.linux.getsockopt(fd, std.os.linux.SOL.SOCKET, std.os.linux.SO.TYPE, @ptrCast(&t), &len);
28 if (std.os.linux.E.init(rc) != .SUCCESS) return error.NotASocket;
29 return @intCast(t);
30 }
31
20 pub fn forkPty(ws: root.Winsize) error{ForkPtyFailed}!root.ForkedPty { 32 pub fn forkPty(ws: root.Winsize) error{ForkPtyFailed}!root.ForkedPty {
21 var master: c_int = undefined; 33 var master: c_int = undefined;
22 var cws: c.struct_winsize = .{ .ws_row = ws.row, .ws_col = ws.col, .ws_xpixel = 0, .ws_ypixel = 0 }; 34 var cws: c.struct_winsize = .{ .ws_row = ws.row, .ws_col = ws.col, .ws_xpixel = 0, .ws_ypixel = 0 };
src/server/quic_server.zig
Old New
@@ -9,6 +9,7 @@
9 const std = @import("std"); 9 const std = @import("std");
10 10
11 const quic = @import("quic"); 11 const quic = @import("quic");
12 const server_os = @import("server_os");
12 13
13 /// The C view of the QUIC stack, imported once in quic.zig and shared: 14 /// The C view of the QUIC stack, imported once in quic.zig and shared:
14 /// two @cImport blocks over the same headers produce two *distinct* Zig 15 /// two @cImport blocks over the same headers produce two *distinct* Zig
@@ -323,17 +324,7 @@ pub const Listener = struct {
323 ) !*Listener { 324 ) !*Listener {
324 if (g_listener_live) return error.ListenerAlreadyRunning; 325 if (g_listener_live) return error.ListenerAlreadyRunning;
325 326
326 var sock_type: i32 = undefined; 327 if ((server_os.sockType(fd) catch return error.NotAUdpSocket) != std.posix.SOCK.DGRAM) return error.NotAUdpSocket;
327 var sock_type_len: std.posix.socklen_t = @sizeOf(@TypeOf(sock_type));
328 const rc = std.os.linux.getsockopt(
329 fd,
330 std.os.linux.SOL.SOCKET,
331 std.os.linux.SO.TYPE,
332 @ptrCast(&sock_type),
333 &sock_type_len,
334 );
335 if (std.os.linux.E.init(rc) != .SUCCESS) return error.NotAUdpSocket;
336 if (sock_type != std.os.linux.SOCK.DGRAM) return error.NotAUdpSocket;
337 328
338 return finishInit(alloc, fd, key, handler, idle_ms); 329 return finishInit(alloc, fd, key, handler, idle_ms);
339 } 330 }
src/server/server.zig
Old New
@@ -177,11 +177,7 @@ const Sink = union(enum) {
177 /// unbounded growth just moved into the listener. 177 /// unbounded growth just moved into the listener.
178 fn send(self: Sink, bytes: []const u8) !usize { 178 fn send(self: Sink, bytes: []const u8) !usize {
179 return switch (self) { 179 return switch (self) {
180 .socket => |fd| std.posix.send( 180 .socket => |fd| server_os.sendNoSig(fd, bytes),
181 fd,
182 bytes,
183 std.posix.MSG.DONTWAIT | std.posix.MSG.NOSIGNAL,
184 ),
185 .quic => |q| q.listener.send(q.id, bytes), 181 .quic => |q| q.listener.send(q.id, bytes),
186 }; 182 };
187 } 183 }
src/server/server_agent.zig
Old New
@@ -12,6 +12,7 @@
12 12
13 const std = @import("std"); 13 const std = @import("std");
14 const proto = @import("term").protocol; 14 const proto = @import("term").protocol;
15 const server_os = @import("server_os");
15 const xdg = @import("xdg"); 16 const xdg = @import("xdg");
16 const serve = @import("serve"); 17 const serve = @import("serve");
17 const srv_mod = @import("server.zig"); 18 const srv_mod = @import("server.zig");
@@ -131,7 +132,7 @@ pub const AgentRelay = struct {
131 const dir = std.fmt.allocPrint( 132 const dir = std.fmt.allocPrint(
132 alloc, 133 alloc,
133 "{s}/mux-agent-{d}-{x:0>12}", 134 "{s}/mux-agent-{d}-{x:0>12}",
134 .{ parent, std.os.linux.getpid(), std.crypto.random.int(u48) }, 135 .{ parent, server_os.getpid(), std.crypto.random.int(u48) },
135 ) catch { 136 ) catch {
136 std.debug.print( 137 std.debug.print(
137 "mux d: agent forwarding unavailable (out of memory naming the " ++ 138 "mux d: agent forwarding unavailable (out of memory naming the " ++
src/server/server_test_await.zig
Old New
@@ -1,5 +1,6 @@
1 const std = @import("std"); 1 const std = @import("std");
2 const proto = @import("term").protocol; 2 const proto = @import("term").protocol;
3 const server_os = @import("server_os");
3 const shellint = @import("shellint.zig"); 4 const shellint = @import("shellint.zig");
4 const TmpDir = @import("testtmp").TmpDir; 5 const TmpDir = @import("testtmp").TmpDir;
5 const h = @import("server_test_harness.zig"); 6 const h = @import("server_test_harness.zig");
@@ -371,7 +372,7 @@ test "Server: an entry already at the daemon's pid name costs neither the marks
371 const planted = try std.fmt.allocPrint( 372 const planted = try std.fmt.allocPrint(
372 alloc, 373 alloc,
373 "{s}/mux-shellint-{d}", 374 "{s}/mux-shellint-{d}",
374 .{ td.tmp.path(), std.os.linux.getpid() }, 375 .{ td.tmp.path(), server_os.getpid() },
375 ); 376 );
376 defer alloc.free(planted); 377 defer alloc.free(planted);
377 try std.fs.cwd().writeFile(.{ .sub_path = planted, .data = "not a directory" }); 378 try std.fs.cwd().writeFile(.{ .sub_path = planted, .data = "not a directory" });
src/server/server_test_harness.zig
Old New
@@ -220,12 +220,9 @@ pub fn applyFrame(alloc: std.mem.Allocator, replica: *Engine, frame: proto.Frame
220 pub const SockPair = struct { daemon: std.posix.fd_t, peer: std.posix.fd_t }; 220 pub const SockPair = struct { daemon: std.posix.fd_t, peer: std.posix.fd_t };
221 221
222 pub fn connectedPair() !SockPair { 222 pub fn connectedPair() !SockPair {
223 var fds: [2]i32 = undefined; 223 var fds: [2]std.posix.fd_t = undefined;
224 const rc = std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds); 224 const rc = std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds);
225 switch (std.posix.errno(rc)) { 225 if (rc != 0) return std.posix.unexpectedErrno(std.posix.errno(rc));
226 .SUCCESS => {},
227 else => |e| return std.posix.unexpectedErrno(e),
228 }
229 return .{ .daemon = fds[0], .peer = fds[1] }; 226 return .{ .daemon = fds[0], .peer = fds[1] };
230 } 227 }
231 228
src/server/shellint.zig
Old New
@@ -4,6 +4,7 @@
4 //! nothing and the session runs on the pgid/settle fallbacks. 4 //! nothing and the session runs on the pgid/settle fallbacks.
5 const std = @import("std"); 5 const std = @import("std");
6 const xdg = @import("xdg"); 6 const xdg = @import("xdg");
7 const server_os = @import("server_os");
7 8
8 /// One copy for both shells: a second would drift, silently, in one of 9 /// One copy for both shells: a second would drift, silently, in one of
9 /// them. `local code=$?` must stay FIRST: any line above it clobbers $?. 10 /// them. `local code=$?` must stay FIRST: any line above it clobbers $?.
@@ -174,7 +175,7 @@ pub fn install(
174 const dir = std.fmt.allocPrint( 175 const dir = std.fmt.allocPrint(
175 arena, 176 arena,
176 "{s}/mux-shellint-{d}-{x:0>12}", 177 "{s}/mux-shellint-{d}-{x:0>12}",
177 .{ parent_dir, std.os.linux.getpid(), std.crypto.random.int(u48) }, 178 .{ parent_dir, server_os.getpid(), std.crypto.random.int(u48) },
178 ) catch { 179 ) catch {
179 std.debug.print( 180 std.debug.print(
180 "mux d: shell integration unavailable (out of memory naming the shim " ++ 181 "mux d: shell integration unavailable (out of memory naming the shim " ++
@@ -443,7 +444,7 @@ test "install names the shim directory after the daemon and degrades in place" {
443 const prefix = try std.fmt.bufPrint( 444 const prefix = try std.fmt.bufPrint(
444 &want, 445 &want,
445 "{s}/mux-shellint-{d}-", 446 "{s}/mux-shellint-{d}-",
446 .{ t.dir, std.os.linux.getpid() }, 447 .{ t.dir, server_os.getpid() },
447 ); 448 );
448 // A prefix, not the whole name: the pid is followed by 12 hex digits of 449 // A prefix, not the whole name: the pid is followed by 12 hex digits of
449 // randomness, and the two halves answer different questions — the pid 450 // randomness, and the two halves answer different questions — the pid
src/sockpath.zig
Old New
@@ -172,7 +172,7 @@ test "answers: a live listener, a stale socket file, and a path with nothing on
172 test "`answers` is a read and `claim` is a decision: an unreachable socket is a no to one and an errno to the other" { 172 test "`answers` is a read and `claim` is a decision: an unreachable socket is a no to one and an errno to the other" {
173 // chmod does not bite root, so the connect would succeed and the test 173 // chmod does not bite root, so the connect would succeed and the test
174 // would assert the opposite of what it is named for. 174 // would assert the opposite of what it is named for.
175 if (std.os.linux.geteuid() == 0) return error.SkipZigTest; 175 if (std.posix.geteuid() == 0) return error.SkipZigTest;
176 176
177 const testtmp = @import("testtmp"); 177 const testtmp = @import("testtmp");
178 var tmp = try testtmp.TmpDir.make(); 178 var tmp = try testtmp.TmpDir.make();
src/tui/interact.zig
Old New
@@ -17,6 +17,7 @@ const Replica = @import("term").replica.Replica;
17 const proto = @import("term").protocol; 17 const proto = @import("term").protocol;
18 const predict = @import("predict.zig"); 18 const predict = @import("predict.zig");
19 const client_core = @import("client").core; 19 const client_core = @import("client").core;
20 const client_os = @import("client_os");
20 // Named `paint_mod` because paintOverlay holds a local ArrayList called 21 // Named `paint_mod` because paintOverlay holds a local ArrayList called
21 // `paint`, which a container-level `paint` would collide with. 22 // `paint`, which a container-level `paint` would collide with.
22 const paint_mod = @import("paint.zig"); 23 const paint_mod = @import("paint.zig");
@@ -629,8 +630,7 @@ pub fn winchRaised() bool {
629 /// second copy would drift on exactly the 0x0 case below. 630 /// second copy would drift on exactly the 0x0 case below.
630 pub fn ttySize(fd: std.posix.fd_t) ?proto.Size { 631 pub fn ttySize(fd: std.posix.fd_t) ?proto.Size {
631 if (!std.posix.isatty(fd)) return null; 632 if (!std.posix.isatty(fd)) return null;
632 var ws: std.posix.winsize = undefined; 633 const ws = client_os.winSize(fd) orelse return null;
633 if (std.os.linux.ioctl(fd, std.os.linux.T.IOCGWINSZ, @intFromPtr(&ws)) != 0) return null;
634 // A pty can report 0x0 and a zero-sized grid is invalid for the engine, 634 // A pty can report 0x0 and a zero-sized grid is invalid for the engine,
635 // so that is "unknown". The floor is the daemon's own, read rather than 635 // so that is "unknown". The floor is the daemon's own, read rather than
636 // respelled: a size it refuses to move is as unusable as no size at all. 636 // respelled: a size it refuses to move is as unusable as no size at all.
@@ -3624,23 +3624,16 @@ const SelectionTransport = struct {
3624 } 3624 }
3625 }; 3625 };
3626 3626
3627 /// A real pts pair, sized by ioctl — the OS answering about the OS, which 3627 /// A real pts pair — the OS answering about the OS, which is the only
3628 /// is the only thing `ttySize`'s own ioctl can be judged against. 3628 /// thing `ttySize`'s own read of the terminal can be judged against. The
3629 fn ptsPair() !struct { master: std.posix.fd_t, slave: std.posix.fd_t } { 3629 /// opening is `client_os.openPtyPair`'s; this name stays because the tests
3630 const master = try std.posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR }, 0); 3630 /// below read as a story about a pts pair rather than about a platform row.
3631 errdefer std.posix.close(master); 3631 fn ptsPair() !client_os.PtyPair {
3632 var unlock: c_int = 0; 3632 return client_os.openPtyPair();
3633 if (std.os.linux.ioctl(master, std.os.linux.T.IOCSPTLCK, @intFromPtr(&unlock)) != 0) return error.Unsupported;
3634 var idx: c_uint = 0;
3635 if (std.os.linux.ioctl(master, std.os.linux.T.IOCGPTN, @intFromPtr(&idx)) != 0) return error.Unsupported;
3636 var name_buf: [32]u8 = undefined;
3637 const name = try std.fmt.bufPrint(&name_buf, "/dev/pts/{d}", .{idx});
3638 return .{ .master = master, .slave = try std.posix.open(name, .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0) };
3639 } 3633 }
3640 3634
3641 fn setTtySize(master: std.posix.fd_t, cols: u16, rows: u16) !void { 3635 fn setTtySize(master: std.posix.fd_t, cols: u16, rows: u16) !void {
3642 const ws: std.posix.winsize = .{ .col = cols, .row = rows, .xpixel = 0, .ypixel = 0 }; 3636 return client_os.setWinSize(master, .{ .col = cols, .row = rows, .xpixel = 0, .ypixel = 0 });
3643 if (std.os.linux.ioctl(master, std.os.linux.T.IOCSWINSZ, @intFromPtr(&ws)) != 0) return error.Unsupported;
3644 } 3637 }
3645 3638
3646 test "interact: a terminal under the daemon's floor measures as unknown" { 3639 test "interact: a terminal under the daemon's floor measures as unknown" {
src/xdg.zig
Old New
@@ -412,7 +412,7 @@ test "reapDeadPid: a dead owner's entry goes; a live owner's, a stranger's and a
412 var b3: [48]u8 = undefined; 412 var b3: [48]u8 = undefined;
413 const dead_dir = try std.fmt.bufPrint(&b0, "mux-agent-{d}-abc", .{dead}); 413 const dead_dir = try std.fmt.bufPrint(&b0, "mux-agent-{d}-abc", .{dead});
414 const dead_sock = try std.fmt.bufPrint(&b1, "mux-ask-{d}.sock", .{dead}); 414 const dead_sock = try std.fmt.bufPrint(&b1, "mux-ask-{d}.sock", .{dead});
415 const ours = try std.fmt.bufPrint(&b2, "mux-agent-{d}-abc", .{std.os.linux.getpid()}); 415 const ours = try std.fmt.bufPrint(&b2, "mux-agent-{d}-abc", .{std.c.getpid()});
416 const not_a_pid = try std.fmt.bufPrint(&b3, "mux-agent-{d}x", .{dead}); 416 const not_a_pid = try std.fmt.bufPrint(&b3, "mux-agent-{d}x", .{dead});
417 // A directory with something in it, so a plain rmdir would not do. 417 // A directory with something in it, so a plain rmdir would not do.
418 try tmp.dir.makePath(dead_dir); 418 try tmp.dir.makePath(dead_dir);