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");