authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-14 14:15:12-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-12-14 14:15:12-08:00
log93c6ab4952cb5bed06cb77725893ebf8e12240bd
tree0c57707bc2a87a45ae0cc06d2142bc3aefc1568f
parentc56663dee8b0043eba214562765b973aebfa7185
parentf9b8808d7494757fc47dfd506264669270f4e573
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #10322 from vrischmann/more-io_uring

implement more io_uring opcodes

2 files changed, 516 insertions(+), 1 deletions(-)

lib/std/os/linux.zig+6
......@@ -3576,6 +3576,12 @@ pub const IORING_OP = enum(u8) {
35763576 PROVIDE_BUFFERS,
35773577 REMOVE_BUFFERS,
35783578 TEE,
3579 SHUTDOWN,
3580 RENAMEAT,
3581 UNLINKAT,
3582 MKDIRAT,
3583 SYMLINKAT,
3584 LINKAT,
35793585
35803586 _,
35813587};
lib/std/os/linux/io_uring.zig+510-1
......@@ -731,6 +731,101 @@ pub const IO_Uring = struct {
731731 return sqe;
732732 }
733733
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
734829 /// Registers an array of file descriptors.
735830 /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must
736831 /// 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 {
798893 }
799894
800895 /// 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.
802897 /// This means that events that complete inline while being submitted do not trigger a notification event.
803898 /// Only a single eventfd can be registered at any given point in time.
804899 pub fn register_eventfd_async(self: *IO_Uring, fd: os.fd_t) !void {
......@@ -1279,6 +1374,89 @@ pub fn io_uring_prep_cancel(
12791374 sqe.rw_flags = flags;
12801375}
12811376
1377pub 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
1385pub 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
1405pub 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
1415pub 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
1424pub 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
1440pub 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
12821460test "structs/offsets/entries" {
12831461 if (builtin.os.tag != .linux) return error.SkipZigTest;
12841462
......@@ -2191,3 +2369,334 @@ test "register_files_update" {
21912369
21922370 try ring.unregister_files();
21932371}
2372
2373test "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
2433test "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
2502test "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
2552test "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
2595test "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
2644test "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}