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 {
672672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
673673 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,
676 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,
677 ipBind: *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,
675 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
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,
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,
679681 netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize },
680682 netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize },
681683 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 {
203203 else => sleepPosix,
204204 },
205205
206 .listen = switch (builtin.os.tag) {
206 .netListenIp = switch (builtin.os.tag) {
207207 .windows => @panic("TODO"),
208 else => listenPosix,
208 else => netListenIpPosix,
209209 },
210 .accept = switch (builtin.os.tag) {
210 .netListenUnix = netListenUnix,
211 .netAccept = switch (builtin.os.tag) {
211212 .windows => @panic("TODO"),
212 else => acceptPosix,
213 else => netAcceptPosix,
213214 },
214 .ipBind = switch (builtin.os.tag) {
215 .netBindIp = switch (builtin.os.tag) {
215216 .windows => @panic("TODO"),
216 else => ipBindPosix,
217 else => netBindIpPosix,
217218 },
218 .ipConnect = switch (builtin.os.tag) {
219 .netConnectIp = switch (builtin.os.tag) {
219220 .windows => @panic("TODO"),
220 else => ipConnectPosix,
221 else => netConnectIpPosix,
221222 },
223 .netConnectUnix = netConnectUnix,
222224 .netClose = netClose,
223225 .netRead = switch (builtin.os.tag) {
224226 .windows => @panic("TODO"),
......@@ -1636,7 +1638,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
16361638 return result.?;
16371639}
16381640
1639fn listenPosix(
1641fn netListenIpPosix(
16401642 userdata: ?*anyopaque,
16411643 address: Io.net.IpAddress,
16421644 options: Io.net.IpAddress.ListenOptions,
......@@ -1702,6 +1704,13 @@ fn listenPosix(
17021704 };
17031705}
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
17051714fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
17061715 while (true) {
17071716 try pool.checkCancel();
......@@ -1784,7 +1793,7 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio
17841793 }
17851794}
17861795
1787fn ipConnectPosix(
1796fn netConnectIpPosix(
17881797 userdata: ?*anyopaque,
17891798 address: *const Io.net.IpAddress,
17901799 options: Io.net.IpAddress.ConnectOptions,
......@@ -1806,7 +1815,14 @@ fn ipConnectPosix(
18061815 } };
18071816}
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(
18101826 userdata: ?*anyopaque,
18111827 address: *const Io.net.IpAddress,
18121828 options: Io.net.IpAddress.BindOptions,
......@@ -1871,9 +1887,8 @@ fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: Io.net.IpAdd
18711887const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩
18721888const 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 {
18751891 const pool: *Pool = @ptrCast(@alignCast(userdata));
1876 const listen_fd = server.socket.handle;
18771892 var storage: PosixAddress = undefined;
18781893 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
18791894 const fd = while (true) {
lib/std/Io/net.zig+38-4
......@@ -219,7 +219,7 @@ pub const IpAddress = union(enum) {
219219 /// Waits for a TCP connection. When using this API, `bind` does not need
220220 /// to be called. The returned `Server` has an open `stream`.
221221 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);
223223 }
224224
225225 pub const BindError = error{
......@@ -262,7 +262,7 @@ pub const IpAddress = union(enum) {
262262 /// One bound `Socket` can be used to receive messages from multiple
263263 /// different addresses.
264264 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);
266266 }
267267
268268 pub const ConnectError = error{
......@@ -298,7 +298,7 @@ pub const IpAddress = union(enum) {
298298
299299 /// Initiates a connection-oriented network stream.
300300 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);
302302 }
303303};
304304
......@@ -775,6 +775,39 @@ pub const Ip6Address = struct {
775775 };
776776};
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
778811pub const ReceiveFlags = packed struct(u8) {
779812 oob: bool = false,
780813 peek: bool = false,
......@@ -917,6 +950,7 @@ pub const Socket = struct {
917950 else => std.posix.fd_t,
918951 };
919952
953 /// Leaves `address` in a valid state.
920954 pub fn close(s: *Socket, io: Io) void {
921955 io.vtable.netClose(io.userdata, s.handle);
922956 s.handle = undefined;
......@@ -1156,7 +1190,7 @@ pub const Server = struct {
11561190
11571191 /// Blocks until a client connects to the server.
11581192 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);
11601194 }
11611195};
11621196
lib/std/Io/net/test.zig+5-21
......@@ -290,15 +290,16 @@ test "listen on a unix socket, send bytes, receive bytes" {
290290 const socket_path = try generateFileName("socket.unix");
291291 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);
294294 defer std.fs.cwd().deleteFile(socket_path) catch {};
295295
296 var server = try socket_addr.listen(io, .{});
297 defer server.deinit(io);
296 var server = try socket_addr.listen(io);
297 defer server.socket.close(io);
298298
299299 const S = struct {
300300 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);
302303 defer stream.close(io);
303304
304305 var stream_writer = stream.writer(io, &.{});
......@@ -319,23 +320,6 @@ test "listen on a unix socket, send bytes, receive bytes" {
319320 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
320321}
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
339323fn generateFileName(base_name: []const u8) ![]const u8 {
340324 const random_bytes_count = 12;
341325 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);