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 @@...@@ -10,5 +10,6 @@
10* address the cancelation race condition (signal received between checkCancel and syscall)10* address the cancelation race condition (signal received between checkCancel and syscall)
11* update signal values to be an enum11* update signal values to be an enum
12* move fs.File.Writer to Io12* move fs.File.Writer to Io
13* add non-blocking flag to network operations, handle EAGAIN
13* finish moving std.fs to Io14* finish moving std.fs to Io
14* finish moving all of std.posix into Threaded15* finish moving all of std.posix into Threaded
lib/std/Io.zig+3-3
...@@ -672,12 +672,12 @@ pub const VTable = struct {...@@ -672,12 +672,12 @@ pub const VTable = struct {
672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
673 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,673 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,
676 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,676 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
677 netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,677 netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
678 netConnectIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream,678 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,679 netListenUnix: *const fn (?*anyopaque, *const net.UnixAddress, net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle,
680 netConnectUnix: *const fn (?*anyopaque, net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,680 netConnectUnix: *const fn (?*anyopaque, *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,
681 netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize },681 netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize },
682 netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize },682 netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize },
683 /// Returns 0 on end of stream.683 /// Returns 0 on end of stream.
lib/std/Io/Threaded.zig+91-7
...@@ -1756,11 +1756,80 @@ fn netListenIpPosix(...@@ -1756,11 +1756,80 @@ fn netListenIpPosix(
1756 };1756 };
1757}1757}
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;
1760 const pool: *Pool = @ptrCast(@alignCast(userdata));1765 const pool: *Pool = @ptrCast(@alignCast(userdata));
1761 _ = pool;1766 const protocol: u32 = 0;
1762 _ = address;1767 const socket_fd = while (true) {
1763 @panic("TODO");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;
1764}1833}
17651834
1766fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {1835fn 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...@@ -1791,7 +1860,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka
1791 .ADDRINUSE => return error.AddressInUse,1860 .ADDRINUSE => return error.AddressInUse,
1792 .ADDRNOTAVAIL => return error.AddressUnavailable,1861 .ADDRNOTAVAIL => return error.AddressUnavailable,
1793 .AFNOSUPPORT => return error.AddressFamilyUnsupported,1862 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1794 .AGAIN, .INPROGRESS => |err| return errnoBug(err),1863 .AGAIN, .INPROGRESS => return error.WouldBlock,
1795 .ALREADY => return error.ConnectionPending,1864 .ALREADY => return error.ConnectionPending,
1796 .BADF => |err| return errnoBug(err),1865 .BADF => |err| return errnoBug(err),
1797 .CONNREFUSED => return error.ConnectionRefused,1866 .CONNREFUSED => return error.ConnectionRefused,
...@@ -1804,8 +1873,8 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka...@@ -1804,8 +1873,8 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka
1804 .PROTOTYPE => |err| return errnoBug(err),1873 .PROTOTYPE => |err| return errnoBug(err),
1805 .TIMEDOUT => return error.ConnectionTimedOut,1874 .TIMEDOUT => return error.ConnectionTimedOut,
1806 .CONNABORTED => |err| return errnoBug(err),1875 .CONNABORTED => |err| return errnoBug(err),
1876 .ACCES => return error.AccessDenied,
1807 // UNIX socket error codes:1877 // UNIX socket error codes:
1808 .ACCES => |err| return errnoBug(err),
1809 .PERM => |err| return errnoBug(err),1878 .PERM => |err| return errnoBug(err),
1810 .NOENT => |err| return errnoBug(err),1879 .NOENT => |err| return errnoBug(err),
1811 else => |err| return posix.unexpectedErrno(err),1880 else => |err| return posix.unexpectedErrno(err),
...@@ -1867,7 +1936,10 @@ fn netConnectIpPosix(...@@ -1867,7 +1936,10 @@ fn netConnectIpPosix(
1867 } };1936 } };
1868}1937}
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 {
1871 const pool: *Pool = @ptrCast(@alignCast(userdata));1943 const pool: *Pool = @ptrCast(@alignCast(userdata));
1872 _ = pool;1944 _ = pool;
1873 _ = address;1945 _ = address;
...@@ -2503,6 +2575,11 @@ const PosixAddress = extern union {...@@ -2503,6 +2575,11 @@ const PosixAddress = extern union {
2503 in6: posix.sockaddr.in6,2575 in6: posix.sockaddr.in6,
2504};2576};
25052577
2578const UnixAddress = extern union {
2579 any: posix.sockaddr,
2580 un: posix.sockaddr.un,
2581};
2582
2506fn posixAddressFamily(a: *const Io.net.IpAddress) posix.sa_family_t {2583fn posixAddressFamily(a: *const Io.net.IpAddress) posix.sa_family_t {
2507 return switch (a.*) {2584 return switch (a.*) {
2508 .ip4 => posix.AF.INET,2585 .ip4 => posix.AF.INET,
...@@ -2531,6 +2608,13 @@ fn addressToPosix(a: *const Io.net.IpAddress, storage: *PosixAddress) posix.sock...@@ -2531,6 +2608,13 @@ fn addressToPosix(a: *const Io.net.IpAddress, storage: *PosixAddress) posix.sock
2531 };2608 };
2532}2609}
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
2534fn address4FromPosix(in: *posix.sockaddr.in) Io.net.Ip4Address {2618fn address4FromPosix(in: *posix.sockaddr.in) Io.net.Ip4Address {
2535 return .{2619 return .{
2536 .port = std.mem.bigToNative(u16, in.port),2620 .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) {...@@ -51,6 +51,8 @@ pub const has_unix_sockets = switch (native_os) {
51 else => true,51 else => true,
52};52};
5353
54pub const default_kernel_backlog = 128;
55
54pub const IpAddress = union(enum) {56pub const IpAddress = union(enum) {
55 ip4: Ip4Address,57 ip4: Ip4Address,
56 ip6: Ip6Address,58 ip6: Ip6Address,
...@@ -210,7 +212,7 @@ pub const IpAddress = union(enum) {...@@ -210,7 +212,7 @@ pub const IpAddress = union(enum) {
210 /// How many connections the kernel will accept on the application's behalf.212 /// How many connections the kernel will accept on the application's behalf.
211 /// If more than this many connections pool in the kernel, clients will start213 /// If more than this many connections pool in the kernel, clients will start
212 /// seeing "Connection refused".214 /// seeing "Connection refused".
213 kernel_backlog: u31 = 128,215 kernel_backlog: u31 = default_kernel_backlog,
214 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.216 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
215 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.217 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
216 reuse_address: bool = false,218 reuse_address: bool = false,
...@@ -288,6 +290,11 @@ pub const IpAddress = union(enum) {...@@ -288,6 +290,11 @@ pub const IpAddress = union(enum) {
288 ProtocolUnsupportedBySystem,290 ProtocolUnsupportedBySystem,
289 ProtocolUnsupportedByAddressFamily,291 ProtocolUnsupportedByAddressFamily,
290 SocketModeUnsupported,292 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,
291 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;298 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
292299
293 pub const ConnectOptions = struct {300 pub const ConnectOptions = struct {
...@@ -804,19 +811,40 @@ pub const UnixAddress = struct {...@@ -804,19 +811,40 @@ pub const UnixAddress = struct {
804 return .{ .path = p };811 return .{ .path = p };
805 }812 }
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 {
810 assert(ua.path.len <= max_len);838 assert(ua.path.len <= max_len);
811 return .{ .socket = .{839 return .{ .socket = .{
812 .handle = try io.vtable.netListenUnix(io.userdata, ua),840 .handle = try io.vtable.netListenUnix(io.userdata, ua, options),
813 .address = .{ .ip4 = .loopback(0) },841 .address = .{ .ip4 = .loopback(0) },
814 } };842 } };
815 }843 }
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 {
820 assert(ua.path.len <= max_len);848 assert(ua.path.len <= max_len);
821 return .{ .socket = .{849 return .{ .socket = .{
822 .handle = try io.vtable.netConnectUnix(io.userdata, ua),850 .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" {...@@ -273,7 +273,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
273 const socket_addr = try net.UnixAddress.init(socket_path);273 const socket_addr = try net.UnixAddress.init(socket_path);
274 defer std.fs.cwd().deleteFile(socket_path) catch {};274 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, .{});
277 defer server.socket.close(io);277 defer server.socket.close(io);
278278
279 const S = struct {279 const S = struct {