| ... | ... | @@ -295,6 +295,120 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 295 | 295 | try client_task.await(io); |
| 296 | 296 | } |
| 297 | 297 | |
| 298 | test "listen on a unix socket, pass file descriptor" { |
| 299 | if (builtin.os.tag == .windows or builtin.os.tag == .wasi) { |
| 300 | // Windows and WASI don't have the concept of control/ancillary data. |
| 301 | return error.SkipZigTest; |
| 302 | } |
| 303 | |
| 304 | const io = testing.io; |
| 305 | const gpa = testing.allocator; |
| 306 | |
| 307 | const socket_path = try generateFileName(gpa, io, "socket.unix"); |
| 308 | defer gpa.free(socket_path); |
| 309 | const temp_file_path = try generateFileName(gpa, io, "temp_file"); |
| 310 | defer gpa.free(temp_file_path); |
| 311 | |
| 312 | defer Io.Dir.cwd().deleteFile(io, socket_path) catch {}; |
| 313 | defer Io.Dir.cwd().deleteFile(io, temp_file_path) catch {}; |
| 314 | |
| 315 | const socket_addr = try net.UnixAddress.init(socket_path); |
| 316 | |
| 317 | var server = socket_addr.listen(io, .{}) catch |err| switch (err) { |
| 318 | error.AddressFamilyUnsupported => return error.SkipZigTest, |
| 319 | error.NetworkDown => return error.SkipZigTest, |
| 320 | else => |e| return e, |
| 321 | }; |
| 322 | defer server.socket.close(io); |
| 323 | |
| 324 | const cmsg_alignment = switch (builtin.os.tag) { |
| 325 | .driverkit, |
| 326 | .ios, |
| 327 | .maccatalyst, |
| 328 | .macos, |
| 329 | .tvos, |
| 330 | .visionos, |
| 331 | .watchos, |
| 332 | => 4, |
| 333 | else => @sizeOf(usize), |
| 334 | }; |
| 335 | |
| 336 | const cmsghdr_aligned_len = comptime mem.alignForward(usize, @sizeOf(std.posix.cmsghdr), cmsg_alignment); |
| 337 | const cmsghdr_align = @alignOf(std.posix.cmsghdr); |
| 338 | const fd_t_len = @sizeOf(std.posix.fd_t); |
| 339 | |
| 340 | const cmsg_buf_len = |
| 341 | cmsghdr_aligned_len + |
| 342 | comptime mem.alignForward(usize, fd_t_len, cmsg_alignment); |
| 343 | |
| 344 | const S = struct { |
| 345 | fn clientFn(path: []const u8, file_path: []const u8) !void { |
| 346 | const temp_file = try Io.Dir.cwd().createFile(io, file_path, .{}); |
| 347 | defer temp_file.close(io); |
| 348 | |
| 349 | const server_path: net.UnixAddress = try .init(path); |
| 350 | var stream = try server_path.connect(io); |
| 351 | defer stream.close(io); |
| 352 | |
| 353 | var cmsg_buf: [cmsg_buf_len]u8 align(cmsghdr_align) = @splat(0); |
| 354 | |
| 355 | const header_ptr: *std.posix.cmsghdr = @ptrCast(&cmsg_buf); |
| 356 | header_ptr.* = .{ |
| 357 | .len = @intCast(cmsghdr_aligned_len + fd_t_len), |
| 358 | .level = std.posix.SOL.SOCKET, |
| 359 | .type = std.posix.SCM.RIGHTS, |
| 360 | }; |
| 361 | |
| 362 | const fds = mem.bytesAsSlice(std.posix.fd_t, cmsg_buf[cmsghdr_aligned_len..]); |
| 363 | fds[0] = temp_file.handle; |
| 364 | |
| 365 | var stream_writer = stream.writer(io, &.{}); |
| 366 | stream_writer.control = &cmsg_buf; |
| 367 | try stream_writer.interface.writeAll("Hello world!"); |
| 368 | } |
| 369 | }; |
| 370 | |
| 371 | var client_task = io.concurrent(S.clientFn, .{ socket_path, temp_file_path }) catch |err| switch (err) { |
| 372 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| 373 | }; |
| 374 | defer client_task.cancel(io) catch {}; |
| 375 | |
| 376 | var stream = try server.accept(io); |
| 377 | defer stream.close(io); |
| 378 | |
| 379 | var control_buf: [32]u8 align(cmsghdr_align) = undefined; |
| 380 | var buf: [16]u8 = undefined; |
| 381 | var stream_reader = stream.readerWithControl(io, &.{}, &control_buf); |
| 382 | |
| 383 | const n = try stream_reader.interface.readSliceShort(&buf); |
| 384 | const cmsg_buf = stream_reader.controlSlice(); |
| 385 | |
| 386 | try testing.expect(!stream_reader.control_truncated); // The control buffer should be big enough to receive the entire control message |
| 387 | |
| 388 | try testing.expectEqual(12, n); |
| 389 | try testing.expectEqualStrings("Hello world!", buf[0..n]); |
| 390 | |
| 391 | try testing.expect(cmsg_buf.len >= cmsghdr_aligned_len + fd_t_len); |
| 392 | |
| 393 | const header_ptr: *const std.posix.cmsghdr = @ptrCast(@alignCast(cmsg_buf)); |
| 394 | |
| 395 | try testing.expectEqual(cmsghdr_aligned_len + fd_t_len, header_ptr.len); |
| 396 | try testing.expectEqual(std.posix.SOL.SOCKET, header_ptr.level); |
| 397 | try testing.expectEqual(std.posix.SCM.RIGHTS, header_ptr.type); |
| 398 | |
| 399 | const fds = mem.bytesAsSlice(std.posix.fd_t, cmsg_buf[cmsghdr_aligned_len..]); |
| 400 | |
| 401 | const temp_file: Io.File = .{ |
| 402 | .handle = fds[0], |
| 403 | .flags = .{ .nonblocking = false }, |
| 404 | }; |
| 405 | defer temp_file.close(io); |
| 406 | var temp_file_writer = temp_file.writer(io, &.{}); |
| 407 | try temp_file_writer.interface.writeAll("if this is a real file descriptor, this shouldn't fail"); |
| 408 | |
| 409 | try client_task.await(io); |
| 410 | } |
| 411 | |
| 298 | 412 | fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 { |
| 299 | 413 | const random_bytes_count = 12; |
| 300 | 414 | const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); |