This commit is contained in:
Bernard Assan 2025-11-26 00:41:33 +08:00 committed by GitHub
commit 351afb5d9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 7161 additions and 4050 deletions

View file

@ -473,7 +473,6 @@ set(ZIG_STAGE2_SOURCES
lib/std/os/linux.zig lib/std/os/linux.zig
lib/std/os/linux.zig lib/std/os/linux.zig
lib/std/os/linux/IoUring.zig lib/std/os/linux/IoUring.zig
lib/std/os/linux/io_uring_sqe.zig
lib/std/os/linux/x86_64.zig lib/std/os/linux/x86_64.zig
lib/std/os/linux/x86_64.zig lib/std/os/linux/x86_64.zig
lib/std/os/windows.zig lib/std/os/windows.zig

View file

@ -24,7 +24,7 @@ pub fn cwd() Dir {
return switch (native_os) { return switch (native_os) {
.windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
.wasi => .{ .handle = std.options.wasiCwd() }, .wasi => .{ .handle = std.options.wasiCwd() },
else => .{ .handle = std.posix.AT.FDCWD }, else => .{ .handle = std.posix.At.fdcwd },
}; };
} }

View file

@ -1353,8 +1353,7 @@ fn dirStatPathLinux(
var path_buffer: [posix.PATH_MAX]u8 = undefined; var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer); const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
const flags: u32 = linux.AT.NO_AUTOMOUNT | const flags: linux.At = .{ .no_automount = true, .symlink_nofollow = if (!options.follow_symlinks) true else false };
@as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
while (true) { while (true) {
try t.checkCancel(); try t.checkCancel();
@ -1363,7 +1362,15 @@ fn dirStatPathLinux(
dir.handle, dir.handle,
sub_path_posix, sub_path_posix,
flags, flags,
linux.STATX_INO | linux.STATX_SIZE | linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME, .{
.ino = true,
.size = true,
.type = true,
.mode = true,
.atime = true,
.mtime = true,
.ctime = true,
},
&statx, &statx,
); );
switch (linux.errno(rc)) { switch (linux.errno(rc)) {
@ -1396,12 +1403,12 @@ fn dirStatPathPosix(
var path_buffer: [posix.PATH_MAX]u8 = undefined; var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer); const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; const flags: posix.At = .{ .symlink_nofollow = if (!options.follow_symlinks) true else false };
while (true) { while (true) {
try t.checkCancel(); try t.checkCancel();
var stat = std.mem.zeroes(posix.Stat); var stat = std.mem.zeroes(posix.Stat);
switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) { switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, @bitCast(flags)))) {
.SUCCESS => return statFromPosix(&stat), .SUCCESS => return statFromPosix(&stat),
.INTR => continue, .INTR => continue,
.CANCELED => return error.Canceled, .CANCELED => return error.Canceled,
@ -1509,8 +1516,16 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
const rc = linux.statx( const rc = linux.statx(
file.handle, file.handle,
"", "",
linux.AT.EMPTY_PATH, .{ .empty_path = true },
linux.STATX_INO | linux.STATX_SIZE | linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME, .{
.ino = true,
.size = true,
.type = true,
.mode = true,
.atime = true,
.mtime = true,
.ctime = true,
},
&statx, &statx,
); );
switch (linux.errno(rc)) { switch (linux.errno(rc)) {
@ -1617,7 +1632,7 @@ fn dirAccessPosix(
var path_buffer: [posix.PATH_MAX]u8 = undefined; var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer); const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0); const flags: posix.At = .{ .symlink_nofollow = if (!options.follow_symlinks) true else false };
const mode: u32 = const mode: u32 =
@as(u32, if (options.read) posix.R_OK else 0) | @as(u32, if (options.read) posix.R_OK else 0) |
@ -1626,7 +1641,7 @@ fn dirAccessPosix(
while (true) { while (true) {
try t.checkCancel(); try t.checkCancel();
switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) { switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, @bitCast(flags)))) {
.SUCCESS => return, .SUCCESS => return,
.INTR => continue, .INTR => continue,
.CANCELED => return error.Canceled, .CANCELED => return error.Canceled,
@ -2886,7 +2901,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr
fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelfExeError!Io.File { fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelfExeError!Io.File {
const t: *Threaded = @ptrCast(@alignCast(userdata)); const t: *Threaded = @ptrCast(@alignCast(userdata));
switch (native_os) { switch (native_os) {
.linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.At.fdcwd }, "/proc/self/exe", flags),
.windows => { .windows => {
// If ImagePathName is a symlink, then it will contain the path of the symlink, // If ImagePathName is a symlink, then it will contain the path of the symlink,
// not the path that the symlink points to. However, because we are opening // not the path that the symlink points to. However, because we are opening
@ -5941,7 +5956,7 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {
.linux => { .linux => {
const linux = std.os.linux; const linux = std.os.linux;
switch (linux.errno(linux.futex_3arg( switch (linux.errno(linux.futex_3arg(
&ptr.raw, ptr,
.{ .cmd = .WAKE, .private = true }, .{ .cmd = .WAKE, .private = true },
@min(max_waiters, std.math.maxInt(i32)), @min(max_waiters, std.math.maxInt(i32)),
))) { ))) {

View file

@ -1217,8 +1217,8 @@ const LinuxThreadImpl = struct {
thread: *ThreadCompletion, thread: *ThreadCompletion,
const ThreadCompletion = struct { const ThreadCompletion = struct {
completion: Completion = Completion.init(.running), completion: Completion = .init(.running),
child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1), child_tid: std.atomic.Value(i32) = .init(1),
parent_tid: i32 = undefined, parent_tid: i32 = undefined,
mapped: []align(std.heap.page_size_min) u8, mapped: []align(std.heap.page_size_min) u8,
@ -1663,7 +1663,7 @@ const LinuxThreadImpl = struct {
if (tid == 0) break; if (tid == 0) break;
switch (linux.errno(linux.futex_4arg( switch (linux.errno(linux.futex_4arg(
&self.thread.child_tid.raw, @ptrCast(&self.thread.child_tid),
.{ .cmd = .WAIT, .private = false }, .{ .cmd = .WAIT, .private = false },
@bitCast(tid), @bitCast(tid),
null, null,

View file

@ -263,7 +263,7 @@ const LinuxImpl = struct {
} }
const rc = linux.futex_4arg( const rc = linux.futex_4arg(
&ptr.raw, ptr,
.{ .cmd = .WAIT, .private = true }, .{ .cmd = .WAIT, .private = true },
expect, expect,
if (timeout != null) &ts else null, if (timeout != null) &ts else null,
@ -285,7 +285,7 @@ const LinuxImpl = struct {
fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
const rc = linux.futex_3arg( const rc = linux.futex_3arg(
&ptr.raw, ptr,
.{ .cmd = .WAKE, .private = true }, .{ .cmd = .WAKE, .private = true },
@min(max_waiters, std.math.maxInt(i32)), @min(max_waiters, std.math.maxInt(i32)),
); );

File diff suppressed because it is too large Load diff

View file

@ -187,7 +187,7 @@ pub fn cwd() Dir {
} else if (native_os == .wasi) { } else if (native_os == .wasi) {
return .{ .fd = std.options.wasiCwd() }; return .{ .fd = std.options.wasiCwd() };
} else { } else {
return .{ .fd = posix.AT.FDCWD }; return .{ .fd = posix.At.fdcwd };
} }
} }

View file

@ -148,7 +148,7 @@ pub const Iterator = switch (native_os) {
const stat_info = posix.fstatat( const stat_info = posix.fstatat(
self.dir.fd, self.dir.fd,
name, name,
posix.AT.SYMLINK_NOFOLLOW, .{ .symlink_nofollow = true },
) catch |err| switch (err) { ) catch |err| switch (err) {
error.NameTooLong => unreachable, error.NameTooLong => unreachable,
error.SymLinkLoop => unreachable, error.SymLinkLoop => unreachable,
@ -1112,7 +1112,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
// directory, so we need to handle that case specifically and translate the error // directory, so we need to handle that case specifically and translate the error
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => { .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
// Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them) // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e; const fstat = posix.fstatatZ(self.fd, sub_path_c, .{ .symlink_nofollow = true }) catch return e;
const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR; const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
return if (is_dir) error.IsDir else e; return if (is_dir) error.IsDir else e;
}, },
@ -1163,7 +1163,7 @@ pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.deleteDirW(sub_path_w.span()); return self.deleteDirW(sub_path_w.span());
} else if (native_os == .wasi and !builtin.link_libc) { } else if (native_os == .wasi and !builtin.link_libc) {
posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) { posix.unlinkat(self.fd, sub_path, posix.At.REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
else => |e| return e, else => |e| return e,
}; };
@ -1175,7 +1175,7 @@ pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
/// Same as `deleteDir` except the parameter is null-terminated. /// Same as `deleteDir` except the parameter is null-terminated.
pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void { pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) { posix.unlinkatZ(self.fd, sub_path_c, posix.At.REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
else => |e| return e, else => |e| return e,
}; };
@ -1184,7 +1184,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed. /// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
/// This function is Windows-only. /// This function is Windows-only.
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void { pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) { posix.unlinkatW(self.fd, sub_path_w, posix.At.REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
else => |e| return e, else => |e| return e,
}; };

View file

@ -213,34 +213,6 @@ pub const X_OK = 1;
pub const W_OK = 2; pub const W_OK = 2;
pub const R_OK = 4; pub const R_OK = 4;
pub const W = struct {
pub const NOHANG = 1;
pub const UNTRACED = 2;
pub const STOPPED = 2;
pub const EXITED = 4;
pub const CONTINUED = 8;
pub const NOWAIT = 0x1000000;
pub fn EXITSTATUS(s: u32) u8 {
return @as(u8, @intCast((s & 0xff00) >> 8));
}
pub fn TERMSIG(s: u32) u32 {
return s & 0x7f;
}
pub fn STOPSIG(s: u32) u32 {
return EXITSTATUS(s);
}
pub fn IFEXITED(s: u32) bool {
return TERMSIG(s) == 0;
}
pub fn IFSTOPPED(s: u32) bool {
return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00;
}
pub fn IFSIGNALED(s: u32) bool {
return (s & 0xffff) -% 1 < 0xff;
}
};
pub const Flock = extern struct { pub const Flock = extern struct {
type: i16, type: i16,
whence: i16, whence: i16,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,679 +0,0 @@
//! Contains only the definition of `io_uring_sqe`.
//! Split into its own file to compartmentalize the initialization methods.
const std = @import("../../std.zig");
const linux = std.os.linux;
pub const io_uring_sqe = extern struct {
opcode: linux.IORING_OP,
flags: u8,
ioprio: u16,
fd: i32,
off: u64,
addr: u64,
len: u32,
rw_flags: u32,
user_data: u64,
buf_index: u16,
personality: u16,
splice_fd_in: i32,
addr3: u64,
resv: u64,
pub fn prep_nop(sqe: *linux.io_uring_sqe) void {
sqe.* = .{
.opcode = .NOP,
.flags = 0,
.ioprio = 0,
.fd = 0,
.off = 0,
.addr = 0,
.len = 0,
.rw_flags = 0,
.user_data = 0,
.buf_index = 0,
.personality = 0,
.splice_fd_in = 0,
.addr3 = 0,
.resv = 0,
};
}
pub fn prep_fsync(sqe: *linux.io_uring_sqe, fd: linux.fd_t, flags: u32) void {
sqe.* = .{
.opcode = .FSYNC,
.flags = 0,
.ioprio = 0,
.fd = fd,
.off = 0,
.addr = 0,
.len = 0,
.rw_flags = flags,
.user_data = 0,
.buf_index = 0,
.personality = 0,
.splice_fd_in = 0,
.addr3 = 0,
.resv = 0,
};
}
pub fn prep_rw(
sqe: *linux.io_uring_sqe,
op: linux.IORING_OP,
fd: linux.fd_t,
addr: u64,
len: usize,
offset: u64,
) void {
sqe.* = .{
.opcode = op,
.flags = 0,
.ioprio = 0,
.fd = fd,
.off = offset,
.addr = addr,
.len = @intCast(len),
.rw_flags = 0,
.user_data = 0,
.buf_index = 0,
.personality = 0,
.splice_fd_in = 0,
.addr3 = 0,
.resv = 0,
};
}
pub fn prep_read(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []u8, offset: u64) void {
sqe.prep_rw(.READ, fd, @intFromPtr(buffer.ptr), buffer.len, offset);
}
pub fn prep_write(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, offset: u64) void {
sqe.prep_rw(.WRITE, fd, @intFromPtr(buffer.ptr), buffer.len, offset);
}
pub fn prep_splice(sqe: *linux.io_uring_sqe, fd_in: linux.fd_t, off_in: u64, fd_out: linux.fd_t, off_out: u64, len: usize) void {
sqe.prep_rw(.SPLICE, fd_out, undefined, len, off_out);
sqe.addr = off_in;
sqe.splice_fd_in = fd_in;
}
pub fn prep_readv(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
iovecs: []const std.posix.iovec,
offset: u64,
) void {
sqe.prep_rw(.READV, fd, @intFromPtr(iovecs.ptr), iovecs.len, offset);
}
pub fn prep_writev(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
iovecs: []const std.posix.iovec_const,
offset: u64,
) void {
sqe.prep_rw(.WRITEV, fd, @intFromPtr(iovecs.ptr), iovecs.len, offset);
}
pub fn prep_read_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: *std.posix.iovec, offset: u64, buffer_index: u16) void {
sqe.prep_rw(.READ_FIXED, fd, @intFromPtr(buffer.base), buffer.len, offset);
sqe.buf_index = buffer_index;
}
pub fn prep_write_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: *std.posix.iovec, offset: u64, buffer_index: u16) void {
sqe.prep_rw(.WRITE_FIXED, fd, @intFromPtr(buffer.base), buffer.len, offset);
sqe.buf_index = buffer_index;
}
pub fn prep_accept(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
addr: ?*linux.sockaddr,
addrlen: ?*linux.socklen_t,
flags: u32,
) void {
// `addr` holds a pointer to `sockaddr`, and `addr2` holds a pointer to socklen_t`.
// `addr2` maps to `sqe.off` (u64) instead of `sqe.len` (which is only a u32).
sqe.prep_rw(.ACCEPT, fd, @intFromPtr(addr), 0, @intFromPtr(addrlen));
sqe.rw_flags = flags;
}
pub fn prep_accept_direct(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
addr: ?*linux.sockaddr,
addrlen: ?*linux.socklen_t,
flags: u32,
file_index: u32,
) void {
prep_accept(sqe, fd, addr, addrlen, flags);
__io_uring_set_target_fixed_file(sqe, file_index);
}
pub fn prep_multishot_accept_direct(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
addr: ?*linux.sockaddr,
addrlen: ?*linux.socklen_t,
flags: u32,
) void {
prep_multishot_accept(sqe, fd, addr, addrlen, flags);
__io_uring_set_target_fixed_file(sqe, linux.IORING_FILE_INDEX_ALLOC);
}
fn __io_uring_set_target_fixed_file(sqe: *linux.io_uring_sqe, file_index: u32) void {
const sqe_file_index: u32 = if (file_index == linux.IORING_FILE_INDEX_ALLOC)
linux.IORING_FILE_INDEX_ALLOC
else
// 0 means no fixed files, indexes should be encoded as "index + 1"
file_index + 1;
// This filed is overloaded in liburing:
// splice_fd_in: i32
// sqe_file_index: u32
sqe.splice_fd_in = @bitCast(sqe_file_index);
}
pub fn prep_connect(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
addr: *const linux.sockaddr,
addrlen: linux.socklen_t,
) void {
// `addrlen` maps to `sqe.off` (u64) instead of `sqe.len` (which is only a u32).
sqe.prep_rw(.CONNECT, fd, @intFromPtr(addr), 0, addrlen);
}
pub fn prep_epoll_ctl(
sqe: *linux.io_uring_sqe,
epfd: linux.fd_t,
fd: linux.fd_t,
op: u32,
ev: ?*linux.epoll_event,
) void {
sqe.prep_rw(.EPOLL_CTL, epfd, @intFromPtr(ev), op, @intCast(fd));
}
pub fn prep_recv(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []u8, flags: u32) void {
sqe.prep_rw(.RECV, fd, @intFromPtr(buffer.ptr), buffer.len, 0);
sqe.rw_flags = flags;
}
pub fn prep_recv_multishot(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
buffer: []u8,
flags: u32,
) void {
sqe.prep_recv(fd, buffer, flags);
sqe.ioprio |= linux.IORING_RECV_MULTISHOT;
}
pub fn prep_recvmsg(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
msg: *linux.msghdr,
flags: u32,
) void {
sqe.prep_rw(.RECVMSG, fd, @intFromPtr(msg), 1, 0);
sqe.rw_flags = flags;
}
pub fn prep_recvmsg_multishot(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
msg: *linux.msghdr,
flags: u32,
) void {
sqe.prep_recvmsg(fd, msg, flags);
sqe.ioprio |= linux.IORING_RECV_MULTISHOT;
}
pub fn prep_send(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, flags: u32) void {
sqe.prep_rw(.SEND, fd, @intFromPtr(buffer.ptr), buffer.len, 0);
sqe.rw_flags = flags;
}
pub fn prep_send_zc(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, flags: u32, zc_flags: u16) void {
sqe.prep_rw(.SEND_ZC, fd, @intFromPtr(buffer.ptr), buffer.len, 0);
sqe.rw_flags = flags;
sqe.ioprio = zc_flags;
}
pub fn prep_send_zc_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, flags: u32, zc_flags: u16, buf_index: u16) void {
prep_send_zc(sqe, fd, buffer, flags, zc_flags);
sqe.ioprio |= linux.IORING_RECVSEND_FIXED_BUF;
sqe.buf_index = buf_index;
}
pub fn prep_sendmsg_zc(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
msg: *const linux.msghdr_const,
flags: u32,
) void {
prep_sendmsg(sqe, fd, msg, flags);
sqe.opcode = .SENDMSG_ZC;
}
pub fn prep_sendmsg(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
msg: *const linux.msghdr_const,
flags: u32,
) void {
sqe.prep_rw(.SENDMSG, fd, @intFromPtr(msg), 1, 0);
sqe.rw_flags = flags;
}
pub fn prep_openat(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
path: [*:0]const u8,
flags: linux.O,
mode: linux.mode_t,
) void {
sqe.prep_rw(.OPENAT, fd, @intFromPtr(path), mode, 0);
sqe.rw_flags = @bitCast(flags);
}
pub fn prep_openat_direct(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
path: [*:0]const u8,
flags: linux.O,
mode: linux.mode_t,
file_index: u32,
) void {
prep_openat(sqe, fd, path, flags, mode);
__io_uring_set_target_fixed_file(sqe, file_index);
}
pub fn prep_close(sqe: *linux.io_uring_sqe, fd: linux.fd_t) void {
sqe.* = .{
.opcode = .CLOSE,
.flags = 0,
.ioprio = 0,
.fd = fd,
.off = 0,
.addr = 0,
.len = 0,
.rw_flags = 0,
.user_data = 0,
.buf_index = 0,
.personality = 0,
.splice_fd_in = 0,
.addr3 = 0,
.resv = 0,
};
}
pub fn prep_close_direct(sqe: *linux.io_uring_sqe, file_index: u32) void {
prep_close(sqe, 0);
__io_uring_set_target_fixed_file(sqe, file_index);
}
pub fn prep_timeout(
sqe: *linux.io_uring_sqe,
ts: *const linux.kernel_timespec,
count: u32,
flags: u32,
) void {
sqe.prep_rw(.TIMEOUT, -1, @intFromPtr(ts), 1, count);
sqe.rw_flags = flags;
}
pub fn prep_timeout_remove(sqe: *linux.io_uring_sqe, timeout_user_data: u64, flags: u32) void {
sqe.* = .{
.opcode = .TIMEOUT_REMOVE,
.flags = 0,
.ioprio = 0,
.fd = -1,
.off = 0,
.addr = timeout_user_data,
.len = 0,
.rw_flags = flags,
.user_data = 0,
.buf_index = 0,
.personality = 0,
.splice_fd_in = 0,
.addr3 = 0,
.resv = 0,
};
}
pub fn prep_link_timeout(
sqe: *linux.io_uring_sqe,
ts: *const linux.kernel_timespec,
flags: u32,
) void {
sqe.prep_rw(.LINK_TIMEOUT, -1, @intFromPtr(ts), 1, 0);
sqe.rw_flags = flags;
}
pub fn prep_poll_add(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
poll_mask: u32,
) void {
sqe.prep_rw(.POLL_ADD, fd, @intFromPtr(@as(?*anyopaque, null)), 0, 0);
// Poll masks previously used to comprise of 16 bits in the flags union of
// a SQE, but were then extended to comprise of 32 bits in order to make
// room for additional option flags. To ensure that the correct bits of
// poll masks are consistently and properly read across multiple kernel
// versions, poll masks are enforced to be little-endian.
// https://www.spinics.net/lists/io-uring/msg02848.html
sqe.rw_flags = std.mem.nativeToLittle(u32, poll_mask);
}
pub fn prep_poll_remove(
sqe: *linux.io_uring_sqe,
target_user_data: u64,
) void {
sqe.prep_rw(.POLL_REMOVE, -1, target_user_data, 0, 0);
}
pub fn prep_poll_update(
sqe: *linux.io_uring_sqe,
old_user_data: u64,
new_user_data: u64,
poll_mask: u32,
flags: u32,
) void {
sqe.prep_rw(.POLL_REMOVE, -1, old_user_data, flags, new_user_data);
// Poll masks previously used to comprise of 16 bits in the flags union of
// a SQE, but were then extended to comprise of 32 bits in order to make
// room for additional option flags. To ensure that the correct bits of
// poll masks are consistently and properly read across multiple kernel
// versions, poll masks are enforced to be little-endian.
// https://www.spinics.net/lists/io-uring/msg02848.html
sqe.rw_flags = std.mem.nativeToLittle(u32, poll_mask);
}
pub fn prep_fallocate(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
mode: i32,
offset: u64,
len: u64,
) void {
sqe.* = .{
.opcode = .FALLOCATE,
.flags = 0,
.ioprio = 0,
.fd = fd,
.off = offset,
.addr = len,
.len = @intCast(mode),
.rw_flags = 0,
.user_data = 0,
.buf_index = 0,
.personality = 0,
.splice_fd_in = 0,
.addr3 = 0,
.resv = 0,
};
}
pub fn prep_statx(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
path: [*:0]const u8,
flags: u32,
mask: u32,
buf: *linux.Statx,
) void {
sqe.prep_rw(.STATX, fd, @intFromPtr(path), mask, @intFromPtr(buf));
sqe.rw_flags = flags;
}
pub fn prep_cancel(
sqe: *linux.io_uring_sqe,
cancel_user_data: u64,
flags: u32,
) void {
sqe.prep_rw(.ASYNC_CANCEL, -1, cancel_user_data, 0, 0);
sqe.rw_flags = flags;
}
pub fn prep_cancel_fd(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
flags: u32,
) void {
sqe.prep_rw(.ASYNC_CANCEL, fd, 0, 0, 0);
sqe.rw_flags = flags | linux.IORING_ASYNC_CANCEL_FD;
}
pub fn prep_shutdown(
sqe: *linux.io_uring_sqe,
sockfd: linux.socket_t,
how: u32,
) void {
sqe.prep_rw(.SHUTDOWN, sockfd, 0, how, 0);
}
pub fn prep_renameat(
sqe: *linux.io_uring_sqe,
old_dir_fd: linux.fd_t,
old_path: [*:0]const u8,
new_dir_fd: linux.fd_t,
new_path: [*:0]const u8,
flags: u32,
) void {
sqe.prep_rw(
.RENAMEAT,
old_dir_fd,
@intFromPtr(old_path),
0,
@intFromPtr(new_path),
);
sqe.len = @bitCast(new_dir_fd);
sqe.rw_flags = flags;
}
pub fn prep_unlinkat(
sqe: *linux.io_uring_sqe,
dir_fd: linux.fd_t,
path: [*:0]const u8,
flags: u32,
) void {
sqe.prep_rw(.UNLINKAT, dir_fd, @intFromPtr(path), 0, 0);
sqe.rw_flags = flags;
}
pub fn prep_mkdirat(
sqe: *linux.io_uring_sqe,
dir_fd: linux.fd_t,
path: [*:0]const u8,
mode: linux.mode_t,
) void {
sqe.prep_rw(.MKDIRAT, dir_fd, @intFromPtr(path), mode, 0);
}
pub fn prep_symlinkat(
sqe: *linux.io_uring_sqe,
target: [*:0]const u8,
new_dir_fd: linux.fd_t,
link_path: [*:0]const u8,
) void {
sqe.prep_rw(
.SYMLINKAT,
new_dir_fd,
@intFromPtr(target),
0,
@intFromPtr(link_path),
);
}
pub fn prep_linkat(
sqe: *linux.io_uring_sqe,
old_dir_fd: linux.fd_t,
old_path: [*:0]const u8,
new_dir_fd: linux.fd_t,
new_path: [*:0]const u8,
flags: u32,
) void {
sqe.prep_rw(
.LINKAT,
old_dir_fd,
@intFromPtr(old_path),
0,
@intFromPtr(new_path),
);
sqe.len = @bitCast(new_dir_fd);
sqe.rw_flags = flags;
}
pub fn prep_files_update(
sqe: *linux.io_uring_sqe,
fds: []const linux.fd_t,
offset: u32,
) void {
sqe.prep_rw(.FILES_UPDATE, -1, @intFromPtr(fds.ptr), fds.len, @intCast(offset));
}
pub fn prep_files_update_alloc(
sqe: *linux.io_uring_sqe,
fds: []linux.fd_t,
) void {
sqe.prep_rw(.FILES_UPDATE, -1, @intFromPtr(fds.ptr), fds.len, linux.IORING_FILE_INDEX_ALLOC);
}
pub fn prep_provide_buffers(
sqe: *linux.io_uring_sqe,
buffers: [*]u8,
buffer_len: usize,
num: usize,
group_id: usize,
buffer_id: usize,
) void {
const ptr = @intFromPtr(buffers);
sqe.prep_rw(.PROVIDE_BUFFERS, @intCast(num), ptr, buffer_len, buffer_id);
sqe.buf_index = @intCast(group_id);
}
pub fn prep_remove_buffers(
sqe: *linux.io_uring_sqe,
num: usize,
group_id: usize,
) void {
sqe.prep_rw(.REMOVE_BUFFERS, @intCast(num), 0, 0, 0);
sqe.buf_index = @intCast(group_id);
}
pub fn prep_multishot_accept(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
addr: ?*linux.sockaddr,
addrlen: ?*linux.socklen_t,
flags: u32,
) void {
prep_accept(sqe, fd, addr, addrlen, flags);
sqe.ioprio |= linux.IORING_ACCEPT_MULTISHOT;
}
pub fn prep_socket(
sqe: *linux.io_uring_sqe,
domain: u32,
socket_type: u32,
protocol: u32,
flags: u32,
) void {
sqe.prep_rw(.SOCKET, @intCast(domain), 0, protocol, socket_type);
sqe.rw_flags = flags;
}
pub fn prep_socket_direct(
sqe: *linux.io_uring_sqe,
domain: u32,
socket_type: u32,
protocol: u32,
flags: u32,
file_index: u32,
) void {
prep_socket(sqe, domain, socket_type, protocol, flags);
__io_uring_set_target_fixed_file(sqe, file_index);
}
pub fn prep_socket_direct_alloc(
sqe: *linux.io_uring_sqe,
domain: u32,
socket_type: u32,
protocol: u32,
flags: u32,
) void {
prep_socket(sqe, domain, socket_type, protocol, flags);
__io_uring_set_target_fixed_file(sqe, linux.IORING_FILE_INDEX_ALLOC);
}
pub fn prep_waitid(
sqe: *linux.io_uring_sqe,
id_type: linux.P,
id: i32,
infop: *linux.siginfo_t,
options: u32,
flags: u32,
) void {
sqe.prep_rw(.WAITID, id, 0, @intFromEnum(id_type), @intFromPtr(infop));
sqe.rw_flags = flags;
sqe.splice_fd_in = @bitCast(options);
}
pub fn prep_bind(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
addr: *const linux.sockaddr,
addrlen: linux.socklen_t,
flags: u32,
) void {
sqe.prep_rw(.BIND, fd, @intFromPtr(addr), 0, addrlen);
sqe.rw_flags = flags;
}
pub fn prep_listen(
sqe: *linux.io_uring_sqe,
fd: linux.fd_t,
backlog: usize,
flags: u32,
) void {
sqe.prep_rw(.LISTEN, fd, 0, backlog, 0);
sqe.rw_flags = flags;
}
pub fn prep_cmd_sock(
sqe: *linux.io_uring_sqe,
cmd_op: linux.IO_URING_SOCKET_OP,
fd: linux.fd_t,
level: u32,
optname: u32,
optval: u64,
optlen: u32,
) void {
sqe.prep_rw(.URING_CMD, fd, 0, 0, 0);
// off is overloaded with cmd_op, https://github.com/axboe/liburing/blob/e1003e496e66f9b0ae06674869795edf772d5500/src/include/liburing/io_uring.h#L39
sqe.off = @intFromEnum(cmd_op);
// addr is overloaded, https://github.com/axboe/liburing/blob/e1003e496e66f9b0ae06674869795edf772d5500/src/include/liburing/io_uring.h#L46
sqe.addr = @bitCast(packed struct {
level: u32,
optname: u32,
}{
.level = level,
.optname = optname,
});
// splice_fd_in if overloaded u32 -> i32
sqe.splice_fd_in = @bitCast(optlen);
// addr3 is overloaded, https://github.com/axboe/liburing/blob/e1003e496e66f9b0ae06674869795edf772d5500/src/include/liburing/io_uring.h#L102
sqe.addr3 = optval;
}
pub fn set_flags(sqe: *linux.io_uring_sqe, flags: u8) void {
sqe.flags |= flags;
}
/// This SQE forms a link with the next SQE in the submission ring. Next SQE
/// will not be started before this one completes. Forms a chain of SQEs.
pub fn link_next(sqe: *linux.io_uring_sqe) void {
sqe.flags |= linux.IOSQE_IO_LINK;
}
};

View file

@ -45,34 +45,33 @@ test "timer" {
var err: linux.E = linux.errno(epoll_fd); var err: linux.E = linux.errno(epoll_fd);
try expect(err == .SUCCESS); try expect(err == .SUCCESS);
const timer_fd = linux.timerfd_create(linux.TIMERFD_CLOCK.MONOTONIC, .{}); const timer_fd = linux.timerfd_create(.MONOTONIC, .{});
try expect(linux.errno(timer_fd) == .SUCCESS); try expect(linux.errno(timer_fd) == .SUCCESS);
const time_interval = linux.timespec{ const time_interval: linux.timespec = .{
.sec = 0, .sec = 0,
.nsec = 2000000, .nsec = 2000000,
}; };
const new_time = linux.itimerspec{ const new_time: linux.itimerspec = .{
.it_interval = time_interval, .it_interval = time_interval,
.it_value = time_interval, .it_value = time_interval,
}; };
err = linux.errno(linux.timerfd_settime(@as(i32, @intCast(timer_fd)), .{}, &new_time, null)); err = linux.errno(linux.timerfd_settime(@intCast(timer_fd), .{}, &new_time, null));
try expect(err == .SUCCESS); try expect(err == .SUCCESS);
var event = linux.epoll_event{ var event: linux.epoll_event = .{
.events = linux.EPOLL.IN | linux.EPOLL.OUT | linux.EPOLL.ET, .events = linux.EPOLL.IN | linux.EPOLL.OUT | linux.EPOLL.ET,
.data = linux.epoll_data{ .ptr = 0 }, .data = .{ .ptr = 0 },
}; };
err = linux.errno(linux.epoll_ctl(@as(i32, @intCast(epoll_fd)), linux.EPOLL.CTL_ADD, @as(i32, @intCast(timer_fd)), &event)); err = linux.errno(linux.epoll_ctl(@intCast(epoll_fd), .ctl_add, @intCast(timer_fd), &event));
try expect(err == .SUCCESS); try expect(err == .SUCCESS);
const events_one: linux.epoll_event = undefined; var events: [8]linux.epoll_event = @splat(undefined);
var events = [_]linux.epoll_event{events_one} ** 8;
err = linux.errno(linux.epoll_wait(@as(i32, @intCast(epoll_fd)), &events, 8, -1)); err = linux.errno(linux.epoll_wait(@intCast(epoll_fd), &events, 8, -1));
try expect(err == .SUCCESS); try expect(err == .SUCCESS);
} }
@ -85,7 +84,7 @@ test "statx" {
defer file.close(); defer file.close();
var statx_buf: linux.Statx = undefined; var statx_buf: linux.Statx = undefined;
switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) { switch (linux.errno(linux.statx(file.handle, "", .{ .empty_path = true }, linux.Statx.Mask.basic_stats, &statx_buf))) {
.SUCCESS => {}, .SUCCESS => {},
else => unreachable, else => unreachable,
} }
@ -93,17 +92,17 @@ test "statx" {
if (builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) return error.SkipZigTest; // No fstatat, so the rest of the test is meaningless. if (builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) return error.SkipZigTest; // No fstatat, so the rest of the test is meaningless.
var stat_buf: linux.Stat = undefined; var stat_buf: linux.Stat = undefined;
switch (linux.errno(linux.fstatat(file.handle, "", &stat_buf, linux.AT.EMPTY_PATH))) { switch (linux.errno(linux.fstatat(file.handle, "", &stat_buf, .{ .empty_path = true }))) {
.SUCCESS => {}, .SUCCESS => {},
else => unreachable, else => unreachable,
} }
try expect(stat_buf.mode == statx_buf.mode); try expect(stat_buf.mode == statx_buf.mode);
try expect(@as(u32, @bitCast(stat_buf.uid)) == statx_buf.uid); try expect(stat_buf.uid == statx_buf.uid);
try expect(@as(u32, @bitCast(stat_buf.gid)) == statx_buf.gid); try expect(stat_buf.gid == statx_buf.gid);
try expect(@as(u64, @bitCast(@as(i64, stat_buf.size))) == statx_buf.size); try expect(stat_buf.size == statx_buf.size);
try expect(@as(u64, @bitCast(@as(i64, stat_buf.blksize))) == statx_buf.blksize); try expect(stat_buf.blksize == statx_buf.blksize);
try expect(@as(u64, @bitCast(@as(i64, stat_buf.blocks))) == statx_buf.blocks); try expect(stat_buf.blocks == statx_buf.blocks);
} }
test "user and group ids" { test "user and group ids" {
@ -190,39 +189,39 @@ comptime {
assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true }))); assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
// Check futex_param4 union is packed correctly // Check futex_param4 union is packed correctly
const param_union = linux.futex_param4{ const param_union: linux.futex_param4 = .{
.val2 = 0xaabbcc, .val2 = 0xaabbcc,
}; };
assert(@intFromPtr(param_union.timeout) == 0xaabbcc); assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
} }
test "futex v1" { test "futex v1" {
var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); var lock: std.atomic.Value(u32) = .init(1);
var rc: usize = 0; var rc: usize = 0;
// No-op wait, lock value is not expected value // No-op wait, lock value is not expected value
rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, .{ .timeout = null }, null, 0); rc = linux.futex(&lock, .{ .cmd = .WAIT, .private = true }, 2, .{ .timeout = null }, null, 0);
try expectEqual(.AGAIN, linux.errno(rc)); try expectEqual(.AGAIN, linux.errno(rc));
rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, null); rc = linux.futex_4arg(&lock, .{ .cmd = .WAIT, .private = true }, 2, null);
try expectEqual(.AGAIN, linux.errno(rc)); try expectEqual(.AGAIN, linux.errno(rc));
// Short-fuse wait, timeout kicks in // Short-fuse wait, timeout kicks in
rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, .{ .timeout = &.{ .sec = 0, .nsec = 2 } }, null, 0); rc = linux.futex(&lock, .{ .cmd = .WAIT, .private = true }, 1, .{ .timeout = &.{ .sec = 0, .nsec = 2 } }, null, 0);
try expectEqual(.TIMEDOUT, linux.errno(rc)); try expectEqual(.TIMEDOUT, linux.errno(rc));
rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, &.{ .sec = 0, .nsec = 2 }); rc = linux.futex_4arg(&lock, .{ .cmd = .WAIT, .private = true }, 1, &.{ .sec = 0, .nsec = 2 });
try expectEqual(.TIMEDOUT, linux.errno(rc)); try expectEqual(.TIMEDOUT, linux.errno(rc));
// Wakeup (no waiters) // Wakeup (no waiters)
rc = linux.futex(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2, .{ .timeout = null }, null, 0); rc = linux.futex(&lock, .{ .cmd = .WAKE, .private = true }, 2, .{ .timeout = null }, null, 0);
try expectEqual(0, rc); try expectEqual(0, rc);
rc = linux.futex_3arg(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2); rc = linux.futex_3arg(&lock, .{ .cmd = .WAKE, .private = true }, 2);
try expectEqual(0, rc); try expectEqual(0, rc);
// CMP_REQUEUE - val3 mismatch // CMP_REQUEUE - val3 mismatch
rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, 2, .{ .val2 = 0 }, null, 99); rc = linux.futex(&lock, .{ .cmd = .CMP_REQUEUE, .private = true }, 2, .{ .val2 = 0 }, null, 99);
try expectEqual(.AGAIN, linux.errno(rc)); try expectEqual(.AGAIN, linux.errno(rc));
// CMP_REQUEUE - requeue (but no waiters, so ... not much) // CMP_REQUEUE - requeue (but no waiters, so ... not much)
@ -230,14 +229,14 @@ test "futex v1" {
const val3 = 1; const val3 = 1;
const wake_nr = 3; const wake_nr = 3;
const requeue_max = std.math.maxInt(u31); const requeue_max = std.math.maxInt(u31);
var target_lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); const target_lock: std.atomic.Value(u32) = .init(1);
rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, wake_nr, .{ .val2 = requeue_max }, &target_lock.raw, val3); rc = linux.futex(&lock, .{ .cmd = .CMP_REQUEUE, .private = true }, wake_nr, .{ .val2 = requeue_max }, &target_lock, val3);
try expectEqual(0, rc); try expectEqual(0, rc);
} }
// WAKE_OP - just to see if we can construct the arguments ... // WAKE_OP - just to see if we can construct the arguments ...
{ {
var lock2: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); const lock2: std.atomic.Value(u32) = .init(1);
const wake1_nr = 2; const wake1_nr = 2;
const wake2_nr = 3; const wake2_nr = 3;
const wake_op = linux.FUTEX_WAKE_OP{ const wake_op = linux.FUTEX_WAKE_OP{
@ -248,65 +247,66 @@ test "futex v1" {
.cmdarg = 5, .cmdarg = 5,
}; };
rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_OP, .private = true }, wake1_nr, .{ .val2 = wake2_nr }, &lock2.raw, @bitCast(wake_op)); rc = linux.futex(&lock, .{ .cmd = .WAKE_OP, .private = true }, wake1_nr, .{ .val2 = wake2_nr }, &lock2, @bitCast(wake_op));
try expectEqual(0, rc); try expectEqual(0, rc);
} }
// WAIT_BITSET // WAIT_BITSET
{ {
// val1 return early // val1 return early
rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff); rc = linux.futex(&lock, .{ .cmd = .WAIT_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff);
try expectEqual(.AGAIN, linux.errno(rc)); try expectEqual(.AGAIN, linux.errno(rc));
// timeout wait // timeout wait
const timeout: linux.timespec = .{ .sec = 0, .nsec = 2 }; const timeout: linux.timespec = .{ .sec = 0, .nsec = 2 };
rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 1, .{ .timeout = &timeout }, null, 0xfff); rc = linux.futex(&lock, .{ .cmd = .WAIT_BITSET, .private = true }, 1, .{ .timeout = &timeout }, null, 0xfff);
try expectEqual(.TIMEDOUT, linux.errno(rc)); try expectEqual(.TIMEDOUT, linux.errno(rc));
} }
// WAKE_BITSET // WAKE_BITSET
{ {
rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff000); rc = linux.futex(&lock, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff000);
try expectEqual(0, rc); try expectEqual(0, rc);
// bitmask must have at least 1 bit set: // bitmask must have at least 1 bit set:
rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0); rc = linux.futex(&lock, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0);
try expectEqual(.INVAL, linux.errno(rc)); try expectEqual(.INVAL, linux.errno(rc));
} }
} }
comptime { comptime {
assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false }))); std.debug.assert(2 == @as(u32, @bitCast(linux.Futex2.Wait{ .size = .U32, .private = false })));
assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true }))); std.debug.assert(128 == @as(u32, @bitCast(linux.Futex2.Wait{ .size = @enumFromInt(0), .private = true })));
} }
test "futex2_waitv" { test "futex2_waitv" {
const locks = [_]std.atomic.Value(u32){ const locks: [3]std.atomic.Value(u32) = .{
std.atomic.Value(u32).init(1), .init(1),
std.atomic.Value(u32).init(1), .init(1),
std.atomic.Value(u32).init(1), .init(1),
}; };
const futexes = [_]linux.futex2_waitone{ const futexes: [3]linux.Futex2.WaitOne = .{
.{ .{
.val = 1, .val = 1,
.uaddr = @intFromPtr(&locks[0].raw), .uaddr = @intFromPtr(&locks[0]),
.flags = .{ .size = .U32, .private = true }, .flags = .{ .size = .U32, .private = true },
}, },
.{ .{
.val = 1, .val = 1,
.uaddr = @intFromPtr(&locks[1].raw), .uaddr = @intFromPtr(&locks[1]),
.flags = .{ .size = .U32, .private = true }, .flags = .{ .size = .U32, .private = true },
}, },
.{ .{
.val = 1, .val = 1,
.uaddr = @intFromPtr(&locks[2].raw), .uaddr = @intFromPtr(&locks[2]),
.flags = .{ .size = .U32, .private = true }, .flags = .{ .size = .U32, .private = true },
}, },
}; };
const timeout = linux.kernel_timespec{ .sec = 0, .nsec = 2 }; // absolute timeout, so this is 1970... // absolute timeout, so this is 1970...
const rc = linux.futex2_waitv(&futexes, futexes.len, .{}, &timeout, .MONOTONIC); const timeout: linux.kernel_timespec = .{ .sec = 0, .nsec = 2 };
const rc = linux.futex2_waitv(futexes[0..], .{}, &timeout, .MONOTONIC);
switch (linux.errno(rc)) { switch (linux.errno(rc)) {
.NOSYS => return error.SkipZigTest, // futex2_waitv added in kernel v5.16 .NOSYS => return error.SkipZigTest, // futex2_waitv added in kernel v5.16
else => |err| try expectEqual(.TIMEDOUT, err), else => |err| try expectEqual(.TIMEDOUT, err),
@ -316,40 +316,40 @@ test "futex2_waitv" {
// Futex v2 API is only supported on recent kernels (v6.7), so skip tests if the syscalls // Futex v2 API is only supported on recent kernels (v6.7), so skip tests if the syscalls
// return ENOSYS. // return ENOSYS.
fn futex2_skip_if_unsupported() !void { fn futex2_skip_if_unsupported() !void {
const lock: u32 = 0; const lock: std.atomic.Value(u32) = .init(0);
const rc = linux.futex2_wake(&lock, 0, 1, .{ .size = .U32, .private = true }); const rc = linux.futex2_wake(&lock, .empty, 1, .{ .size = .U32, .private = true });
if (linux.errno(rc) == .NOSYS) { if (linux.errno(rc) == .NOSYS) {
return error.SkipZigTest; return error.SkipZigTest;
} }
} }
test "futex2_wait" { test "futex2_wait" {
var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); const lock: std.atomic.Value(u32) = .init(1);
var rc: usize = 0; var rc: usize = 0;
const mask = 0x1; const mask: linux.Futex2.Bitset = .{ .waiter1 = true };
try futex2_skip_if_unsupported(); try futex2_skip_if_unsupported();
// The API for 8,16,64 bit futexes is defined, but as of kernel v6.14 // The API for 8,16,64 bit futexes is defined, but as of kernel v6.14
// (at least) they're not implemented. // (at least) they're not implemented.
if (false) { if (false) {
rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U8, .private = true }, null, .MONOTONIC); rc = linux.futex2_wait(&lock, 1, mask, .{ .size = .U8, .private = true }, null, .MONOTONIC);
try expectEqual(.INVAL, linux.errno(rc)); try expectEqual(.INVAL, linux.errno(rc));
rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U16, .private = true }, null, .MONOTONIC); rc = linux.futex2_wait(&lock, 1, mask, .{ .size = .U16, .private = true }, null, .MONOTONIC);
try expectEqual(.INVAL, linux.errno(rc)); try expectEqual(.INVAL, linux.errno(rc));
rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U64, .private = true }, null, .MONOTONIC); rc = linux.futex2_wait(&lock, 1, mask, .{ .size = .U64, .private = true }, null, .MONOTONIC);
try expectEqual(.INVAL, linux.errno(rc)); try expectEqual(.INVAL, linux.errno(rc));
} }
const flags = linux.FUTEX2_FLAGS{ .size = .U32, .private = true }; const flags: linux.Futex2.Wait = .{ .size = .U32, .private = true };
// no-wait, lock state mismatch // no-wait, lock state mismatch
rc = linux.futex2_wait(&lock.raw, 2, mask, flags, null, .MONOTONIC); rc = linux.futex2_wait(&lock, 2, mask, flags, null, .MONOTONIC);
try expectEqual(.AGAIN, linux.errno(rc)); try expectEqual(.AGAIN, linux.errno(rc));
// hit timeout on wait // hit timeout on wait
rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .MONOTONIC); rc = linux.futex2_wait(&lock, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .MONOTONIC);
try expectEqual(.TIMEDOUT, linux.errno(rc)); try expectEqual(.TIMEDOUT, linux.errno(rc));
// timeout is absolute // timeout is absolute
@ -363,40 +363,40 @@ test "futex2_wait" {
.sec = curr.sec, .sec = curr.sec,
.nsec = curr.nsec + 2, .nsec = curr.nsec + 2,
}; };
rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &timeout, .MONOTONIC); rc = linux.futex2_wait(&lock, 1, mask, flags, &timeout, .MONOTONIC);
try expectEqual(.TIMEDOUT, linux.errno(rc)); try expectEqual(.TIMEDOUT, linux.errno(rc));
} }
rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .REALTIME); rc = linux.futex2_wait(&lock, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .REALTIME);
try expectEqual(.TIMEDOUT, linux.errno(rc)); try expectEqual(.TIMEDOUT, linux.errno(rc));
} }
test "futex2_wake" { test "futex2_wake" {
var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); const lock: std.atomic.Value(u32) = .init(1);
try futex2_skip_if_unsupported(); try futex2_skip_if_unsupported();
const rc = linux.futex2_wake(&lock.raw, 0xFF, 1, .{ .size = .U32, .private = true }); const rc = linux.futex2_wake(&lock, .fromInt(0xFF), 1, .{ .size = .U32, .private = true });
try expectEqual(0, rc); try expectEqual(0, rc);
} }
test "futex2_requeue" { test "futex2_requeue" {
try futex2_skip_if_unsupported(); try futex2_skip_if_unsupported();
const locks = [_]std.atomic.Value(u32){ const locks: [2]std.atomic.Value(u32) = .{
std.atomic.Value(u32).init(1), .init(1),
std.atomic.Value(u32).init(1), .init(1),
}; };
const futexes = [_]linux.futex2_waitone{ const futexes: [2]linux.Futex2.WaitOne = .{
.{ .{
.val = 1, .val = 1,
.uaddr = @intFromPtr(&locks[0].raw), .uaddr = @intFromPtr(&locks[0]),
.flags = .{ .size = .U32, .private = true }, .flags = .{ .size = .U32, .private = true },
}, },
.{ .{
.val = 1, .val = 1,
.uaddr = @intFromPtr(&locks[1].raw), .uaddr = @intFromPtr(&locks[1]),
.flags = .{ .size = .U32, .private = true }, .flags = .{ .size = .U32, .private = true },
}, },
}; };

View file

@ -63,7 +63,9 @@ pub const AF = system.AF;
pub const AF_SUN = system.AF_SUN; pub const AF_SUN = system.AF_SUN;
pub const AI = system.AI; pub const AI = system.AI;
pub const ARCH = system.ARCH; pub const ARCH = system.ARCH;
pub const AT = system.AT; /// DEPRECATED: use `At`
pub const AT = At;
pub const At = system.At;
pub const AT_SUN = system.AT_SUN; pub const AT_SUN = system.AT_SUN;
pub const CLOCK = system.CLOCK; pub const CLOCK = system.CLOCK;
pub const CPU_COUNT = system.CPU_COUNT; pub const CPU_COUNT = system.CPU_COUNT;
@ -480,7 +482,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
} }
defer close(pathfd); defer close(pathfd);
const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) { const stat = fstatatZ(pathfd, "", .{ .empty_path = true }) catch |err| switch (err) {
error.NameTooLong => unreachable, error.NameTooLong => unreachable,
error.FileNotFound => unreachable, error.FileNotFound => unreachable,
error.Streaming => unreachable, error.Streaming => unreachable,
@ -1553,7 +1555,7 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
if (native_os == .windows) { if (native_os == .windows) {
@compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
} else if (native_os == .wasi and !builtin.link_libc) { } else if (native_os == .wasi and !builtin.link_libc) {
return openat(AT.FDCWD, file_path, flags, perm); return openat(At.fdcwd, file_path, flags, perm);
} }
const file_path_c = try toPosixPath(file_path); const file_path_c = try toPosixPath(file_path);
return openZ(&file_path_c, flags, perm); return openZ(&file_path_c, flags, perm);
@ -1943,7 +1945,7 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!
if (native_os == .windows) { if (native_os == .windows) {
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
} else if (native_os == .wasi and !builtin.link_libc) { } else if (native_os == .wasi and !builtin.link_libc) {
return symlinkat(target_path, AT.FDCWD, sym_link_path); return symlinkat(target_path, At.fdcwd, sym_link_path);
} }
const target_path_c = try toPosixPath(target_path); const target_path_c = try toPosixPath(target_path);
const sym_link_path_c = try toPosixPath(sym_link_path); const sym_link_path_c = try toPosixPath(sym_link_path);
@ -2103,7 +2105,7 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8) LinkError!void {
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn link(oldpath: []const u8, newpath: []const u8) LinkError!void { pub fn link(oldpath: []const u8, newpath: []const u8) LinkError!void {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
return linkat(AT.FDCWD, oldpath, AT.FDCWD, newpath, 0) catch |err| switch (err) { return linkat(At.fdcwd, oldpath, At.fdcwd, newpath, 0) catch |err| switch (err) {
error.NotDir => unreachable, // link() does not support directories error.NotDir => unreachable, // link() does not support directories
else => |e| return e, else => |e| return e,
}; };
@ -2163,7 +2165,7 @@ pub fn linkat(
const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
const old_flags: wasi.lookupflags_t = .{ const old_flags: wasi.lookupflags_t = .{
.SYMLINK_FOLLOW = (flags & AT.SYMLINK_FOLLOW) != 0, .SYMLINK_FOLLOW = (flags & At.SYMLINK_FOLLOW) != 0,
}; };
switch (wasi.path_link( switch (wasi.path_link(
old.dir_fd, old.dir_fd,
@ -2234,7 +2236,7 @@ pub const UnlinkError = error{
/// See also `unlinkZ`. /// See also `unlinkZ`.
pub fn unlink(file_path: []const u8) UnlinkError!void { pub fn unlink(file_path: []const u8) UnlinkError!void {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) { return unlinkat(At.fdcwd, file_path, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // only occurs when targeting directories error.DirNotEmpty => unreachable, // only occurs when targeting directories
else => |e| return e, else => |e| return e,
}; };
@ -2308,7 +2310,7 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
/// WASI-only. Same as `unlinkat` but targeting WASI. /// WASI-only. Same as `unlinkat` but targeting WASI.
/// See also `unlinkat`. /// See also `unlinkat`.
pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
const remove_dir = (flags & AT.REMOVEDIR) != 0; const remove_dir = (flags & At.REMOVEDIR) != 0;
const res = if (remove_dir) const res = if (remove_dir)
wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len) wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)
else else
@ -2373,7 +2375,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only. /// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
const remove_dir = (flags & AT.REMOVEDIR) != 0; const remove_dir = (flags & At.REMOVEDIR) != 0;
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
} }
@ -2421,7 +2423,7 @@ pub const RenameError = error{
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path); return renameat(At.fdcwd, old_path, At.FDCWD, new_path);
} else if (native_os == .windows) { } else if (native_os == .windows) {
const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); const old_path_w = try windows.sliceToPrefixedFileW(null, old_path);
const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); const new_path_w = try windows.sliceToPrefixedFileW(null, new_path);
@ -2644,7 +2646,7 @@ pub const MakeDirError = std.Io.Dir.MakeError;
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void { pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
return mkdirat(AT.FDCWD, dir_path, mode); return mkdirat(At.fdcwd, dir_path, mode);
} else if (native_os == .windows) { } else if (native_os == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
return mkdirW(dir_path_w.span(), mode); return mkdirW(dir_path_w.span(), mode);
@ -2729,7 +2731,7 @@ pub const DeleteDirError = error{
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn rmdir(dir_path: []const u8) DeleteDirError!void { pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
return unlinkat(AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { return unlinkat(At.fdcwd, dir_path, At.REMOVEDIR) catch |err| switch (err) {
error.FileSystem => unreachable, // only occurs when targeting files error.FileSystem => unreachable, // only occurs when targeting files
error.IsDir => unreachable, // only occurs when targeting files error.IsDir => unreachable, // only occurs when targeting files
else => |e| return e, else => |e| return e,
@ -2856,7 +2858,7 @@ pub const FchdirError = error{
} || UnexpectedError; } || UnexpectedError;
pub fn fchdir(dirfd: fd_t) FchdirError!void { pub fn fchdir(dirfd: fd_t) FchdirError!void {
if (dirfd == AT.FDCWD) return; if (dirfd == At.fdcwd) return;
while (true) { while (true) {
switch (errno(system.fchdir(dirfd))) { switch (errno(system.fchdir(dirfd))) {
.SUCCESS => return, .SUCCESS => return,
@ -2909,7 +2911,7 @@ pub const ReadLinkError = error{
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
return readlinkat(AT.FDCWD, file_path, out_buffer); return readlinkat(At.fdcwd, file_path, out_buffer);
} else if (native_os == .windows) { } else if (native_os == .windows) {
var file_path_w = try windows.sliceToPrefixedFileW(null, file_path); var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
const result_w = try readlinkW(file_path_w.span(), &file_path_w.data); const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
@ -3869,7 +3871,7 @@ pub const FStatAtError = FStatError || error{
/// On WASI, `pathname` should be encoded as valid UTF-8. /// On WASI, `pathname` should be encoded as valid UTF-8.
/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
/// See also `fstatatZ`. /// See also `fstatatZ`.
pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: At) FStatAtError!Stat {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
@compileError("use std.Io instead"); @compileError("use std.Io instead");
} else if (native_os == .windows) { } else if (native_os == .windows) {
@ -3882,14 +3884,14 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat
/// Same as `fstatat` but `pathname` is null-terminated. /// Same as `fstatat` but `pathname` is null-terminated.
/// See also `fstatat`. /// See also `fstatat`.
pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: At) FStatAtError!Stat {
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
@compileError("use std.Io instead"); @compileError("use std.Io instead");
} }
const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat; const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat;
var stat = mem.zeroes(Stat); var stat = mem.zeroes(Stat);
switch (errno(fstatat_sym(dirfd, pathname, &stat, flags))) { switch (errno(fstatat_sym(dirfd, pathname, &stat, @bitCast(flags)))) {
.SUCCESS => return stat, .SUCCESS => return stat,
.INVAL => unreachable, .INVAL => unreachable,
.BADF => unreachable, // Always a race condition. .BADF => unreachable, // Always a race condition.

View file

@ -37,7 +37,7 @@ test "check WASI CWD" {
if (!builtin.link_libc) { if (!builtin.link_libc) {
// WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls
try expectEqual(3, posix.AT.FDCWD); try expectEqual(3, posix.At.fdcwd);
} }
} }
} }
@ -907,7 +907,7 @@ test "pwrite with empty buffer" {
} }
fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW); const st = try posix.fstatat(dir, file, .{ .symlink_nofollow = true });
try expectEqual(mode, st.mode & 0b111_111_111); try expectEqual(mode, st.mode & 0b111_111_111);
} }
@ -932,13 +932,13 @@ test "fchmodat smoke test" {
try posix.symlinkat("regfile", tmp.dir.fd, "symlink"); try posix.symlinkat("regfile", tmp.dir.fd, "symlink");
const sym_mode = blk: { const sym_mode = blk: {
const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW); const st = try posix.fstatat(tmp.dir.fd, "symlink", .{ .symlink_nofollow = true });
break :blk st.mode & 0b111_111_111; break :blk st.mode & 0b111_111_111;
}; };
try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0); try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
try expectMode(tmp.dir.fd, "regfile", 0o640); try expectMode(tmp.dir.fd, "regfile", 0o640);
try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW); try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.At.SYMLINK_NOFOLLOW);
try expectMode(tmp.dir.fd, "regfile", 0o600); try expectMode(tmp.dir.fd, "regfile", 0o600);
try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0); try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0);
@ -946,7 +946,7 @@ test "fchmodat smoke test" {
try expectMode(tmp.dir.fd, "symlink", sym_mode); try expectMode(tmp.dir.fd, "symlink", sym_mode);
var test_link = true; var test_link = true;
posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) { posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.At.SYMLINK_NOFOLLOW) catch |err| switch (err) {
error.OperationNotSupported => test_link = false, error.OperationNotSupported => test_link = false,
else => |e| return e, else => |e| return e,
}; };

View file

@ -523,14 +523,15 @@ fn cleanupStreams(self: *ChildProcess) void {
} }
fn statusToTerm(status: u32) Term { fn statusToTerm(status: u32) Term {
return if (posix.W.IFEXITED(status)) const w: posix.W = @bitCast(status);
Term{ .Exited = posix.W.EXITSTATUS(status) } return if (w.ifExited())
else if (posix.W.IFSIGNALED(status)) .{ .Exited = w.exitStatus() }
Term{ .Signal = posix.W.TERMSIG(status) } else if (w.ifSignaled())
else if (posix.W.IFSTOPPED(status)) .{ .Signal = w.termSig() }
Term{ .Stopped = posix.W.STOPSIG(status) } else if (w.ifStopped())
.{ .Stopped = w.stopSig() }
else else
Term{ .Unknown = status }; .{ .Unknown = status };
} }
fn spawnPosix(self: *ChildProcess) SpawnError!void { fn spawnPosix(self: *ChildProcess) SpawnError!void {