From e7e168727e3c024869de9d114a7dd49d2a80de4d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 20:11:18 -0800 Subject: [PATCH 1/4] std.posix: goodbye connect, eventfd --- lib/std/os/linux/IoUring/test.zig | 17 +++++-- lib/std/posix.zig | 78 +++---------------------------- 2 files changed, 19 insertions(+), 76 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 2b152daf8e..ac2d5ddea5 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -1755,7 +1755,7 @@ test "accept multishot" { // connect client const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(client); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); // test accept completion var cqe = try ring.copy_cqe(); @@ -1865,7 +1865,7 @@ test "accept_direct" { // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // accept completion @@ -1899,7 +1899,7 @@ test "accept_direct" { try testing.expectEqual(@as(u32, 1), try ring.submit()); // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // completion with error const cqe_accept = try ring.copy_cqe(); @@ -1949,7 +1949,7 @@ test "accept_multishot_direct" { for (registered_fds) |_| { // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // accept completion @@ -1964,7 +1964,7 @@ test "accept_multishot_direct" { { // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // completion with error const cqe_accept = try ring.copy_cqe(); @@ -2734,3 +2734,10 @@ fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) !usize { else => return error.SendFailed, } } + +fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) !void { + switch (posix.errno(posix.system.connect(sock, sock_addr, len))) { + .SUCCESS => return, + else => return error.ConnectFailed, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 6bcb181552..f5f3b73a59 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1,27 +1,18 @@ //! POSIX API layer. //! //! This is more cross platform than using OS-specific APIs, however, it is -//! lower-level and less portable than other namespaces such as `std.fs` and +//! lower-level and less portable than other namespaces such as `std.Io` and //! `std.process`. //! //! These APIs are generally lowered to libc function calls if and only if libc //! is linked. Most operating systems other than Windows, Linux, and WASI //! require always linking libc because they use it as the stable syscall ABI. -//! -//! Operating systems that are not POSIX-compliant are sometimes supported by -//! this API layer; sometimes not. Generally, an implementation will be -//! provided only if such implementation is straightforward on that operating -//! system. Otherwise, programmers are expected to use OS-specific logic to -//! deal with the exception. - const builtin = @import("builtin"); const native_os = builtin.os.tag; const std = @import("std.zig"); const Io = std.Io; const mem = std.mem; -const fs = std.fs; -const max_path_bytes = std.fs.max_path_bytes; const maxInt = std.math.maxInt; const cast = std.math.cast; const assert = std.debug.assert; @@ -122,15 +113,14 @@ pub const STDIN_FILENO = system.STDIN_FILENO; pub const STDOUT_FILENO = system.STDOUT_FILENO; pub const SYS = system.SYS; pub const Sigaction = system.Sigaction; +/// Windows has no concept of `stat`. +/// +/// On Linux, the `stat` bits/wrappers are removed due to having to maintain +/// the different varying stat structs per target and libc, leading to runtime +/// errors. Users targeting Linux should add a comptime check and use statx, +/// similar to how `Io.File.stat` does. pub const Stat = switch (native_os) { - // Has no concept of `stat`. .windows => void, - // The `stat` bits/wrappers are removed due to having to maintain the - // different varying `struct stat`s per target and libc, leading to runtime - // errors. - // - // Users targeting linux should add a comptime check and use `statx`, - // similar to how `std.fs.File.stat` does. .linux => void, else => system.Stat, }; @@ -645,26 +635,6 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { } } -pub const EventFdError = error{ - SystemResources, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, -} || UnexpectedError; - -pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 { - const rc = system.eventfd(initval, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - else => |err| return unexpectedErrno(err), - - .INVAL => unreachable, // invalid parameters - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.SystemResources, - .NOMEM => return error.SystemResources, - } -} - pub const GetSockNameError = error{ /// Insufficient resources were available in the system to perform the operation. SystemResources, @@ -707,40 +677,6 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock } } -pub const ConnectError = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError; - -pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } - - while (true) { - switch (errno(system.connect(sock, sock_addr, len))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN, .INPROGRESS => return error.WouldBlock, - .ALREADY => return error.ConnectionPending, - .BADF => unreachable, // sockfd is not a valid open file descriptor. - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .FAULT => unreachable, // The socket structure address is outside the user's address space. - .INTR => continue, - .ISCONN => @panic("AlreadyConnected"), // The socket is already connected. - .HOSTUNREACH => return error.NetworkUnreachable, - .NETUNREACH => return 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 => return error.Timeout, - .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist. - .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused. - else => |err| return unexpectedErrno(err), - } - } -} - pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. From 36eb8dec98c743f369a2badf6a56599c736a45ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 20:24:33 -0800 Subject: [PATCH 2/4] std.posix: goodbye to some functions - fstat - inotify_init1 - inotify_add_watch, inotify_add_watchZ - inotify_rm_watch - sysctlbynameZ --- lib/std/Thread.zig | 15 ++-- lib/std/posix.zig | 113 ---------------------------- lib/std/process.zig | 26 ++++--- lib/std/zig/system.zig | 14 ++-- lib/std/zig/system/darwin/macos.zig | 15 ++-- 5 files changed, 40 insertions(+), 143 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index ad25b1728e..33fd4c0234 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -809,12 +809,15 @@ const PosixThreadImpl = struct { else => { var count: c_int = undefined; var count_len: usize = @sizeOf(c_int); - const name = if (comptime target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; - posix.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { - error.UnknownName => unreachable, - else => |e| return e, - }; - return @as(usize, @intCast(count)); + const name = comptime if (target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; + switch (posix.errno(posix.system.sysctlbyname(name, &count, &count_len, null, 0))) { + .SUCCESS => return @intCast(count), + .FAULT => unreachable, + .PERM => return error.PermissionDenied, + .NOMEM => return error.SystemResources, + .NOENT => unreachable, + else => |err| return posix.unexpectedErrno(err), + } }, } } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f5f3b73a59..6b5686b20a 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -677,89 +677,6 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock } } -pub const FStatError = std.Io.File.StatError; - -/// Return information about a file descriptor. -pub fn fstat(fd: fd_t) FStatError!Stat { - if (native_os == .wasi and !builtin.link_libc) { - @compileError("unsupported OS"); - } - - var stat = mem.zeroes(Stat); - switch (errno(system.fstat(fd, &stat))) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub const INotifyInitError = error{ - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - SystemResources, -} || UnexpectedError; - -/// initialize an inotify instance -pub fn inotify_init1(flags: u32) INotifyInitError!i32 { - const rc = system.inotify_init1(flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INVAL => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const INotifyAddWatchError = error{ - AccessDenied, - NameTooLong, - FileNotFound, - SystemResources, - UserResourceLimitReached, - NotDir, - WatchAlreadyExists, -} || UnexpectedError; - -/// add a watch to an initialized inotify instance -pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 { - const pathname_c = try toPosixPath(pathname); - return inotify_add_watchZ(inotify_fd, &pathname_c, mask); -} - -/// Same as `inotify_add_watch` except pathname is null-terminated. -pub fn inotify_add_watchZ(inotify_fd: i32, pathname: [*:0]const u8, mask: u32) INotifyAddWatchError!i32 { - const rc = system.inotify_add_watch(inotify_fd, pathname, mask); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.UserResourceLimitReached, - .NOTDIR => return error.NotDir, - .EXIST => return error.WatchAlreadyExists, - else => |err| return unexpectedErrno(err), - } -} - -/// remove an existing watch from an inotify instance -pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void { - switch (errno(system.inotify_rm_watch(inotify_fd, wd))) { - .SUCCESS => return, - .BADF => unreachable, - .INVAL => unreachable, - else => unreachable, - } -} - pub const FanotifyInitError = error{ ProcessFdQuotaExceeded, SystemFdQuotaExceeded, @@ -996,36 +913,6 @@ pub fn sysctl( } } -pub const SysCtlByNameError = error{ - PermissionDenied, - SystemResources, - UnknownName, -} || UnexpectedError; - -pub fn sysctlbynameZ( - name: [*:0]const u8, - oldp: ?*anyopaque, - oldlenp: ?*usize, - newp: ?*anyopaque, - newlen: usize, -) SysCtlByNameError!void { - if (native_os == .wasi) { - @compileError("sysctl not supported on WASI"); - } - if (native_os == .haiku) { - @compileError("sysctl not supported on Haiku"); - } - - switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { - .SUCCESS => return, - .FAULT => unreachable, - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => return error.UnknownName, - else => |err| return unexpectedErrno(err), - } -} - pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { switch (errno(system.gettimeofday(tv, tz))) { .SUCCESS => return, diff --git a/lib/std/process.zig b/lib/std/process.zig index 5bcacddc84..8395882c16 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -556,26 +556,28 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { const name = if (native_os == .netbsd) "hw.physmem64" else "hw.physmem"; var physmem: c_ulong = undefined; var len: usize = @sizeOf(c_ulong); - posix.sysctlbynameZ(name, &physmem, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, + switch (posix.errno(posix.system.sysctlbyname(name, &physmem, &len, null, 0))) { + .SUCCESS => return @intCast(physmem), + .FAULT => unreachable, + .PERM => unreachable, // only when setting values + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, else => return error.UnknownTotalSystemMemory, - }; - return @intCast(physmem); + } }, // whole Darwin family .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { // "hw.memsize" returns uint64_t var physmem: u64 = undefined; var len: usize = @sizeOf(u64); - posix.sysctlbynameZ("hw.memsize", &physmem, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value + switch (posix.errno(posix.system.sysctlbyname("hw.memsize", &physmem, &len, null, 0))) { + .SUCCESS => return physmem, + .FAULT => unreachable, + .PERM => unreachable, // only when setting values + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, // constant, known good value else => return error.UnknownTotalSystemMemory, - }; - return physmem; + } }, .openbsd => { const mib: [2]c_int = [_]c_int{ diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index efcf569de5..5046e2f51b 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -260,12 +260,14 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { var value: u32 = undefined; var len: usize = @sizeOf(@TypeOf(value)); - posix.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value - error.Unexpected => return error.OSVersionDetectionFail, - }; + switch (posix.errno(posix.system.sysctlbyname(key, &value, &len, null, 0))) { + .SUCCESS => {}, + .FAULT => unreachable, + .PERM => unreachable, // only when setting values, + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, // constant, known good value + else => return error.OSVersionDetectionFail, + } switch (builtin.target.os.tag) { .freebsd => { diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index 7d80c2b588..c9dc8b57ce 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -2,6 +2,7 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; +const posix = std.posix; const assert = std.debug.assert; const mem = std.mem; const testing = std.testing; @@ -399,12 +400,14 @@ test "detect" { pub fn detectNativeCpuAndFeatures() ?Target.Cpu { var cpu_family: std.c.CPUFAMILY = undefined; var len: usize = @sizeOf(std.c.CPUFAMILY); - std.posix.sysctlbynameZ("hw.cpufamily", &cpu_family, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value - error.Unexpected => unreachable, // EFAULT: stack should be safe, EISDIR/ENOTDIR: constant, known good value - }; + switch (posix.errno(posix.system.sysctlbyname("hw.cpufamily", &cpu_family, &len, null, 0))) { + .SUCCESS => {}, + .FAULT => unreachable, // segmentation fault + .PERM => unreachable, // only when setting values, + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, // constant, known good value + else => unreachable, + } const current_arch = builtin.cpu.arch; switch (current_arch) { From 0c67d9ebdec86a5faac9336c9ed912d798050e17 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 20:27:01 -0800 Subject: [PATCH 3/4] std.posix: goodbye gettimeofday --- lib/std/posix.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 6b5686b20a..660551fee4 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -913,14 +913,6 @@ pub fn sysctl( } } -pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { - switch (errno(system.gettimeofday(tv, tz))) { - .SUCCESS => return, - .INVAL => unreachable, - else => unreachable, - } -} - pub const FcntlError = error{ PermissionDenied, FileBusy, From 6a3226c43cd63fd331c3f4340d4331a8875138e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 21:07:57 -0800 Subject: [PATCH 4/4] std.Io: add net.Socket.createPair and remove the following from std.posix: - socketpair - fcntl --- lib/std/Io.zig | 1 + lib/std/Io/Threaded.zig | 206 +++++++++++++++++++++++++--------------- lib/std/Io/net.zig | 29 ++++++ lib/std/posix.zig | 155 ------------------------------ lib/std/posix/test.zig | 10 +- 5 files changed, 162 insertions(+), 239 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 72df9e34f3..0c25249218 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -688,6 +688,7 @@ pub const VTable = struct { netConnectIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream, netListenUnix: *const fn (?*anyopaque, *const net.UnixAddress, net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle, netConnectUnix: *const fn (?*anyopaque, *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle, + netSocketCreatePair: *const fn (?*anyopaque, net.Socket.CreatePairOptions) net.Socket.CreatePairError![2]net.Socket, netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize }, netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize }, /// Returns 0 on end of stream. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d265c5fdc9..f0562504fd 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1684,6 +1684,7 @@ pub fn io(t: *Threaded) Io { .windows => netConnectUnixWindows, else => netConnectUnixPosix, }, + .netSocketCreatePair = netSocketCreatePair, .netClose = netClose, .netShutdown = switch (native_os) { .windows => netShutdownWindows, @@ -1824,6 +1825,7 @@ pub fn ioBasic(t: *Threaded) Io { .netAccept = netAcceptUnavailable, .netBindIp = netBindIpUnavailable, .netConnectIp = netConnectIpUnavailable, + .netSocketCreatePair = netSocketCreatePairUnavailable, .netConnectUnix = netConnectUnixUnavailable, .netClose = netCloseUnavailable, .netShutdown = netShutdownUnavailable, @@ -10612,43 +10614,36 @@ fn posixConnect( addr_len: posix.socklen_t, ) !void { const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { - .SUCCESS => { - syscall.finish(); - return; - }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .ADDRNOTAVAIL => return error.AddressUnavailable, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN, .INPROGRESS => return error.WouldBlock, - .ALREADY => return error.ConnectionPending, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .FAULT => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTSOCK => |err| return errnoBug(err), - .PROTOTYPE => |err| return errnoBug(err), - .TIMEDOUT => return error.Timeout, - .CONNABORTED => |err| return errnoBug(err), - .ACCES => return error.AccessDenied, - .PERM => |err| return errnoBug(err), - .NOENT => |err| return errnoBug(err), - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + while (true) switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { + .SUCCESS => { + syscall.finish(); + return; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), + .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), + .AGAIN, .INPROGRESS => return syscall.fail(error.WouldBlock), + .ALREADY => return syscall.fail(error.ConnectionPending), + .CONNREFUSED => return syscall.fail(error.ConnectionRefused), + .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), + .HOSTUNREACH => return syscall.fail(error.HostUnreachable), + .NETUNREACH => return syscall.fail(error.NetworkUnreachable), + .TIMEDOUT => return syscall.fail(error.Timeout), + .ACCES => return syscall.fail(error.AccessDenied), + .NETDOWN => return syscall.fail(error.NetworkDown), + .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. + .CONNABORTED => |err| return syscall.errnoBug(err), + .FAULT => |err| return syscall.errnoBug(err), + .ISCONN => |err| return syscall.errnoBug(err), + .NOENT => |err| return syscall.errnoBug(err), + .NOTSOCK => |err| return syscall.errnoBug(err), + .PERM => |err| return syscall.errnoBug(err), + .PROTOTYPE => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; } fn posixConnectUnix( @@ -11106,46 +11101,31 @@ fn openSocketPosix( }!posix.socket_t { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); + const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; const syscall: Syscall = try .start(); const socket_fd = while (true) { - const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; - const socket_rc = posix.system.socket(family, flags, protocol); - switch (posix.errno(socket_rc)) { + const rc = posix.system.socket(family, flags, protocol); + switch (posix.errno(rc)) { .SUCCESS => { - const fd: posix.fd_t = @intCast(socket_rc); - errdefer posix.close(fd); - if (socket_flags_unsupported) while (true) { - try syscall.checkCancel(); - switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { - .SUCCESS => break, - .INTR => continue, - else => |err| { - syscall.finish(); - return posix.unexpectedErrno(err); - }, - } - }; syscall.finish(); + const fd: posix.fd_t = @intCast(rc); + errdefer posix.close(fd); + if (socket_flags_unsupported) try setCloexec(fd); break fd; }, .INTR => { try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .INVAL => return error.ProtocolUnsupportedBySystem, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, - .PROTOTYPE => return error.SocketModeUnsupported, - else => |err| return posix.unexpectedErrno(err), - } - }, + .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), + .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), + .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), + else => |err| return syscall.unexpectedErrno(err), } }; errdefer posix.close(socket_fd); @@ -11158,6 +11138,84 @@ fn openSocketPosix( return socket_fd; } +fn setCloexec(fd: posix.fd_t) error{ Canceled, Unexpected }!void { + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => |err| return syscall.unexpectedErrno(err), + }; +} + +fn netSocketCreatePair( + userdata: ?*anyopaque, + options: net.Socket.CreatePairOptions, +) net.Socket.CreatePairError![2]net.Socket { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + if (!have_networking) return error.OperationUnsupported; + if (@TypeOf(posix.system.socketpair) == void) return error.OperationUnsupported; + if (native_os == .haiku) @panic("TODO"); + + const family: posix.sa_family_t = switch (options.family) { + .ip4 => posix.AF.INET, + .ip6 => posix.AF.INET6, + }; + const mode = posixSocketMode(options.mode); + const protocol = posixProtocol(options.protocol); + const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; + + var sockets: [2]posix.socket_t = undefined; + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.socketpair(family, flags, protocol, &sockets))) { + .SUCCESS => { + syscall.finish(); + errdefer { + posix.close(sockets[0]); + posix.close(sockets[1]); + } + if (socket_flags_unsupported) { + try setCloexec(sockets[0]); + try setCloexec(sockets[1]); + } + var storages: [2]PosixAddress = undefined; + var addr_lens: [2]posix.socklen_t = .{ @sizeOf(PosixAddress), @sizeOf(PosixAddress) }; + try posixGetSockName(sockets[0], &storages[0].any, &addr_lens[0]); + try posixGetSockName(sockets[1], &storages[1].any, &addr_lens[1]); + return .{ + .{ .handle = sockets[0], .address = addressFromPosix(&storages[0]) }, + .{ .handle = sockets[1], .address = addressFromPosix(&storages[1]) }, + }; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), + .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), + .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), + else => |err| return syscall.unexpectedErrno(err), + }; +} + +fn netSocketCreatePairUnavailable( + userdata: ?*anyopaque, + options: net.Socket.CreatePairOptions, +) net.Socket.CreatePairError![2]net.Socket { + _ = userdata; + _ = options; + return error.OperationUnsupported; +} + fn openSocketWsa( t: *Threaded, family: posix.sa_family_t, @@ -11216,20 +11274,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve posix.system.accept(listen_fd, &storage.any, &addr_len); switch (posix.errno(rc)) { .SUCCESS => { + syscall.finish(); const fd: posix.fd_t = @intCast(rc); errdefer posix.close(fd); - if (!have_accept4) while (true) { - try syscall.checkCancel(); - switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { - .SUCCESS => break, - .INTR => continue, - else => |err| { - syscall.finish(); - return posix.unexpectedErrno(err); - }, - } - }; - syscall.finish(); + if (!have_accept4) try setCloexec(fd); break fd; }, .INTR => { diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 76a581180d..21bd13caf3 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1187,6 +1187,35 @@ pub const Socket = struct { ) struct { ?ReceiveTimeoutError, usize } { return io.vtable.netReceive(io.userdata, s.handle, message_buffer, data_buffer, flags, timeout); } + + pub const CreatePairError = error{ + OperationUnsupported, + AccessDenied, + AddressFamilyUnsupported, + ProtocolUnsupportedBySystem, + /// 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, + ProtocolUnsupportedByAddressFamily, + SocketModeUnsupported, + } || Io.UnexpectedError || Io.Cancelable; + + pub const CreatePairOptions = struct { + family: IpAddress.Family = .ip4, + mode: Mode = .stream, + protocol: ?Protocol = null, + }; + + /// Create a set of two sockets that are connected to each other. + /// + /// Also known as "socketpair". + pub fn createPair(io: Io, options: CreatePairOptions) CreatePairError![2]Socket { + return io.vtable.netSocketCreatePair(io.userdata, options); + } }; /// An open socket connection with a network protocol that guarantees diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 660551fee4..5e2cde9aa5 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -509,132 +509,6 @@ pub fn getppid() pid_t { return system.getppid(); } -pub const SocketError = error{ - /// Permission to create a socket of the specified type and/or - /// pro‐tocol is denied. - AccessDenied, - - /// The implementation does not support the specified address family. - AddressFamilyUnsupported, - - /// 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, -} || UnexpectedError; - -pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]socket_t { - // Note to the future: we could provide a shim here for e.g. windows which - // creates a listening socket, then creates a second socket and connects it - // to the listening socket, and then returns the two. - if (@TypeOf(system.socketpair) == void) - @compileError("socketpair() not supported by this OS"); - - // I'm not really sure if haiku supports flags here. I'm following the - // existing filter here from pipe2(), because it sure seems like it - // supports flags there too, but haiku can be hard to understand. - const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku; - const filtered_sock_type = if (!have_sock_flags) - socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC) - else - socket_type; - var socks: [2]socket_t = undefined; - const rc = system.socketpair(domain, filtered_sock_type, protocol, &socks); - switch (errno(rc)) { - .SUCCESS => { - errdefer close(socks[0]); - errdefer close(socks[1]); - if (!have_sock_flags) { - try setSockFlags(socks[0], socket_type); - try setSockFlags(socks[1], socket_type); - } - return socks; - }, - .ACCES => return error.AccessDenied, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .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 unexpectedErrno(err), - } -} - -fn setSockFlags(sock: socket_t, flags: u32) !void { - if ((flags & SOCK.CLOEXEC) != 0) { - if (native_os == .windows) { - // TODO: Find out if this is supported for sockets - } else { - var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - fd_flags |= FD_CLOEXEC; - _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - } - } - if ((flags & SOCK.NONBLOCK) != 0) { - if (native_os == .windows) { - var mode: c_ulong = 1; - if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .NOTINITIALISED => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENOTSOCK => return error.FileDescriptorNotASocket, - // TODO: handle more errors - else => |err| return windows.unexpectedWSAError(err), - } - } - } else { - var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK"); - _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - } - } -} - pub const GetSockNameError = error{ /// Insufficient resources were available in the system to perform the operation. SystemResources, @@ -913,35 +787,6 @@ pub fn sysctl( } } -pub const FcntlError = error{ - PermissionDenied, - FileBusy, - ProcessFdQuotaExceeded, - Locked, - DeadLock, - LockedRegionLimitExceeded, -} || UnexpectedError; - -pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { - while (true) { - const rc = system.fcntl(fd, cmd, arg); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .AGAIN, .ACCES => return error.Locked, - .BADF => unreachable, - .BUSY => return error.FileBusy, - .INVAL => unreachable, // invalid parameters - .PERM => return error.PermissionDenied, - .MFILE => return error.ProcessFdQuotaExceeded, - .NOTDIR => unreachable, // invalid parameter - .DEADLK => return error.DeadLock, - .NOLCK => return error.LockedRegionLimitExceeded, - else => |err| return unexpectedErrno(err), - } - } -} - pub fn getSelfPhdrs() []std.elf.ElfN.Phdr { const getauxval = if (builtin.link_libc) std.c.getauxval else std.os.linux.getauxval; assert(getauxval(std.elf.AT_PHENT) == @sizeOf(std.elf.ElfN.Phdr)); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index e720c4a4e6..5838595fcf 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -273,17 +273,17 @@ test "fcntl" { // Note: The test assumes createFile opens the file with CLOEXEC { - const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0)); try expect((flags & posix.FD_CLOEXEC) != 0); } { - _ = try posix.fcntl(file.handle, posix.F.SETFD, 0); - const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, 0)); + const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0)); try expect((flags & posix.FD_CLOEXEC) == 0); } { - _ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC); - const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)); + const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0)); try expect((flags & posix.FD_CLOEXEC) != 0); } }