| ... | @@ -731,6 +731,101 @@ pub const IO_Uring = struct { | ... | @@ -731,6 +731,101 @@ pub const IO_Uring = struct { |
| 731 | return sqe; | 731 | return sqe; |
| 732 | } | 732 | } |
| 733 | | 733 | |
| | 734 | /// Queues (but does not submit) an SQE to perform a `shutdown(2)`. |
| | 735 | /// Returns a pointer to the SQE. |
| | 736 | /// |
| | 737 | /// The operation is identified by its `user_data`. |
| | 738 | pub fn shutdown( |
| | 739 | self: *IO_Uring, |
| | 740 | user_data: u64, |
| | 741 | sockfd: os.socket_t, |
| | 742 | how: u32, |
| | 743 | ) !*io_uring_sqe { |
| | 744 | const sqe = try self.get_sqe(); |
| | 745 | io_uring_prep_shutdown(sqe, sockfd, how); |
| | 746 | sqe.user_data = user_data; |
| | 747 | return sqe; |
| | 748 | } |
| | 749 | |
| | 750 | /// Queues (but does not submit) an SQE to perform a `renameat2(2)`. |
| | 751 | /// Returns a pointer to the SQE. |
| | 752 | pub fn renameat( |
| | 753 | self: *IO_Uring, |
| | 754 | user_data: u64, |
| | 755 | old_dir_fd: os.fd_t, |
| | 756 | old_path: [*:0]const u8, |
| | 757 | new_dir_fd: os.fd_t, |
| | 758 | new_path: [*:0]const u8, |
| | 759 | flags: u32, |
| | 760 | ) !*io_uring_sqe { |
| | 761 | const sqe = try self.get_sqe(); |
| | 762 | io_uring_prep_renameat(sqe, old_dir_fd, old_path, new_dir_fd, new_path, flags); |
| | 763 | sqe.user_data = user_data; |
| | 764 | return sqe; |
| | 765 | } |
| | 766 | |
| | 767 | /// Queues (but does not submit) an SQE to perform a `unlinkat(2)`. |
| | 768 | /// Returns a pointer to the SQE. |
| | 769 | pub fn unlinkat( |
| | 770 | self: *IO_Uring, |
| | 771 | user_data: u64, |
| | 772 | dir_fd: os.fd_t, |
| | 773 | path: [*:0]const u8, |
| | 774 | flags: u32, |
| | 775 | ) !*io_uring_sqe { |
| | 776 | const sqe = try self.get_sqe(); |
| | 777 | io_uring_prep_unlinkat(sqe, dir_fd, path, flags); |
| | 778 | sqe.user_data = user_data; |
| | 779 | return sqe; |
| | 780 | } |
| | 781 | |
| | 782 | /// Queues (but does not submit) an SQE to perform a `mkdirat(2)`. |
| | 783 | /// Returns a pointer to the SQE. |
| | 784 | pub fn mkdirat( |
| | 785 | self: *IO_Uring, |
| | 786 | user_data: u64, |
| | 787 | dir_fd: os.fd_t, |
| | 788 | path: [*:0]const u8, |
| | 789 | mode: os.mode_t, |
| | 790 | ) !*io_uring_sqe { |
| | 791 | const sqe = try self.get_sqe(); |
| | 792 | io_uring_prep_mkdirat(sqe, dir_fd, path, mode); |
| | 793 | sqe.user_data = user_data; |
| | 794 | return sqe; |
| | 795 | } |
| | 796 | |
| | 797 | /// Queues (but does not submit) an SQE to perform a `symlinkat(2)`. |
| | 798 | /// Returns a pointer to the SQE. |
| | 799 | pub fn symlinkat( |
| | 800 | self: *IO_Uring, |
| | 801 | user_data: u64, |
| | 802 | target: [*:0]const u8, |
| | 803 | new_dir_fd: os.fd_t, |
| | 804 | link_path: [*:0]const u8, |
| | 805 | ) !*io_uring_sqe { |
| | 806 | const sqe = try self.get_sqe(); |
| | 807 | io_uring_prep_symlinkat(sqe, target, new_dir_fd, link_path); |
| | 808 | sqe.user_data = user_data; |
| | 809 | return sqe; |
| | 810 | } |
| | 811 | |
| | 812 | /// Queues (but does not submit) an SQE to perform a `linkat(2)`. |
| | 813 | /// Returns a pointer to the SQE. |
| | 814 | pub fn linkat( |
| | 815 | self: *IO_Uring, |
| | 816 | user_data: u64, |
| | 817 | old_dir_fd: os.fd_t, |
| | 818 | old_path: [*:0]const u8, |
| | 819 | new_dir_fd: os.fd_t, |
| | 820 | new_path: [*:0]const u8, |
| | 821 | flags: u32, |
| | 822 | ) !*io_uring_sqe { |
| | 823 | const sqe = try self.get_sqe(); |
| | 824 | io_uring_prep_linkat(sqe, old_dir_fd, old_path, new_dir_fd, new_path, flags); |
| | 825 | sqe.user_data = user_data; |
| | 826 | return sqe; |
| | 827 | } |
| | 828 | |
| 734 | /// Registers an array of file descriptors. | 829 | /// Registers an array of file descriptors. |
| 735 | /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must | 830 | /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must |
| 736 | /// retrieve a reference to the file, and once I/O has completed the file reference must be | 831 | /// retrieve a reference to the file, and once I/O has completed the file reference must be |
| ... | @@ -798,7 +893,7 @@ pub const IO_Uring = struct { | ... | @@ -798,7 +893,7 @@ pub const IO_Uring = struct { |
| 798 | } | 893 | } |
| 799 | | 894 | |
| 800 | /// Registers the file descriptor for an eventfd that will be notified of completion events on | 895 | /// Registers the file descriptor for an eventfd that will be notified of completion events on |
| 801 | /// an io_uring instance. Notifications are only posted for events that complete in an async manner. | 896 | /// an io_uring instance. Notifications are only posted for events that complete in an async manner. |
| 802 | /// This means that events that complete inline while being submitted do not trigger a notification event. | 897 | /// This means that events that complete inline while being submitted do not trigger a notification event. |
| 803 | /// Only a single eventfd can be registered at any given point in time. | 898 | /// Only a single eventfd can be registered at any given point in time. |
| 804 | pub fn register_eventfd_async(self: *IO_Uring, fd: os.fd_t) !void { | 899 | pub fn register_eventfd_async(self: *IO_Uring, fd: os.fd_t) !void { |
| ... | @@ -1279,6 +1374,89 @@ pub fn io_uring_prep_cancel( | ... | @@ -1279,6 +1374,89 @@ pub fn io_uring_prep_cancel( |
| 1279 | sqe.rw_flags = flags; | 1374 | sqe.rw_flags = flags; |
| 1280 | } | 1375 | } |
| 1281 | | 1376 | |
| | 1377 | pub fn io_uring_prep_shutdown( |
| | 1378 | sqe: *io_uring_sqe, |
| | 1379 | sockfd: os.socket_t, |
| | 1380 | how: u32, |
| | 1381 | ) void { |
| | 1382 | io_uring_prep_rw(.SHUTDOWN, sqe, sockfd, 0, how, 0); |
| | 1383 | } |
| | 1384 | |
| | 1385 | pub fn io_uring_prep_renameat( |
| | 1386 | sqe: *io_uring_sqe, |
| | 1387 | old_dir_fd: os.fd_t, |
| | 1388 | old_path: [*:0]const u8, |
| | 1389 | new_dir_fd: os.fd_t, |
| | 1390 | new_path: [*:0]const u8, |
| | 1391 | flags: u32, |
| | 1392 | ) void { |
| | 1393 | io_uring_prep_rw( |
| | 1394 | .RENAMEAT, |
| | 1395 | sqe, |
| | 1396 | old_dir_fd, |
| | 1397 | @ptrToInt(old_path), |
| | 1398 | 0, |
| | 1399 | @ptrToInt(new_path), |
| | 1400 | ); |
| | 1401 | sqe.len = @bitCast(u32, new_dir_fd); |
| | 1402 | sqe.rw_flags = flags; |
| | 1403 | } |
| | 1404 | |
| | 1405 | pub fn io_uring_prep_unlinkat( |
| | 1406 | sqe: *io_uring_sqe, |
| | 1407 | dir_fd: os.fd_t, |
| | 1408 | path: [*:0]const u8, |
| | 1409 | flags: u32, |
| | 1410 | ) void { |
| | 1411 | io_uring_prep_rw(.UNLINKAT, sqe, dir_fd, @ptrToInt(path), 0, 0); |
| | 1412 | sqe.rw_flags = flags; |
| | 1413 | } |
| | 1414 | |
| | 1415 | pub fn io_uring_prep_mkdirat( |
| | 1416 | sqe: *io_uring_sqe, |
| | 1417 | dir_fd: os.fd_t, |
| | 1418 | path: [*:0]const u8, |
| | 1419 | mode: os.mode_t, |
| | 1420 | ) void { |
| | 1421 | io_uring_prep_rw(.MKDIRAT, sqe, dir_fd, @ptrToInt(path), mode, 0); |
| | 1422 | } |
| | 1423 | |
| | 1424 | pub fn io_uring_prep_symlinkat( |
| | 1425 | sqe: *io_uring_sqe, |
| | 1426 | target: [*:0]const u8, |
| | 1427 | new_dir_fd: os.fd_t, |
| | 1428 | link_path: [*:0]const u8, |
| | 1429 | ) void { |
| | 1430 | io_uring_prep_rw( |
| | 1431 | .SYMLINKAT, |
| | 1432 | sqe, |
| | 1433 | new_dir_fd, |
| | 1434 | @ptrToInt(target), |
| | 1435 | 0, |
| | 1436 | @ptrToInt(link_path), |
| | 1437 | ); |
| | 1438 | } |
| | 1439 | |
| | 1440 | pub fn io_uring_prep_linkat( |
| | 1441 | sqe: *io_uring_sqe, |
| | 1442 | old_dir_fd: os.fd_t, |
| | 1443 | old_path: [*:0]const u8, |
| | 1444 | new_dir_fd: os.fd_t, |
| | 1445 | new_path: [*:0]const u8, |
| | 1446 | flags: u32, |
| | 1447 | ) void { |
| | 1448 | io_uring_prep_rw( |
| | 1449 | .LINKAT, |
| | 1450 | sqe, |
| | 1451 | old_dir_fd, |
| | 1452 | @ptrToInt(old_path), |
| | 1453 | 0, |
| | 1454 | @ptrToInt(new_path), |
| | 1455 | ); |
| | 1456 | sqe.len = @bitCast(u32, new_dir_fd); |
| | 1457 | sqe.rw_flags = flags; |
| | 1458 | } |
| | 1459 | |
| 1282 | test "structs/offsets/entries" { | 1460 | test "structs/offsets/entries" { |
| 1283 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 1461 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 1284 | | 1462 | |
| ... | @@ -2191,3 +2369,334 @@ test "register_files_update" { | ... | @@ -2191,3 +2369,334 @@ test "register_files_update" { |
| 2191 | | 2369 | |
| 2192 | try ring.unregister_files(); | 2370 | try ring.unregister_files(); |
| 2193 | } | 2371 | } |
| | 2372 | |
| | 2373 | test "shutdown" { |
| | 2374 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 2375 | |
| | 2376 | var ring = IO_Uring.init(16, 0) catch |err| switch (err) { |
| | 2377 | error.SystemOutdated => return error.SkipZigTest, |
| | 2378 | error.PermissionDenied => return error.SkipZigTest, |
| | 2379 | else => return err, |
| | 2380 | }; |
| | 2381 | defer ring.deinit(); |
| | 2382 | |
| | 2383 | const address = try net.Address.parseIp4("127.0.0.1", 3131); |
| | 2384 | |
| | 2385 | // Socket bound, expect shutdown to work |
| | 2386 | { |
| | 2387 | const server = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); |
| | 2388 | defer os.close(server); |
| | 2389 | try os.setsockopt(server, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); |
| | 2390 | try os.bind(server, &address.any, address.getOsSockLen()); |
| | 2391 | try os.listen(server, 1); |
| | 2392 | |
| | 2393 | var shutdown_sqe = try ring.shutdown(0x445445445, server, os.linux.SHUT.RD); |
| | 2394 | try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); |
| | 2395 | try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); |
| | 2396 | |
| | 2397 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2398 | |
| | 2399 | const cqe = try ring.copy_cqe(); |
| | 2400 | switch (cqe.err()) { |
| | 2401 | .SUCCESS => {}, |
| | 2402 | // This kernel's io_uring does not yet implement shutdown (kernel version < 5.11) |
| | 2403 | .INVAL => return error.SkipZigTest, |
| | 2404 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2405 | } |
| | 2406 | |
| | 2407 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2408 | .user_data = 0x445445445, |
| | 2409 | .res = 0, |
| | 2410 | .flags = 0, |
| | 2411 | }, cqe); |
| | 2412 | } |
| | 2413 | |
| | 2414 | // Socket not bound, expect to fail with ENOTCONN |
| | 2415 | { |
| | 2416 | const server = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); |
| | 2417 | defer os.close(server); |
| | 2418 | |
| | 2419 | var shutdown_sqe = ring.shutdown(0x445445445, server, os.linux.SHUT.RD) catch |err| switch (err) { |
| | 2420 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2421 | }; |
| | 2422 | try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); |
| | 2423 | try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); |
| | 2424 | |
| | 2425 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2426 | |
| | 2427 | const cqe = try ring.copy_cqe(); |
| | 2428 | try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data); |
| | 2429 | try testing.expectEqual(os.linux.E.NOTCONN, cqe.err()); |
| | 2430 | } |
| | 2431 | } |
| | 2432 | |
| | 2433 | test "renameat" { |
| | 2434 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 2435 | |
| | 2436 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 2437 | error.SystemOutdated => return error.SkipZigTest, |
| | 2438 | error.PermissionDenied => return error.SkipZigTest, |
| | 2439 | else => return err, |
| | 2440 | }; |
| | 2441 | defer ring.deinit(); |
| | 2442 | |
| | 2443 | const old_path = "test_io_uring_renameat_old"; |
| | 2444 | const new_path = "test_io_uring_renameat_new"; |
| | 2445 | |
| | 2446 | // Write old file with data |
| | 2447 | |
| | 2448 | const old_file = try std.fs.cwd().createFile(old_path, .{ .truncate = true, .mode = 0o666 }); |
| | 2449 | defer { |
| | 2450 | old_file.close(); |
| | 2451 | std.fs.cwd().deleteFile(new_path) catch {}; |
| | 2452 | } |
| | 2453 | try old_file.writeAll("hello"); |
| | 2454 | |
| | 2455 | // Submit renameat |
| | 2456 | |
| | 2457 | var sqe = try ring.renameat( |
| | 2458 | 0x12121212, |
| | 2459 | linux.AT.FDCWD, |
| | 2460 | old_path, |
| | 2461 | linux.AT.FDCWD, |
| | 2462 | new_path, |
| | 2463 | 0, |
| | 2464 | ); |
| | 2465 | try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode); |
| | 2466 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); |
| | 2467 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len)); |
| | 2468 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2469 | |
| | 2470 | const cqe = try ring.copy_cqe(); |
| | 2471 | switch (cqe.err()) { |
| | 2472 | .SUCCESS => {}, |
| | 2473 | // This kernel's io_uring does not yet implement renameat (kernel version < 5.11) |
| | 2474 | .INVAL => return error.SkipZigTest, |
| | 2475 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2476 | } |
| | 2477 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2478 | .user_data = 0x12121212, |
| | 2479 | .res = 0, |
| | 2480 | .flags = 0, |
| | 2481 | }, cqe); |
| | 2482 | |
| | 2483 | // Validate that the old file doesn't exist anymore |
| | 2484 | { |
| | 2485 | _ = std.fs.cwd().openFile(old_path, .{}) catch |err| switch (err) { |
| | 2486 | error.FileNotFound => {}, |
| | 2487 | else => std.debug.panic("unexpected error: {}", .{err}), |
| | 2488 | }; |
| | 2489 | } |
| | 2490 | |
| | 2491 | // Validate that the new file exists with the proper content |
| | 2492 | { |
| | 2493 | const new_file = try std.fs.cwd().openFile(new_path, .{}); |
| | 2494 | defer new_file.close(); |
| | 2495 | |
| | 2496 | var new_file_data: [16]u8 = undefined; |
| | 2497 | const read = try new_file.readAll(&new_file_data); |
| | 2498 | try testing.expectEqualStrings("hello", new_file_data[0..read]); |
| | 2499 | } |
| | 2500 | } |
| | 2501 | |
| | 2502 | test "unlinkat" { |
| | 2503 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 2504 | |
| | 2505 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 2506 | error.SystemOutdated => return error.SkipZigTest, |
| | 2507 | error.PermissionDenied => return error.SkipZigTest, |
| | 2508 | else => return err, |
| | 2509 | }; |
| | 2510 | defer ring.deinit(); |
| | 2511 | |
| | 2512 | const path = "test_io_uring_unlinkat"; |
| | 2513 | |
| | 2514 | // Write old file with data |
| | 2515 | |
| | 2516 | const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); |
| | 2517 | defer file.close(); |
| | 2518 | defer std.fs.cwd().deleteFile(path) catch {}; |
| | 2519 | |
| | 2520 | // Submit unlinkat |
| | 2521 | |
| | 2522 | var sqe = try ring.unlinkat( |
| | 2523 | 0x12121212, |
| | 2524 | linux.AT.FDCWD, |
| | 2525 | path, |
| | 2526 | 0, |
| | 2527 | ); |
| | 2528 | try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode); |
| | 2529 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); |
| | 2530 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2531 | |
| | 2532 | const cqe = try ring.copy_cqe(); |
| | 2533 | switch (cqe.err()) { |
| | 2534 | .SUCCESS => {}, |
| | 2535 | // This kernel's io_uring does not yet implement unlinkat (kernel version < 5.11) |
| | 2536 | .INVAL => return error.SkipZigTest, |
| | 2537 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2538 | } |
| | 2539 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2540 | .user_data = 0x12121212, |
| | 2541 | .res = 0, |
| | 2542 | .flags = 0, |
| | 2543 | }, cqe); |
| | 2544 | |
| | 2545 | // Validate that the file doesn't exist anymore |
| | 2546 | _ = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { |
| | 2547 | error.FileNotFound => {}, |
| | 2548 | else => std.debug.panic("unexpected error: {}", .{err}), |
| | 2549 | }; |
| | 2550 | } |
| | 2551 | |
| | 2552 | test "mkdirat" { |
| | 2553 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 2554 | |
| | 2555 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 2556 | error.SystemOutdated => return error.SkipZigTest, |
| | 2557 | error.PermissionDenied => return error.SkipZigTest, |
| | 2558 | else => return err, |
| | 2559 | }; |
| | 2560 | defer ring.deinit(); |
| | 2561 | |
| | 2562 | const path = "test_io_uring_mkdirat"; |
| | 2563 | |
| | 2564 | defer std.fs.cwd().deleteDir(path) catch {}; |
| | 2565 | |
| | 2566 | // Submit mkdirat |
| | 2567 | |
| | 2568 | var sqe = try ring.mkdirat( |
| | 2569 | 0x12121212, |
| | 2570 | linux.AT.FDCWD, |
| | 2571 | path, |
| | 2572 | 0o0755, |
| | 2573 | ); |
| | 2574 | try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode); |
| | 2575 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); |
| | 2576 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2577 | |
| | 2578 | const cqe = try ring.copy_cqe(); |
| | 2579 | switch (cqe.err()) { |
| | 2580 | .SUCCESS => {}, |
| | 2581 | // This kernel's io_uring does not yet implement mkdirat (kernel version < 5.15) |
| | 2582 | .INVAL => return error.SkipZigTest, |
| | 2583 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2584 | } |
| | 2585 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2586 | .user_data = 0x12121212, |
| | 2587 | .res = 0, |
| | 2588 | .flags = 0, |
| | 2589 | }, cqe); |
| | 2590 | |
| | 2591 | // Validate that the directory exist |
| | 2592 | _ = try std.fs.cwd().openDir(path, .{}); |
| | 2593 | } |
| | 2594 | |
| | 2595 | test "symlinkat" { |
| | 2596 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 2597 | |
| | 2598 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 2599 | error.SystemOutdated => return error.SkipZigTest, |
| | 2600 | error.PermissionDenied => return error.SkipZigTest, |
| | 2601 | else => return err, |
| | 2602 | }; |
| | 2603 | defer ring.deinit(); |
| | 2604 | |
| | 2605 | const path = "test_io_uring_symlinkat"; |
| | 2606 | const link_path = "test_io_uring_symlinkat_link"; |
| | 2607 | |
| | 2608 | const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); |
| | 2609 | defer { |
| | 2610 | file.close(); |
| | 2611 | std.fs.cwd().deleteFile(path) catch {}; |
| | 2612 | std.fs.cwd().deleteFile(link_path) catch {}; |
| | 2613 | } |
| | 2614 | |
| | 2615 | // Submit symlinkat |
| | 2616 | |
| | 2617 | var sqe = try ring.symlinkat( |
| | 2618 | 0x12121212, |
| | 2619 | path, |
| | 2620 | linux.AT.FDCWD, |
| | 2621 | link_path, |
| | 2622 | ); |
| | 2623 | try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode); |
| | 2624 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); |
| | 2625 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2626 | |
| | 2627 | const cqe = try ring.copy_cqe(); |
| | 2628 | switch (cqe.err()) { |
| | 2629 | .SUCCESS => {}, |
| | 2630 | // This kernel's io_uring does not yet implement symlinkat (kernel version < 5.15) |
| | 2631 | .INVAL => return error.SkipZigTest, |
| | 2632 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2633 | } |
| | 2634 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2635 | .user_data = 0x12121212, |
| | 2636 | .res = 0, |
| | 2637 | .flags = 0, |
| | 2638 | }, cqe); |
| | 2639 | |
| | 2640 | // Validate that the symlink exist |
| | 2641 | _ = try std.fs.cwd().openFile(link_path, .{}); |
| | 2642 | } |
| | 2643 | |
| | 2644 | test "linkat" { |
| | 2645 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 2646 | |
| | 2647 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 2648 | error.SystemOutdated => return error.SkipZigTest, |
| | 2649 | error.PermissionDenied => return error.SkipZigTest, |
| | 2650 | else => return err, |
| | 2651 | }; |
| | 2652 | defer ring.deinit(); |
| | 2653 | |
| | 2654 | const first_path = "test_io_uring_linkat_first"; |
| | 2655 | const second_path = "test_io_uring_linkat_second"; |
| | 2656 | |
| | 2657 | // Write file with data |
| | 2658 | |
| | 2659 | const first_file = try std.fs.cwd().createFile(first_path, .{ .truncate = true, .mode = 0o666 }); |
| | 2660 | defer { |
| | 2661 | first_file.close(); |
| | 2662 | std.fs.cwd().deleteFile(first_path) catch {}; |
| | 2663 | std.fs.cwd().deleteFile(second_path) catch {}; |
| | 2664 | } |
| | 2665 | try first_file.writeAll("hello"); |
| | 2666 | |
| | 2667 | // Submit linkat |
| | 2668 | |
| | 2669 | var sqe = try ring.linkat( |
| | 2670 | 0x12121212, |
| | 2671 | linux.AT.FDCWD, |
| | 2672 | first_path, |
| | 2673 | linux.AT.FDCWD, |
| | 2674 | second_path, |
| | 2675 | 0, |
| | 2676 | ); |
| | 2677 | try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode); |
| | 2678 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); |
| | 2679 | try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len)); |
| | 2680 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2681 | |
| | 2682 | const cqe = try ring.copy_cqe(); |
| | 2683 | switch (cqe.err()) { |
| | 2684 | .SUCCESS => {}, |
| | 2685 | // This kernel's io_uring does not yet implement linkat (kernel version < 5.15) |
| | 2686 | .INVAL => return error.SkipZigTest, |
| | 2687 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2688 | } |
| | 2689 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2690 | .user_data = 0x12121212, |
| | 2691 | .res = 0, |
| | 2692 | .flags = 0, |
| | 2693 | }, cqe); |
| | 2694 | |
| | 2695 | // Validate the second file |
| | 2696 | const second_file = try std.fs.cwd().openFile(second_path, .{}); |
| | 2697 | defer second_file.close(); |
| | 2698 | |
| | 2699 | var second_file_data: [16]u8 = undefined; |
| | 2700 | const read = try second_file.readAll(&second_file_data); |
| | 2701 | try testing.expectEqualStrings("hello", second_file_data[0..read]); |
| | 2702 | } |