| ... | @@ -809,6 +809,23 @@ pub const IO_Uring = struct { | ... | @@ -809,6 +809,23 @@ pub const IO_Uring = struct { |
| 809 | return sqe; | 809 | return sqe; |
| 810 | } | 810 | } |
| 811 | | 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 | |
| 812 | /// Registers an array of file descriptors. | 829 | /// Registers an array of file descriptors. |
| 813 | /// 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 |
| 814 | /// 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 |
| ... | @@ -1420,6 +1437,26 @@ pub fn io_uring_prep_symlinkat( | ... | @@ -1420,6 +1437,26 @@ pub fn io_uring_prep_symlinkat( |
| 1420 | ); | 1437 | ); |
| 1421 | } | 1438 | } |
| 1422 | | 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 | |
| 1423 | test "structs/offsets/entries" { | 1460 | test "structs/offsets/entries" { |
| 1424 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 1461 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 1425 | | 1462 | |
| ... | @@ -2603,3 +2640,63 @@ test "symlinkat" { | ... | @@ -2603,3 +2640,63 @@ test "symlinkat" { |
| 2603 | // Validate that the symlink exist | 2640 | // Validate that the symlink exist |
| 2604 | _ = try std.fs.cwd().openFile(link_path, .{}); | 2641 | _ = try std.fs.cwd().openFile(link_path, .{}); |
| 2605 | } | 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 | } |