| author | |
| committer | |
| log | eb20ecded85ca72256d5dacc52c39226edfe0889 |
| tree | 8a64f13caa04c18544fecb9bae8ffb6e77f2ad91 |
| parent | a0501add57ff187193ddfa3c79b3d26cc96fa1d8 |
| parent | f19259c85b9fc3678d61f8c0d306dc8513b75a39 |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/370357 files changed, 339 insertions(+), 22 deletions(-)
lib/std/Io.zig+3-1| ... | ... | @@ -430,6 +430,7 @@ pub const Operation = union(enum) { |
| 430 | 430 | pub const NetRead = struct { |
| 431 | 431 | socket_handle: net.Socket.Handle, |
| 432 | 432 | data: [][]u8, |
| 433 | control: []u8 = &.{}, | |
| 433 | 434 | |
| 434 | 435 | pub const Error = error{ |
| 435 | 436 | SystemResources, |
| ... | ... | @@ -443,7 +444,7 @@ pub const Operation = union(enum) { |
| 443 | 444 | ConnectionTimedOut, |
| 444 | 445 | } || Io.UnexpectedError; |
| 445 | 446 | |
| 446 | pub const Result = Error!usize; | |
| 447 | pub const Result = Error!net.Stream.ReadResult; | |
| 447 | 448 | }; |
| 448 | 449 | |
| 449 | 450 | pub const NetWrite = struct { |
| ... | ... | @@ -451,6 +452,7 @@ pub const Operation = union(enum) { |
| 451 | 452 | header: []const u8 = &.{}, |
| 452 | 453 | data: []const []const u8, |
| 453 | 454 | splat: usize = 1, |
| 455 | control: []const u8 = &.{}, | |
| 454 | 456 | |
| 455 | 457 | pub const Error = error{ |
| 456 | 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 | 2583 | }, |
| 2584 | 2584 | }, |
| 2585 | 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 | 2587 | error.Canceled => |e| return e, |
| 2588 | 2588 | else => |e| e, |
| 2589 | 2589 | }, |
| 2590 | 2590 | }, |
| 2591 | 2591 | .net_write => |o| return .{ |
| 2592 | 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 | 2594 | 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 | 2596 | error.Canceled => |e| return e, |
| 2597 | 2597 | else => |e| e, |
| 2598 | 2598 | }, |
| ... | ... | @@ -3293,7 +3293,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io |
| 3293 | 3293 | // TODO integrate with overlapped I/O or equivalent to avoid this error |
| 3294 | 3294 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3295 | 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 | 3297 | error.Canceled => |e| return e, |
| 3298 | 3298 | else => |e| e, |
| 3299 | 3299 | }, |
| ... | ... | @@ -3303,7 +3303,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io |
| 3303 | 3303 | // TODO integrate with overlapped I/O or equivalent to avoid this error |
| 3304 | 3304 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3305 | 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 | 3307 | error.Canceled => |e| return e, |
| 3308 | 3308 | else => |e| e, |
| 3309 | 3309 | }, |
| ... | ... | @@ -12883,14 +12883,14 @@ fn deferAcceptAfd(t: *Threaded, listen_handle: net.Socket.Handle, info: windows. |
| 12883 | 12883 | } |
| 12884 | 12884 | } |
| 12885 | 12885 | |
| 12886 | fn netRead(socket_handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { | |
| 12886 | fn netRead(socket_handle: net.Socket.Handle, data: [][]u8, control: []u8) net.Stream.Reader.Error!net.Stream.ReadResult { | |
| 12887 | 12887 | if (!have_networking) return error.NetworkDown; |
| 12888 | 12888 | |
| 12889 | if (is_windows) return netReadWindows(socket_handle, data); | |
| 12890 | return netReadPosix(socket_handle, data); | |
| 12889 | if (is_windows) return .{ .data_len = try netReadWindows(socket_handle, data) }; | |
| 12890 | return netReadPosix(socket_handle, data, control); | |
| 12891 | 12891 | } |
| 12892 | 12892 | |
| 12893 | fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { | |
| 12893 | fn netReadPosix(fd: net.Socket.Handle, data: [][]u8, control: []u8) net.Stream.Reader.Error!net.Stream.ReadResult { | |
| 12894 | 12894 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 12895 | 12895 | var i: usize = 0; |
| 12896 | 12896 | for (data) |buf| { |
| ... | ... | @@ -12936,13 +12936,31 @@ fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usi |
| 12936 | 12936 | } |
| 12937 | 12937 | } |
| 12938 | 12938 | |
| 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 | 12952 | const syscall: Syscall = try .start(); |
| 12940 | 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 | 12956 | switch (posix.errno(rc)) { |
| 12943 | 12957 | .SUCCESS => { |
| 12944 | 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 | 12965 | .INTR => { |
| 12948 | 12966 | try syscall.checkCancel(); |
| ... | ... | @@ -13229,7 +13247,6 @@ fn netReceivePosix( |
| 13229 | 13247 | @as(u32, if (flags.oob) posix.MSG.OOB else 0) | |
| 13230 | 13248 | @as(u32, if (flags.peek) posix.MSG.PEEK else 0) | |
| 13231 | 13249 | @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) | |
| 13232 | posix.MSG.NOSIGNAL | | |
| 13233 | 13250 | @as(u32, if (nonblocking) posix.MSG.DONTWAIT else 0); |
| 13234 | 13251 | |
| 13235 | 13252 | var storage: PosixAddress = undefined; |
| ... | ... | @@ -13241,7 +13258,7 @@ fn netReceivePosix( |
| 13241 | 13258 | .iovlen = 1, |
| 13242 | 13259 | .control = message.control.ptr, |
| 13243 | 13260 | .controllen = @intCast(message.control.len), |
| 13244 | .flags = undefined, | |
| 13261 | .flags = 0, | |
| 13245 | 13262 | }; |
| 13246 | 13263 | |
| 13247 | 13264 | const syscall = try Syscall.start(); |
| ... | ... | @@ -13362,6 +13379,7 @@ fn netWritePosix( |
| 13362 | 13379 | header: []const u8, |
| 13363 | 13380 | data: []const []const u8, |
| 13364 | 13381 | splat: usize, |
| 13382 | control: []const u8, | |
| 13365 | 13383 | ) net.Stream.Writer.Error!usize { |
| 13366 | 13384 | if (!have_networking) return error.NetworkDown; |
| 13367 | 13385 | |
| ... | ... | @@ -13371,8 +13389,8 @@ fn netWritePosix( |
| 13371 | 13389 | .namelen = 0, |
| 13372 | 13390 | .iov = &iovecs, |
| 13373 | 13391 | .iovlen = 0, |
| 13374 | .control = null, | |
| 13375 | .controllen = 0, | |
| 13392 | .control = if (control.len == 0) null else @constCast(control.ptr), | |
| 13393 | .controllen = @intCast(control.len), | |
| 13376 | 13394 | .flags = 0, |
| 13377 | 13395 | }; |
| 13378 | 13396 | addBuf(&iovecs, &msg.iovlen, header); |
| ... | ... | @@ -13454,9 +13472,13 @@ fn netWriteWindows( |
| 13454 | 13472 | header: []const u8, |
| 13455 | 13473 | data: []const []const u8, |
| 13456 | 13474 | splat: usize, |
| 13475 | control: []const u8, | |
| 13457 | 13476 | ) net.Stream.Writer.Error!usize { |
| 13458 | 13477 | if (!have_networking) return error.NetworkDown; |
| 13459 | 13478 | |
| 13479 | // Windows doesn't have the concept of control/ancillary data. | |
| 13480 | _ = control; | |
| 13481 | ||
| 13460 | 13482 | var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"const") = undefined; |
| 13461 | 13483 | var len: u32 = 0; |
| 13462 | 13484 | addAfdBuf(.@"const", &iovecs, &len, header); |
lib/std/Io/net.zig+125-6| ... | ... | @@ -1269,12 +1269,36 @@ pub const Stream = struct { |
| 1269 | 1269 | |
| 1270 | 1270 | const max_iovecs_len = 8; |
| 1271 | 1271 | |
| 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 | 1283 | /// This is a low-level API that calls the `Io` interface function directly. |
| 1273 | 1284 | /// For a higher level API, see `reader`. |
| 1274 | 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 | 1299 | .socket_handle = s.socket.handle, |
| 1277 | 1300 | .data = data, |
| 1301 | .control = control, | |
| 1278 | 1302 | } })).net_read; |
| 1279 | 1303 | } |
| 1280 | 1304 | |
| ... | ... | @@ -1289,12 +1313,25 @@ pub const Stream = struct { |
| 1289 | 1313 | pub const Reader = struct { |
| 1290 | 1314 | io: Io, |
| 1291 | 1315 | interface: Io.Reader, |
| 1316 | control_buffer: []align(cmsg_align) u8, | |
| 1317 | control_len: usize, | |
| 1318 | control_truncated: bool, | |
| 1292 | 1319 | stream: Stream, |
| 1293 | 1320 | err: ?Error, |
| 1294 | 1321 | |
| 1295 | 1322 | pub const Error = Io.Operation.NetRead.Error || Io.Cancelable; |
| 1296 | 1323 | |
| 1297 | 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 | 1335 | return .{ |
| 1299 | 1336 | .io = io, |
| 1300 | 1337 | .interface = .{ |
| ... | ... | @@ -1306,6 +1343,9 @@ pub const Stream = struct { |
| 1306 | 1343 | .seek = 0, |
| 1307 | 1344 | .end = 0, |
| 1308 | 1345 | }, |
| 1346 | .control_buffer = control_buffer, | |
| 1347 | .control_len = 0, | |
| 1348 | .control_truncated = false, | |
| 1309 | 1349 | .stream = stream, |
| 1310 | 1350 | .err = null, |
| 1311 | 1351 | }; |
| ... | ... | @@ -1326,24 +1366,44 @@ pub const Stream = struct { |
| 1326 | 1366 | const dest_n, const data_size = try io_r.writableVector(&iovecs_buffer, data); |
| 1327 | 1367 | const dest = iovecs_buffer[0..dest_n]; |
| 1328 | 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 | 1370 | r.err = err; |
| 1331 | 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 | 1376 | return error.EndOfStream; |
| 1335 | 1377 | } |
| 1336 | if (n > data_size) { | |
| 1337 | r.interface.end += n - data_size; | |
| 1378 | if (result.data_len > data_size) { | |
| 1379 | r.interface.end += result.data_len - data_size; | |
| 1338 | 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 | }; |
| 1343 | 1401 | |
| 1344 | 1402 | pub const Writer = struct { |
| 1345 | 1403 | io: Io, |
| 1346 | 1404 | interface: Io.Writer, |
| 1405 | /// Will be sent on next drain, then set back to an empty slice. | |
| 1406 | control: []const u8 = &.{}, | |
| 1347 | 1407 | stream: Stream, |
| 1348 | 1408 | err: ?Error = null, |
| 1349 | 1409 | write_file_err: ?WriteFileError = null, |
| ... | ... | @@ -1386,6 +1446,7 @@ pub const Stream = struct { |
| 1386 | 1446 | .header = buffered, |
| 1387 | 1447 | .data = data, |
| 1388 | 1448 | .splat = splat, |
| 1449 | .control = w.control, | |
| 1389 | 1450 | } }) catch |err| { |
| 1390 | 1451 | w.err = err; |
| 1391 | 1452 | return error.WriteFailed; |
| ... | ... | @@ -1394,6 +1455,7 @@ pub const Stream = struct { |
| 1394 | 1455 | w.err = err; |
| 1395 | 1456 | return error.WriteFailed; |
| 1396 | 1457 | }; |
| 1458 | w.control = &.{}; | |
| 1397 | 1459 | return io_w.consume(n); |
| 1398 | 1460 | } |
| 1399 | 1461 | |
| ... | ... | @@ -1424,11 +1486,68 @@ pub const Stream = struct { |
| 1424 | 1486 | return .init(stream, io, buffer); |
| 1425 | 1487 | } |
| 1426 | 1488 | |
| 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 | 1499 | pub fn writer(stream: Stream, io: Io, buffer: []u8) Writer { |
| 1428 | 1500 | return .init(stream, io, buffer); |
| 1429 | 1501 | } |
| 1430 | 1502 | }; |
| 1431 | 1503 | |
| 1504 | pub 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 | |
| 1507 | pub 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 | ||
| 1432 | 1551 | pub const Server = struct { |
| 1433 | 1552 | socket: Socket, |
| 1434 | 1553 | options: AcceptOptions, |
lib/std/Io/net/test.zig+94| ... | ... | @@ -4,6 +4,7 @@ const std = @import("std"); |
| 4 | 4 | const Io = std.Io; |
| 5 | 5 | const net = std.Io.net; |
| 6 | 6 | const mem = std.mem; |
| 7 | const posix = std.posix; | |
| 7 | 8 | const testing = std.testing; |
| 8 | 9 | const Allocator = std.mem.Allocator; |
| 9 | 10 | |
| ... | ... | @@ -295,6 +296,99 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 295 | 296 | try client_task.await(io); |
| 296 | 297 | } |
| 297 | 298 | |
| 299 | test "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 | ||
| 298 | 392 | fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 { |
| 299 | 393 | const random_bytes_count = 12; |
| 300 | 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 | 3373 | restorer: ?*const fn () callconv(.c) void = null, |
| 3374 | 3374 | mask: sigset_t, |
| 3375 | 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 | 3389 | else => common_linux_Sigaction, |
| 3377 | 3390 | }, |
| 3378 | 3391 | .emscripten => emscripten.Sigaction, |
| ... | ... | @@ -4432,6 +4445,68 @@ const posix_cmsghdr = extern struct { |
| 4432 | 4445 | type: c_int, |
| 4433 | 4446 | }; |
| 4434 | 4447 | |
| 4448 | // Alignment used by POSIX specified CMSG_* Macros | |
| 4449 | // https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html | |
| 4450 | pub 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 | }; | |
| 4504 | comptime { | |
| 4505 | if (@TypeOf(cmsg_align) != void and @TypeOf(cmsghdr) != void) { | |
| 4506 | assert(cmsg_align >= @alignOf(cmsghdr)); | |
| 4507 | } | |
| 4508 | } | |
| 4509 | ||
| 4435 | 4510 | pub const nfds_t = switch (native_os) { |
| 4436 | 4511 | .linux => linux.nfds_t, |
| 4437 | 4512 | .emscripten => emscripten.nfds_t, |
lib/std/os/linux.zig+3| ... | ... | @@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct { |
| 11244 | 11244 | type: i32, |
| 11245 | 11245 | }; |
| 11246 | 11246 | |
| 11247 | // https://github.com/torvalds/linux/blob/72d3fcf802c45d00b300f25b848a93c3a2bd7c7e/include/linux/socket.h#L133 | |
| 11248 | pub const cmsg_align = @sizeOf(c_long); | |
| 11249 | ||
| 11247 | 11250 | pub const riscv_hwprobe = extern struct { |
| 11248 | 11251 | key: i64, |
| 11249 | 11252 | value: u64, |
lib/std/posix.zig+2| ... | ... | @@ -150,6 +150,8 @@ pub const blkcnt_t = system.blkcnt_t; |
| 150 | 150 | pub const blksize_t = system.blksize_t; |
| 151 | 151 | pub const clock_t = system.clock_t; |
| 152 | 152 | pub const clockid_t = system.clockid_t; |
| 153 | pub const cmsghdr = system.cmsghdr; | |
| 154 | pub const cmsg_align = system.cmsg_align; | |
| 153 | 155 | pub const timerfd_clockid_t = system.timerfd_clockid_t; |
| 154 | 156 | pub const cpu_set_t = system.cpu_set_t; |
| 155 | 157 | pub const dev_t = system.dev_t; |