From ac533617212975b75f321971eda0f77a7aba29f1 Mon Sep 17 00:00:00 2001 From: Bernard Assan Date: Thu, 23 Oct 2025 17:31:00 +0000 Subject: [PATCH] IoUring: Working on Pipe2 flags Build on the extensive work already done IoUring: add fixed_fd_install, ftruncate, cmd_discard Working on IoUring pipe flags Signed-off-by: Bernard Assan --- lib/std/os/linux.zig | 163 ++++++++++++++++++++++++++++++++++- lib/std/os/linux/IoUring.zig | 123 +++++++++++++++++++++++++- 2 files changed, 281 insertions(+), 5 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 005a21a22f..0705e1b9c3 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -494,6 +494,161 @@ pub const O = switch (native_arch) { else => @compileError("missing std.os.linux.O constants for this architecture"), }; +pub const Pipe2 = switch (native_arch) { + .x86_64, .x86, .riscv32, .riscv64, .loongarch64 => packed struct(u32) { + _: u7 = 0, + EXCL: bool = false, // + _9: u3 = 0, + NONBLOCK: bool = false, // + _13: u2 = 0, + DIRECT: bool = false, // + _16: u4 = 0, + CLOEXEC: bool = false, // + _21: u12 = 0, + }, + .aarch64, .aarch64_be, .arm, .armeb, .thumb, .thumbeb => packed struct(u32) { + ACCMODE: ACCMODE = .RDONLY, + _2: u4 = 0, + CREAT: bool = false, + EXCL: bool = false, // + NOCTTY: bool = false, + TRUNC: bool = false, + APPEND: bool = false, + NONBLOCK: bool = false, // + DSYNC: bool = false, + ASYNC: bool = false, + DIRECTORY: bool = false, + NOFOLLOW: bool = false, + DIRECT: bool = false, // + LARGEFILE: bool = false, + NOATIME: bool = false, + CLOEXEC: bool = false, // + SYNC: bool = false, + PATH: bool = false, + TMPFILE: bool = false, + _23: u9 = 0, + }, + .sparc64 => packed struct(u32) { + ACCMODE: ACCMODE = .RDONLY, + _2: u1 = 0, + APPEND: bool = false, + _4: u2 = 0, + ASYNC: bool = false, + _7: u2 = 0, + CREAT: bool = false, + TRUNC: bool = false, + EXCL: bool = false, // + _12: u1 = 0, + DSYNC: bool = false, + NONBLOCK: bool = false, // + NOCTTY: bool = false, + DIRECTORY: bool = false, + NOFOLLOW: bool = false, + _18: u2 = 0, + DIRECT: bool = false, // + NOATIME: bool = false, + CLOEXEC: bool = false, // + SYNC: bool = false, + PATH: bool = false, + TMPFILE: bool = false, + _27: u6 = 0, + }, + .mips, .mipsel, .mips64, .mips64el => packed struct(u32) { + ACCMODE: ACCMODE = .RDONLY, + _2: u1 = 0, + APPEND: bool = false, + DSYNC: bool = false, + _5: u2 = 0, + NONBLOCK: bool = false, // + CREAT: bool = false, + TRUNC: bool = false, + EXCL: bool = false, // + NOCTTY: bool = false, + ASYNC: bool = false, + LARGEFILE: bool = false, + SYNC: bool = false, + DIRECT: bool = false, // + DIRECTORY: bool = false, + NOFOLLOW: bool = false, + NOATIME: bool = false, // + CLOEXEC: bool = false, + _20: u1 = 0, + PATH: bool = false, + TMPFILE: bool = false, + _23: u9 = 0, + }, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => packed struct(u32) { + ACCMODE: ACCMODE = .RDONLY, + _2: u4 = 0, + CREAT: bool = false, + EXCL: bool = false, // + NOCTTY: bool = false, + TRUNC: bool = false, + APPEND: bool = false, + NONBLOCK: bool = false, // + DSYNC: bool = false, + ASYNC: bool = false, + DIRECTORY: bool = false, + NOFOLLOW: bool = false, + LARGEFILE: bool = false, + DIRECT: bool = false, // + NOATIME: bool = false, + CLOEXEC: bool = false, // + SYNC: bool = false, + PATH: bool = false, + TMPFILE: bool = false, + _23: u9 = 0, + }, + .hexagon, .or1k, .s390x => packed struct(u32) { + ACCMODE: ACCMODE = .RDONLY, + _2: u4 = 0, + CREAT: bool = false, + EXCL: bool = false, // + NOCTTY: bool = false, + TRUNC: bool = false, + APPEND: bool = false, + NONBLOCK: bool = false, // + DSYNC: bool = false, + ASYNC: bool = false, + DIRECT: bool = false, // + LARGEFILE: bool = false, + DIRECTORY: bool = false, + NOFOLLOW: bool = false, + NOATIME: bool = false, + CLOEXEC: bool = false, // + _20: u1 = 0, + PATH: bool = false, + _22: u10 = 0, + + // #define O_RSYNC 04010000 + // #define O_SYNC 04010000 + // #define O_TMPFILE 020200000 + // #define O_NDELAY O_NONBLOCK + }, + .m68k => packed struct(u32) { + ACCMODE: ACCMODE = .RDONLY, + _2: u4 = 0, + CREAT: bool = false, + EXCL: bool = false, // + NOCTTY: bool = false, + TRUNC: bool = false, + APPEND: bool = false, + NONBLOCK: bool = false, // + DSYNC: bool = false, + ASYNC: bool = false, + DIRECTORY: bool = false, + NOFOLLOW: bool = false, + DIRECT: bool = false, // + LARGEFILE: bool = false, + NOATIME: bool = false, + CLOEXEC: bool = false, // + _20: u1 = 0, + PATH: bool = false, + _22: u10 = 0, + }, + else => @compileError("missing std.os.linux.O constants for this architecture"), +}; + /// Set by startup code, used by `getauxval`. pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; @@ -745,7 +900,6 @@ pub fn futex_4arg(uaddr: *const u32, futex_op: FUTEX_OP, val: u32, timeout: ?*co /// most recently woken, nor...) /// /// Requires at least kernel v5.16. -// TODO: can't we use slices here? and assert `Futex2.waitone_max` pub fn futex2_waitv( /// The length of `futexes` slice must not exceed `Futex2.waitone_max` futexes: []const Futex2.WaitOne, @@ -3757,8 +3911,11 @@ pub const Futex2 = struct { /// `Bitset` with all bits set for the FUTEX_xxx_BITSET OPs to request a /// match of any bit. matches FUTEX_BITSET_MATCH_ANY - pub const match_any: Bitset = @bitCast(@as(u64, 0x00000000ffffffff)); - /// Bitset must not be empty, this is only useful in test + pub const match_any: Bitset = @bitCast(@as(u64, 0x0000_0000_ffff_ffff)); + /// An empty `Bitset` will not wake any threads because the kernel + /// requires at least one bit to be set in the bitmask to identify + /// which waiters should be woken up. Therefore, no action will be + /// taken if the bitset is zero, this is only useful in test pub const empty: Bitset = .{}; /// Create from raw u64 value diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index fd31e6794e..b9b731a5b9 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -1806,7 +1806,6 @@ pub fn futex_wait( return sqe; } -// TODO: ensure flags and Wait in futexv are correct /// Available since kernel 6.7 pub fn futex_waitv( self: *IoUring, @@ -1821,6 +1820,55 @@ pub fn futex_waitv( return sqe; } +pub fn fixed_fd_install( + self: *IoUring, + user_data: u64, + fd: linux.fd_t, + flags: uflags.FixedFd, +) !*Sqe { + const sqe = try self.get_sqe(); + sqe.prep_fixed_fd_install(fd, flags); + sqe.user_data = user_data; + return sqe; +} + +pub fn ftruncate( + self: *IoUring, + user_data: u64, + fd: linux.fd_t, + offset: u64, +) !*Sqe { + const sqe = try self.get_sqe(); + sqe.prep_ftruncate(fd, offset); + sqe.user_data = user_data; + return sqe; +} + +pub fn cmd_discard( + self: *IoUring, + user_data: u64, + fd: linux.fd_t, + offset: u64, + nbytes: u64, +) !*Sqe { + const sqe = try self.get_sqe(); + sqe.prep_cmd_discard(fd, offset, nbytes); + sqe.user_data = user_data; + return sqe; +} + +pub fn pipe( + self: *IoUring, + user_data: u64, + fds: *[2]linux.fd_t, + flags: uflags., +) !*Sqe { + const sqe = try self.get_sqe(); + sqe.prep_pipe(fds, offset, nbytes); + sqe.user_data = user_data; + return sqe; +} + pub fn register_buffers_sparse(self: *IoUring, nr: u32) !void { assert(self.fd >= 0); @@ -2332,7 +2380,7 @@ pub const Sqe = extern struct { /// msg_flags | timeout_flags | accept_flags | cancel_flags | open_flags | /// statx_flags | fadvise_advice | splice_flags | rename_flags | /// unlink_flags | hardlink_flags xattr_flags | msg_ring_flags | - /// uring_cmd_flags | waitid_flags | futex_flags install_fd_flags | + /// uring_cmd_flags | waitid_flags | futex_flags | install_fd_flags | /// nop_flags | pipe_flags rw_flags: u32, /// data to be passed back at completion time @@ -3323,6 +3371,73 @@ pub const Sqe = extern struct { sqe.rw_flags = flags; } + pub fn prep_fixed_fd_install( + sqe: *Sqe, + fd: linux.fd_t, + flags: uflags.FixedFd, + ) void { + sqe.prep_rw( + .fixed_fd_install, + fd, + undefined, + 0, + 0, + ); + sqe.flags = .{ .fixed_file = true }; + sqe.rw_flags = @bitCast(flags); + } + + pub fn prep_ftruncate( + sqe: *Sqe, + fd: linux.fd_t, + offset: u64, + ) void { + sqe.prep_rw( + .ftruncate, + fd, + undefined, + 0, + offset, + ); + } + + pub fn prep_cmd_discard( + sqe: *Sqe, + fd: linux.fd_t, + offset: u64, + nbytes: u64, + ) void { + sqe.prep_rw( + .uring_cmd, + fd, + undefined, + 0, + 0, + ); + // sqe.off maps to sqe.cmd_op in liburing + sqe.off = constants.BLOCK_URING_CMD_DISCARD; + sqe.addr = offset; + sqe.addr3 = nbytes; + } + + pub fn prep_pipe( + sqe: *Sqe, + fd: linux.fd_t, + offset: u64, + nbytes: u64, + ) void { + sqe.prep_rw( + .uring_cmd, + fd, + undefined, + 0, + 0, + ); + // sqe.off maps to sqe.cmd_op in liburing + sqe.off = constants.BLOCK_URING_CMD_DISCARD; + sqe.addr = offset; + sqe.addr3 = nbytes; + } // TODO: maybe remove unused flag fields? pub fn prep_bind( sqe: *Sqe, @@ -4143,6 +4258,10 @@ pub const ZcrxIfqRegister = extern struct { // COMMIT: move IoUring constants to Constants pub const constants = struct { + /// io_uring block file commands, see IORING_OP_URING_CMD. + /// It's a different number space from ioctl(), reuse the block's code 0x12. + /// It is the value of ioctl.IO(0x12, 0) at runtime + pub const BLOCK_URING_CMD_DISCARD = 0x1200; /// If sqe.file_index (splice_fd_in in Zig Struct) is set to this for /// opcodes that instantiate a new an available direct descriptor instead /// of having the application pass one direct descriptor