commit 6a3226c43cd63fd331c3f4340d4331a8875138e3 (tree)
parent 0c67d9ebdec86a5faac9336c9ed912d798050e17
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 29 Jan 2026 21:07:57 -0800
std.Io: add net.Socket.createPair
and remove the following from std.posix:
- socketpair
- fcntl
Diffstat:
5 files changed, 162 insertions(+), 239 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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);
}
}