main zmx / src / posix.zig
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}