808d5586
refactor: every remaining std.os.linux spelling goes through an os row or std.c
a73x 2026-09-03 15:10
Commit message
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); |