authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-29 21:07:57-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 02:07:26-08:00
log6a3226c43cd63fd331c3f4340d4331a8875138e3
treebc76f81dda9c9031cafa963e8fc723b6e41ce688
parent0c67d9ebdec86a5faac9336c9ed912d798050e17

std.Io: add net.Socket.createPair

and remove the following from std.posix: - socketpair - fcntl

5 files changed, 162 insertions(+), 239 deletions(-)

lib/std/Io.zig+1
......@@ -688,6 +688,7 @@ pub const VTable = struct {
688688 netConnectIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream,
689689 netListenUnix: *const fn (?*anyopaque, *const net.UnixAddress, net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle,
690690 netConnectUnix: *const fn (?*anyopaque, *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,
691 netSocketCreatePair: *const fn (?*anyopaque, net.Socket.CreatePairOptions) net.Socket.CreatePairError![2]net.Socket,
691692 netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize },
692693 netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize },
693694 /// Returns 0 on end of stream.
lib/std/Io/Threaded.zig+127-79
......@@ -1684,6 +1684,7 @@ pub fn io(t: *Threaded) Io {
16841684 .windows => netConnectUnixWindows,
16851685 else => netConnectUnixPosix,
16861686 },
1687 .netSocketCreatePair = netSocketCreatePair,
16871688 .netClose = netClose,
16881689 .netShutdown = switch (native_os) {
16891690 .windows => netShutdownWindows,
......@@ -1824,6 +1825,7 @@ pub fn ioBasic(t: *Threaded) Io {
18241825 .netAccept = netAcceptUnavailable,
18251826 .netBindIp = netBindIpUnavailable,
18261827 .netConnectIp = netConnectIpUnavailable,
1828 .netSocketCreatePair = netSocketCreatePairUnavailable,
18271829 .netConnectUnix = netConnectUnixUnavailable,
18281830 .netClose = netCloseUnavailable,
18291831 .netShutdown = netShutdownUnavailable,
......@@ -10612,43 +10614,36 @@ fn posixConnect(
1061210614 addr_len: posix.socklen_t,
1061310615) !void {
1061410616 const syscall: Syscall = try .start();
10615 while (true) {
10616 switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
10617 .SUCCESS => {
10618 syscall.finish();
10619 return;
10620 },
10621 .INTR => {
10622 try syscall.checkCancel();
10623 continue;
10624 },
10625 else => |e| {
10626 syscall.finish();
10627 switch (e) {
10628 .ADDRNOTAVAIL => return error.AddressUnavailable,
10629 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
10630 .AGAIN, .INPROGRESS => return error.WouldBlock,
10631 .ALREADY => return error.ConnectionPending,
10632 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10633 .CONNREFUSED => return error.ConnectionRefused,
10634 .CONNRESET => return error.ConnectionResetByPeer,
10635 .FAULT => |err| return errnoBug(err),
10636 .ISCONN => |err| return errnoBug(err),
10637 .HOSTUNREACH => return error.HostUnreachable,
10638 .NETUNREACH => return error.NetworkUnreachable,
10639 .NOTSOCK => |err| return errnoBug(err),
10640 .PROTOTYPE => |err| return errnoBug(err),
10641 .TIMEDOUT => return error.Timeout,
10642 .CONNABORTED => |err| return errnoBug(err),
10643 .ACCES => return error.AccessDenied,
10644 .PERM => |err| return errnoBug(err),
10645 .NOENT => |err| return errnoBug(err),
10646 .NETDOWN => return error.NetworkDown,
10647 else => |err| return posix.unexpectedErrno(err),
10648 }
10649 },
10650 }
10651 }
10617 while (true) switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
10618 .SUCCESS => {
10619 syscall.finish();
10620 return;
10621 },
10622 .INTR => {
10623 try syscall.checkCancel();
10624 continue;
10625 },
10626 .ADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable),
10627 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
10628 .AGAIN, .INPROGRESS => return syscall.fail(error.WouldBlock),
10629 .ALREADY => return syscall.fail(error.ConnectionPending),
10630 .CONNREFUSED => return syscall.fail(error.ConnectionRefused),
10631 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
10632 .HOSTUNREACH => return syscall.fail(error.HostUnreachable),
10633 .NETUNREACH => return syscall.fail(error.NetworkUnreachable),
10634 .TIMEDOUT => return syscall.fail(error.Timeout),
10635 .ACCES => return syscall.fail(error.AccessDenied),
10636 .NETDOWN => return syscall.fail(error.NetworkDown),
10637 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
10638 .CONNABORTED => |err| return syscall.errnoBug(err),
10639 .FAULT => |err| return syscall.errnoBug(err),
10640 .ISCONN => |err| return syscall.errnoBug(err),
10641 .NOENT => |err| return syscall.errnoBug(err),
10642 .NOTSOCK => |err| return syscall.errnoBug(err),
10643 .PERM => |err| return syscall.errnoBug(err),
10644 .PROTOTYPE => |err| return syscall.errnoBug(err),
10645 else => |err| return syscall.unexpectedErrno(err),
10646 };
1065210647}
1065310648
1065410649fn posixConnectUnix(
......@@ -11106,46 +11101,31 @@ fn openSocketPosix(
1110611101}!posix.socket_t {
1110711102 const mode = posixSocketMode(options.mode);
1110811103 const protocol = posixProtocol(options.protocol);
11104 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
1110911105 const syscall: Syscall = try .start();
1111011106 const socket_fd = while (true) {
11111 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
11112 const socket_rc = posix.system.socket(family, flags, protocol);
11113 switch (posix.errno(socket_rc)) {
11107 const rc = posix.system.socket(family, flags, protocol);
11108 switch (posix.errno(rc)) {
1111411109 .SUCCESS => {
11115 const fd: posix.fd_t = @intCast(socket_rc);
11116 errdefer posix.close(fd);
11117 if (socket_flags_unsupported) while (true) {
11118 try syscall.checkCancel();
11119 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
11120 .SUCCESS => break,
11121 .INTR => continue,
11122 else => |err| {
11123 syscall.finish();
11124 return posix.unexpectedErrno(err);
11125 },
11126 }
11127 };
1112811110 syscall.finish();
11111 const fd: posix.fd_t = @intCast(rc);
11112 errdefer posix.close(fd);
11113 if (socket_flags_unsupported) try setCloexec(fd);
1112911114 break fd;
1113011115 },
1113111116 .INTR => {
1113211117 try syscall.checkCancel();
1113311118 continue;
1113411119 },
11135 else => |e| {
11136 syscall.finish();
11137 switch (e) {
11138 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
11139 .INVAL => return error.ProtocolUnsupportedBySystem,
11140 .MFILE => return error.ProcessFdQuotaExceeded,
11141 .NFILE => return error.SystemFdQuotaExceeded,
11142 .NOBUFS => return error.SystemResources,
11143 .NOMEM => return error.SystemResources,
11144 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
11145 .PROTOTYPE => return error.SocketModeUnsupported,
11146 else => |err| return posix.unexpectedErrno(err),
11147 }
11148 },
11120 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
11121 .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem),
11122 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
11123 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
11124 .NOBUFS => return syscall.fail(error.SystemResources),
11125 .NOMEM => return syscall.fail(error.SystemResources),
11126 .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily),
11127 .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported),
11128 else => |err| return syscall.unexpectedErrno(err),
1114911129 }
1115011130 };
1115111131 errdefer posix.close(socket_fd);
......@@ -11158,6 +11138,84 @@ fn openSocketPosix(
1115811138 return socket_fd;
1115911139}
1116011140
11141fn setCloexec(fd: posix.fd_t) error{ Canceled, Unexpected }!void {
11142 const syscall: Syscall = try .start();
11143 while (true) switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
11144 .SUCCESS => return syscall.finish(),
11145 .INTR => {
11146 try syscall.checkCancel();
11147 continue;
11148 },
11149 else => |err| return syscall.unexpectedErrno(err),
11150 };
11151}
11152
11153fn netSocketCreatePair(
11154 userdata: ?*anyopaque,
11155 options: net.Socket.CreatePairOptions,
11156) net.Socket.CreatePairError![2]net.Socket {
11157 const t: *Threaded = @ptrCast(@alignCast(userdata));
11158 _ = t;
11159 if (!have_networking) return error.OperationUnsupported;
11160 if (@TypeOf(posix.system.socketpair) == void) return error.OperationUnsupported;
11161 if (native_os == .haiku) @panic("TODO");
11162
11163 const family: posix.sa_family_t = switch (options.family) {
11164 .ip4 => posix.AF.INET,
11165 .ip6 => posix.AF.INET6,
11166 };
11167 const mode = posixSocketMode(options.mode);
11168 const protocol = posixProtocol(options.protocol);
11169 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
11170
11171 var sockets: [2]posix.socket_t = undefined;
11172 const syscall: Syscall = try .start();
11173 while (true) switch (posix.errno(posix.system.socketpair(family, flags, protocol, &sockets))) {
11174 .SUCCESS => {
11175 syscall.finish();
11176 errdefer {
11177 posix.close(sockets[0]);
11178 posix.close(sockets[1]);
11179 }
11180 if (socket_flags_unsupported) {
11181 try setCloexec(sockets[0]);
11182 try setCloexec(sockets[1]);
11183 }
11184 var storages: [2]PosixAddress = undefined;
11185 var addr_lens: [2]posix.socklen_t = .{ @sizeOf(PosixAddress), @sizeOf(PosixAddress) };
11186 try posixGetSockName(sockets[0], &storages[0].any, &addr_lens[0]);
11187 try posixGetSockName(sockets[1], &storages[1].any, &addr_lens[1]);
11188 return .{
11189 .{ .handle = sockets[0], .address = addressFromPosix(&storages[0]) },
11190 .{ .handle = sockets[1], .address = addressFromPosix(&storages[1]) },
11191 };
11192 },
11193 .INTR => {
11194 try syscall.checkCancel();
11195 continue;
11196 },
11197 .ACCES => return syscall.fail(error.AccessDenied),
11198 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
11199 .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem),
11200 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
11201 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
11202 .NOBUFS => return syscall.fail(error.SystemResources),
11203 .NOMEM => return syscall.fail(error.SystemResources),
11204 .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily),
11205 .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported),
11206 else => |err| return syscall.unexpectedErrno(err),
11207 };
11208}
11209
11210fn netSocketCreatePairUnavailable(
11211 userdata: ?*anyopaque,
11212 options: net.Socket.CreatePairOptions,
11213) net.Socket.CreatePairError![2]net.Socket {
11214 _ = userdata;
11215 _ = options;
11216 return error.OperationUnsupported;
11217}
11218
1116111219fn openSocketWsa(
1116211220 t: *Threaded,
1116311221 family: posix.sa_family_t,
......@@ -11216,20 +11274,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve
1121611274 posix.system.accept(listen_fd, &storage.any, &addr_len);
1121711275 switch (posix.errno(rc)) {
1121811276 .SUCCESS => {
11277 syscall.finish();
1121911278 const fd: posix.fd_t = @intCast(rc);
1122011279 errdefer posix.close(fd);
11221 if (!have_accept4) while (true) {
11222 try syscall.checkCancel();
11223 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
11224 .SUCCESS => break,
11225 .INTR => continue,
11226 else => |err| {
11227 syscall.finish();
11228 return posix.unexpectedErrno(err);
11229 },
11230 }
11231 };
11232 syscall.finish();
11280 if (!have_accept4) try setCloexec(fd);
1123311281 break fd;
1123411282 },
1123511283 .INTR => {
lib/std/Io/net.zig+29
......@@ -1187,6 +1187,35 @@ pub const Socket = struct {
11871187 ) struct { ?ReceiveTimeoutError, usize } {
11881188 return io.vtable.netReceive(io.userdata, s.handle, message_buffer, data_buffer, flags, timeout);
11891189 }
1190
1191 pub const CreatePairError = error{
1192 OperationUnsupported,
1193 AccessDenied,
1194 AddressFamilyUnsupported,
1195 ProtocolUnsupportedBySystem,
1196 /// The per-process limit on the number of open file descriptors has been reached.
1197 ProcessFdQuotaExceeded,
1198 /// The system-wide limit on the total number of open files has been reached.
1199 SystemFdQuotaExceeded,
1200 /// Insufficient memory is available. The socket cannot be created
1201 /// until sufficient resources are freed.
1202 SystemResources,
1203 ProtocolUnsupportedByAddressFamily,
1204 SocketModeUnsupported,
1205 } || Io.UnexpectedError || Io.Cancelable;
1206
1207 pub const CreatePairOptions = struct {
1208 family: IpAddress.Family = .ip4,
1209 mode: Mode = .stream,
1210 protocol: ?Protocol = null,
1211 };
1212
1213 /// Create a set of two sockets that are connected to each other.
1214 ///
1215 /// Also known as "socketpair".
1216 pub fn createPair(io: Io, options: CreatePairOptions) CreatePairError![2]Socket {
1217 return io.vtable.netSocketCreatePair(io.userdata, options);
1218 }
11901219};
11911220
11921221/// An open socket connection with a network protocol that guarantees
lib/std/posix.zig-155
......@@ -509,132 +509,6 @@ pub fn getppid() pid_t {
509509 return system.getppid();
510510}
511511
512pub const SocketError = error{
513 /// Permission to create a socket of the specified type and/or
514 /// pro‐tocol is denied.
515 AccessDenied,
516
517 /// The implementation does not support the specified address family.
518 AddressFamilyUnsupported,
519
520 /// Unknown protocol, or protocol family not available.
521 ProtocolFamilyNotAvailable,
522
523 /// The per-process limit on the number of open file descriptors has been reached.
524 ProcessFdQuotaExceeded,
525
526 /// The system-wide limit on the total number of open files has been reached.
527 SystemFdQuotaExceeded,
528
529 /// Insufficient memory is available. The socket cannot be created until sufficient
530 /// resources are freed.
531 SystemResources,
532
533 /// The protocol type or the specified protocol is not supported within this domain.
534 ProtocolNotSupported,
535
536 /// The socket type is not supported by the protocol.
537 SocketTypeNotSupported,
538} || UnexpectedError;
539
540pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]socket_t {
541 // Note to the future: we could provide a shim here for e.g. windows which
542 // creates a listening socket, then creates a second socket and connects it
543 // to the listening socket, and then returns the two.
544 if (@TypeOf(system.socketpair) == void)
545 @compileError("socketpair() not supported by this OS");
546
547 // I'm not really sure if haiku supports flags here. I'm following the
548 // existing filter here from pipe2(), because it sure seems like it
549 // supports flags there too, but haiku can be hard to understand.
550 const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku;
551 const filtered_sock_type = if (!have_sock_flags)
552 socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC)
553 else
554 socket_type;
555 var socks: [2]socket_t = undefined;
556 const rc = system.socketpair(domain, filtered_sock_type, protocol, &socks);
557 switch (errno(rc)) {
558 .SUCCESS => {
559 errdefer close(socks[0]);
560 errdefer close(socks[1]);
561 if (!have_sock_flags) {
562 try setSockFlags(socks[0], socket_type);
563 try setSockFlags(socks[1], socket_type);
564 }
565 return socks;
566 },
567 .ACCES => return error.AccessDenied,
568 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
569 .INVAL => return error.ProtocolFamilyNotAvailable,
570 .MFILE => return error.ProcessFdQuotaExceeded,
571 .NFILE => return error.SystemFdQuotaExceeded,
572 .NOBUFS => return error.SystemResources,
573 .NOMEM => return error.SystemResources,
574 .PROTONOSUPPORT => return error.ProtocolNotSupported,
575 .PROTOTYPE => return error.SocketTypeNotSupported,
576 else => |err| return unexpectedErrno(err),
577 }
578}
579
580fn setSockFlags(sock: socket_t, flags: u32) !void {
581 if ((flags & SOCK.CLOEXEC) != 0) {
582 if (native_os == .windows) {
583 // TODO: Find out if this is supported for sockets
584 } else {
585 var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) {
586 error.FileBusy => unreachable,
587 error.Locked => unreachable,
588 error.PermissionDenied => unreachable,
589 error.DeadLock => unreachable,
590 error.LockedRegionLimitExceeded => unreachable,
591 else => |e| return e,
592 };
593 fd_flags |= FD_CLOEXEC;
594 _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) {
595 error.FileBusy => unreachable,
596 error.Locked => unreachable,
597 error.PermissionDenied => unreachable,
598 error.DeadLock => unreachable,
599 error.LockedRegionLimitExceeded => unreachable,
600 else => |e| return e,
601 };
602 }
603 }
604 if ((flags & SOCK.NONBLOCK) != 0) {
605 if (native_os == .windows) {
606 var mode: c_ulong = 1;
607 if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) {
608 switch (windows.ws2_32.WSAGetLastError()) {
609 .NOTINITIALISED => unreachable,
610 .ENETDOWN => return error.NetworkDown,
611 .ENOTSOCK => return error.FileDescriptorNotASocket,
612 // TODO: handle more errors
613 else => |err| return windows.unexpectedWSAError(err),
614 }
615 }
616 } else {
617 var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) {
618 error.FileBusy => unreachable,
619 error.Locked => unreachable,
620 error.PermissionDenied => unreachable,
621 error.DeadLock => unreachable,
622 error.LockedRegionLimitExceeded => unreachable,
623 else => |e| return e,
624 };
625 fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK");
626 _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) {
627 error.FileBusy => unreachable,
628 error.Locked => unreachable,
629 error.PermissionDenied => unreachable,
630 error.DeadLock => unreachable,
631 error.LockedRegionLimitExceeded => unreachable,
632 else => |e| return e,
633 };
634 }
635 }
636}
637
638512pub const GetSockNameError = error{
639513 /// Insufficient resources were available in the system to perform the operation.
640514 SystemResources,
......@@ -913,35 +787,6 @@ pub fn sysctl(
913787 }
914788}
915789
916pub const FcntlError = error{
917 PermissionDenied,
918 FileBusy,
919 ProcessFdQuotaExceeded,
920 Locked,
921 DeadLock,
922 LockedRegionLimitExceeded,
923} || UnexpectedError;
924
925pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
926 while (true) {
927 const rc = system.fcntl(fd, cmd, arg);
928 switch (errno(rc)) {
929 .SUCCESS => return @intCast(rc),
930 .INTR => continue,
931 .AGAIN, .ACCES => return error.Locked,
932 .BADF => unreachable,
933 .BUSY => return error.FileBusy,
934 .INVAL => unreachable, // invalid parameters
935 .PERM => return error.PermissionDenied,
936 .MFILE => return error.ProcessFdQuotaExceeded,
937 .NOTDIR => unreachable, // invalid parameter
938 .DEADLK => return error.DeadLock,
939 .NOLCK => return error.LockedRegionLimitExceeded,
940 else => |err| return unexpectedErrno(err),
941 }
942 }
943}
944
945790pub fn getSelfPhdrs() []std.elf.ElfN.Phdr {
946791 const getauxval = if (builtin.link_libc) std.c.getauxval else std.os.linux.getauxval;
947792 assert(getauxval(std.elf.AT_PHENT) == @sizeOf(std.elf.ElfN.Phdr));
lib/std/posix/test.zig+5-5
......@@ -273,17 +273,17 @@ test "fcntl" {
273273
274274 // Note: The test assumes createFile opens the file with CLOEXEC
275275 {
276 const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
276 const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
277277 try expect((flags & posix.FD_CLOEXEC) != 0);
278278 }
279279 {
280 _ = try posix.fcntl(file.handle, posix.F.SETFD, 0);
281 const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
280 _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, 0));
281 const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
282282 try expect((flags & posix.FD_CLOEXEC) == 0);
283283 }
284284 {
285 _ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC);
286 const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
285 _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC));
286 const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
287287 try expect((flags & posix.FD_CLOEXEC) != 0);
288288 }
289289}