| ... | @@ -295,6 +295,120 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -295,6 +295,120 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 295 | try client_task.await(io); | 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 | fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 { | 412 | fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 { |
| 299 | const random_bytes_count = 12; | 413 | const random_bytes_count = 12; |
| 300 | const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); | 414 | const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); |