authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-01 23:28:24+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-01 23:28:24+02:00
logeb20ecded85ca72256d5dacc52c39226edfe0889
tree8a64f13caa04c18544fecb9bae8ffb6e77f2ad91
parenta0501add57ff187193ddfa3c79b3d26cc96fa1d8
parentf19259c85b9fc3678d61f8c0d306dc8513b75a39

Merge pull request 'std.Io.Operation: improve handling of control data' (#37035) from control-data into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37035

7 files changed, 339 insertions(+), 22 deletions(-)

lib/std/Io.zig+3-1
...@@ -430,6 +430,7 @@ pub const Operation = union(enum) {...@@ -430,6 +430,7 @@ pub const Operation = union(enum) {
430 pub const NetRead = struct {430 pub const NetRead = struct {
431 socket_handle: net.Socket.Handle,431 socket_handle: net.Socket.Handle,
432 data: [][]u8,432 data: [][]u8,
433 control: []u8 = &.{},
433434
434 pub const Error = error{435 pub const Error = error{
435 SystemResources,436 SystemResources,
...@@ -443,7 +444,7 @@ pub const Operation = union(enum) {...@@ -443,7 +444,7 @@ pub const Operation = union(enum) {
443 ConnectionTimedOut,444 ConnectionTimedOut,
444 } || Io.UnexpectedError;445 } || Io.UnexpectedError;
445446
446 pub const Result = Error!usize;447 pub const Result = Error!net.Stream.ReadResult;
447 };448 };
448449
449 pub const NetWrite = struct {450 pub const NetWrite = struct {
...@@ -451,6 +452,7 @@ pub const Operation = union(enum) {...@@ -451,6 +452,7 @@ pub const Operation = union(enum) {
451 header: []const u8 = &.{},452 header: []const u8 = &.{},
452 data: []const []const u8,453 data: []const []const u8,
453 splat: usize = 1,454 splat: usize = 1,
455 control: []const u8 = &.{},
454456
455 pub const Error = error{457 pub const Error = error{
456 /// Another TCP Fast Open is already in progress.458 /// Another TCP Fast Open is already in progress.
lib/std/Io/Threaded.zig+37-15
...@@ -2583,16 +2583,16 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper...@@ -2583,16 +2583,16 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper
2583 },2583 },
2584 },2584 },
2585 .net_read => |o| return .{2585 .net_read => |o| return .{
2586 .net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) {2586 .net_read = netRead(o.socket_handle, o.data, o.control) catch |err| switch (err) {
2587 error.Canceled => |e| return e,2587 error.Canceled => |e| return e,
2588 else => |e| e,2588 else => |e| e,
2589 },2589 },
2590 },2590 },
2591 .net_write => |o| return .{2591 .net_write => |o| return .{
2592 .net_write = (if (is_windows)2592 .net_write = (if (is_windows)
2593 netWriteWindows(o.socket_handle, o.header, o.data, o.splat)2593 netWriteWindows(o.socket_handle, o.header, o.data, o.splat, o.control)
2594 else2594 else
2595 netWritePosix(o.socket_handle, o.header, o.data, o.splat)) catch |err| switch (err) {2595 netWritePosix(o.socket_handle, o.header, o.data, o.splat, o.control)) catch |err| switch (err) {
2596 error.Canceled => |e| return e,2596 error.Canceled => |e| return e,
2597 else => |e| e,2597 else => |e| e,
2598 },2598 },
...@@ -3293,7 +3293,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io...@@ -3293,7 +3293,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io
3293 // TODO integrate with overlapped I/O or equivalent to avoid this error3293 // TODO integrate with overlapped I/O or equivalent to avoid this error
3294 if (concurrency) return error.ConcurrencyUnavailable;3294 if (concurrency) return error.ConcurrencyUnavailable;
3295 batchCompleteBlockingWindows(b, operation_userdata, .{3295 batchCompleteBlockingWindows(b, operation_userdata, .{
3296 .net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) {3296 .net_read = netRead(o.socket_handle, o.data, o.control) catch |err| switch (err) {
3297 error.Canceled => |e| return e,3297 error.Canceled => |e| return e,
3298 else => |e| e,3298 else => |e| e,
3299 },3299 },
...@@ -3303,7 +3303,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io...@@ -3303,7 +3303,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io
3303 // TODO integrate with overlapped I/O or equivalent to avoid this error3303 // TODO integrate with overlapped I/O or equivalent to avoid this error
3304 if (concurrency) return error.ConcurrencyUnavailable;3304 if (concurrency) return error.ConcurrencyUnavailable;
3305 batchCompleteBlockingWindows(b, operation_userdata, .{3305 batchCompleteBlockingWindows(b, operation_userdata, .{
3306 .net_write = netWriteWindows(o.socket_handle, o.header, o.data, o.splat) catch |err| switch (err) {3306 .net_write = netWriteWindows(o.socket_handle, o.header, o.data, o.splat, o.control) catch |err| switch (err) {
3307 error.Canceled => |e| return e,3307 error.Canceled => |e| return e,
3308 else => |e| e,3308 else => |e| e,
3309 },3309 },
...@@ -12883,14 +12883,14 @@ fn deferAcceptAfd(t: *Threaded, listen_handle: net.Socket.Handle, info: windows....@@ -12883,14 +12883,14 @@ fn deferAcceptAfd(t: *Threaded, listen_handle: net.Socket.Handle, info: windows.
12883 }12883 }
12884}12884}
1288512885
12886fn netRead(socket_handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {12886fn netRead(socket_handle: net.Socket.Handle, data: [][]u8, control: []u8) net.Stream.Reader.Error!net.Stream.ReadResult {
12887 if (!have_networking) return error.NetworkDown;12887 if (!have_networking) return error.NetworkDown;
1288812888
12889 if (is_windows) return netReadWindows(socket_handle, data);12889 if (is_windows) return .{ .data_len = try netReadWindows(socket_handle, data) };
12890 return netReadPosix(socket_handle, data);12890 return netReadPosix(socket_handle, data, control);
12891}12891}
1289212892
12893fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {12893fn netReadPosix(fd: net.Socket.Handle, data: [][]u8, control: []u8) net.Stream.Reader.Error!net.Stream.ReadResult {
12894 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;12894 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
12895 var i: usize = 0;12895 var i: usize = 0;
12896 for (data) |buf| {12896 for (data) |buf| {
...@@ -12936,13 +12936,31 @@ fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usi...@@ -12936,13 +12936,31 @@ fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usi
12936 }12936 }
12937 }12937 }
1293812938
12939 var msg: posix.msghdr = .{
12940 .name = null,
12941 .namelen = 0,
12942 .iov = dest.ptr,
12943 .iovlen = @intCast(dest.len),
12944 .control = control.ptr,
12945 .controllen = @intCast(control.len),
12946 .flags = 0,
12947 };
12948
12949 const flags: u32 =
12950 @as(u32, if (@hasDecl(posix.MSG, "CMSG_CLOEXEC")) posix.MSG.CMSG_CLOEXEC else 0);
12951
12939 const syscall: Syscall = try .start();12952 const syscall: Syscall = try .start();
12940 while (true) {12953 while (true) {
12941 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));12954 const rc = posix.system.recvmsg(fd, &msg, flags);
12955 const rc_control = msg.controllen;
12942 switch (posix.errno(rc)) {12956 switch (posix.errno(rc)) {
12943 .SUCCESS => {12957 .SUCCESS => {
12944 syscall.finish();12958 syscall.finish();
12945 return @intCast(rc);12959 return .{
12960 .data_len = @intCast(rc),
12961 .control_len = @intCast(rc_control),
12962 .control_truncated = (msg.flags & posix.MSG.CTRUNC) != 0,
12963 };
12946 },12964 },
12947 .INTR => {12965 .INTR => {
12948 try syscall.checkCancel();12966 try syscall.checkCancel();
...@@ -13229,7 +13247,6 @@ fn netReceivePosix(...@@ -13229,7 +13247,6 @@ fn netReceivePosix(
13229 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |13247 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |
13230 @as(u32, if (flags.peek) posix.MSG.PEEK else 0) |13248 @as(u32, if (flags.peek) posix.MSG.PEEK else 0) |
13231 @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) |13249 @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) |
13232 posix.MSG.NOSIGNAL |
13233 @as(u32, if (nonblocking) posix.MSG.DONTWAIT else 0);13250 @as(u32, if (nonblocking) posix.MSG.DONTWAIT else 0);
1323413251
13235 var storage: PosixAddress = undefined;13252 var storage: PosixAddress = undefined;
...@@ -13241,7 +13258,7 @@ fn netReceivePosix(...@@ -13241,7 +13258,7 @@ fn netReceivePosix(
13241 .iovlen = 1,13258 .iovlen = 1,
13242 .control = message.control.ptr,13259 .control = message.control.ptr,
13243 .controllen = @intCast(message.control.len),13260 .controllen = @intCast(message.control.len),
13244 .flags = undefined,13261 .flags = 0,
13245 };13262 };
1324613263
13247 const syscall = try Syscall.start();13264 const syscall = try Syscall.start();
...@@ -13362,6 +13379,7 @@ fn netWritePosix(...@@ -13362,6 +13379,7 @@ fn netWritePosix(
13362 header: []const u8,13379 header: []const u8,
13363 data: []const []const u8,13380 data: []const []const u8,
13364 splat: usize,13381 splat: usize,
13382 control: []const u8,
13365) net.Stream.Writer.Error!usize {13383) net.Stream.Writer.Error!usize {
13366 if (!have_networking) return error.NetworkDown;13384 if (!have_networking) return error.NetworkDown;
1336713385
...@@ -13371,8 +13389,8 @@ fn netWritePosix(...@@ -13371,8 +13389,8 @@ fn netWritePosix(
13371 .namelen = 0,13389 .namelen = 0,
13372 .iov = &iovecs,13390 .iov = &iovecs,
13373 .iovlen = 0,13391 .iovlen = 0,
13374 .control = null,13392 .control = if (control.len == 0) null else @constCast(control.ptr),
13375 .controllen = 0,13393 .controllen = @intCast(control.len),
13376 .flags = 0,13394 .flags = 0,
13377 };13395 };
13378 addBuf(&iovecs, &msg.iovlen, header);13396 addBuf(&iovecs, &msg.iovlen, header);
...@@ -13454,9 +13472,13 @@ fn netWriteWindows(...@@ -13454,9 +13472,13 @@ fn netWriteWindows(
13454 header: []const u8,13472 header: []const u8,
13455 data: []const []const u8,13473 data: []const []const u8,
13456 splat: usize,13474 splat: usize,
13475 control: []const u8,
13457) net.Stream.Writer.Error!usize {13476) net.Stream.Writer.Error!usize {
13458 if (!have_networking) return error.NetworkDown;13477 if (!have_networking) return error.NetworkDown;
1345913478
13479 // Windows doesn't have the concept of control/ancillary data.
13480 _ = control;
13481
13460 var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"const") = undefined;13482 var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"const") = undefined;
13461 var len: u32 = 0;13483 var len: u32 = 0;
13462 addAfdBuf(.@"const", &iovecs, &len, header);13484 addAfdBuf(.@"const", &iovecs, &len, header);
lib/std/Io/net.zig+125-6
...@@ -1269,12 +1269,36 @@ pub const Stream = struct {...@@ -1269,12 +1269,36 @@ pub const Stream = struct {
12691269
1270 const max_iovecs_len = 8;1270 const max_iovecs_len = 8;
12711271
1272 pub const ReadResult = struct {
1273 data_len: usize,
1274 control_len: usize = 0,
1275 /// Whether only some of the control data was received.
1276 ///
1277 /// When control data is truncated, the extra data essentially
1278 /// disappears into the ether. This is indicative of a design problem,
1279 /// such as the control buffer being too small.
1280 control_truncated: bool = false,
1281 };
1282
1272 /// This is a low-level API that calls the `Io` interface function directly.1283 /// This is a low-level API that calls the `Io` interface function directly.
1273 /// For a higher level API, see `reader`.1284 /// For a higher level API, see `reader`.
1274 pub fn read(s: *const Stream, io: Io, data: [][]u8) Reader.Error!usize {1285 pub fn read(s: *const Stream, io: Io, data: [][]u8) Reader.Error!usize {
1275 return (try io.operate(.{ .net_read = .{1286 const rc, _ = try (try io.operate(.{ .net_read = .{
1287 .socket_handle = s.socket.handle,
1288 .data = data,
1289 } })).net_read;
1290 return rc;
1291 }
1292
1293 /// Read with control data.
1294 ///
1295 /// This is a low-level API that calls the `Io` interface function directly.
1296 /// For a higher level API, see `reader`.
1297 pub fn readWithControl(s: *const Stream, io: Io, data: [][]u8, control: []u8) Reader.Error!ReadResult {
1298 return try (try io.operate(.{ .net_read = .{
1276 .socket_handle = s.socket.handle,1299 .socket_handle = s.socket.handle,
1277 .data = data,1300 .data = data,
1301 .control = control,
1278 } })).net_read;1302 } })).net_read;
1279 }1303 }
12801304
...@@ -1289,12 +1313,25 @@ pub const Stream = struct {...@@ -1289,12 +1313,25 @@ pub const Stream = struct {
1289 pub const Reader = struct {1313 pub const Reader = struct {
1290 io: Io,1314 io: Io,
1291 interface: Io.Reader,1315 interface: Io.Reader,
1316 control_buffer: []align(cmsg_align) u8,
1317 control_len: usize,
1318 control_truncated: bool,
1292 stream: Stream,1319 stream: Stream,
1293 err: ?Error,1320 err: ?Error,
12941321
1295 pub const Error = Io.Operation.NetRead.Error || Io.Cancelable;1322 pub const Error = Io.Operation.NetRead.Error || Io.Cancelable;
12961323
1297 pub fn init(stream: Stream, io: Io, buffer: []u8) Reader {1324 pub fn init(stream: Stream, io: Io, buffer: []u8) Reader {
1325 return initWithControl(stream, io, buffer, &.{});
1326 }
1327
1328 /// Same as `init`, but also provides a buffer for storing control data.
1329 pub fn initWithControl(
1330 stream: Stream,
1331 io: Io,
1332 buffer: []u8,
1333 control_buffer: []align(cmsg_align) u8,
1334 ) Reader {
1298 return .{1335 return .{
1299 .io = io,1336 .io = io,
1300 .interface = .{1337 .interface = .{
...@@ -1306,6 +1343,9 @@ pub const Stream = struct {...@@ -1306,6 +1343,9 @@ pub const Stream = struct {
1306 .seek = 0,1343 .seek = 0,
1307 .end = 0,1344 .end = 0,
1308 },1345 },
1346 .control_buffer = control_buffer,
1347 .control_len = 0,
1348 .control_truncated = false,
1309 .stream = stream,1349 .stream = stream,
1310 .err = null,1350 .err = null,
1311 };1351 };
...@@ -1326,24 +1366,44 @@ pub const Stream = struct {...@@ -1326,24 +1366,44 @@ pub const Stream = struct {
1326 const dest_n, const data_size = try io_r.writableVector(&iovecs_buffer, data);1366 const dest_n, const data_size = try io_r.writableVector(&iovecs_buffer, data);
1327 const dest = iovecs_buffer[0..dest_n];1367 const dest = iovecs_buffer[0..dest_n];
1328 assert(dest[0].len > 0);1368 assert(dest[0].len > 0);
1329 const n = r.stream.read(io, dest) catch |err| {1369 const result = r.stream.readWithControl(io, dest, r.control_buffer[r.control_len..]) catch |err| {
1330 r.err = err;1370 r.err = err;
1331 return error.ReadFailed;1371 return error.ReadFailed;
1332 };1372 };
1333 if (n == 0) {1373 r.control_len += result.control_len;
1374 r.control_truncated = r.control_truncated or result.control_truncated;
1375 if (result.data_len == 0) {
1334 return error.EndOfStream;1376 return error.EndOfStream;
1335 }1377 }
1336 if (n > data_size) {1378 if (result.data_len > data_size) {
1337 r.interface.end += n - data_size;1379 r.interface.end += result.data_len - data_size;
1338 return data_size;1380 return data_size;
1339 }1381 }
1340 return n;1382 return result.data_len;
1383 }
1384
1385 /// Access raw buffered control data.
1386 pub fn controlSlice(r: *const Reader) []align(cmsg_align) u8 {
1387 return r.control_buffer[0..r.control_len];
1388 }
1389
1390 /// Iterate over buffered control messages.
1391 pub fn controlIterator(r: *const Reader) cmsg.Iterator {
1392 return .{ .control = r.controlSlice() };
1393 }
1394
1395 /// Clear buffered control data.
1396 pub fn clearControl(r: *Reader) void {
1397 r.control_len = 0;
1398 r.control_truncated = false;
1341 }1399 }
1342 };1400 };
13431401
1344 pub const Writer = struct {1402 pub const Writer = struct {
1345 io: Io,1403 io: Io,
1346 interface: Io.Writer,1404 interface: Io.Writer,
1405 /// Will be sent on next drain, then set back to an empty slice.
1406 control: []const u8 = &.{},
1347 stream: Stream,1407 stream: Stream,
1348 err: ?Error = null,1408 err: ?Error = null,
1349 write_file_err: ?WriteFileError = null,1409 write_file_err: ?WriteFileError = null,
...@@ -1386,6 +1446,7 @@ pub const Stream = struct {...@@ -1386,6 +1446,7 @@ pub const Stream = struct {
1386 .header = buffered,1446 .header = buffered,
1387 .data = data,1447 .data = data,
1388 .splat = splat,1448 .splat = splat,
1449 .control = w.control,
1389 } }) catch |err| {1450 } }) catch |err| {
1390 w.err = err;1451 w.err = err;
1391 return error.WriteFailed;1452 return error.WriteFailed;
...@@ -1394,6 +1455,7 @@ pub const Stream = struct {...@@ -1394,6 +1455,7 @@ pub const Stream = struct {
1394 w.err = err;1455 w.err = err;
1395 return error.WriteFailed;1456 return error.WriteFailed;
1396 };1457 };
1458 w.control = &.{};
1397 return io_w.consume(n);1459 return io_w.consume(n);
1398 }1460 }
13991461
...@@ -1424,11 +1486,68 @@ pub const Stream = struct {...@@ -1424,11 +1486,68 @@ pub const Stream = struct {
1424 return .init(stream, io, buffer);1486 return .init(stream, io, buffer);
1425 }1487 }
14261488
1489 /// Same as `reader`, but also provides a buffer for storing control data.
1490 pub fn readerWithControl(
1491 stream: Stream,
1492 io: Io,
1493 buffer: []u8,
1494 control_buffer: []align(cmsg_align) u8,
1495 ) Reader {
1496 return .initWithControl(stream, io, buffer, control_buffer);
1497 }
1498
1427 pub fn writer(stream: Stream, io: Io, buffer: []u8) Writer {1499 pub fn writer(stream: Stream, io: Io, buffer: []u8) Writer {
1428 return .init(stream, io, buffer);1500 return .init(stream, io, buffer);
1429 }1501 }
1430};1502};
14311503
1504pub const cmsg_align = if (@TypeOf(std.posix.cmsg_align) == void) 1 else std.posix.cmsg_align;
1505/// Utility Functions for interacting with POSIX socket control messages.
1506/// See also https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html
1507pub const cmsg = struct {
1508 const cmsghdr = std.posix.cmsghdr;
1509
1510 /// Equivalent to `CMSG_SPACE` in C.
1511 pub fn space(data_len: usize) usize {
1512 return std.mem.alignForward(usize, @sizeOf(cmsghdr), cmsg_align) +
1513 std.mem.alignForward(usize, data_len, cmsg_align);
1514 }
1515
1516 /// Equivalent to `CMSG_LEN` in C.
1517 pub fn len(data_len: @FieldType(cmsghdr, "len")) @TypeOf(data_len) {
1518 return std.mem.alignForward(@TypeOf(data_len), @sizeOf(cmsghdr), cmsg_align) + data_len;
1519 }
1520
1521 /// Equivalent to `CMSG_DATA` in C.
1522 pub fn data(header: *align(cmsg_align) cmsghdr) []align(cmsg_align) u8 {
1523 const bytes: [*]u8 = @ptrCast(header);
1524 return @alignCast(bytes[0..header.len][std.mem.alignForward(usize, @sizeOf(cmsghdr), cmsg_align)..]);
1525 }
1526
1527 /// Equivalent to `CMSG_FIRSTHDR` and `CMSG_NXTHDR` in C.
1528 pub const Iterator = struct {
1529 control: []align(cmsg_align) u8,
1530
1531 pub const Message = struct {
1532 header: *cmsghdr,
1533 data: []align(cmsg_align) u8,
1534 };
1535
1536 pub fn next(it: *Iterator) ?Message {
1537 if (it.control.len < @sizeOf(cmsghdr)) return null;
1538 const header: *align(cmsg_align) cmsghdr = @ptrCast(it.control.ptr);
1539 if (it.control.len < header.len) return null;
1540 const next_header = std.mem.alignForward(usize, header.len, cmsg_align);
1541 if (it.control.len < next_header) {
1542 it.control = &.{};
1543 } else {
1544 it.control = @alignCast(it.control[next_header..]);
1545 }
1546 return .{ .header = header, .data = data(header) };
1547 }
1548 };
1549};
1550
1432pub const Server = struct {1551pub const Server = struct {
1433 socket: Socket,1552 socket: Socket,
1434 options: AcceptOptions,1553 options: AcceptOptions,
lib/std/Io/net/test.zig+94
...@@ -4,6 +4,7 @@ const std = @import("std");...@@ -4,6 +4,7 @@ const std = @import("std");
4const Io = std.Io;4const Io = std.Io;
5const net = std.Io.net;5const net = std.Io.net;
6const mem = std.mem;6const mem = std.mem;
7const posix = std.posix;
7const testing = std.testing;8const testing = std.testing;
8const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
910
...@@ -295,6 +296,99 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -295,6 +296,99 @@ test "listen on a unix socket, send bytes, receive bytes" {
295 try client_task.await(io);296 try client_task.await(io);
296}297}
297298
299test "listen on a unix socket, pass file descriptor" {
300 if (builtin.os.tag == .windows or builtin.os.tag == .wasi) {
301 // Windows and WASI don't have the concept of control/ancillary data.
302 return error.SkipZigTest;
303 }
304
305 const io = testing.io;
306 const gpa = testing.allocator;
307
308 const socket_path = try generateFileName(gpa, io, "socket.unix");
309 defer gpa.free(socket_path);
310 const temp_file_path = try generateFileName(gpa, io, "temp_file");
311 defer gpa.free(temp_file_path);
312
313 defer Io.Dir.cwd().deleteFile(io, socket_path) catch {};
314 defer Io.Dir.cwd().deleteFile(io, temp_file_path) catch {};
315
316 const socket_addr = try net.UnixAddress.init(socket_path);
317
318 var server = socket_addr.listen(io, .{}) catch |err| switch (err) {
319 error.AddressFamilyUnsupported => return error.SkipZigTest,
320 error.NetworkDown => return error.SkipZigTest,
321 else => |e| return e,
322 };
323 defer server.socket.close(io);
324
325 const S = struct {
326 fn clientFn(path: []const u8, file_path: []const u8) !void {
327 const temp_file = try Io.Dir.cwd().createFile(io, file_path, .{});
328 defer temp_file.close(io);
329
330 const server_path: net.UnixAddress = try .init(path);
331 var stream = try server_path.connect(io);
332 defer stream.close(io);
333
334 var cmsg_buf: [net.cmsg.space(@sizeOf(posix.fd_t))]u8 align(Io.net.cmsg_align) = @splat(0);
335
336 const header: *align(Io.net.cmsg_align) posix.cmsghdr = @ptrCast(&cmsg_buf);
337 header.* = .{
338 .len = net.cmsg.len(@sizeOf(posix.fd_t)),
339 .level = posix.SOL.SOCKET,
340 .type = posix.SCM.RIGHTS,
341 };
342
343 const fds: []posix.fd_t = @ptrCast(net.cmsg.data(header));
344 fds[0] = temp_file.handle;
345
346 var stream_writer = stream.writer(io, &.{});
347 stream_writer.control = &cmsg_buf;
348 try stream_writer.interface.writeAll("Hello world!");
349 }
350 };
351
352 var client_task = io.concurrent(S.clientFn, .{ socket_path, temp_file_path }) catch |err| switch (err) {
353 error.ConcurrencyUnavailable => return error.SkipZigTest,
354 };
355 defer client_task.cancel(io) catch {};
356
357 var stream = try server.accept(io);
358 defer stream.close(io);
359
360 var control_buf: [32]u8 align(Io.net.cmsg_align) = undefined;
361 var buf: [16]u8 = undefined;
362 var stream_reader = stream.readerWithControl(io, &.{}, &control_buf);
363
364 const n = try stream_reader.interface.readSliceShort(&buf);
365
366 try testing.expect(!stream_reader.control_truncated); // The control buffer should be big enough to receive the entire control message
367
368 try testing.expectEqual(12, n);
369 try testing.expectEqualStrings("Hello world!", buf[0..n]);
370
371 var it = stream_reader.controlIterator();
372 const control = it.next() orelse return error.TestUnexpectedResult;
373 try testing.expectEqual(null, it.next());
374
375 try testing.expectEqual(net.cmsg.len(@sizeOf(posix.fd_t)), control.header.len);
376 try testing.expectEqual(posix.SOL.SOCKET, control.header.level);
377 try testing.expectEqual(posix.SCM.RIGHTS, control.header.type);
378
379 const fds: []posix.fd_t = @ptrCast(control.data);
380
381 const temp_file: Io.File = .{
382 .handle = fds[0],
383 .flags = .{ .nonblocking = false },
384 };
385 defer temp_file.close(io);
386 var temp_file_writer = temp_file.writer(io, &.{});
387 try temp_file_writer.interface.writeAll("if this is a real file descriptor, this shouldn't fail");
388
389 try client_task.await(io);
390}
391
298fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 {392fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 {
299 const random_bytes_count = 12;393 const random_bytes_count = 12;
300 const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count);394 const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count);
lib/std/c.zig+75
...@@ -3373,6 +3373,19 @@ pub const Sigaction = switch (native_os) {...@@ -3373,6 +3373,19 @@ pub const Sigaction = switch (native_os) {
3373 restorer: ?*const fn () callconv(.c) void = null,3373 restorer: ?*const fn () callconv(.c) void = null,
3374 mask: sigset_t,3374 mask: sigset_t,
3375 } else common_linux_Sigaction,3375 } else common_linux_Sigaction,
3376 .sparc64 => if (builtin.abi == .gnu) extern struct {
3377 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3378 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3379
3380 handler: extern union {
3381 handler: ?handler_fn,
3382 sigaction: ?sigaction_fn,
3383 },
3384 mask: sigset_t,
3385 __glibc_reserved0: c_int = 0,
3386 flags: c_uint,
3387 restorer: ?*const fn () callconv(.c) void = null,
3388 } else common_linux_Sigaction,
3376 else => common_linux_Sigaction,3389 else => common_linux_Sigaction,
3377 },3390 },
3378 .emscripten => emscripten.Sigaction,3391 .emscripten => emscripten.Sigaction,
...@@ -4432,6 +4445,68 @@ const posix_cmsghdr = extern struct {...@@ -4432,6 +4445,68 @@ const posix_cmsghdr = extern struct {
4432 type: c_int,4445 type: c_int,
4433};4446};
44344447
4448// Alignment used by POSIX specified CMSG_* Macros
4449// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html
4450pub const cmsg_align = switch (native_os) {
4451 .linux => linux.cmsg_align,
4452 // https://github.com/emscripten-core/emscripten/blob/96371ed7888fc78c040179f4d4faa82a6a07a116/system/lib/libc/musl/include/sys/socket.h#L362
4453 .emscripten => @sizeOf(usize),
4454 // https://github.com/freebsd/freebsd-src/blob/b197d2abcb6895d78bc9df8404e374397aa44748/sys/sys/socket.h#L584
4455 // https://github.com/freebsd/freebsd-src/blob/d2018cedb414ef17e77b7fc2fa19fd81328da46b/sys/sys/_align.h#L30
4456 .freebsd => @sizeOf(*anyopaque),
4457 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d1f4fb943c73e2b61ddabff6aa48ce7551db72f4/sys/sys/socket.h#L441
4458 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d1f4fb943c73e2b61ddabff6aa48ce7551db72f4/sys/cpu/x86_64/include/alignbytes.h#L35
4459 .dragonfly => switch (builtin.cpu.arch) {
4460 .x86_64 => @sizeOf(c_long),
4461 else => {},
4462 },
4463 // https://github.com/NetBSD/src/blob/ba8e1774fd9c0c26ecca461c07bc95d9ebb69579/sys/sys/socket.h#L544
4464 .netbsd => switch (builtin.cpu.arch) {
4465 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/arm/include/cdefs.h#L63
4466 .aarch64, .aarch64_be => @sizeOf(i128),
4467 .arm, .armeb => @sizeOf(c_longlong),
4468 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/i386/include/cdefs.h#L9
4469 .x86 => @sizeOf(c_int),
4470 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/amd64/include/cdefs.h#L6
4471 .x86_64 => @sizeOf(c_long),
4472 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/mips/include/cdefs.h#L74
4473 .mips, .mips64, .mipsel, .mips64el => 8,
4474 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/sparc/include/cdefs.h#L8
4475 .sparc64 => 16,
4476 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/sparc/include/cdefs.h#L10
4477 .sparc => 8,
4478 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/alpha/include/cdefs.h#L6
4479 .alpha => 8,
4480 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/m68k/include/cdefs.h#L6
4481 .m68k => @sizeOf(c_int),
4482 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/hppa/include/cdefs.h#L12
4483 .hppa => 8,
4484 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/riscv/include/cdefs.h#L5
4485 .riscv32, .riscv32be, .riscv64, .riscv64be => @as(comptime_int, builtin.target.cMaxIntAlignment()),
4486 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/powerpc/include/cdefs.h#L6
4487 .powerpc, .powerpcle, .powerpc64, .powerpc64le => @sizeOf(f64),
4488 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/or1k/include/cdefs.h#L6
4489 .or1k => @as(comptime_int, builtin.target.cMaxIntAlignment()),
4490 else => {},
4491 },
4492 // https://github.com/openbsd/src/blob/4a7ecdb29021f26ee0ef397afdd2931e20e63071/sys/sys/socket.h#L564
4493 // https://github.com/openbsd/src/blob/4a7ecdb29021f26ee0ef397afdd2931e20e63071/sys/arch/amd64/include/_types.h#L50
4494 .openbsd => @sizeOf(c_long),
4495 // https://github.com/illumos/illumos-gate/blob/c1e950d976948602134f0e0e5dadf3d48a7ee996/usr/src/uts/common/sys/socket.h#L469
4496 .illumos => 4,
4497 // https://github.com/haiku/haiku/blob/b54f586058fd6623645512e4631468cede9933b9/headers/posix/sys/socket.h#L152
4498 // https://github.com/haiku/haiku/blob/6059d64cb7cd145f0f1eb5b89c383f71e4b6bebf/headers/posix/sys/param.h#L28
4499 .haiku => @sizeOf(c_long),
4500 // https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1077
4501 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => 4,
4502 else => {},
4503};
4504comptime {
4505 if (@TypeOf(cmsg_align) != void and @TypeOf(cmsghdr) != void) {
4506 assert(cmsg_align >= @alignOf(cmsghdr));
4507 }
4508}
4509
4435pub const nfds_t = switch (native_os) {4510pub const nfds_t = switch (native_os) {
4436 .linux => linux.nfds_t,4511 .linux => linux.nfds_t,
4437 .emscripten => emscripten.nfds_t,4512 .emscripten => emscripten.nfds_t,
lib/std/os/linux.zig+3
...@@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct {...@@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct {
11244 type: i32,11244 type: i32,
11245};11245};
1124611246
11247// https://github.com/torvalds/linux/blob/72d3fcf802c45d00b300f25b848a93c3a2bd7c7e/include/linux/socket.h#L133
11248pub const cmsg_align = @sizeOf(c_long);
11249
11247pub const riscv_hwprobe = extern struct {11250pub const riscv_hwprobe = extern struct {
11248 key: i64,11251 key: i64,
11249 value: u64,11252 value: u64,
lib/std/posix.zig+2
...@@ -150,6 +150,8 @@ pub const blkcnt_t = system.blkcnt_t;...@@ -150,6 +150,8 @@ pub const blkcnt_t = system.blkcnt_t;
150pub const blksize_t = system.blksize_t;150pub const blksize_t = system.blksize_t;
151pub const clock_t = system.clock_t;151pub const clock_t = system.clock_t;
152pub const clockid_t = system.clockid_t;152pub const clockid_t = system.clockid_t;
153pub const cmsghdr = system.cmsghdr;
154pub const cmsg_align = system.cmsg_align;
153pub const timerfd_clockid_t = system.timerfd_clockid_t;155pub const timerfd_clockid_t = system.timerfd_clockid_t;
154pub const cpu_set_t = system.cpu_set_t;156pub const cpu_set_t = system.cpu_set_t;
155pub const dev_t = system.dev_t;157pub const dev_t = system.dev_t;