authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-13 20:17:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
loge8cea8accb7f289fd00ee82063df32882748ac48
treee4cb30b59d3e4004a3113ea794c890ee9dc8afd5
parentd680b9e9b2320612a4e988763f5090c4ae8f9a8f

std.Io.Threaded: implement netListenUnix


5 files changed, 130 insertions(+), 17 deletions(-)

BRANCH_TODO+1
......@@ -10,5 +10,6 @@
1010* address the cancelation race condition (signal received between checkCancel and syscall)
1111* update signal values to be an enum
1212* move fs.File.Writer to Io
13* add non-blocking flag to network operations, handle EAGAIN
1314* finish moving std.fs to Io
1415* finish moving all of std.posix into Threaded
lib/std/Io.zig+3-3
......@@ -672,12 +672,12 @@ pub const VTable = struct {
672672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
673673 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
674674
675 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
675 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
676676 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
677677 netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
678678 netConnectIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream,
679 netListenUnix: *const fn (?*anyopaque, net.UnixAddress) net.UnixAddress.ListenError!net.Socket.Handle,
680 netConnectUnix: *const fn (?*anyopaque, net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,
679 netListenUnix: *const fn (?*anyopaque, *const net.UnixAddress, net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle,
680 netConnectUnix: *const fn (?*anyopaque, *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,
681681 netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize },
682682 netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize },
683683 /// Returns 0 on end of stream.
lib/std/Io/Threaded.zig+91-7
......@@ -1756,11 +1756,80 @@ fn netListenIpPosix(
17561756 };
17571757}
17581758
1759fn netListenUnix(userdata: ?*anyopaque, address: Io.net.UnixAddress) Io.net.UnixAddress.ListenError!Io.net.Socket.Handle {
1759fn netListenUnix(
1760 userdata: ?*anyopaque,
1761 address: *const Io.net.UnixAddress,
1762 options: Io.net.UnixAddress.ListenOptions,
1763) Io.net.UnixAddress.ListenError!Io.net.Socket.Handle {
1764 if (!Io.net.has_unix_sockets) return error.AddressFamilyUnsupported;
17601765 const pool: *Pool = @ptrCast(@alignCast(userdata));
1761 _ = pool;
1762 _ = address;
1763 @panic("TODO");
1766 const protocol: u32 = 0;
1767 const socket_fd = while (true) {
1768 try pool.checkCancel();
1769 const flags: u32 = posix.SOCK.STREAM | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
1770 const socket_rc = posix.system.socket(posix.AF.UNIX, flags, protocol);
1771 switch (posix.errno(socket_rc)) {
1772 .SUCCESS => {
1773 const fd: posix.fd_t = @intCast(socket_rc);
1774 errdefer posix.close(fd);
1775 if (socket_flags_unsupported) while (true) {
1776 try pool.checkCancel();
1777 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
1778 .SUCCESS => break,
1779 .INTR => continue,
1780 else => |err| return posix.unexpectedErrno(err),
1781 }
1782 };
1783 break fd;
1784 },
1785 .INTR => continue,
1786 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1787 .MFILE => return error.ProcessFdQuotaExceeded,
1788 .NFILE => return error.SystemFdQuotaExceeded,
1789 .NOBUFS => return error.SystemResources,
1790 .NOMEM => return error.SystemResources,
1791 else => |err| return posix.unexpectedErrno(err),
1792 }
1793 };
1794 errdefer posix.close(socket_fd);
1795
1796 var storage: UnixAddress = undefined;
1797 const addr_len = addressUnixToPosix(address, &storage);
1798 while (true) {
1799 try pool.checkCancel();
1800 switch (posix.errno(posix.system.bind(socket_fd, &storage.any, addr_len))) {
1801 .SUCCESS => break,
1802 .INTR => continue,
1803 .ACCES => return error.AccessDenied,
1804 .PERM => return error.PermissionDenied,
1805 .ADDRINUSE => return error.AddressInUse,
1806 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1807 .ADDRNOTAVAIL => return error.AddressUnavailable,
1808 .NOMEM => return error.SystemResources,
1809 .LOOP => return error.SymLinkLoop,
1810 .NOENT => return error.FileNotFound,
1811 .NOTDIR => return error.NotDir,
1812 .ROFS => return error.ReadOnlyFileSystem,
1813 .BADF => |err| return errnoBug(err), // always a race condition if this error is returned
1814 .INVAL => |err| return errnoBug(err), // invalid parameters
1815 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
1816 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
1817 .NAMETOOLONG => |err| return errnoBug(err),
1818 else => |err| return posix.unexpectedErrno(err),
1819 }
1820 }
1821
1822 while (true) {
1823 try pool.checkCancel();
1824 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
1825 .SUCCESS => break,
1826 .ADDRINUSE => return error.AddressInUse,
1827 .BADF => |err| return errnoBug(err),
1828 else => |err| return posix.unexpectedErrno(err),
1829 }
1830 }
1831
1832 return socket_fd;
17641833}
17651834
17661835fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
......@@ -1791,7 +1860,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka
17911860 .ADDRINUSE => return error.AddressInUse,
17921861 .ADDRNOTAVAIL => return error.AddressUnavailable,
17931862 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1794 .AGAIN, .INPROGRESS => |err| return errnoBug(err),
1863 .AGAIN, .INPROGRESS => return error.WouldBlock,
17951864 .ALREADY => return error.ConnectionPending,
17961865 .BADF => |err| return errnoBug(err),
17971866 .CONNREFUSED => return error.ConnectionRefused,
......@@ -1804,8 +1873,8 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka
18041873 .PROTOTYPE => |err| return errnoBug(err),
18051874 .TIMEDOUT => return error.ConnectionTimedOut,
18061875 .CONNABORTED => |err| return errnoBug(err),
1876 .ACCES => return error.AccessDenied,
18071877 // UNIX socket error codes:
1808 .ACCES => |err| return errnoBug(err),
18091878 .PERM => |err| return errnoBug(err),
18101879 .NOENT => |err| return errnoBug(err),
18111880 else => |err| return posix.unexpectedErrno(err),
......@@ -1867,7 +1936,10 @@ fn netConnectIpPosix(
18671936 } };
18681937}
18691938
1870fn netConnectUnix(userdata: ?*anyopaque, address: Io.net.UnixAddress) Io.net.UnixAddress.ConnectError!Io.net.Socket.Handle {
1939fn netConnectUnix(
1940 userdata: ?*anyopaque,
1941 address: *const Io.net.UnixAddress,
1942) Io.net.UnixAddress.ConnectError!Io.net.Socket.Handle {
18711943 const pool: *Pool = @ptrCast(@alignCast(userdata));
18721944 _ = pool;
18731945 _ = address;
......@@ -2503,6 +2575,11 @@ const PosixAddress = extern union {
25032575 in6: posix.sockaddr.in6,
25042576};
25052577
2578const UnixAddress = extern union {
2579 any: posix.sockaddr,
2580 un: posix.sockaddr.un,
2581};
2582
25062583fn posixAddressFamily(a: *const Io.net.IpAddress) posix.sa_family_t {
25072584 return switch (a.*) {
25082585 .ip4 => posix.AF.INET,
......@@ -2531,6 +2608,13 @@ fn addressToPosix(a: *const Io.net.IpAddress, storage: *PosixAddress) posix.sock
25312608 };
25322609}
25332610
2611fn addressUnixToPosix(a: *const Io.net.UnixAddress, storage: *UnixAddress) posix.socklen_t {
2612 @memcpy(storage.un.path[0..a.path.len], a.path);
2613 storage.un.family = posix.AF.UNIX;
2614 storage.un.path[a.path.len] = 0;
2615 return @sizeOf(posix.sockaddr.un);
2616}
2617
25342618fn address4FromPosix(in: *posix.sockaddr.in) Io.net.Ip4Address {
25352619 return .{
25362620 .port = std.mem.bigToNative(u16, in.port),
lib/std/Io/net.zig+34-6
......@@ -51,6 +51,8 @@ pub const has_unix_sockets = switch (native_os) {
5151 else => true,
5252};
5353
54pub const default_kernel_backlog = 128;
55
5456pub const IpAddress = union(enum) {
5557 ip4: Ip4Address,
5658 ip6: Ip6Address,
......@@ -210,7 +212,7 @@ pub const IpAddress = union(enum) {
210212 /// How many connections the kernel will accept on the application's behalf.
211213 /// If more than this many connections pool in the kernel, clients will start
212214 /// seeing "Connection refused".
213 kernel_backlog: u31 = 128,
215 kernel_backlog: u31 = default_kernel_backlog,
214216 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
215217 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
216218 reuse_address: bool = false,
......@@ -288,6 +290,11 @@ pub const IpAddress = union(enum) {
288290 ProtocolUnsupportedBySystem,
289291 ProtocolUnsupportedByAddressFamily,
290292 SocketModeUnsupported,
293 /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
294 /// the connection request failed because of a local firewall rule.
295 AccessDenied,
296 /// Non-blocking was requested and the operation cannot return immediately.
297 WouldBlock,
291298 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
292299
293300 pub const ConnectOptions = struct {
......@@ -804,19 +811,40 @@ pub const UnixAddress = struct {
804811 return .{ .path = p };
805812 }
806813
807 pub const ListenError = error{};
814 pub const ListenError = error{
815 AddressFamilyUnsupported,
816 AddressInUse,
817 NetworkDown,
818 SystemResources,
819 SymLinkLoop,
820 FileNotFound,
821 NotDir,
822 ReadOnlyFileSystem,
823 ProcessFdQuotaExceeded,
824 SystemFdQuotaExceeded,
825 AccessDenied,
826 PermissionDenied,
827 AddressUnavailable,
828 } || Io.Cancelable || Io.UnexpectedError;
829
830 pub const ListenOptions = struct {
831 /// How many connections the kernel will accept on the application's behalf.
832 /// If more than this many connections pool in the kernel, clients will start
833 /// seeing "Connection refused".
834 kernel_backlog: u31 = default_kernel_backlog,
835 };
808836
809 pub fn listen(ua: UnixAddress, io: Io) ListenError!Server {
837 pub fn listen(ua: *const UnixAddress, io: Io, options: ListenOptions) ListenError!Server {
810838 assert(ua.path.len <= max_len);
811839 return .{ .socket = .{
812 .handle = try io.vtable.netListenUnix(io.userdata, ua),
840 .handle = try io.vtable.netListenUnix(io.userdata, ua, options),
813841 .address = .{ .ip4 = .loopback(0) },
814842 } };
815843 }
816844
817 pub const ConnectError = error{};
845 pub const ConnectError = error{} || Io.Cancelable || Io.UnexpectedError;
818846
819 pub fn connect(ua: UnixAddress, io: Io) ConnectError!Stream {
847 pub fn connect(ua: *const UnixAddress, io: Io) ConnectError!Stream {
820848 assert(ua.path.len <= max_len);
821849 return .{ .socket = .{
822850 .handle = try io.vtable.netConnectUnix(io.userdata, ua),
lib/std/Io/net/test.zig+1-1
......@@ -273,7 +273,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
273273 const socket_addr = try net.UnixAddress.init(socket_path);
274274 defer std.fs.cwd().deleteFile(socket_path) catch {};
275275
276 var server = try socket_addr.listen(io);
276 var server = try socket_addr.listen(io, .{});
277277 defer server.socket.close(io);
278278
279279 const S = struct {