Commit 9e5eb55

Eric Bower  ·  2026-07-26 14:13:24 -0400 EDT
parent 4232d74
refactor: use posix.zig for all posix fns

The zig team is planning to completely kill std.posix so we might as well
get ahead of it and bring everything we need into zmx.
6 files changed,  +251, -113
+0, -1
1@@ -1,6 +1,5 @@
2 const builtin = @import("builtin");
3 const std = @import("std");
4-const posix = std.posix;
5 
6 pub const c = switch (builtin.os.tag) {
7     .macos => @cImport({
+6, -7
 1@@ -1,5 +1,4 @@
 2 const std = @import("std");
 3-const posix = std.posix;
 4 const cross = @import("cross.zig");
 5 const socket = @import("socket.zig");
 6 const lib_posix = @import("posix.zig");
 7@@ -172,7 +171,7 @@ pub const SocketBuffer = struct {
 8         }
 9 
10         var tmp: [4096]u8 = undefined;
11-        const n = try posix.read(fd, &tmp);
12+        const n = try lib_posix.read(fd, &tmp);
13         if (n > 0) {
14             try self.buf.appendSlice(self.alloc, tmp[0..n]);
15         }
16@@ -239,8 +238,8 @@ pub fn probeSession(
17     send(fd, .Info, "") catch return error.Unexpected;
18     send(fd, .LabelGet, "") catch {};
19 
20-    var poll_fds = [_]posix.pollfd{.{ .fd = fd, .events = posix.POLL.IN, .revents = 0 }};
21-    const poll_result = posix.poll(&poll_fds, timeout_ms) catch return error.Unexpected;
22+    var poll_fds = [_]lib_posix.pollfd{.{ .fd = fd, .events = lib_posix.POLL.IN, .revents = 0 }};
23+    const poll_result = lib_posix.poll(&poll_fds, timeout_ms) catch return error.Unexpected;
24     if (poll_result == 0) {
25         return error.Timeout;
26     }
27@@ -270,7 +269,7 @@ pub fn probeSession(
28         }
29 
30         // No complete message available, wait for more data
31-        const more = posix.poll(&poll_fds, 50) catch break;
32+        const more = lib_posix.poll(&poll_fds, 50) catch break;
33         if (more == 0) break;
34         const n_read = sb.read(fd) catch break;
35         if (n_read == 0) break;
36@@ -325,8 +324,8 @@ pub fn roundTripForTag(
37 
38     send(fd, request_tag, payload) catch return error.Unexpected;
39 
40-    var poll_fds = [_]posix.pollfd{.{ .fd = fd, .events = posix.POLL.IN, .revents = 0 }};
41-    const poll_result = posix.poll(&poll_fds, timeout_ms) catch return error.Unexpected;
42+    var poll_fds = [_]lib_posix.pollfd{.{ .fd = fd, .events = lib_posix.POLL.IN, .revents = 0 }};
43+    const poll_result = lib_posix.poll(&poll_fds, timeout_ms) catch return error.Unexpected;
44     if (poll_result == 0) return error.Timeout;
45 
46     var sb = SocketBuffer.init(alloc) catch return error.Unexpected;
+89, -90
  1@@ -1,5 +1,4 @@
  2 const std = @import("std");
  3-const posix = std.posix;
  4 const build_options = @import("build_options");
  5 const ghostty_vt = @import("ghostty-vt");
  6 const ipc = @import("ipc.zig");
  7@@ -33,10 +32,10 @@ fn zmxLogFn(
  8 /// Self-pipe woken by signal handlers. std.posix.poll loops on .INTR internally
  9 /// (PollError has no Interrupted member), so a signal that lands during poll()
 10 /// never surfaces; the handler writes a byte here and poll() wakes on POLLIN.
 11-var sig_pipe: [2]posix.fd_t = .{ -1, -1 };
 12+var sig_pipe: [2]lib_posix.fd_t = .{ -1, -1 };
 13 
 14 // https://github.com/ziglang/zig/blob/738d2be9d6b6ef3ff3559130c05159ef53336224/lib/std/posix.zig#L3505
 15-const O_NONBLOCK: usize = 1 << @bitOffsetOf(posix.O, "NONBLOCK");
 16+const O_NONBLOCK: usize = 1 << @bitOffsetOf(lib_posix.O, "NONBLOCK");
 17 
 18 const SessionMatch = struct {
 19     name: []const u8,
 20@@ -83,7 +82,7 @@ fn openSignalPipe() !void {
 21 fn drainSignalPipe() void {
 22     var b: [16]u8 = undefined;
 23     while (true) {
 24-        const n = posix.read(sig_pipe[0], &b) catch return;
 25+        const n = lib_posix.read(sig_pipe[0], &b) catch return;
 26         if (n == 0) return;
 27     }
 28 }
 29@@ -805,12 +804,12 @@ const Daemon = struct {
 30         // main() set SIGPIPE to SIG_IGN, which (unlike handlers) survives
 31         // exec. Restore the default so the shell and its children behave
 32         // normally (e.g. `yes | head` should exit 141 via SIGPIPE).
 33-        const dfl: posix.Sigaction = .{
 34-            .handler = .{ .handler = posix.SIG.DFL },
 35-            .mask = posix.sigemptyset(),
 36+        const dfl: lib_posix.Sigaction = .{
 37+            .handler = .{ .handler = lib_posix.SIG.DFL },
 38+            .mask = lib_posix.sigemptyset(),
 39             .flags = 0,
 40         };
 41-        posix.sigaction(posix.SIG.PIPE, &dfl, null);
 42+        lib_posix.sigaction(lib_posix.SIG.PIPE, &dfl, null);
 43 
 44         const session_env = try std.fmt.allocPrintSentinel(
 45             alloc,
 46@@ -848,7 +847,7 @@ const Daemon = struct {
 47 
 48     /// spawnPty runs forkpty() and executes the shell or shell command the user provides.
 49     fn spawnPty(self: *Daemon) !c_int {
 50-        const size = ipc.getTerminalSize(posix.STDOUT_FILENO);
 51+        const size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
 52         var ws: cross.c.struct_winsize = .{
 53             .ws_row = size.rows,
 54             .ws_col = size.cols,
 55@@ -878,8 +877,8 @@ const Daemon = struct {
 56         std.log.info("pty spawned session={s} pid={d}", .{ self.session_name, pid });
 57 
 58         // make pty non-blocking
 59-        const flags = try lib_posix.fcntl(master_fd, posix.F.GETFL, 0);
 60-        _ = try lib_posix.fcntl(master_fd, posix.F.SETFL, flags | O_NONBLOCK);
 61+        const flags = try lib_posix.fcntl(master_fd, lib_posix.F.GETFL, 0);
 62+        _ = try lib_posix.fcntl(master_fd, lib_posix.F.SETFL, flags | O_NONBLOCK);
 63         return master_fd;
 64     }
 65 
 66@@ -946,7 +945,7 @@ const Daemon = struct {
 67                         std.log.warn("failed to open /dev/null: {s}", .{@errorName(err)});
 68                         return err;
 69                     };
 70-                    inline for (.{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO }) |fd| {
 71+                    inline for (.{ lib_posix.STDIN_FILENO, lib_posix.STDOUT_FILENO, lib_posix.STDERR_FILENO }) |fd| {
 72                         _ = lib_posix.dup2(devnull, fd) catch |err| {
 73                             std.log.warn("dup2 /dev/null -> {d}: {s}", .{ fd, @errorName(err) });
 74                             return err;
 75@@ -1221,11 +1220,11 @@ const Daemon = struct {
 76         //   https://www.gnu.org/software/bash/manual/html_node/Signals.html
 77         // negative pid means kill process and children
 78         std.log.info("sending SIGHUP session={s} pid={d}", .{ self.session_name, self.pid });
 79-        posix.kill(-self.pid, posix.SIG.HUP) catch |err| {
 80+        lib_posix.kill(-self.pid, lib_posix.SIG.HUP) catch |err| {
 81             std.log.warn("failed to send SIGHUP to pty child err={s}", .{@errorName(err)});
 82         };
 83         std.Io.sleep(self.io, std.Io.Duration.fromMilliseconds(500), .real) catch unreachable;
 84-        posix.kill(-self.pid, posix.SIG.KILL) catch |err| {
 85+        lib_posix.kill(-self.pid, lib_posix.SIG.KILL) catch |err| {
 86             std.log.warn("failed to send SIGKILL to pty child err={s}", .{@errorName(err)});
 87         };
 88     }
 89@@ -1339,7 +1338,7 @@ const Daemon = struct {
 90             client.has_pending_output = true;
 91         }
 92         if (self.clients.items.len > 0) {
 93-            posix.kill(self.pid, posix.SIG.WINCH) catch |err| {
 94+            lib_posix.kill(self.pid, lib_posix.SIG.WINCH) catch |err| {
 95                 std.log.warn("failed to send SIGWINCH err={s}", .{@errorName(err)});
 96             };
 97         }
 98@@ -1580,7 +1579,7 @@ fn help(io: std.Io) !void {
 99 }
100 
101 fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detached: bool, is_run_cmd: bool) !u8 {
102-    var poll_fds = try std.ArrayList(posix.pollfd).initCapacity(alloc, 4);
103+    var poll_fds = try std.ArrayList(lib_posix.pollfd).initCapacity(alloc, 4);
104     defer poll_fds.deinit(alloc);
105 
106     var read_buf = try ipc.SocketBuffer.init(alloc);
107@@ -1599,7 +1598,7 @@ fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detache
108         for (client_socket_fds.items) |client_sock_fd| {
109             try poll_fds.append(alloc, .{
110                 .fd = client_sock_fd,
111-                .events = posix.POLL.IN,
112+                .events = lib_posix.POLL.IN,
113                 .revents = 0,
114             });
115         }
116@@ -1607,20 +1606,20 @@ fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detache
117         // Poll for write if we have pending data
118         if (stdout_buf.items.len > 0) {
119             try poll_fds.append(alloc, .{
120-                .fd = posix.STDOUT_FILENO,
121-                .events = posix.POLL.OUT,
122+                .fd = lib_posix.STDOUT_FILENO,
123+                .events = lib_posix.POLL.OUT,
124                 .revents = 0,
125             });
126         }
127 
128-        _ = posix.poll(poll_fds.items, -1) catch |err| {
129+        _ = lib_posix.poll(poll_fds.items, -1) catch |err| {
130             if (err == error.Interrupted) continue; // EINTR from signal, loop again
131             return err;
132         };
133 
134         // Handle socket read (incoming Output messages from daemon)
135         for (poll_fds.items) |*poll_fd| {
136-            if (poll_fd.revents & posix.POLL.IN != 0) {
137+            if (poll_fd.revents & lib_posix.POLL.IN != 0) {
138                 const n = read_buf.read(poll_fd.fd) catch |err| {
139                     if (err == error.WouldBlock) continue;
140                     if (err == error.ConnectionResetByPeer or err == error.BrokenPipe) {
141@@ -1642,7 +1641,7 @@ fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detache
142                     switch (msg.header.tag) {
143                         .Ack => {
144                             if (detached) {
145-                                _ = lib_posix.write(posix.STDOUT_FILENO, "command sent!\n") catch |err| blk: {
146+                                _ = lib_posix.write(lib_posix.STDOUT_FILENO, "command sent!\n") catch |err| blk: {
147                                     if (err == error.WouldBlock) break :blk 0;
148                                     return err;
149                                 };
150@@ -1704,7 +1703,7 @@ fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detache
151         if (task_complete_code) |exit_code| {
152             // Flush any remaining output before returning
153             flush_loop: while (stdout_buf.items.len > 0) {
154-                const n = lib_posix.write(posix.STDOUT_FILENO, stdout_buf.items) catch |err| {
155+                const n = lib_posix.write(lib_posix.STDOUT_FILENO, stdout_buf.items) catch |err| {
156                     if (err == error.WouldBlock) break :flush_loop;
157                     return err;
158                 };
159@@ -1714,7 +1713,7 @@ fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detache
160         }
161 
162         if (stdout_buf.items.len > 0) {
163-            const n = lib_posix.write(posix.STDOUT_FILENO, stdout_buf.items) catch |err| blk: {
164+            const n = lib_posix.write(lib_posix.STDOUT_FILENO, stdout_buf.items) catch |err| blk: {
165                 if (err == error.WouldBlock) break :blk 0;
166                 return err;
167             };
168@@ -1725,7 +1724,7 @@ fn tail(alloc: std.mem.Allocator, client_socket_fds: std.ArrayList(i32), detache
169 
170         // Check for HUP/ERR on any socket
171         for (poll_fds.items) |poll_fd| {
172-            if (poll_fd.revents & (posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL) != 0) {
173+            if (poll_fd.revents & (lib_posix.POLL.HUP | lib_posix.POLL.ERR | lib_posix.POLL.NVAL) != 0) {
174                 return 0;
175             }
176         }
177@@ -2022,7 +2021,7 @@ fn kill(alloc: std.mem.Allocator, io: std.Io, cfg: *Cfg, session_name: []const u
178     // accept backlog.
179     var drain: [256]u8 = undefined;
180     while (true) {
181-        const n = posix.read(fd, &drain) catch break;
182+        const n = lib_posix.read(fd, &drain) catch break;
183         if (n == 0) break;
184     }
185 
186@@ -2179,8 +2178,8 @@ fn fetchHistory(
187     errdefer result.deinit(alloc);
188 
189     while (true) {
190-        var poll_fds = [_]posix.pollfd{.{ .fd = fd, .events = posix.POLL.IN, .revents = 0 }};
191-        const poll_result = posix.poll(&poll_fds, 5000) catch return error.Timeout;
192+        var poll_fds = [_]lib_posix.pollfd{.{ .fd = fd, .events = lib_posix.POLL.IN, .revents = 0 }};
193+        const poll_result = lib_posix.poll(&poll_fds, 5000) catch return error.Timeout;
194         if (poll_result == 0) {
195             return error.Timeout;
196         }
197@@ -2236,8 +2235,8 @@ fn history(alloc: std.mem.Allocator, io: std.Io, cfg: *Cfg, session_name: []cons
198     defer sb.deinit();
199 
200     while (true) {
201-        var poll_fds = [_]posix.pollfd{.{ .fd = fd, .events = posix.POLL.IN, .revents = 0 }};
202-        const poll_result = posix.poll(&poll_fds, 5000) catch return;
203+        var poll_fds = [_]lib_posix.pollfd{.{ .fd = fd, .events = lib_posix.POLL.IN, .revents = 0 }};
204+        const poll_result = lib_posix.poll(&poll_fds, 5000) catch return;
205         if (poll_result == 0) {
206             std.log.err("timeout waiting for history response", .{});
207             return;
208@@ -2248,7 +2247,7 @@ fn history(alloc: std.mem.Allocator, io: std.Io, cfg: *Cfg, session_name: []cons
209 
210         while (sb.next()) |msg| {
211             if (msg.header.tag == .History) {
212-                _ = lib_posix.write(posix.STDOUT_FILENO, msg.payload) catch return;
213+                _ = lib_posix.write(lib_posix.STDOUT_FILENO, msg.payload) catch return;
214                 return;
215             }
216         }
217@@ -2314,15 +2313,15 @@ fn attach(daemon: *Daemon) !void {
218     // skip terminal setup entirely rather than applying undefined stack bytes
219     // via tcsetattr.
220     var orig_termios: cross.c.termios = undefined;
221-    const stdin_is_tty = cross.c.tcgetattr(posix.STDIN_FILENO, &orig_termios) == 0;
222+    const stdin_is_tty = cross.c.tcgetattr(lib_posix.STDIN_FILENO, &orig_termios) == 0;
223 
224     defer {
225         if (stdin_is_tty) {
226-            _ = cross.c.tcsetattr(posix.STDIN_FILENO, cross.c.TCSAFLUSH, &orig_termios);
227+            _ = cross.c.tcsetattr(lib_posix.STDIN_FILENO, cross.c.TCSAFLUSH, &orig_termios);
228         }
229         // Reset terminal modes on detach
230         const restore_seq = "\x1bc";
231-        _ = lib_posix.write(posix.STDOUT_FILENO, restore_seq) catch {};
232+        _ = lib_posix.write(lib_posix.STDOUT_FILENO, restore_seq) catch {};
233     }
234 
235     if (stdin_is_tty) {
236@@ -2340,13 +2339,13 @@ fn attach(daemon: *Daemon) !void {
237         raw_termios.c_cc[cross.c.VMIN] = 1; // Minimum chars to read: return after 1 byte
238         raw_termios.c_cc[cross.c.VTIME] = 0; // Read timeout: no timeout, return immediately
239 
240-        _ = cross.c.tcsetattr(posix.STDIN_FILENO, cross.c.TCSANOW, &raw_termios);
241+        _ = cross.c.tcsetattr(lib_posix.STDIN_FILENO, cross.c.TCSANOW, &raw_termios);
242     }
243 
244     // Clear screen before attaching. This provides a clean slate before
245     // the session restore.
246     const clear_seq = "\x1b[2J\x1b[H";
247-    _ = try lib_posix.write(posix.STDOUT_FILENO, clear_seq);
248+    _ = try lib_posix.write(lib_posix.STDOUT_FILENO, clear_seq);
249 
250     const looper = try clientLoop(client_sock);
251     switch (looper.kind) {
252@@ -2399,13 +2398,13 @@ fn writeFile(daemon: *Daemon, file_path: []const u8) !void {
253         try w.interface.print("session \"{s}\" created\n", .{daemon.session_name});
254         try w.interface.flush();
255     }
256-    const stdin_fd = posix.STDIN_FILENO;
257+    const stdin_fd = lib_posix.STDIN_FILENO;
258     var stdin_buf = try std.ArrayList(u8).initCapacity(daemon.alloc, 4096);
259     defer stdin_buf.deinit(daemon.alloc);
260 
261     while (true) {
262         var tmp: [4096]u8 = undefined;
263-        const n = posix.read(stdin_fd, &tmp) catch |err| {
264+        const n = lib_posix.read(stdin_fd, &tmp) catch |err| {
265             if (err == error.WouldBlock) break;
266             return err;
267         };
268@@ -2680,22 +2679,22 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
269     defer lib_posix.close(client_sock_fd);
270 
271     try openSignalPipe();
272-    installWakeHandler(@intFromEnum(posix.SIG.WINCH));
273+    installWakeHandler(@intFromEnum(lib_posix.SIG.WINCH));
274 
275     // Make socket non-blocking to avoid blocking on writes
276-    var sock_flags = try lib_posix.fcntl(client_sock_fd, posix.F.GETFL, 0);
277+    var sock_flags = try lib_posix.fcntl(client_sock_fd, lib_posix.F.GETFL, 0);
278     sock_flags |= O_NONBLOCK;
279-    _ = try lib_posix.fcntl(client_sock_fd, posix.F.SETFL, sock_flags);
280+    _ = try lib_posix.fcntl(client_sock_fd, lib_posix.F.SETFL, sock_flags);
281 
282     // Buffer for outgoing socket writes
283     var sock_write_buf = try std.ArrayList(u8).initCapacity(alloc, 4096);
284     defer sock_write_buf.deinit(alloc);
285 
286     // Send init message with terminal size (buffered)
287-    const size = ipc.getTerminalSize(posix.STDOUT_FILENO);
288+    const size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
289     try ipc.appendMessage(alloc, &sock_write_buf, .Init, std.mem.asBytes(&size));
290 
291-    var poll_fds = try std.ArrayList(posix.pollfd).initCapacity(alloc, 4);
292+    var poll_fds = try std.ArrayList(lib_posix.pollfd).initCapacity(alloc, 4);
293     defer poll_fds.deinit(alloc);
294 
295     var read_buf = try ipc.SocketBuffer.init(alloc);
296@@ -2704,28 +2703,28 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
297     var stdout_buf = try std.ArrayList(u8).initCapacity(alloc, 4096);
298     defer stdout_buf.deinit(alloc);
299 
300-    const stdin_fd = posix.STDIN_FILENO;
301+    const stdin_fd = lib_posix.STDIN_FILENO;
302 
303     // Make stdin non-blocking. O_NONBLOCK is set on the open file description,
304     // which is shared with the parent shell; restore on exit to avoid
305     // corrupting the parent's stdin.
306-    const stdin_orig_flags = try lib_posix.fcntl(stdin_fd, posix.F.GETFL, 0);
307-    _ = try lib_posix.fcntl(stdin_fd, posix.F.SETFL, stdin_orig_flags | O_NONBLOCK);
308-    defer _ = lib_posix.fcntl(stdin_fd, posix.F.SETFL, stdin_orig_flags) catch {};
309+    const stdin_orig_flags = try lib_posix.fcntl(stdin_fd, lib_posix.F.GETFL, 0);
310+    _ = try lib_posix.fcntl(stdin_fd, lib_posix.F.SETFL, stdin_orig_flags | O_NONBLOCK);
311+    defer _ = lib_posix.fcntl(stdin_fd, lib_posix.F.SETFL, stdin_orig_flags) catch {};
312 
313     while (true) {
314         poll_fds.clearRetainingCapacity();
315 
316         try poll_fds.append(alloc, .{
317             .fd = stdin_fd,
318-            .events = posix.POLL.IN,
319+            .events = lib_posix.POLL.IN,
320             .revents = 0,
321         });
322 
323         // Poll socket for read, and also for write if we have pending data
324-        var sock_events: i16 = posix.POLL.IN;
325+        var sock_events: i16 = lib_posix.POLL.IN;
326         if (sock_write_buf.items.len > 0) {
327-            sock_events |= posix.POLL.OUT;
328+            sock_events |= lib_posix.POLL.OUT;
329         }
330         try poll_fds.append(alloc, .{
331             .fd = client_sock_fd,
332@@ -2733,29 +2732,29 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
333             .revents = 0,
334         });
335 
336-        try poll_fds.append(alloc, .{ .fd = sig_pipe[0], .events = posix.POLL.IN, .revents = 0 });
337+        try poll_fds.append(alloc, .{ .fd = sig_pipe[0], .events = lib_posix.POLL.IN, .revents = 0 });
338 
339         if (stdout_buf.items.len > 0) {
340             try poll_fds.append(alloc, .{
341-                .fd = posix.STDOUT_FILENO,
342-                .events = posix.POLL.OUT,
343+                .fd = lib_posix.STDOUT_FILENO,
344+                .events = lib_posix.POLL.OUT,
345                 .revents = 0,
346             });
347         }
348 
349-        _ = try posix.poll(poll_fds.items, -1);
350+        _ = try lib_posix.poll(poll_fds.items, -1);
351 
352-        if (poll_fds.items[2].revents & posix.POLL.IN != 0) {
353+        if (poll_fds.items[2].revents & lib_posix.POLL.IN != 0) {
354             drainSignalPipe();
355-            const next_size = ipc.getTerminalSize(posix.STDOUT_FILENO);
356+            const next_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
357             try ipc.appendMessage(alloc, &sock_write_buf, .Resize, std.mem.asBytes(&next_size));
358         }
359 
360         // Handle stdin -> socket (Input)
361-        const inp_flags = (posix.POLL.IN | posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL);
362+        const inp_flags = (lib_posix.POLL.IN | lib_posix.POLL.HUP | lib_posix.POLL.ERR | lib_posix.POLL.NVAL);
363         if (poll_fds.items[0].revents & inp_flags != 0) {
364             var buf: [4096]u8 = undefined;
365-            const n_opt: ?usize = posix.read(stdin_fd, &buf) catch |err| blk: {
366+            const n_opt: ?usize = lib_posix.read(stdin_fd, &buf) catch |err| blk: {
367                 if (err == error.WouldBlock) break :blk null;
368                 return err;
369             };
370@@ -2778,7 +2777,7 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
371         }
372 
373         // Handle socket read (incoming Output messages from daemon)
374-        if (poll_fds.items[1].revents & posix.POLL.IN != 0) {
375+        if (poll_fds.items[1].revents & lib_posix.POLL.IN != 0) {
376             const n = read_buf.read(client_sock_fd) catch |err| {
377                 if (err == error.WouldBlock) continue;
378                 if (err == error.ConnectionResetByPeer or err == error.BrokenPipe) {
379@@ -2803,7 +2802,7 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
380                     .Resize => {
381                         // daemon is asking for the client's window size usually in response
382                         // to this client being set as leader.
383-                        const next_size = ipc.getTerminalSize(posix.STDOUT_FILENO);
384+                        const next_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
385                         try ipc.appendMessage(
386                             alloc,
387                             &sock_write_buf,
388@@ -2821,7 +2820,7 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
389         }
390 
391         // Handle socket write (flush buffered messages to daemon)
392-        if (poll_fds.items[1].revents & posix.POLL.OUT != 0) {
393+        if (poll_fds.items[1].revents & lib_posix.POLL.OUT != 0) {
394             if (sock_write_buf.items.len > 0) {
395                 const n = lib_posix.write(client_sock_fd, sock_write_buf.items) catch |err| blk: {
396                     if (err == error.WouldBlock) break :blk 0;
397@@ -2838,7 +2837,7 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
398         }
399 
400         if (stdout_buf.items.len > 0) {
401-            const n = lib_posix.write(posix.STDOUT_FILENO, stdout_buf.items) catch |err| blk: {
402+            const n = lib_posix.write(lib_posix.STDOUT_FILENO, stdout_buf.items) catch |err| blk: {
403                 if (err == error.WouldBlock) break :blk 0;
404                 return err;
405             };
406@@ -2847,7 +2846,7 @@ fn clientLoop(client_sock_fd: i32) !ClientResult {
407             }
408         }
409 
410-        if (poll_fds.items[1].revents & (posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL) != 0) {
411+        if (poll_fds.items[1].revents & (lib_posix.POLL.HUP | lib_posix.POLL.ERR | lib_posix.POLL.NVAL) != 0) {
412             std.log.info("poll hup|err|nval", .{});
413             return ClientResult{ .kind = .detach, .session_name = null };
414         }
415@@ -2861,7 +2860,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
416     daemon.pty_fd = pty_fd;
417     try openSignalPipe();
418     installWakeHandler(@intFromEnum(lib_posix.SIG.TERM));
419-    var poll_fds = try std.ArrayList(posix.pollfd).initCapacity(daemon.alloc, 8);
420+    var poll_fds = try std.ArrayList(lib_posix.pollfd).initCapacity(daemon.alloc, 8);
421     defer poll_fds.deinit(daemon.alloc);
422 
423     const init_size = ipc.getTerminalSize(pty_fd);
424@@ -2885,13 +2884,13 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
425 
426         try poll_fds.append(daemon.alloc, .{
427             .fd = server_sock_fd,
428-            .events = posix.POLL.IN,
429+            .events = lib_posix.POLL.IN,
430             .revents = 0,
431         });
432 
433-        var pty_events: i16 = posix.POLL.IN;
434+        var pty_events: i16 = lib_posix.POLL.IN;
435         if (daemon.pty_write_buf.items.len > 0) {
436-            pty_events |= posix.POLL.OUT;
437+            pty_events |= lib_posix.POLL.OUT;
438         }
439         try poll_fds.append(daemon.alloc, .{
440             .fd = pty_fd,
441@@ -2899,12 +2898,12 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
442             .revents = 0,
443         });
444 
445-        try poll_fds.append(daemon.alloc, .{ .fd = sig_pipe[0], .events = posix.POLL.IN, .revents = 0 });
446+        try poll_fds.append(daemon.alloc, .{ .fd = sig_pipe[0], .events = lib_posix.POLL.IN, .revents = 0 });
447 
448         for (daemon.clients.items) |client| {
449-            var events: i16 = posix.POLL.IN;
450+            var events: i16 = lib_posix.POLL.IN;
451             if (client.has_pending_output) {
452-                events |= posix.POLL.OUT;
453+                events |= lib_posix.POLL.OUT;
454             }
455             try poll_fds.append(daemon.alloc, .{
456                 .fd = client.socket_fd,
457@@ -2913,9 +2912,9 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
458             });
459         }
460 
461-        _ = try posix.poll(poll_fds.items, -1);
462+        _ = try lib_posix.poll(poll_fds.items, -1);
463 
464-        if (poll_fds.items[2].revents & posix.POLL.IN != 0) {
465+        if (poll_fds.items[2].revents & lib_posix.POLL.IN != 0) {
466             drainSignalPipe();
467             std.log.info(
468                 "SIGTERM received, shutting down gracefully session={s}",
469@@ -2924,15 +2923,15 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
470             break :daemon_loop;
471         }
472 
473-        if (poll_fds.items[0].revents & (posix.POLL.ERR | posix.POLL.HUP | posix.POLL.NVAL) != 0) {
474+        if (poll_fds.items[0].revents & (lib_posix.POLL.ERR | lib_posix.POLL.HUP | lib_posix.POLL.NVAL) != 0) {
475             std.log.err("server socket error revents={d}", .{poll_fds.items[0].revents});
476             break :daemon_loop;
477-        } else if (poll_fds.items[0].revents & posix.POLL.IN != 0) {
478+        } else if (poll_fds.items[0].revents & lib_posix.POLL.IN != 0) {
479             const client_fd = try lib_posix.accept(
480                 server_sock_fd,
481                 null,
482                 null,
483-                posix.SOCK.NONBLOCK | posix.SOCK.CLOEXEC,
484+                lib_posix.SOCK.NONBLOCK | lib_posix.SOCK.CLOEXEC,
485             );
486             const client = try daemon.alloc.create(Client);
487             client.* = Client{
488@@ -2953,14 +2952,14 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
489             );
490         }
491 
492-        const inp_flags = posix.POLL.IN | posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL;
493+        const inp_flags = lib_posix.POLL.IN | lib_posix.POLL.HUP | lib_posix.POLL.ERR | lib_posix.POLL.NVAL;
494         if (poll_fds.items[1].revents & inp_flags != 0) {
495             // Read from PTY. Buffer is sized to N_TTY_BUF_SIZE (4096): the hard
496             // kernel limit for the N_TTY line discipline. A larger buffer doesn't
497             // help: each read() from a PTY master returns at most 4096 bytes
498             // regardless of the userspace buffer size.
499             var buf: [4096]u8 = undefined;
500-            const n_opt: ?usize = posix.read(pty_fd, &buf) catch |err| blk: {
501+            const n_opt: ?usize = lib_posix.read(pty_fd, &buf) catch |err| blk: {
502                 if (err == error.WouldBlock) break :blk null;
503                 break :blk 0;
504             };
505@@ -3039,7 +3038,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
506             }
507         }
508 
509-        if (poll_fds.items[1].revents & posix.POLL.OUT != 0) {
510+        if (poll_fds.items[1].revents & lib_posix.POLL.OUT != 0) {
511             while (daemon.pty_write_buf.items.len > 0) {
512                 const n = lib_posix.write(pty_fd, daemon.pty_write_buf.items) catch |err| {
513                     if (err != error.WouldBlock) {
514@@ -3069,7 +3068,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
515             const client = daemon.clients.items[i];
516             const revents = poll_fds.items[i + 3].revents;
517 
518-            if (revents & posix.POLL.IN != 0) {
519+            if (revents & lib_posix.POLL.IN != 0) {
520                 const n = client.read_buf.read(client.socket_fd) catch |err| {
521                     if (err == error.WouldBlock) continue;
522                     std.log.debug(
523@@ -3123,7 +3122,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
524                 }
525             }
526 
527-            if (revents & posix.POLL.OUT != 0) {
528+            if (revents & lib_posix.POLL.OUT != 0) {
529                 // Flush pending output buffers
530                 const n = lib_posix.write(client.socket_fd, client.write_buf.items) catch |err| blk: {
531                     if (err == error.WouldBlock) break :blk 0;
532@@ -3142,7 +3141,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
533                 }
534             }
535 
536-            if (revents & (posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL) != 0) {
537+            if (revents & (lib_posix.POLL.HUP | lib_posix.POLL.ERR | lib_posix.POLL.NVAL) != 0) {
538                 const last = daemon.closeClient(client, i, false);
539                 if (last) break :daemon_loop;
540             }
541@@ -3150,7 +3149,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
542     }
543 }
544 
545-fn wakeSignalPipe(_: std.os.linux.SIG, _: *const posix.siginfo_t, _: ?*anyopaque) callconv(.c) void {
546+fn wakeSignalPipe(_: std.os.linux.SIG, _: *const lib_posix.siginfo_t, _: ?*anyopaque) callconv(.c) void {
547     const saved = std.c._errno().*;
548     _ = std.c.write(sig_pipe[1], "x", 1);
549     std.c._errno().* = saved;
550@@ -3160,19 +3159,19 @@ fn wakeSignalPipe(_: std.os.linux.SIG, _: *const posix.siginfo_t, _: ?*anyopaque
551 // setting wakes the loop. The handler writes to sig_pipe instead; poll()
552 // wakes on its read end.
553 fn installWakeHandler(sig: u6) void {
554-    const act: posix.Sigaction = .{
555+    const act: lib_posix.Sigaction = .{
556         .handler = .{ .sigaction = wakeSignalPipe },
557-        .mask = posix.sigemptyset(),
558-        .flags = posix.SA.SIGINFO,
559+        .mask = lib_posix.sigemptyset(),
560+        .flags = lib_posix.SA.SIGINFO,
561     };
562-    posix.sigaction(@as(posix.SIG, @enumFromInt(sig)), &act, null);
563+    lib_posix.sigaction(@as(lib_posix.SIG, @enumFromInt(sig)), &act, null);
564 }
565 
566 fn ignoreSigpipe() void {
567-    const act: posix.Sigaction = .{
568-        .handler = .{ .handler = posix.SIG.IGN },
569-        .mask = posix.sigemptyset(),
570+    const act: lib_posix.Sigaction = .{
571+        .handler = .{ .handler = lib_posix.SIG.IGN },
572+        .mask = lib_posix.sigemptyset(),
573         .flags = 0,
574     };
575-    posix.sigaction(posix.SIG.PIPE, &act, null);
576+    lib_posix.sigaction(lib_posix.SIG.PIPE, &act, null);
577 }
+151, -8
  1@@ -6,6 +6,7 @@ const mem = std.mem;
  2 const native_os = builtin.os.tag;
  3 const use_libc = builtin.link_libc;
  4 const linux = std.os.linux;
  5+const cast = std.math.cast;
  6 
  7 /// A libc-compatible API layer.
  8 const system = if (use_libc)
  9@@ -23,28 +24,62 @@ else switch (native_os) {
 10     },
 11 };
 12 
 13-pub const SIG = system.SIG;
 14 const E = system.E;
 15 const PATH_MAX = system.PATH_MAX;
 16 const pid_t = system.pid_t;
 17-const AT = system.AT;
 18 const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
 19 const uid_t = system.uid_t;
 20-const fd_t = system.fd_t;
 21 const mode_t = system.mode_t;
 22 const socket_t = fd_t;
 23-const SOCK = system.SOCK;
 24-const F = system.F;
 25-const O = system.O;
 26-const AF = system.AF;
 27 const FD_CLOEXEC = system.FD_CLOEXEC;
 28-const sockaddr = system.sockaddr;
 29+pub const SA = system.SA;
 30+pub const fd_t = system.fd_t;
 31+pub const O = system.O;
 32+pub const F = system.F;
 33+pub const sigset_t = system.sigset_t;
 34+pub const nfds_t = system.nfds_t;
 35+pub const SOCK = system.SOCK;
 36+pub const AT = system.AT;
 37+pub const AF = system.AF;
 38+pub const sockaddr = system.sockaddr;
 39 pub const socklen_t = system.socklen_t;
 40+pub const pollfd = system.pollfd;
 41+pub const POLL = system.POLL;
 42+pub const STDERR_FILENO = system.STDERR_FILENO;
 43+pub const STDIN_FILENO = system.STDIN_FILENO;
 44+pub const STDOUT_FILENO = system.STDOUT_FILENO;
 45+pub const Sigaction = system.Sigaction;
 46+pub const SIG = system.SIG;
 47+pub const siginfo_t = system.siginfo_t;
 48 
 49 pub fn getuid() uid_t {
 50     return system.getuid();
 51 }
 52 
 53+/// Return an empty sigset_t.
 54+pub fn sigemptyset() sigset_t {
 55+    if (builtin.link_libc) {
 56+        var set: sigset_t = undefined;
 57+        switch (errno(system.sigemptyset(&set))) {
 58+            .SUCCESS => return set,
 59+            else => unreachable,
 60+        }
 61+    }
 62+    return system.sigemptyset();
 63+}
 64+
 65+/// Examine and change a signal action.
 66+pub fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
 67+    switch (errno(system.sigaction(sig, act, oact))) {
 68+        .SUCCESS => return,
 69+        // EINVAL means the signal is either invalid or some signal that cannot have its action
 70+        // changed. For POSIX, this means SIGKILL/SIGSTOP. For e.g. Solaris, this also includes the
 71+        // non-standard SIGWAITING, SIGCANCEL, and SIGLWP. Either way, programmer error.
 72+        .INVAL => unreachable,
 73+        else => unreachable,
 74+    }
 75+}
 76+
 77 /// Get an environment variable.
 78 /// See also `getenvZ`.
 79 pub fn getenv(key: []const u8) ?[:0]const u8 {
 80@@ -555,6 +590,78 @@ pub fn setsid() SetSidError!pid_t {
 81     }
 82 }
 83 
 84+pub const ReadError = error{
 85+    InputOutput,
 86+    SystemResources,
 87+    IsDir,
 88+    OperationAborted,
 89+    BrokenPipe,
 90+    ConnectionResetByPeer,
 91+    ConnectionTimedOut,
 92+    NotOpenForReading,
 93+    SocketNotConnected,
 94+
 95+    /// This error occurs when no global event loop is configured,
 96+    /// and reading from the file descriptor would block.
 97+    WouldBlock,
 98+
 99+    /// reading a timerfd with CANCEL_ON_SET will lead to this error
100+    /// when the clock goes through a discontinuous change
101+    Canceled,
102+
103+    /// In WASI, this error occurs when the file descriptor does
104+    /// not hold the required rights to read from it.
105+    AccessDenied,
106+
107+    /// This error occurs in Linux if the process to be read from
108+    /// no longer exists.
109+    ProcessNotFound,
110+
111+    /// Unable to read file due to lock.
112+    LockViolation,
113+} || UnexpectedError;
114+
115+/// Returns the number of bytes that were read, which can be less than
116+/// buf.len. If 0 bytes were read, that means EOF.
117+/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock
118+/// when EAGAIN is received.
119+///
120+/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
121+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
122+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
123+/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
124+/// The corresponding POSIX limit is `maxInt(isize)`.
125+pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
126+    if (buf.len == 0) return 0;
127+    // Prevents EINVAL.
128+    const max_count = switch (native_os) {
129+        .linux => 0x7ffff000,
130+        .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32),
131+        else => maxInt(isize),
132+    };
133+    while (true) {
134+        const rc = system.read(fd, buf.ptr, @min(buf.len, max_count));
135+        switch (errno(rc)) {
136+            .SUCCESS => return @intCast(rc),
137+            .INTR => continue,
138+            .INVAL => unreachable,
139+            .FAULT => unreachable,
140+            .SRCH => return error.ProcessNotFound,
141+            .AGAIN => return error.WouldBlock,
142+            .CANCELED => return error.Canceled,
143+            .BADF => return error.NotOpenForReading, // Can be a race condition.
144+            .IO => return error.InputOutput,
145+            .ISDIR => return error.IsDir,
146+            .NOBUFS => return error.SystemResources,
147+            .NOMEM => return error.SystemResources,
148+            .NOTCONN => return error.SocketNotConnected,
149+            .CONNRESET => return error.ConnectionResetByPeer,
150+            .TIMEDOUT => return error.ConnectionTimedOut,
151+            else => |err| return unexpectedErrno(err),
152+        }
153+    }
154+}
155+
156 /// Open and possibly create a file. Keeps trying if it gets interrupted.
157 /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
158 /// On WASI, `file_path` should be encoded as valid UTF-8.
159@@ -1106,6 +1213,30 @@ pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
160     }
161 }
162 
163+pub const PollError = error{
164+    /// The network subsystem has failed.
165+    NetworkSubsystemFailed,
166+
167+    /// The kernel had no space to allocate file descriptor tables.
168+    SystemResources,
169+} || UnexpectedError;
170+
171+pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
172+    while (true) {
173+        const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources;
174+        const rc = system.poll(fds.ptr, fds_count, timeout);
175+        switch (errno(rc)) {
176+            .SUCCESS => return @intCast(rc),
177+            .FAULT => unreachable,
178+            .INTR => continue,
179+            .INVAL => unreachable,
180+            .NOMEM => return error.SystemResources,
181+            else => |err| return unexpectedErrno(err),
182+        }
183+    }
184+    unreachable;
185+}
186+
187 /// Call this when you made a syscall or something that sets errno
188 /// and you get an unexpected error.
189 fn unexpectedErrno(err: E) UnexpectedError {
190@@ -1168,3 +1299,15 @@ pub fn initUnix(path: []const u8) !Address {
191 
192     return Address{ .un = sock_addr };
193 }
194+
195+const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError;
196+
197+pub fn kill(pid: pid_t, sig: SIG) KillError!void {
198+    switch (errno(system.kill(pid, sig))) {
199+        .SUCCESS => return,
200+        .INVAL => unreachable, // invalid signal
201+        .PERM => return error.PermissionDenied,
202+        .SRCH => return error.ProcessNotFound,
203+        else => |err| return unexpectedErrno(err),
204+    }
205+}
+5, -6
 1@@ -1,5 +1,4 @@
 2 const std = @import("std");
 3-const posix = std.posix;
 4 const lib_posix = @import("posix.zig");
 5 
 6 pub fn getSeshPrefix() []const u8 {
 7@@ -31,7 +30,7 @@ pub fn getSeshName(alloc: std.mem.Allocator, sesh: []const u8) ![]const u8 {
 8 
 9 pub fn sessionConnect(sesh: []const u8) !i32 {
10     var unix_addr = try lib_posix.initUnix(sesh);
11-    const socket_fd = try lib_posix.socket(posix.AF.UNIX, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
12+    const socket_fd = try lib_posix.socket(lib_posix.AF.UNIX, lib_posix.SOCK.STREAM | lib_posix.SOCK.CLOEXEC, 0);
13     errdefer lib_posix.close(socket_fd);
14     try lib_posix.connect(socket_fd, &unix_addr.any, unix_addr.getOsSockLen());
15     return socket_fd;
16@@ -62,8 +61,8 @@ pub fn createSocket(sesh: []const u8) !i32 {
17     // SOCK.STREAM: Reliable, bidirectional communication
18     // SOCK.NONBLOCK: Set socket to non-blocking
19     const fd = try lib_posix.socket(
20-        posix.AF.UNIX,
21-        posix.SOCK.STREAM | posix.SOCK.NONBLOCK | posix.SOCK.CLOEXEC,
22+        lib_posix.AF.UNIX,
23+        lib_posix.SOCK.STREAM | lib_posix.SOCK.NONBLOCK | lib_posix.SOCK.CLOEXEC,
24         0,
25     );
26     errdefer lib_posix.close(fd);
27@@ -78,7 +77,7 @@ pub fn createSocket(sesh: []const u8) !i32 {
28 /// Derived from the platform's sockaddr_un.path field, minus 1 for the
29 /// required null terminator.
30 pub const max_socket_path_len: usize = @typeInfo(
31-    @TypeOf(@as(posix.sockaddr.un, undefined).path),
32+    @TypeOf(@as(lib_posix.sockaddr.un, undefined).path),
33 ).array.len - 1;
34 
35 pub fn getSocketPath(
36@@ -124,7 +123,7 @@ pub fn maxSessionNameLen(socket_dir: []const u8) ?usize {
37 
38 test "max_socket_path_len matches platform sockaddr_un" {
39     const path_field_len = @typeInfo(
40-        @TypeOf(@as(posix.sockaddr.un, undefined).path),
41+        @TypeOf(@as(lib_posix.sockaddr.un, undefined).path),
42     ).array.len;
43     try std.testing.expectEqual(path_field_len - 1, max_socket_path_len);
44     try std.testing.expect(max_socket_path_len > 0);
+0, -1
1@@ -1,5 +1,4 @@
2 const std = @import("std");
3-const posix = std.posix;
4 const ghostty_vt = @import("ghostty-vt");
5 const ipc = @import("ipc.zig");
6 const socket = @import("socket.zig");