authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-10 22:37:54-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
log3b346223681b40ce1271688cbe4d650a43b8e613
tree9ebfebd19c17f345d5c483806a55127e5589f687
parent9e681cab56eb306f3e5ba88a951625408f72f696

std.Io: add unix domain sockets API

note that "reuseaddr" does nothing for these

4 files changed, 77 insertions(+), 42 deletions(-)

lib/std/Io.zig+6-4
...@@ -672,10 +672,12 @@ pub const VTable = struct {...@@ -672,10 +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 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,675 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
676 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,676 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
677 ipBind: *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 ipConnect: *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,
680 netConnectUnix: *const fn (?*anyopaque, net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,
679 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 },
680 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 },
681 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,683 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
lib/std/Io/Threaded.zig+28-13
...@@ -203,22 +203,24 @@ pub fn io(pool: *Pool) Io {...@@ -203,22 +203,24 @@ pub fn io(pool: *Pool) Io {
203 else => sleepPosix,203 else => sleepPosix,
204 },204 },
205205
206 .listen = switch (builtin.os.tag) {206 .netListenIp = switch (builtin.os.tag) {
207 .windows => @panic("TODO"),207 .windows => @panic("TODO"),
208 else => listenPosix,208 else => netListenIpPosix,
209 },209 },
210 .accept = switch (builtin.os.tag) {210 .netListenUnix = netListenUnix,
211 .netAccept = switch (builtin.os.tag) {
211 .windows => @panic("TODO"),212 .windows => @panic("TODO"),
212 else => acceptPosix,213 else => netAcceptPosix,
213 },214 },
214 .ipBind = switch (builtin.os.tag) {215 .netBindIp = switch (builtin.os.tag) {
215 .windows => @panic("TODO"),216 .windows => @panic("TODO"),
216 else => ipBindPosix,217 else => netBindIpPosix,
217 },218 },
218 .ipConnect = switch (builtin.os.tag) {219 .netConnectIp = switch (builtin.os.tag) {
219 .windows => @panic("TODO"),220 .windows => @panic("TODO"),
220 else => ipConnectPosix,221 else => netConnectIpPosix,
221 },222 },
223 .netConnectUnix = netConnectUnix,
222 .netClose = netClose,224 .netClose = netClose,
223 .netRead = switch (builtin.os.tag) {225 .netRead = switch (builtin.os.tag) {
224 .windows => @panic("TODO"),226 .windows => @panic("TODO"),
...@@ -1636,7 +1638,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {...@@ -1636,7 +1638,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
1636 return result.?;1638 return result.?;
1637}1639}
16381640
1639fn listenPosix(1641fn netListenIpPosix(
1640 userdata: ?*anyopaque,1642 userdata: ?*anyopaque,
1641 address: Io.net.IpAddress,1643 address: Io.net.IpAddress,
1642 options: Io.net.IpAddress.ListenOptions,1644 options: Io.net.IpAddress.ListenOptions,
...@@ -1702,6 +1704,13 @@ fn listenPosix(...@@ -1702,6 +1704,13 @@ fn listenPosix(
1702 };1704 };
1703}1705}
17041706
1707fn netListenUnix(userdata: ?*anyopaque, address: Io.net.UnixAddress) Io.net.UnixAddress.ListenError!Io.net.Socket.Handle {
1708 const pool: *Pool = @ptrCast(@alignCast(userdata));
1709 _ = pool;
1710 _ = address;
1711 @panic("TODO");
1712}
1713
1705fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {1714fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
1706 while (true) {1715 while (true) {
1707 try pool.checkCancel();1716 try pool.checkCancel();
...@@ -1784,7 +1793,7 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio...@@ -1784,7 +1793,7 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio
1784 }1793 }
1785}1794}
17861795
1787fn ipConnectPosix(1796fn netConnectIpPosix(
1788 userdata: ?*anyopaque,1797 userdata: ?*anyopaque,
1789 address: *const Io.net.IpAddress,1798 address: *const Io.net.IpAddress,
1790 options: Io.net.IpAddress.ConnectOptions,1799 options: Io.net.IpAddress.ConnectOptions,
...@@ -1806,7 +1815,14 @@ fn ipConnectPosix(...@@ -1806,7 +1815,14 @@ fn ipConnectPosix(
1806 } };1815 } };
1807}1816}
18081817
1809fn ipBindPosix(1818fn netConnectUnix(userdata: ?*anyopaque, address: Io.net.UnixAddress) Io.net.UnixAddress.ConnectError!Io.net.Socket.Handle {
1819 const pool: *Pool = @ptrCast(@alignCast(userdata));
1820 _ = pool;
1821 _ = address;
1822 @panic("TODO");
1823}
1824
1825fn netBindIpPosix(
1810 userdata: ?*anyopaque,1826 userdata: ?*anyopaque,
1811 address: *const Io.net.IpAddress,1827 address: *const Io.net.IpAddress,
1812 options: Io.net.IpAddress.BindOptions,1828 options: Io.net.IpAddress.BindOptions,
...@@ -1871,9 +1887,8 @@ fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: Io.net.IpAdd...@@ -1871,9 +1887,8 @@ fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: Io.net.IpAdd
1871const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩1887const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩
1872const have_accept4 = !socket_flags_unsupported;1888const have_accept4 = !socket_flags_unsupported;
18731889
1874fn acceptPosix(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Stream {1890fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: Io.net.Socket.Handle) Io.net.Server.AcceptError!Io.net.Stream {
1875 const pool: *Pool = @ptrCast(@alignCast(userdata));1891 const pool: *Pool = @ptrCast(@alignCast(userdata));
1876 const listen_fd = server.socket.handle;
1877 var storage: PosixAddress = undefined;1892 var storage: PosixAddress = undefined;
1878 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);1893 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
1879 const fd = while (true) {1894 const fd = while (true) {
lib/std/Io/net.zig+38-4
...@@ -219,7 +219,7 @@ pub const IpAddress = union(enum) {...@@ -219,7 +219,7 @@ pub const IpAddress = union(enum) {
219 /// Waits for a TCP connection. When using this API, `bind` does not need219 /// Waits for a TCP connection. When using this API, `bind` does not need
220 /// to be called. The returned `Server` has an open `stream`.220 /// to be called. The returned `Server` has an open `stream`.
221 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {221 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {
222 return io.vtable.listen(io.userdata, address, options);222 return io.vtable.netListenIp(io.userdata, address, options);
223 }223 }
224224
225 pub const BindError = error{225 pub const BindError = error{
...@@ -262,7 +262,7 @@ pub const IpAddress = union(enum) {...@@ -262,7 +262,7 @@ pub const IpAddress = union(enum) {
262 /// One bound `Socket` can be used to receive messages from multiple262 /// One bound `Socket` can be used to receive messages from multiple
263 /// different addresses.263 /// different addresses.
264 pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {264 pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {
265 return io.vtable.ipBind(io.userdata, address, options);265 return io.vtable.netBindIp(io.userdata, address, options);
266 }266 }
267267
268 pub const ConnectError = error{268 pub const ConnectError = error{
...@@ -298,7 +298,7 @@ pub const IpAddress = union(enum) {...@@ -298,7 +298,7 @@ pub const IpAddress = union(enum) {
298298
299 /// Initiates a connection-oriented network stream.299 /// Initiates a connection-oriented network stream.
300 pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {300 pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
301 return io.vtable.ipConnect(io.userdata, address, options);301 return io.vtable.netConnectIp(io.userdata, address, options);
302 }302 }
303};303};
304304
...@@ -775,6 +775,39 @@ pub const Ip6Address = struct {...@@ -775,6 +775,39 @@ pub const Ip6Address = struct {
775 };775 };
776};776};
777777
778pub const UnixAddress = struct {
779 path: []const u8,
780
781 pub const max_len = 108;
782
783 pub const InitError = error{NameTooLong};
784
785 pub fn init(p: []const u8) InitError!UnixAddress {
786 if (p.len > max_len) return error.NameTooLong;
787 return .{ .path = p };
788 }
789
790 pub const ListenError = error{};
791
792 pub fn listen(ua: UnixAddress, io: Io) ListenError!Server {
793 assert(ua.path.len <= max_len);
794 return .{ .socket = .{
795 .handle = try io.vtable.netListenUnix(io.userdata, ua),
796 .address = .{ .ip4 = .loopback(0) },
797 } };
798 }
799
800 pub const ConnectError = error{};
801
802 pub fn connect(ua: UnixAddress, io: Io) ConnectError!Stream {
803 assert(ua.path.len <= max_len);
804 return .{ .socket = .{
805 .handle = try io.vtable.netConnectUnix(io.userdata, ua),
806 .address = .{ .ip4 = .loopback(0) },
807 } };
808 }
809};
810
778pub const ReceiveFlags = packed struct(u8) {811pub const ReceiveFlags = packed struct(u8) {
779 oob: bool = false,812 oob: bool = false,
780 peek: bool = false,813 peek: bool = false,
...@@ -917,6 +950,7 @@ pub const Socket = struct {...@@ -917,6 +950,7 @@ pub const Socket = struct {
917 else => std.posix.fd_t,950 else => std.posix.fd_t,
918 };951 };
919952
953 /// Leaves `address` in a valid state.
920 pub fn close(s: *Socket, io: Io) void {954 pub fn close(s: *Socket, io: Io) void {
921 io.vtable.netClose(io.userdata, s.handle);955 io.vtable.netClose(io.userdata, s.handle);
922 s.handle = undefined;956 s.handle = undefined;
...@@ -1156,7 +1190,7 @@ pub const Server = struct {...@@ -1156,7 +1190,7 @@ pub const Server = struct {
11561190
1157 /// Blocks until a client connects to the server.1191 /// Blocks until a client connects to the server.
1158 pub fn accept(s: *Server, io: Io) AcceptError!Stream {1192 pub fn accept(s: *Server, io: Io) AcceptError!Stream {
1159 return io.vtable.accept(io.userdata, s);1193 return io.vtable.netAccept(io.userdata, s.socket.handle);
1160 }1194 }
1161};1195};
11621196
lib/std/Io/net/test.zig+5-21
...@@ -290,15 +290,16 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -290,15 +290,16 @@ test "listen on a unix socket, send bytes, receive bytes" {
290 const socket_path = try generateFileName("socket.unix");290 const socket_path = try generateFileName("socket.unix");
291 defer testing.allocator.free(socket_path);291 defer testing.allocator.free(socket_path);
292292
293 const socket_addr = try net.IpAddress.initUnix(socket_path);293 const socket_addr = try net.UnixAddress.init(socket_path);
294 defer std.fs.cwd().deleteFile(socket_path) catch {};294 defer std.fs.cwd().deleteFile(socket_path) catch {};
295295
296 var server = try socket_addr.listen(io, .{});296 var server = try socket_addr.listen(io);
297 defer server.deinit(io);297 defer server.socket.close(io);
298298
299 const S = struct {299 const S = struct {
300 fn clientFn(path: []const u8) !void {300 fn clientFn(path: []const u8) !void {
301 var stream = try net.connectUnixSocket(path);301 const server_path: net.UnixAddress = try .init(path);
302 var stream = try server_path.connect(io);
302 defer stream.close(io);303 defer stream.close(io);
303304
304 var stream_writer = stream.writer(io, &.{});305 var stream_writer = stream.writer(io, &.{});
...@@ -319,23 +320,6 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -319,23 +320,6 @@ test "listen on a unix socket, send bytes, receive bytes" {
319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);320 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
320}321}
321322
322test "listen on a unix socket with reuse_address option" {
323 if (!net.has_unix_sockets) return error.SkipZigTest;
324 // Windows doesn't implement reuse port option.
325 if (builtin.os.tag == .windows) return error.SkipZigTest;
326
327 const io = testing.io;
328
329 const socket_path = try generateFileName("socket.unix");
330 defer testing.allocator.free(socket_path);
331
332 const socket_addr = try net.Address.initUnix(socket_path);
333 defer std.fs.cwd().deleteFile(socket_path) catch {};
334
335 var server = try socket_addr.listen(io, .{ .reuse_address = true });
336 server.deinit(io);
337}
338
339fn generateFileName(base_name: []const u8) ![]const u8 {323fn generateFileName(base_name: []const u8) ![]const u8 {
340 const random_bytes_count = 12;324 const random_bytes_count = 12;
341 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);325 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);