Eric Bower
·
2026-07-30
1const builtin = @import("builtin");
2const std = @import("std");
3const maxInt = std.math.maxInt;
4const assert = std.debug.assert;
5const mem = std.mem;
6const native_os = builtin.os.tag;
7const use_libc = builtin.link_libc;
8const linux = std.os.linux;
9const cast = std.math.cast;
10
11/// A libc-compatible API layer.
12const system = if (use_libc)
13 std.c
14else switch (native_os) {
15 .linux => linux,
16 .plan9 => std.os.plan9,
17 else => struct {
18 pub const ucontext_t = void;
19 pub const pid_t = void;
20 pub const pollfd = void;
21 pub const fd_t = void;
22 pub const uid_t = void;
23 pub const gid_t = void;
24 },
25};
26
27const E = system.E;
28const PATH_MAX = system.PATH_MAX;
29const pid_t = system.pid_t;
30const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
31const uid_t = system.uid_t;
32const mode_t = system.mode_t;
33const FD_CLOEXEC = system.FD_CLOEXEC;
34pub const socket_t = fd_t;
35pub const SA = system.SA;
36pub const fd_t = system.fd_t;
37pub const O = system.O;
38pub const F = system.F;
39pub const sigset_t = system.sigset_t;
40pub const nfds_t = system.nfds_t;
41pub const SOCK = system.SOCK;
42pub const AT = system.AT;
43pub const AF = system.AF;
44pub const sockaddr = system.sockaddr;
45pub const socklen_t = system.socklen_t;
46pub const pollfd = system.pollfd;
47pub const POLL = system.POLL;
48pub const STDERR_FILENO = system.STDERR_FILENO;
49pub const STDIN_FILENO = system.STDIN_FILENO;
50pub const STDOUT_FILENO = system.STDOUT_FILENO;
51pub const Sigaction = system.Sigaction;
52pub const SIG = system.SIG;
53pub const siginfo_t = system.siginfo_t;
54
55// https://github.com/ziglang/zig/blob/738d2be9d6b6ef3ff3559130c05159ef53336224/lib/std/posix.zig#L3505
56pub const O_NONBLOCK: usize = 1 << @bitOffsetOf(O, "NONBLOCK");
57
58pub fn getuid() uid_t {
59 return system.getuid();
60}
61
62/// Return an empty sigset_t.
63pub fn sigemptyset() sigset_t {
64 if (builtin.link_libc) {
65 var set: sigset_t = undefined;
66 switch (errno(system.sigemptyset(&set))) {
67 .SUCCESS => return set,
68 else => unreachable,
69 }
70 }
71 return system.sigemptyset();
72}
73
74/// Examine and change a signal action.
75pub fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
76 switch (errno(system.sigaction(sig, act, oact))) {
77 .SUCCESS => return,
78 // EINVAL means the signal is either invalid or some signal that cannot have its action
79 // changed. For POSIX, this means SIGKILL/SIGSTOP. For e.g. Solaris, this also includes the
80 // non-standard SIGWAITING, SIGCANCEL, and SIGLWP. Either way, programmer error.
81 .INVAL => unreachable,
82 else => unreachable,
83 }
84}
85
86/// Get an environment variable.
87/// See also `getenvZ`.
88pub fn getenv(key: []const u8) ?[:0]const u8 {
89 if (mem.indexOfScalar(u8, key, '=') != null) {
90 return null;
91 }
92 if (builtin.link_libc) {
93 var ptr = std.c.environ;
94 while (ptr[0]) |line| : (ptr += 1) {
95 var line_i: usize = 0;
96 while (line[line_i] != 0) : (line_i += 1) {
97 if (line_i == key.len) break;
98 if (line[line_i] != key[line_i]) break;
99 }
100 if ((line_i != key.len) or (line[line_i] != '=')) continue;
101
102 return mem.sliceTo(line + line_i + 1, 0);
103 }
104 return null;
105 }
106 // The simplified start logic doesn't populate environ.
107 if (std.start.simplified_logic) return null;
108 // TODO see https://github.com/ziglang/zig/issues/4524
109 for (std.os.environ) |ptr| {
110 var line_i: usize = 0;
111 while (ptr[line_i] != 0) : (line_i += 1) {
112 if (line_i == key.len) break;
113 if (ptr[line_i] != key[line_i]) break;
114 }
115 if ((line_i != key.len) or (ptr[line_i] != '=')) continue;
116
117 return mem.sliceTo(ptr + line_i + 1, 0);
118 }
119 return null;
120}
121
122const UnexpectedError = error{
123 /// The Operating System returned an undocumented error code.
124 ///
125 /// This error is in theory not possible, but it would be better
126 /// to handle this error than to invoke undefined behavior.
127 ///
128 /// When this error code is observed, it usually means the Zig Standard
129 /// Library needs a small patch to add the error code to the error set for
130 /// the respective function.
131 Unexpected,
132};
133
134const SocketError = error{
135 /// Permission to create a socket of the specified type and/or
136 /// pro‐tocol is denied.
137 AccessDenied,
138
139 /// The implementation does not support the specified address family.
140 AddressFamilyNotSupported,
141
142 /// Unknown protocol, or protocol family not available.
143 ProtocolFamilyNotAvailable,
144
145 /// The per-process limit on the number of open file descriptors has been reached.
146 ProcessFdQuotaExceeded,
147
148 /// The system-wide limit on the total number of open files has been reached.
149 SystemFdQuotaExceeded,
150
151 /// Insufficient memory is available. The socket cannot be created until sufficient
152 /// resources are freed.
153 SystemResources,
154
155 /// The protocol type or the specified protocol is not supported within this domain.
156 ProtocolNotSupported,
157
158 /// The socket type is not supported by the protocol.
159 SocketTypeNotSupported,
160} || UnexpectedError;
161
162pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
163 const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku;
164 const filtered_sock_type = if (!have_sock_flags)
165 socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC)
166 else
167 socket_type;
168 const rc = system.socket(domain, filtered_sock_type, protocol);
169 switch (errno(rc)) {
170 .SUCCESS => {
171 const fd: fd_t = @intCast(rc);
172 errdefer close(fd);
173 if (!have_sock_flags) {
174 try setSockFlags(fd, socket_type);
175 }
176 return fd;
177 },
178 .ACCES => return error.AccessDenied,
179 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
180 .INVAL => return error.ProtocolFamilyNotAvailable,
181 .MFILE => return error.ProcessFdQuotaExceeded,
182 .NFILE => return error.SystemFdQuotaExceeded,
183 .NOBUFS => return error.SystemResources,
184 .NOMEM => return error.SystemResources,
185 .PROTONOSUPPORT => return error.ProtocolNotSupported,
186 .PROTOTYPE => return error.SocketTypeNotSupported,
187 else => |err| return unexpectedErrno(err),
188 }
189}
190
191pub fn close(fd: fd_t) void {
192 return std.Io.Threaded.closeFd(fd);
193 // switch (errno(system.close(fd))) {
194 // .BADF => unreachable, // Always a race condition.
195 // .SUCCESS, .INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
196 // else => return,
197 // }
198}
199
200const ConnectError = error{
201 /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
202 /// file, or search permission is denied for one of the directories in the path prefix.
203 /// or
204 /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
205 /// the connection request failed because of a local firewall rule.
206 AccessDenied,
207
208 /// See AccessDenied
209 PermissionDenied,
210
211 /// Local address is already in use.
212 AddressInUse,
213
214 /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
215 /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
216 /// in the ephemeral port range are currently in use. See the discussion of
217 /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
218 AddressNotAvailable,
219
220 /// The passed address didn't have the correct address family in its sa_family field.
221 AddressFamilyNotSupported,
222
223 /// Insufficient entries in the routing cache.
224 SystemResources,
225
226 /// A connect() on a stream socket found no one listening on the remote address.
227 ConnectionRefused,
228
229 /// Network is unreachable.
230 NetworkUnreachable,
231
232 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
233 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
234 ConnectionTimedOut,
235
236 /// This error occurs when no global event loop is configured,
237 /// and connecting to the socket would block.
238 WouldBlock,
239
240 /// The given path for the unix socket does not exist.
241 FileNotFound,
242
243 /// Connection was reset by peer before connect could complete.
244 ConnectionResetByPeer,
245
246 /// Socket is non-blocking and already has a pending connection in progress.
247 ConnectionPending,
248} || UnexpectedError;
249
250/// Initiate a connection on a socket.
251/// If `sockfd` is opened in non blocking mode, the function will
252/// return error.WouldBlock when EAGAIN or EINPROGRESS is received.
253pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
254 while (true) {
255 switch (errno(system.connect(sock, sock_addr, len))) {
256 .SUCCESS => return,
257 .ACCES => return error.AccessDenied,
258 .PERM => return error.PermissionDenied,
259 .ADDRINUSE => return error.AddressInUse,
260 .ADDRNOTAVAIL => return error.AddressNotAvailable,
261 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
262 .AGAIN, .INPROGRESS => return error.WouldBlock,
263 .ALREADY => return error.ConnectionPending,
264 .BADF => unreachable, // sockfd is not a valid open file descriptor.
265 .CONNREFUSED => return error.ConnectionRefused,
266 .CONNRESET => return error.ConnectionResetByPeer,
267 .FAULT => unreachable, // The socket structure address is outside the user's address space.
268 .INTR => continue,
269 .ISCONN => unreachable, // The socket is already connected.
270 .HOSTUNREACH => return error.NetworkUnreachable,
271 .NETUNREACH => return error.NetworkUnreachable,
272 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
273 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
274 .TIMEDOUT => return error.ConnectionTimedOut,
275 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.
276 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.
277 else => |err| return unexpectedErrno(err),
278 }
279 }
280}
281
282const BindError = error{
283 /// The address is protected, and the user is not the superuser.
284 /// For UNIX domain sockets: Search permission is denied on a component
285 /// of the path prefix.
286 AccessDenied,
287
288 /// The given address is already in use, or in the case of Internet domain sockets,
289 /// The port number was specified as zero in the socket
290 /// address structure, but, upon attempting to bind to an ephemeral port, it was
291 /// determined that all port numbers in the ephemeral port range are currently in
292 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
293 AddressInUse,
294
295 /// A nonexistent interface was requested or the requested address was not local.
296 AddressNotAvailable,
297
298 /// The address is not valid for the address family of socket.
299 AddressFamilyNotSupported,
300
301 /// Too many symbolic links were encountered in resolving addr.
302 SymLinkLoop,
303
304 /// addr is too long.
305 NameTooLong,
306
307 /// A component in the directory prefix of the socket pathname does not exist.
308 FileNotFound,
309
310 /// Insufficient kernel memory was available.
311 SystemResources,
312
313 /// A component of the path prefix is not a directory.
314 NotDir,
315
316 /// The socket inode would reside on a read-only filesystem.
317 ReadOnlyFileSystem,
318
319 /// The network subsystem has failed.
320 NetworkSubsystemFailed,
321
322 FileDescriptorNotASocket,
323
324 AlreadyBound,
325} || UnexpectedError;
326
327/// addr is `*const T` where T is one of the sockaddr
328pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {
329 const rc = system.bind(sock, addr, len);
330 switch (errno(rc)) {
331 .SUCCESS => return,
332 .ACCES, .PERM => return error.AccessDenied,
333 .ADDRINUSE => return error.AddressInUse,
334 .BADF => unreachable, // always a race condition if this error is returned
335 .INVAL => unreachable, // invalid parameters
336 .NOTSOCK => unreachable, // invalid `sockfd`
337 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
338 .ADDRNOTAVAIL => return error.AddressNotAvailable,
339 .FAULT => unreachable, // invalid `addr` pointer
340 .LOOP => return error.SymLinkLoop,
341 .NAMETOOLONG => return error.NameTooLong,
342 .NOENT => return error.FileNotFound,
343 .NOMEM => return error.SystemResources,
344 .NOTDIR => return error.NotDir,
345 .ROFS => return error.ReadOnlyFileSystem,
346 else => |err| return unexpectedErrno(err),
347 }
348}
349
350const ListenError = error{
351 /// Another socket is already listening on the same port.
352 /// For Internet domain sockets, the socket referred to by sockfd had not previously
353 /// been bound to an address and, upon attempting to bind it to an ephemeral port, it
354 /// was determined that all port numbers in the ephemeral port range are currently in
355 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
356 AddressInUse,
357
358 /// The file descriptor sockfd does not refer to a socket.
359 FileDescriptorNotASocket,
360
361 /// The socket is not of a type that supports the listen() operation.
362 OperationNotSupported,
363
364 /// The network subsystem has failed.
365 NetworkSubsystemFailed,
366
367 /// Ran out of system resources
368 /// On Windows it can either run out of socket descriptors or buffer space
369 SystemResources,
370
371 /// Already connected
372 AlreadyConnected,
373
374 /// Socket has not been bound yet
375 SocketNotBound,
376} || UnexpectedError;
377
378pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
379 const rc = system.listen(sock, backlog);
380 switch (errno(rc)) {
381 .SUCCESS => return,
382 .ADDRINUSE => return error.AddressInUse,
383 .BADF => unreachable,
384 .NOTSOCK => return error.FileDescriptorNotASocket,
385 .OPNOTSUPP => return error.OperationNotSupported,
386 else => |err| return unexpectedErrno(err),
387 }
388}
389
390/// Obtains errno from the return value of a system function call.
391///
392/// For some systems this will obtain the value directly from the syscall return value;
393/// for others it will use a thread-local errno variable. Therefore, this
394/// function only returns a well-defined value when it is called directly after
395/// the system function call whose errno value is intended to be observed.
396fn errno(rc: anytype) E {
397 if (use_libc) {
398 return if (rc == -1) @enumFromInt(std.c._errno().*) else .SUCCESS;
399 }
400 const signed: isize = @bitCast(rc);
401 const int = if (signed > -4096 and signed < 0) -signed else 0;
402 return @enumFromInt(int);
403}
404
405fn setSockFlags(sock: socket_t, flags: u32) !void {
406 if ((flags & SOCK.CLOEXEC) != 0) {
407 var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) {
408 error.FileBusy => unreachable,
409 error.Locked => unreachable,
410 error.PermissionDenied => unreachable,
411 error.DeadLock => unreachable,
412 error.LockedRegionLimitExceeded => unreachable,
413 else => |e| return e,
414 };
415 fd_flags |= FD_CLOEXEC;
416 _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) {
417 error.FileBusy => unreachable,
418 error.Locked => unreachable,
419 error.PermissionDenied => unreachable,
420 error.DeadLock => unreachable,
421 error.LockedRegionLimitExceeded => unreachable,
422 else => |e| return e,
423 };
424 }
425 if ((flags & SOCK.NONBLOCK) != 0) {
426 var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) {
427 error.FileBusy => unreachable,
428 error.Locked => unreachable,
429 error.PermissionDenied => unreachable,
430 error.DeadLock => unreachable,
431 error.LockedRegionLimitExceeded => unreachable,
432 else => |e| return e,
433 };
434 fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK");
435 _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) {
436 error.FileBusy => unreachable,
437 error.Locked => unreachable,
438 error.PermissionDenied => unreachable,
439 error.DeadLock => unreachable,
440 error.LockedRegionLimitExceeded => unreachable,
441 else => |e| return e,
442 };
443 }
444}
445
446const FcntlError = error{
447 PermissionDenied,
448 FileBusy,
449 ProcessFdQuotaExceeded,
450 Locked,
451 DeadLock,
452 LockedRegionLimitExceeded,
453} || UnexpectedError;
454
455pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
456 while (true) {
457 const rc = system.fcntl(fd, cmd, arg);
458 switch (errno(rc)) {
459 .SUCCESS => return @intCast(rc),
460 .INTR => continue,
461 .AGAIN, .ACCES => return error.Locked,
462 .BADF => unreachable,
463 .BUSY => return error.FileBusy,
464 .INVAL => unreachable, // invalid parameters
465 .PERM => return error.PermissionDenied,
466 .MFILE => return error.ProcessFdQuotaExceeded,
467 .NOTDIR => unreachable, // invalid parameter
468 .DEADLK => return error.DeadLock,
469 .NOLCK => return error.LockedRegionLimitExceeded,
470 else => |err| return unexpectedErrno(err),
471 }
472 }
473}
474
475const WriteError = error{
476 DiskQuota,
477 FileTooBig,
478 InputOutput,
479 NoSpaceLeft,
480 DeviceBusy,
481 InvalidArgument,
482
483 /// File descriptor does not hold the required rights to write to it.
484 AccessDenied,
485 PermissionDenied,
486 BrokenPipe,
487 SystemResources,
488 OperationAborted,
489 NotOpenForWriting,
490
491 /// The process cannot access the file because another process has locked
492 /// a portion of the file. Windows-only.
493 LockViolation,
494
495 /// This error occurs when no global event loop is configured,
496 /// and reading from the file descriptor would block.
497 WouldBlock,
498
499 /// Connection reset by peer.
500 ConnectionResetByPeer,
501
502 /// This error occurs in Linux if the process being written to
503 /// no longer exists.
504 ProcessNotFound,
505 /// This error occurs when a device gets disconnected before or mid-flush
506 /// while it's being written to - errno(6): No such device or address.
507 NoDevice,
508
509 /// The socket type requires that message be sent atomically, and the size of the message
510 /// to be sent made this impossible. The message is not transmitted.
511 MessageTooBig,
512} || UnexpectedError;
513
514/// Write to a file descriptor.
515/// Retries when interrupted by a signal.
516/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
517///
518/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
519/// occur for various reasons; for example, because there was insufficient space on the disk
520/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
521/// similar was interrupted by a signal handler after it had transferred some, but before it had
522/// transferred all of the requested bytes. In the event of a partial write, the caller can make
523/// another write() call to transfer the remaining bytes. The subsequent call will either
524/// transfer further bytes or may result in an error (e.g., if the disk is now full).
525///
526/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
527/// return error.WouldBlock when EAGAIN is received.
528/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
529/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
530///
531/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
532/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
533/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
534/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
535/// The corresponding POSIX limit is `maxInt(isize)`.
536pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
537 if (bytes.len == 0) return 0;
538 const max_count = switch (native_os) {
539 .linux => 0x7ffff000,
540 .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32),
541 else => maxInt(isize),
542 };
543 while (true) {
544 const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count));
545 switch (errno(rc)) {
546 .SUCCESS => return @intCast(rc),
547 .INTR => continue,
548 .INVAL => return error.InvalidArgument,
549 .FAULT => unreachable,
550 .SRCH => return error.ProcessNotFound,
551 .AGAIN => return error.WouldBlock,
552 .BADF => return error.NotOpenForWriting, // can be a race condition.
553 .DESTADDRREQ => unreachable, // `connect` was never called.
554 .DQUOT => return error.DiskQuota,
555 .FBIG => return error.FileTooBig,
556 .IO => return error.InputOutput,
557 .NOSPC => return error.NoSpaceLeft,
558 .ACCES => return error.AccessDenied,
559 .PERM => return error.PermissionDenied,
560 .PIPE => return error.BrokenPipe,
561 .CONNRESET => return error.ConnectionResetByPeer,
562 .BUSY => return error.DeviceBusy,
563 .NXIO => return error.NoDevice,
564 .MSGSIZE => return error.MessageTooBig,
565 else => |err| return unexpectedErrno(err),
566 }
567 }
568}
569
570pub const ForkError = error{SystemResources} || UnexpectedError;
571
572pub fn fork() ForkError!pid_t {
573 const rc = system.fork();
574 switch (errno(rc)) {
575 .SUCCESS => return @intCast(rc),
576 .AGAIN => return error.SystemResources,
577 .NOMEM => return error.SystemResources,
578 else => |err| return unexpectedErrno(err),
579 }
580}
581
582const SetSidError = error{
583 /// The calling process is already a process group leader, or the process group ID of a process other than the calling process matches the process ID of the calling process.
584 PermissionDenied,
585} || UnexpectedError;
586
587pub fn setsid() SetSidError!pid_t {
588 const rc = system.setsid();
589 switch (errno(rc)) {
590 .SUCCESS => return rc,
591 .PERM => return error.PermissionDenied,
592 else => |err| return unexpectedErrno(err),
593 }
594}
595
596pub const ReadError = error{
597 InputOutput,
598 SystemResources,
599 IsDir,
600 OperationAborted,
601 BrokenPipe,
602 ConnectionResetByPeer,
603 ConnectionTimedOut,
604 NotOpenForReading,
605 SocketNotConnected,
606
607 /// This error occurs when no global event loop is configured,
608 /// and reading from the file descriptor would block.
609 WouldBlock,
610
611 /// reading a timerfd with CANCEL_ON_SET will lead to this error
612 /// when the clock goes through a discontinuous change
613 Canceled,
614
615 /// In WASI, this error occurs when the file descriptor does
616 /// not hold the required rights to read from it.
617 AccessDenied,
618
619 /// This error occurs in Linux if the process to be read from
620 /// no longer exists.
621 ProcessNotFound,
622
623 /// Unable to read file due to lock.
624 LockViolation,
625} || UnexpectedError;
626
627/// Returns the number of bytes that were read, which can be less than
628/// buf.len. If 0 bytes were read, that means EOF.
629/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock
630/// when EAGAIN is received.
631///
632/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
633/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
634/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
635/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
636/// The corresponding POSIX limit is `maxInt(isize)`.
637pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
638 if (buf.len == 0) return 0;
639 // Prevents EINVAL.
640 const max_count = switch (native_os) {
641 .linux => 0x7ffff000,
642 .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32),
643 else => maxInt(isize),
644 };
645 while (true) {
646 const rc = system.read(fd, buf.ptr, @min(buf.len, max_count));
647 switch (errno(rc)) {
648 .SUCCESS => return @intCast(rc),
649 .INTR => continue,
650 .INVAL => unreachable,
651 .FAULT => unreachable,
652 .SRCH => return error.ProcessNotFound,
653 .AGAIN => return error.WouldBlock,
654 .CANCELED => return error.Canceled,
655 .BADF => return error.NotOpenForReading, // Can be a race condition.
656 .IO => return error.InputOutput,
657 .ISDIR => return error.IsDir,
658 .NOBUFS => return error.SystemResources,
659 .NOMEM => return error.SystemResources,
660 .NOTCONN => return error.SocketNotConnected,
661 .CONNRESET => return error.ConnectionResetByPeer,
662 .TIMEDOUT => return error.ConnectionTimedOut,
663 else => |err| return unexpectedErrno(err),
664 }
665 }
666}
667
668/// Open and possibly create a file. Keeps trying if it gets interrupted.
669/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
670/// On WASI, `file_path` should be encoded as valid UTF-8.
671/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
672/// See also `open`.
673fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
674 const open_sym = if (lfs64_abi) system.open64 else system.open;
675 while (true) {
676 const rc = open_sym(file_path, flags, perm);
677 switch (errno(rc)) {
678 .SUCCESS => return @intCast(rc),
679 .INTR => continue,
680
681 .FAULT => unreachable,
682 .INVAL => return error.BadPathName,
683 .ACCES => return error.AccessDenied,
684 .FBIG => return error.FileTooBig,
685 .OVERFLOW => return error.FileTooBig,
686 .ISDIR => return error.IsDir,
687 .LOOP => return error.SymLinkLoop,
688 .MFILE => return error.ProcessFdQuotaExceeded,
689 .NAMETOOLONG => return error.NameTooLong,
690 .NFILE => return error.SystemFdQuotaExceeded,
691 .NODEV => return error.NoDevice,
692 .NOENT => return error.FileNotFound,
693 .SRCH => return error.ProcessNotFound,
694 .NOMEM => return error.SystemResources,
695 .NOSPC => return error.NoSpaceLeft,
696 .NOTDIR => return error.NotDir,
697 .PERM => return error.PermissionDenied,
698 .EXIST => return error.PathAlreadyExists,
699 .BUSY => return error.DeviceBusy,
700 .ILSEQ => |err| if (native_os == .wasi)
701 return error.InvalidUtf8
702 else
703 return unexpectedErrno(err),
704 else => |err| return unexpectedErrno(err),
705 }
706 }
707}
708
709/// Open and possibly create a file. Keeps trying if it gets interrupted.
710/// `file_path` is relative to the open directory handle `dir_fd`.
711/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
712/// On WASI, `file_path` should be encoded as valid UTF-8.
713/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
714/// See also `openat`.
715fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
716 const openat_sym = if (lfs64_abi) system.openat64 else system.openat;
717 while (true) {
718 const rc = openat_sym(dir_fd, file_path, flags, mode);
719 switch (errno(rc)) {
720 .SUCCESS => return @intCast(rc),
721 .INTR => continue,
722
723 .FAULT => unreachable,
724 .INVAL => return error.BadPathName,
725 .BADF => unreachable,
726 .ACCES => return error.AccessDenied,
727 .FBIG => return error.FileTooBig,
728 .OVERFLOW => return error.FileTooBig,
729 .ISDIR => return error.IsDir,
730 .LOOP => return error.SymLinkLoop,
731 .MFILE => return error.ProcessFdQuotaExceeded,
732 .NAMETOOLONG => return error.NameTooLong,
733 .NFILE => return error.SystemFdQuotaExceeded,
734 .NODEV => return error.NoDevice,
735 .NOENT => return error.FileNotFound,
736 .SRCH => return error.ProcessNotFound,
737 .NOMEM => return error.SystemResources,
738 .NOSPC => return error.NoSpaceLeft,
739 .NOTDIR => return error.NotDir,
740 .PERM => return error.PermissionDenied,
741 .EXIST => return error.PathAlreadyExists,
742 .BUSY => return error.DeviceBusy,
743 .OPNOTSUPP => return error.FileLocksNotSupported,
744 .AGAIN => return error.WouldBlock,
745 .TXTBSY => return error.FileBusy,
746 .NXIO => return error.NoDevice,
747 .ILSEQ => |err| if (native_os == .wasi)
748 return error.InvalidUtf8
749 else
750 return unexpectedErrno(err),
751 else => |err| return unexpectedErrno(err),
752 }
753 }
754}
755
756/// Open and possibly create a file. Keeps trying if it gets interrupted.
757/// `file_path` is relative to the open directory handle `dir_fd`.
758/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
759/// On WASI, `file_path` should be encoded as valid UTF-8.
760/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
761/// See also `openatZ`.
762fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
763 const file_path_c = try toPosixPath(file_path);
764 return openatZ(dir_fd, &file_path_c, flags, mode);
765}
766
767const OpenError = error{
768 /// In WASI, this error may occur when the file descriptor does
769 /// not hold the required rights to open a new resource relative to it.
770 AccessDenied,
771 PermissionDenied,
772 SymLinkLoop,
773 ProcessFdQuotaExceeded,
774 SystemFdQuotaExceeded,
775 NoDevice,
776 /// Either:
777 /// * One of the path components does not exist.
778 /// * Cwd was used, but cwd has been deleted.
779 /// * The path associated with the open directory handle has been deleted.
780 /// * On macOS, multiple processes or threads raced to create the same file
781 /// with `O.EXCL` set to `false`.
782 FileNotFound,
783
784 /// The path exceeded `max_path_bytes` bytes.
785 NameTooLong,
786
787 /// Insufficient kernel memory was available, or
788 /// the named file is a FIFO and per-user hard limit on
789 /// memory allocation for pipes has been reached.
790 SystemResources,
791
792 /// The file is too large to be opened. This error is unreachable
793 /// for 64-bit targets, as well as when opening directories.
794 FileTooBig,
795
796 /// The path refers to directory but the `DIRECTORY` flag was not provided.
797 IsDir,
798
799 /// A new path cannot be created because the device has no room for the new file.
800 /// This error is only reachable when the `CREAT` flag is provided.
801 NoSpaceLeft,
802
803 /// A component used as a directory in the path was not, in fact, a directory, or
804 /// `DIRECTORY` was specified and the path was not a directory.
805 NotDir,
806
807 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
808 PathAlreadyExists,
809 DeviceBusy,
810
811 /// The underlying filesystem does not support file locks
812 FileLocksNotSupported,
813
814 /// Path contains characters that are disallowed by the underlying filesystem.
815 BadPathName,
816
817 /// WASI-only; file paths must be valid UTF-8.
818 InvalidUtf8,
819
820 /// Windows-only; file paths provided by the user must be valid WTF-8.
821 /// https://simonsapin.github.io/wtf-8/
822 InvalidWtf8,
823
824 /// On Windows, `\\server` or `\\server\share` was not found.
825 NetworkNotFound,
826
827 /// This error occurs in Linux if the process to be open was not found.
828 ProcessNotFound,
829
830 /// One of these three things:
831 /// * pathname refers to an executable image which is currently being
832 /// executed and write access was requested.
833 /// * pathname refers to a file that is currently in use as a swap
834 /// file, and the O_TRUNC flag was specified.
835 /// * pathname refers to a file that is currently being read by the
836 /// kernel (e.g., for module/firmware loading), and write access was
837 /// requested.
838 FileBusy,
839
840 WouldBlock,
841} || UnexpectedError;
842
843/// Open and possibly create a file. Keeps trying if it gets interrupted.
844/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
845/// On WASI, `file_path` should be encoded as valid UTF-8.
846/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
847/// See also `openZ`.
848pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
849 const file_path_c = try toPosixPath(file_path);
850 return openZ(&file_path_c, flags, perm);
851}
852
853pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
854 while (true) {
855 switch (errno(system.dup2(old_fd, new_fd))) {
856 .SUCCESS => return,
857 .BUSY, .INTR => continue,
858 .MFILE => return error.ProcessFdQuotaExceeded,
859 .INVAL => unreachable, // invalid parameters passed to dup2
860 .BADF => unreachable, // invalid file descriptor
861 else => |err| return unexpectedErrno(err),
862 }
863 }
864}
865
866/// This function ignores PATH environment variable. See `execvpeZ` for that.
867fn execveZ(
868 path: [*:0]const u8,
869 child_argv: [*:null]const ?[*:0]const u8,
870 envp: [*:null]const ?[*:0]const u8,
871) ExecveError {
872 switch (errno(system.execve(path, child_argv, envp))) {
873 .SUCCESS => unreachable,
874 .FAULT => unreachable,
875 .@"2BIG" => return error.SystemResources,
876 .MFILE => return error.ProcessFdQuotaExceeded,
877 .NAMETOOLONG => return error.NameTooLong,
878 .NFILE => return error.SystemFdQuotaExceeded,
879 .NOMEM => return error.SystemResources,
880 .ACCES => return error.AccessDenied,
881 .PERM => return error.PermissionDenied,
882 .INVAL => return error.InvalidExe,
883 .NOEXEC => return error.InvalidExe,
884 .IO => return error.FileSystem,
885 .LOOP => return error.FileSystem,
886 .ISDIR => return error.IsDir,
887 .NOENT => return error.FileNotFound,
888 .NOTDIR => return error.NotDir,
889 .TXTBSY => return error.FileBusy,
890 else => |err| switch (native_os) {
891 .macos, .ios, .tvos, .watchos, .visionos => switch (err) {
892 .BADEXEC => return error.InvalidExe,
893 .BADARCH => return error.InvalidExe,
894 else => return unexpectedErrno(err),
895 },
896 .linux => switch (err) {
897 .LIBBAD => return error.InvalidExe,
898 else => return unexpectedErrno(err),
899 },
900 else => return unexpectedErrno(err),
901 },
902 }
903}
904
905/// Get an environment variable with a null-terminated name.
906/// See also `getenv`.
907fn getenvZ(key: [*:0]const u8) ?[:0]const u8 {
908 if (builtin.link_libc) {
909 const value = system.getenv(key) orelse return null;
910 return mem.sliceTo(value, 0);
911 }
912 return getenv(mem.sliceTo(key, 0));
913}
914
915const Arg0Expand = enum {
916 expand,
917 no_expand,
918};
919
920/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
921/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
922/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
923fn execvpeZ_expandArg0(
924 comptime arg0_expand: Arg0Expand,
925 file: [*:0]const u8,
926 child_argv: switch (arg0_expand) {
927 .expand => [*:null]?[*:0]const u8,
928 .no_expand => [*:null]const ?[*:0]const u8,
929 },
930 envp: [*:null]const ?[*:0]const u8,
931) ExecveError {
932 const file_slice = mem.sliceTo(file, 0);
933 if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
934
935 const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
936 // Use of PATH_MAX here is valid as the path_buf will be passed
937 // directly to the operating system in execveZ.
938 var path_buf: [PATH_MAX]u8 = undefined;
939 var it = mem.tokenizeScalar(u8, PATH, ':');
940 var seen_eacces = false;
941 var err: ExecveError = error.FileNotFound;
942
943 // In case of expanding arg0 we must put it back if we return with an error.
944 const prev_arg0 = child_argv[0];
945 defer switch (arg0_expand) {
946 .expand => child_argv[0] = prev_arg0,
947 .no_expand => {},
948 };
949
950 while (it.next()) |search_path| {
951 const path_len = search_path.len + file_slice.len + 1;
952 if (path_buf.len < path_len + 1) return error.NameTooLong;
953 @memcpy(path_buf[0..search_path.len], search_path);
954 path_buf[search_path.len] = '/';
955 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
956 path_buf[path_len] = 0;
957 const full_path = path_buf[0..path_len :0].ptr;
958 switch (arg0_expand) {
959 .expand => child_argv[0] = full_path,
960 .no_expand => {},
961 }
962 err = execveZ(full_path, child_argv, envp);
963 switch (err) {
964 error.AccessDenied => seen_eacces = true,
965 error.FileNotFound, error.NotDir => {},
966 else => |e| return e,
967 }
968 }
969 if (seen_eacces) return error.AccessDenied;
970 return err;
971}
972
973const ExecveError = error{
974 SystemResources,
975 AccessDenied,
976 PermissionDenied,
977 InvalidExe,
978 FileSystem,
979 IsDir,
980 FileNotFound,
981 NotDir,
982 FileBusy,
983 ProcessFdQuotaExceeded,
984 SystemFdQuotaExceeded,
985 NameTooLong,
986} || UnexpectedError;
987
988/// This function also uses the PATH environment variable to get the full path to the executable.
989/// If `file` is an absolute path, this is the same as `execveZ`.
990pub fn execvpeZ(
991 file: [*:0]const u8,
992 argv_ptr: [*:null]const ?[*:0]const u8,
993 envp: [*:null]const ?[*:0]const u8,
994) ExecveError {
995 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp);
996}
997
998/// Exits all threads of the program with the specified status code.
999pub fn exit(status: u8) noreturn {
1000 if (builtin.link_libc) {
1001 std.c.exit(status);
1002 }
1003 if (native_os == .linux and !builtin.single_threaded) {
1004 linux.exit_group(status);
1005 }
1006 if (native_os == .uefi) {
1007 const uefi = std.os.uefi;
1008 // exit() is only available if exitBootServices() has not been called yet.
1009 // This call to exit should not fail, so we catch-ignore errors.
1010 if (uefi.system_table.boot_services) |bs| {
1011 bs.exit(uefi.handle, @enumFromInt(status), null) catch {};
1012 }
1013 // If we can't exit, reboot the system instead.
1014 uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null);
1015 }
1016 system.exit(status);
1017}
1018
1019/// Creates a unidirectional data channel that can be used for interprocess communication.
1020fn pipe() PipeError![2]fd_t {
1021 var fds: [2]fd_t = undefined;
1022 switch (errno(system.pipe(&fds))) {
1023 .SUCCESS => return fds,
1024 .INVAL => unreachable, // Invalid parameters to pipe()
1025 .FAULT => unreachable, // Invalid fds pointer
1026 .NFILE => return error.SystemFdQuotaExceeded,
1027 .MFILE => return error.ProcessFdQuotaExceeded,
1028 else => |err| return unexpectedErrno(err),
1029 }
1030}
1031
1032const PipeError = error{
1033 SystemFdQuotaExceeded,
1034 ProcessFdQuotaExceeded,
1035} || UnexpectedError;
1036
1037pub fn pipe2(flags: O) PipeError![2]fd_t {
1038 if (@TypeOf(system.pipe2) != void) {
1039 var fds: [2]fd_t = undefined;
1040 switch (errno(system.pipe2(&fds, flags))) {
1041 .SUCCESS => return fds,
1042 .INVAL => unreachable, // Invalid flags
1043 .FAULT => unreachable, // Invalid fds pointer
1044 .NFILE => return error.SystemFdQuotaExceeded,
1045 .MFILE => return error.ProcessFdQuotaExceeded,
1046 else => |err| return unexpectedErrno(err),
1047 }
1048 }
1049
1050 const fds: [2]fd_t = try pipe();
1051 errdefer {
1052 close(fds[0]);
1053 close(fds[1]);
1054 }
1055
1056 // https://github.com/ziglang/zig/issues/18882
1057 if (@as(u32, @bitCast(flags)) == 0)
1058 return fds;
1059
1060 // CLOEXEC is special, it's a file descriptor flag and must be set using
1061 // F.SETFD.
1062 if (flags.CLOEXEC) {
1063 for (fds) |fd| {
1064 switch (errno(system.fcntl(fd, F.SETFD, @as(u32, FD_CLOEXEC)))) {
1065 .SUCCESS => {},
1066 .INVAL => unreachable, // Invalid flags
1067 .BADF => unreachable, // Always a race condition
1068 else => |err| return unexpectedErrno(err),
1069 }
1070 }
1071 }
1072
1073 const new_flags: u32 = f: {
1074 var new_flags = flags;
1075 new_flags.CLOEXEC = false;
1076 break :f @bitCast(new_flags);
1077 };
1078 // Set every other flag affecting the file status using F.SETFL.
1079 if (new_flags != 0) {
1080 for (fds) |fd| {
1081 switch (errno(system.fcntl(fd, F.SETFL, new_flags))) {
1082 .SUCCESS => {},
1083 .INVAL => unreachable, // Invalid flags
1084 .BADF => unreachable, // Always a race condition
1085 else => |err| return unexpectedErrno(err),
1086 }
1087 }
1088 }
1089
1090 return fds;
1091}
1092
1093const AcceptError = error{
1094 ConnectionAborted,
1095
1096 /// The file descriptor sockfd does not refer to a socket.
1097 FileDescriptorNotASocket,
1098
1099 /// The per-process limit on the number of open file descriptors has been reached.
1100 ProcessFdQuotaExceeded,
1101
1102 /// The system-wide limit on the total number of open files has been reached.
1103 SystemFdQuotaExceeded,
1104
1105 /// Not enough free memory. This often means that the memory allocation is limited
1106 /// by the socket buffer limits, not by the system memory.
1107 SystemResources,
1108
1109 /// Socket is not listening for new connections.
1110 SocketNotListening,
1111
1112 ProtocolFailure,
1113
1114 /// Firewall rules forbid connection.
1115 BlockedByFirewall,
1116
1117 /// This error occurs when no global event loop is configured,
1118 /// and accepting from the socket would block.
1119 WouldBlock,
1120
1121 /// An incoming connection was indicated, but was subsequently terminated by the
1122 /// remote peer prior to accepting the call.
1123 ConnectionResetByPeer,
1124
1125 /// The network subsystem has failed.
1126 NetworkSubsystemFailed,
1127
1128 /// The referenced socket is not a type that supports connection-oriented service.
1129 OperationNotSupported,
1130} || UnexpectedError;
1131
1132/// Accept a connection on a socket.
1133/// If `sockfd` is opened in non blocking mode, the function will
1134/// return error.WouldBlock when EAGAIN is received.
1135pub fn accept(
1136 /// This argument is a socket that has been created with `socket`, bound to a local address
1137 /// with `bind`, and is listening for connections after a `listen`.
1138 sock: socket_t,
1139 /// This argument is a pointer to a sockaddr structure. This structure is filled in with the
1140 /// address of the peer socket, as known to the communications layer. The exact format of the
1141 /// address returned addr is determined by the socket's address family (see `socket` and the
1142 /// respective protocol man pages).
1143 addr: ?*sockaddr,
1144 /// This argument is a value-result argument: the caller must initialize it to contain the
1145 /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size
1146 /// of the peer address.
1147 ///
1148 /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size`
1149 /// will return a value greater than was supplied to the call.
1150 addr_size: ?*socklen_t,
1151 /// The following values can be bitwise ORed in flags to obtain different behavior:
1152 /// * `SOCK.NONBLOCK` - Set the `NONBLOCK` file status flag on the open file description (see `open`)
1153 /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve
1154 /// the same result.
1155 /// * `SOCK.CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
1156 /// description of the `CLOEXEC` flag in `open` for reasons why this may be useful.
1157 flags: u32,
1158) AcceptError!socket_t {
1159 const have_accept4 = !builtin.target.os.tag.isDarwin();
1160 assert(0 == (flags & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC))); // Unsupported flag(s)
1161
1162 const accepted_sock: socket_t = while (true) {
1163 const rc = if (have_accept4)
1164 system.accept4(sock, addr, addr_size, flags)
1165 else
1166 system.accept(sock, addr, addr_size);
1167
1168 switch (errno(rc)) {
1169 .SUCCESS => break @intCast(rc),
1170 .INTR => continue,
1171 .AGAIN => return error.WouldBlock,
1172 .BADF => unreachable, // always a race condition
1173 .CONNABORTED => return error.ConnectionAborted,
1174 .FAULT => unreachable,
1175 .INVAL => return error.SocketNotListening,
1176 .NOTSOCK => unreachable,
1177 .MFILE => return error.ProcessFdQuotaExceeded,
1178 .NFILE => return error.SystemFdQuotaExceeded,
1179 .NOBUFS => return error.SystemResources,
1180 .NOMEM => return error.SystemResources,
1181 .OPNOTSUPP => unreachable,
1182 .PROTO => return error.ProtocolFailure,
1183 .PERM => return error.BlockedByFirewall,
1184 else => |err| return unexpectedErrno(err),
1185 }
1186 };
1187
1188 errdefer close(accepted_sock);
1189 if (!have_accept4) {
1190 try setSockFlags(accepted_sock, flags);
1191 }
1192 return accepted_sock;
1193}
1194
1195const WaitPidResult = struct {
1196 pid: pid_t,
1197 status: u32,
1198};
1199
1200/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit
1201/// `fork` and `execve` method.
1202pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
1203 var status: if (builtin.link_libc) c_int else u32 = undefined;
1204 while (true) {
1205 const rc = system.waitpid(pid, &status, @intCast(flags));
1206 switch (errno(rc)) {
1207 .SUCCESS => return .{
1208 .pid = @intCast(rc),
1209 .status = @bitCast(status),
1210 },
1211 .INTR => continue,
1212 .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
1213 .INVAL => unreachable, // Invalid flags.
1214 else => unreachable,
1215 }
1216 }
1217}
1218
1219pub const PollError = error{
1220 /// The network subsystem has failed.
1221 NetworkSubsystemFailed,
1222
1223 /// The kernel had no space to allocate file descriptor tables.
1224 SystemResources,
1225} || UnexpectedError;
1226
1227pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
1228 while (true) {
1229 const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources;
1230 const rc = system.poll(fds.ptr, fds_count, timeout);
1231 switch (errno(rc)) {
1232 .SUCCESS => return @intCast(rc),
1233 .FAULT => unreachable,
1234 .INTR => continue,
1235 .INVAL => unreachable,
1236 .NOMEM => return error.SystemResources,
1237 else => |err| return unexpectedErrno(err),
1238 }
1239 }
1240 unreachable;
1241}
1242
1243/// Call this when you made a syscall or something that sets errno
1244/// and you get an unexpected error.
1245fn unexpectedErrno(err: E) UnexpectedError {
1246 if (unexpected_error_tracing) {
1247 std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)});
1248 std.debug.dumpCurrentStackTrace(std.debug.StackUnwindOptions{});
1249 }
1250 return error.Unexpected;
1251}
1252
1253/// Whether or not `error.Unexpected` will print its value and a stack trace.
1254///
1255/// If this happens the fix is to add the error code to the corresponding
1256/// switch expression, possibly introduce a new error in the error set, and
1257/// send a patch to Zig.
1258const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin.zig_backend) {
1259 .stage2_llvm, .stage2_x86_64 => true,
1260 else => false,
1261};
1262
1263/// Used to convert a slice to a null terminated slice on the stack.
1264pub fn toPosixPath(file_path: []const u8) error{NameTooLong}![PATH_MAX - 1:0]u8 {
1265 if (std.debug.runtime_safety) assert(mem.indexOfScalar(u8, file_path, 0) == null);
1266 var path_with_null: [PATH_MAX - 1:0]u8 = undefined;
1267 // >= rather than > to make room for the null byte
1268 if (file_path.len >= PATH_MAX) return error.NameTooLong;
1269 @memcpy(path_with_null[0..file_path.len], file_path);
1270 path_with_null[file_path.len] = 0;
1271 return path_with_null;
1272}
1273
1274const Address = extern union {
1275 any: sockaddr,
1276 un: sockaddr.un,
1277
1278 pub fn getOsSockLen(_: Address) socklen_t {
1279 // Using the full length of the structure here is more portable than returning
1280 // the number of bytes actually used by the currently stored path.
1281 // This also is correct regardless if we are passing a socket address to the kernel
1282 // (e.g. in bind, connect, sendto) since we ensure the path is 0 terminated in
1283 // initUnix() or if we are receiving a socket address from the kernel and must
1284 // provide the full buffer size (e.g. getsockname, getpeername, recvfrom, accept).
1285 //
1286 // To access the path, std.mem.sliceTo(&address.un.path, 0) should be used.
1287 return @as(socklen_t, @intCast(@sizeOf(sockaddr.un)));
1288 }
1289};
1290
1291pub fn initUnix(path: []const u8) !Address {
1292 var sock_addr = sockaddr.un{
1293 .family = AF.UNIX,
1294 .path = undefined,
1295 };
1296
1297 // Add 1 to ensure a terminating 0 is present in the path array for maximum portability.
1298 if (path.len + 1 > sock_addr.path.len) return error.NameTooLong;
1299
1300 @memset(&sock_addr.path, 0);
1301 @memcpy(sock_addr.path[0..path.len], path);
1302
1303 return Address{ .un = sock_addr };
1304}
1305
1306const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError;
1307
1308pub fn kill(pid: pid_t, sig: SIG) KillError!void {
1309 switch (errno(system.kill(pid, sig))) {
1310 .SUCCESS => return,
1311 .INVAL => unreachable, // invalid signal
1312 .PERM => return error.PermissionDenied,
1313 .SRCH => return error.ProcessNotFound,
1314 else => |err| return unexpectedErrno(err),
1315 }
1316}