diff options
author | 2022-01-23 23:00:15 -0800 | |
---|---|---|
committer | 2022-01-23 23:00:15 -0800 | |
commit | de0cf421115ed3814ccc4d46e4d6202aac792ba0 (patch) | |
tree | 8c3f609e9497c30ae2f7512a5b5d7b8e0d37595f /src/io/io_darwin.zig | |
parent | ec9e4eb97e0633e508606af9d18e76a3c63647da (diff) | |
download | bun-de0cf421115ed3814ccc4d46e4d6202aac792ba0.tar.gz bun-de0cf421115ed3814ccc4d46e4d6202aac792ba0.tar.zst bun-de0cf421115ed3814ccc4d46e4d6202aac792ba0.zip |
Use non-cancellable syscalls for HTTP & use errno for errors
Diffstat (limited to 'src/io/io_darwin.zig')
-rw-r--r-- | src/io/io_darwin.zig | 532 |
1 files changed, 503 insertions, 29 deletions
diff --git a/src/io/io_darwin.zig b/src/io/io_darwin.zig index c072cd4bd..97ccd6d23 100644 --- a/src/io/io_darwin.zig +++ b/src/io/io_darwin.zig @@ -15,9 +15,383 @@ const os = struct { pub const EOVERFLOW = 84; pub const ESPIPE = 29; }; + +const SystemErrno = @import("../darwin_c.zig").SystemErrno; +pub const Errno = error{ + EPERM, + ENOENT, + ESRCH, + EINTR, + EIO, + ENXIO, + E2BIG, + ENOEXEC, + EBADF, + ECHILD, + EDEADLK, + ENOMEM, + EACCES, + EFAULT, + ENOTBLK, + EBUSY, + EEXIST, + EXDEV, + ENODEV, + ENOTDIR, + EISDIR, + EINVAL, + ENFILE, + EMFILE, + ENOTTY, + ETXTBSY, + EFBIG, + ENOSPC, + ESPIPE, + EROFS, + EMLINK, + EPIPE, + EDOM, + ERANGE, + EAGAIN, + EINPROGRESS, + EALREADY, + ENOTSOCK, + EDESTADDRREQ, + EMSGSIZE, + EPROTOTYPE, + ENOPROTOOPT, + EPROTONOSUPPORT, + ESOCKTNOSUPPORT, + ENOTSUP, + EPFNOSUPPORT, + EAFNOSUPPORT, + EADDRINUSE, + EADDRNOTAVAIL, + ENETDOWN, + ENETUNREACH, + ENETRESET, + ECONNABORTED, + ECONNRESET, + ENOBUFS, + EISCONN, + ENOTCONN, + ESHUTDOWN, + ETOOMANYREFS, + ETIMEDOUT, + ECONNREFUSED, + ELOOP, + ENAMETOOLONG, + EHOSTDOWN, + EHOSTUNREACH, + ENOTEMPTY, + EPROCLIM, + EUSERS, + EDQUOT, + ESTALE, + EREMOTE, + EBADRPC, + ERPCMISMATCH, + EPROGUNAVAIL, + EPROGMISMATCH, + EPROCUNAVAIL, + ENOLCK, + ENOSYS, + EFTYPE, + EAUTH, + ENEEDAUTH, + EPWROFF, + EDEVERR, + EOVERFLOW, + EBADEXEC, + EBADARCH, + ESHLIBVERS, + EBADMACHO, + ECANCELED, + EIDRM, + ENOMSG, + EILSEQ, + ENOATTR, + EBADMSG, + EMULTIHOP, + ENODATA, + ENOLINK, + ENOSR, + ENOSTR, + EPROTO, + ETIME, + EOPNOTSUPP, + ENOPOLICY, + ENOTRECOVERABLE, + EOWNERDEAD, + EQFULL, + Unexpected, +}; + +const errno_map: [108]Errno = brk: { + var errors: [108]Errno = undefined; + errors[1] = error.EPERM; + errors[2] = error.ENOENT; + errors[3] = error.ESRCH; + errors[4] = error.EINTR; + errors[5] = error.EIO; + errors[6] = error.ENXIO; + errors[7] = error.E2BIG; + errors[8] = error.ENOEXEC; + errors[9] = error.EBADF; + errors[10] = error.ECHILD; + errors[11] = error.EDEADLK; + errors[12] = error.ENOMEM; + errors[13] = error.EACCES; + errors[14] = error.EFAULT; + errors[15] = error.ENOTBLK; + errors[16] = error.EBUSY; + errors[17] = error.EEXIST; + errors[18] = error.EXDEV; + errors[19] = error.ENODEV; + errors[20] = error.ENOTDIR; + errors[21] = error.EISDIR; + errors[22] = error.EINVAL; + errors[23] = error.ENFILE; + errors[24] = error.EMFILE; + errors[25] = error.ENOTTY; + errors[26] = error.ETXTBSY; + errors[27] = error.EFBIG; + errors[28] = error.ENOSPC; + errors[29] = error.ESPIPE; + errors[30] = error.EROFS; + errors[31] = error.EMLINK; + errors[32] = error.EPIPE; + errors[33] = error.EDOM; + errors[34] = error.ERANGE; + errors[35] = error.EAGAIN; + errors[36] = error.EINPROGRESS; + errors[37] = error.EALREADY; + errors[38] = error.ENOTSOCK; + errors[39] = error.EDESTADDRREQ; + errors[40] = error.EMSGSIZE; + errors[41] = error.EPROTOTYPE; + errors[42] = error.ENOPROTOOPT; + errors[43] = error.EPROTONOSUPPORT; + errors[44] = error.ESOCKTNOSUPPORT; + errors[45] = error.ENOTSUP; + errors[46] = error.EPFNOSUPPORT; + errors[47] = error.EAFNOSUPPORT; + errors[48] = error.EADDRINUSE; + errors[49] = error.EADDRNOTAVAIL; + errors[50] = error.ENETDOWN; + errors[51] = error.ENETUNREACH; + errors[52] = error.ENETRESET; + errors[53] = error.ECONNABORTED; + errors[54] = error.ECONNRESET; + errors[55] = error.ENOBUFS; + errors[56] = error.EISCONN; + errors[57] = error.ENOTCONN; + errors[58] = error.ESHUTDOWN; + errors[59] = error.ETOOMANYREFS; + errors[60] = error.ETIMEDOUT; + errors[61] = error.ECONNREFUSED; + errors[62] = error.ELOOP; + errors[63] = error.ENAMETOOLONG; + errors[64] = error.EHOSTDOWN; + errors[65] = error.EHOSTUNREACH; + errors[66] = error.ENOTEMPTY; + errors[67] = error.EPROCLIM; + errors[68] = error.EUSERS; + errors[69] = error.EDQUOT; + errors[70] = error.ESTALE; + errors[71] = error.EREMOTE; + errors[72] = error.EBADRPC; + errors[73] = error.ERPCMISMATCH; + errors[74] = error.EPROGUNAVAIL; + errors[75] = error.EPROGMISMATCH; + errors[76] = error.EPROCUNAVAIL; + errors[77] = error.ENOLCK; + errors[78] = error.ENOSYS; + errors[79] = error.EFTYPE; + errors[80] = error.EAUTH; + errors[81] = error.ENEEDAUTH; + errors[82] = error.EPWROFF; + errors[83] = error.EDEVERR; + errors[84] = error.EOVERFLOW; + errors[85] = error.EBADEXEC; + errors[86] = error.EBADARCH; + errors[87] = error.ESHLIBVERS; + errors[88] = error.EBADMACHO; + errors[89] = error.ECANCELED; + errors[90] = error.EIDRM; + errors[91] = error.ENOMSG; + errors[92] = error.EILSEQ; + errors[93] = error.ENOATTR; + errors[94] = error.EBADMSG; + errors[95] = error.EMULTIHOP; + errors[96] = error.ENODATA; + errors[97] = error.ENOLINK; + errors[98] = error.ENOSR; + errors[99] = error.ENOSTR; + errors[100] = error.EPROTO; + errors[101] = error.ETIME; + errors[102] = error.EOPNOTSUPP; + errors[103] = error.ENOPOLICY; + errors[104] = error.ENOTRECOVERABLE; + errors[105] = error.EOWNERDEAD; + errors[106] = error.EQFULL; + break :brk errors; +}; + +const socket_t = os.socket_t; +const sockaddr = darwin.sockaddr; +const socklen_t = darwin.socklen_t; +const system = darwin; + +pub fn asError(err: anytype) Errno { + return switch (@enumToInt(err)) { + 1...errno_map.len => |val| errno_map[@intCast(u8, val)], + else => error.Unexpected, + }; +} +const fd_t = os.fd_t; + const mem = std.mem; const assert = std.debug.assert; const c = std.c; +const darwin = struct { + pub usingnamespace os.darwin; + pub extern "c" fn @"recvfrom$NOCANCEL"(sockfd: c.fd_t, noalias buf: *anyopaque, len: usize, flags: u32, noalias src_addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t) isize; + pub extern "c" fn @"sendto$NOCANCEL"( + sockfd: c.fd_t, + buf: *const anyopaque, + len: usize, + flags: u32, + dest_addr: ?*const c.sockaddr, + addrlen: c.socklen_t, + ) isize; + pub extern "c" fn @"fcntl$NOCANCEL"(fd: c.fd_t, cmd: c_int, ...) c_int; + pub extern "c" fn @"sendmsg$NOCANCEL"(sockfd: c.fd_t, msg: *const std.x.os.Socket.Message, flags: c_int) isize; + pub extern "c" fn @"recvmsg$NOCANCEL"(sockfd: c.fd_t, msg: *std.x.os.Socket.Message, flags: c_int) isize; + pub extern "c" fn @"connect$NOCANCEL"(sockfd: c.fd_t, sock_addr: *const c.sockaddr, addrlen: c.socklen_t) c_int; + pub extern "c" fn @"accept$NOCANCEL"(sockfd: c.fd_t, noalias addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t) c_int; + pub extern "c" fn @"accept4$NOCANCEL"(sockfd: c.fd_t, noalias addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t, flags: c_uint) c_int; +}; + +pub const Syscall = struct { + pub fn close(fd: std.os.fd_t) CloseError!void { + return switch (darwin.getErrno(darwin.@"close$NOCANCEL"(fd))) { + .SUCCESS => void{}, + .BADF => error.FileDescriptorInvalid, + .IO => error.InputOutput, + else => |err| asError(err), + }; + } + + pub const SocketError = error{ + /// Permission to create a socket of the specified type and/or + /// pro‐tocol is denied. + PermissionDenied, + + /// The implementation does not support the specified address family. + AddressFamilyNotSupported, + + /// Unknown protocol, or protocol family not available. + ProtocolFamilyNotAvailable, + + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + + /// Insufficient memory is available. The socket cannot be created until sufficient + /// resources are freed. + SystemResources, + + /// The protocol type or the specified protocol is not supported within this domain. + ProtocolNotSupported, + + /// The socket type is not supported by the protocol. + SocketTypeNotSupported, + } || Errno; + const SOCK = os.SOCK; + const FD_CLOEXEC = os.FD_CLOEXEC; + pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) Errno!usize { + const rc = darwin.@"fcntl$NOCANCEL"(fd, cmd, arg); + return switch (darwin.getErrno(rc)) { + .SUCCESS => @intCast(usize, rc), + else => |err| asError(err), + }; + } + + const F = std.os.F; + const O = std.os.O; + pub fn setSockFlags(sock: socket_t, flags: u32) !void { + if ((flags & SOCK.CLOEXEC) != 0) { + var fd_flags = try fcntl(sock, F.GETFD, 0); + fd_flags |= FD_CLOEXEC; + _ = try fcntl(sock, F.SETFD, fd_flags); + } + + if ((flags & SOCK.NONBLOCK) != 0) { + var fl_flags = try fcntl(sock, F.GETFL, 0); + fl_flags |= O.NONBLOCK; + _ = try fcntl(sock, F.SETFL, fl_flags); + } + } + + pub const SetSockOptError = error{ + /// The socket is already connected, and a specified option cannot be set while the socket is connected. + AlreadyConnected, + + /// The option is not supported by the protocol. + InvalidProtocolOption, + + /// The send and receive timeout values are too big to fit into the timeout fields in the socket structure. + TimeoutTooBig, + + /// Insufficient resources are available in the system to complete the call. + SystemResources, + + // Setting the socket option requires more elevated permissions. + PermissionDenied, + + NetworkSubsystemFailed, + FileDescriptorNotASocket, + SocketNotBound, + } || Errno; + + pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void { + switch (darwin.getErrno(darwin.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) { + .SUCCESS => {}, + .DOM => return error.TimeoutTooBig, + .ISCONN => return error.AlreadyConnected, + .NOPROTOOPT => return error.InvalidProtocolOption, + .NOMEM => return error.SystemResources, + .NOBUFS => return error.SystemResources, + .PERM => return error.PermissionDenied, + else => |err| return asError(err), + } + } + + pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t { + const filtered_sock_type = socket_type & ~@as(u32, os.SOCK.NONBLOCK | os.SOCK.CLOEXEC); + const rc = darwin.socket(domain, filtered_sock_type, protocol); + switch (darwin.getErrno(rc)) { + .SUCCESS => { + const fd = @intCast(fd_t, rc); + try setSockFlags(fd, socket_type); + return fd; + }, + .ACCES => return error.PermissionDenied, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .INVAL => return error.ProtocolFamilyNotAvailable, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .PROTONOSUPPORT => return error.ProtocolNotSupported, + .PROTOTYPE => return error.SocketTypeNotSupported, + else => |err| return asError(err), + } + } +}; const FIFO = @import("./fifo.zig").FIFO; const Time = @import("./time.zig").Time; @@ -217,7 +591,7 @@ fn flush_timeouts(self: *IO) ?u64 { const timeout_ns = expires - now; if (min_timeout) |min_ns| { - min_timeout = std.math.min(min_ns, timeout_ns); + min_timeout = @minimum(min_ns, timeout_ns); } else { min_timeout = timeout_ns; } @@ -296,7 +670,7 @@ fn submit( const result = OperationImpl.doOperation(op_data); // Requeue onto io_pending if error.WouldBlock - switch (operation_tag) { + switch (comptime operation_tag) { .accept, .connect, .read, .write, .send, .recv => { _ = result catch |err| switch (err) { error.WouldBlock => { @@ -423,11 +797,13 @@ pub fn accept( null, os.SOCK.NONBLOCK | os.SOCK.CLOEXEC, ); - errdefer os.close(fd); + errdefer { + Syscall.close(fd) catch {}; + } // darwin doesn't support os.MSG.NOSIGNAL, // but instead a socket option to avoid SIGPIPE. - os.setsockopt(fd, os.SOL_SOCKET, os.SO_NOSIGPIPE, &mem.toBytes(@as(c_int, 1))) catch |err| return switch (err) { + Syscall.setsockopt(fd, os.SOL_SOCKET, os.SO_NOSIGPIPE, &mem.toBytes(@as(c_int, 1))) catch |err| return switch (err) { error.TimeoutTooBig => unreachable, error.PermissionDenied => error.NetworkSubsystemFailed, error.AlreadyConnected => error.NetworkSubsystemFailed, @@ -446,7 +822,7 @@ pub const CloseError = error{ DiskQuota, InputOutput, NoSpaceLeft, -} || os.UnexpectedError; +} || Errno; pub fn close( self: *IO, @@ -470,19 +846,25 @@ pub fn close( }, struct { fn doOperation(op: anytype) CloseError!void { - return switch (os.system.close(op.fd)) { - 0 => {}, - os.EBADF => error.FileDescriptorInvalid, - os.EINTR => {}, // A success, see https://github.com/ziglang/zig/issues/2425 - os.EIO => error.InputOutput, - else => |errno| os.unexpectedErrno(os.errno(errno)), - }; + return Syscall.close(op.fd); } }, ); } -pub const ConnectError = os.ConnectError; +pub const ConnectError = error{ + AddressFamilyNotSupported, + AddressInUse, + AddressNotAvailable, + ConnectionPending, + ConnectionRefused, + ConnectionResetByPeer, + ConnectionTimedOut, + NetworkUnreachable, + PermissionDenied, + SystemResources, + WouldBlock, +} || Errno; pub fn connect( self: *IO, @@ -491,7 +873,7 @@ pub fn connect( comptime callback: fn ( context: Context, completion: *Completion, - result: ConnectError!void, + result: IO.ConnectError!void, ) void, completion: *Completion, socket: os.socket_t, @@ -508,12 +890,53 @@ pub fn connect( .initiated = false, }, struct { - fn doOperation(op: anytype) ConnectError!void { + fn doOperation(op: anytype) IO.ConnectError!void { // Don't call connect after being rescheduled by io_pending as it gives EISCONN. // Instead, check the socket error to see if has been connected successfully. const result = switch (op.initiated) { - true => os.getsockoptError(op.socket), - else => os.connect(op.socket, &op.address.any, op.address.getOsSockLen()), + true => brk: { + var err_code: i32 = undefined; + var size: u32 = @sizeOf(u32); + const rc = system.getsockopt(op.socket, os.SOL.SOCKET, os.SO.ERROR, @ptrCast([*]u8, &err_code), &size); + assert(size == 4); + break :brk switch (darwin.getErrno(rc)) { + .SUCCESS => switch (@intToEnum(os.E, err_code)) { + .SUCCESS => void{}, + .ACCES => error.PermissionDenied, + .PERM => error.PermissionDenied, + .ADDRINUSE => error.AddressInUse, + .ADDRNOTAVAIL => error.AddressNotAvailable, + .AFNOSUPPORT => error.AddressFamilyNotSupported, + .AGAIN => error.SystemResources, + .ALREADY => error.ConnectionPending, + .CONNREFUSED => error.ConnectionRefused, + // .FAULT => unreachable, // The socket structure address is outside the user's address space. + // .ISCONN => unreachable, // The socket is already connected. + .NETUNREACH => error.NetworkUnreachable, + // .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + // .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + .TIMEDOUT => error.ConnectionTimedOut, + .CONNRESET => error.ConnectionResetByPeer, + else => |err| asError(err), + }, + else => |err| asError(err), + }; + }, + else => switch (darwin.getErrno(darwin.@"connect$NOCANCEL"(op.socket, &op.address.any, op.address.getOsSockLen()))) { + .SUCCESS => void{}, + .ACCES => error.PermissionDenied, + .PERM => error.PermissionDenied, + .ADDRINUSE => error.AddressInUse, + .ADDRNOTAVAIL => error.AddressNotAvailable, + .AFNOSUPPORT => error.AddressFamilyNotSupported, + .AGAIN, .INPROGRESS => error.WouldBlock, + .ALREADY => error.ConnectionPending, + .CONNREFUSED => error.ConnectionRefused, + .CONNRESET => error.ConnectionResetByPeer, + .NETUNREACH => error.NetworkUnreachable, + .TIMEDOUT => error.ConnectionTimedOut, + else => |err| asError(err), + }, }; op.initiated = true; @@ -562,7 +985,7 @@ pub const ReadError = error{ IsDir, SystemResources, Unseekable, -} || os.UnexpectedError; +} || Errno; pub fn read( self: *IO, @@ -604,7 +1027,6 @@ pub fn read( os.EAGAIN => error.WouldBlock, os.EBADF => error.NotOpenForReading, os.ECONNRESET => error.ConnectionResetByPeer, - os.EFAULT => unreachable, os.EINVAL => error.Alignment, os.EIO => error.InputOutput, os.EISDIR => error.IsDir, @@ -613,7 +1035,7 @@ pub fn read( os.ENXIO => error.Unseekable, os.EOVERFLOW => error.Unseekable, os.ESPIPE => error.Unseekable, - else => error.Unexpected, + else => |err| asError(err), }; } } @@ -621,7 +1043,12 @@ pub fn read( ); } -pub const RecvError = os.RecvFromError; +pub const RecvError = error{ + SystemResources, + ConnectionRefused, + ConnectionResetByPeer, + WouldBlock, +} || Errno; pub fn recv( self: *IO, @@ -648,13 +1075,37 @@ pub fn recv( }, struct { fn doOperation(op: anytype) RecvError!usize { - return os.recv(op.socket, op.buf[0..op.len], 0); + const rc = system.@"recvfrom$NOCANCEL"(op.socket, op.buf, op.len, 0, null, null); + return switch (system.getErrno(rc)) { + .SUCCESS => @intCast(usize, rc), + .AGAIN => error.WouldBlock, + .NOMEM => error.SystemResources, + .CONNREFUSED => error.ConnectionRefused, + .CONNRESET => error.ConnectionResetByPeer, + else => |err| asError(err), + }; } }, ); } -pub const SendError = os.SendToError; +pub const SendError = error{ + AccessDenied, + AddressFamilyNotSupported, + BrokenPipe, + ConnectionResetByPeer, + FastOpenAlreadyInProgress, + FileNotFound, + MessageTooBig, + NameTooLong, + NetworkSubsystemFailed, + NetworkUnreachable, + NotDir, + SocketNotConnected, + SymLinkLoop, + SystemResources, + WouldBlock, +} || Errno; pub fn send( self: *IO, @@ -683,13 +1134,34 @@ pub fn send( }, struct { fn doOperation(op: anytype) SendError!usize { - return os.sendto(op.socket, op.buf[0..op.len], op.flags, null, 0); + const rc = system.@"sendto$NOCANCEL"(op.socket, op.buf, op.len, op.flags, null, 0); + return switch (system.getErrno(rc)) { + .SUCCESS => @intCast(usize, rc), + .ACCES => error.AccessDenied, + .AGAIN => error.WouldBlock, + .ALREADY => error.FastOpenAlreadyInProgress, + .CONNRESET => error.ConnectionResetByPeer, + .MSGSIZE => error.MessageTooBig, + .NOBUFS => error.SystemResources, + .NOMEM => error.SystemResources, + .PIPE => error.BrokenPipe, + .AFNOSUPPORT => error.AddressFamilyNotSupported, + .LOOP => error.SymLinkLoop, + .NAMETOOLONG => error.NameTooLong, + .NOENT => error.FileNotFound, + .NOTDIR => error.NotDir, + .HOSTUNREACH => error.NetworkUnreachable, + .NETUNREACH => error.NetworkUnreachable, + .NOTCONN => error.SocketNotConnected, + .NETDOWN => error.NetworkSubsystemFailed, + else => |err| asError(err), + }; } }, ); } -pub const TimeoutError = error{Canceled} || os.UnexpectedError; +pub const TimeoutError = error{Canceled} || Errno; pub fn timeout( self: *IO, @@ -755,11 +1227,13 @@ pub fn write( } pub fn openSocket(family: u32, sock_type: u32, protocol: u32) !os.socket_t { - const fd = try os.socket(family, sock_type | os.SOCK.NONBLOCK, protocol); - errdefer os.close(fd); + const fd = try Syscall.socket(family, sock_type | os.SOCK.NONBLOCK, protocol); + errdefer { + Syscall.close(fd) catch {}; + } // darwin doesn't support os.MSG.NOSIGNAL, but instead a socket option to avoid SIGPIPE. - try os.setsockopt(fd, os.SOL.SOCKET, os.SO.NOSIGPIPE, &mem.toBytes(@as(c_int, 1))); + try Syscall.setsockopt(fd, os.SOL.SOCKET, os.SO.NOSIGPIPE, &mem.toBytes(@as(c_int, 1))); return fd; } @@ -775,7 +1249,7 @@ fn buffer_limit(buffer_len: usize) usize { .macos, .ios, .watchos, .tvos => std.math.maxInt(i32), else => std.math.maxInt(isize), }; - return std.math.min(limit, buffer_len); + return @minimum(limit, buffer_len); } pub var global: IO = undefined; |