From ceb59b48b4de02a8ceaeae2eb480cc769133add3 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 29 Dec 2025 21:02:11 -0500 Subject: [PATCH 1/6] openbsd: Io.Threaded: use futex --- lib/std/Io/Threaded.zig | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 444ca491a040ce028d7a4fb055863f89b951f0f3..9c3645616327412f8b3b0455ca604bde65c44b7d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -390,6 +390,33 @@ const Thread = struct { else => unreachable, }; }, + .openbsd => { + var tm: std.c.timespec = undefined; + var tm_ptr: ?*const std.c.timespec = null; + if (timeout_ns) |ns| { + tm_ptr = &tm; + tm = timestampToPosix(ns); + } + if (thread) |t| try t.beginSyscall(); + const rc = std.c.futex( + ptr, + std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG, + @as(c_int, @bitCast(expect)), + tm_ptr, + null, // uaddr2 is ignored + ); + if (thread) |t| t.endSyscall(); + if (is_debug) switch (posix.errno(rc)) { + .SUCCESS => {}, + .NOSYS => unreachable, // constant op known good value + .AGAIN => {}, // contents of uaddr != val + .INVAL => unreachable, // invalid timeout + .TIMEDOUT => {}, // timeout + .INTR => {}, // a signal arrived + .CANCELED => {}, // a signal arrived and SA_RESTART was set + else => unreachable, + }; + }, else => if (std.Thread.use_pthreads) { // TODO integrate the following function being called with robust cancelation. return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) { @@ -473,6 +500,16 @@ const Thread = struct { else => unreachable, // deadlock due to operating system bug } }, + .openbsd => { + const rc = std.c.futex( + ptr, + std.c.FUTEX.WAKE | std.c.FUTEX.PRIVATE_FLAG, + @min(max_waiters, std.math.maxInt(c_int)), + null, // timeout is ignored + null, // uaddr2 is ignored + ); + assert(rc >= 0); + }, else => if (std.Thread.use_pthreads) { return pthreads_futex.wake(ptr, max_waiters); } else { -- 2.54.0 From 51728f10535d9a10fcee85a6e03ee82e3211a149 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 29 Dec 2025 21:02:11 -0500 Subject: [PATCH 2/6] openbsd: add timespec OMIT and NOW definitions --- lib/std/c.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 5f0d2dedf879bcabaa3117414d737c78a4b3aabc..53d30d80283eff673eb0f2de137e8c5e16ef604f 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -167,6 +167,18 @@ pub const timespec = switch (native_os) { .openbsd, .haiku => extern struct { sec: time_t, nsec: isize, + + /// For use with `utimensat` and `futimens`. + pub const NOW: timespec = .{ + .sec = 0, // ignored + .nsec = -2, + }; + + /// For use with `utimensat` and `futimens`. + pub const OMIT: timespec = .{ + .sec = 0, // ignored + .nsec = -1, + }; }, else => void, }; -- 2.54.0 From 1ea2d5692d5e0ed5adf1a398f43a442b22c7563d Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 29 Dec 2025 21:02:11 -0500 Subject: [PATCH 3/6] openbsd: init Io.Threaded.argv0 for tests Some tests (eg. std.process.openExecutable) require argv0 for OpenBSD, otherwise error.OperationUnsupported is encountered. --- lib/compiler/test_runner.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index da3da684d920bd9758406453a19eda086782b676..95e4b9503c10f43d5eddad5c6c44e200a0e460fa 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -66,13 +66,13 @@ pub fn main() void { } if (listen) { - return mainServer() catch @panic("internal test runner failure"); + return mainServer(args) catch @panic("internal test runner failure"); } else { - return mainTerminal(); + return mainTerminal(args); } } -fn mainServer() !void { +fn mainServer(args: []const [:0]const u8) !void { @disableInstrumentation(); var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer); var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer); @@ -131,7 +131,9 @@ fn mainServer() !void { .run_test => { testing.allocator_instance = .{}; - testing.io_instance = .init(testing.allocator, .{}); + testing.io_instance = .init(testing.allocator, .{ + .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, + }); log_err_count = 0; const index = try server.receiveBody_u32(); const test_fn = builtin.test_functions[index]; @@ -215,7 +217,7 @@ fn mainServer() !void { } } -fn mainTerminal() void { +fn mainTerminal(args: []const [:0]const u8) void { @disableInstrumentation(); if (builtin.fuzz) @panic("fuzz test requires server"); @@ -233,7 +235,9 @@ fn mainTerminal() void { var leaks: usize = 0; for (test_fn_list, 0..) |test_fn, i| { testing.allocator_instance = .{}; - testing.io_instance = .init(testing.allocator, .{}); + testing.io_instance = .init(testing.allocator, .{ + .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, + }); defer { testing.io_instance.deinit(); if (testing.allocator_instance.deinit() == .leak) leaks += 1; -- 2.54.0 From 09f06082f0b43f93d1d4267539cb29c603fe0d11 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 29 Dec 2025 21:02:12 -0500 Subject: [PATCH 4/6] openbsd: make test lib/std/std.zig pass * According to OpenBSD's getdents docs indicate the buffer must be greater or or equal to the block size associated with the file and to refer to stat(2). * Use S_BLKSIZE, which is 512, instead of @sizeOf(std.c.dirent), which is 280. * Oddly the other BSDs are not this picky. --- lib/std/Io/Dir.zig | 1 + lib/std/c.zig | 2 ++ lib/std/fs/test.zig | 1 + 3 files changed, 4 insertions(+) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 6db13f5c6cd1daeb770436519f5168daeb23eb8a..82bf3b927df0cd22a14f640e1b1f58aedc23c373 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -113,6 +113,7 @@ pub const Reader = struct { }, .wasi => @sizeOf(std.os.wasi.dirent_t) + std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)), + .openbsd => std.c.S.BLKSIZE, else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), }; diff --git a/lib/std/c.zig b/lib/std/c.zig index 53d30d80283eff673eb0f2de137e8c5e16ef604f..2fb62a3443433cb16f7d998af7a0b434fbd5a761 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -2377,6 +2377,8 @@ pub const S = switch (native_os) { pub const IWOTH = 0o002; pub const IXOTH = 0o001; + pub const BLKSIZE = 512; + pub fn ISFIFO(m: u32) bool { return m & IFMT == IFIFO; } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index bcb9048e0e05428a7288c25d292d37251b000dee..608bc73e62408ce4b929e6c47d155c2309ad9dae 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -645,6 +645,7 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo test "Dir.realPath smoke test" { if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .openbsd) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { -- 2.54.0 From 3c851ec3969f1396eff5c3af21be7b9e3328c7ae Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 29 Dec 2025 22:00:59 -0500 Subject: [PATCH 5/6] dragonfly: Io.Threaded: use futex --- lib/std/Io/Threaded.zig | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9c3645616327412f8b3b0455ca604bde65c44b7d..2c2307315727337ae178581499b0008403f13c4b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -417,6 +417,25 @@ const Thread = struct { else => unreachable, }; }, + .dragonfly => { + var timeout_us: c_int = undefined; + if (timeout_ns) |ns| { + timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int); + } else { + timeout_us = 0; + } + if (thread) |t| try t.beginSyscall(); + const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us); + if (thread) |t| t.endSyscall(); + if (is_debug) switch (std.posix.errno(rc)) { + .SUCCESS => {}, + .BUSY => {}, // ptr != expect + .AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh + .INTR => {}, // spurious wake + .INVAL => unreachable, // invalid timeout + else => unreachable, + }; + }, else => if (std.Thread.use_pthreads) { // TODO integrate the following function being called with robust cancelation. return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) { @@ -510,6 +529,13 @@ const Thread = struct { ); assert(rc >= 0); }, + .dragonfly => { + // will generally return 0 unless the address is bad + _ = std.c.umtx_wakeup( + @ptrCast(ptr), + @min(max_waiters, std.math.maxInt(c_int)), + ); + }, else => if (std.Thread.use_pthreads) { return pthreads_futex.wake(ptr, max_waiters); } else { -- 2.54.0 From 4b26c49076a1c163e5fdd1bd3a657e0794e5a917 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 29 Dec 2025 21:39:00 -0500 Subject: [PATCH 6/6] dragonfly: make test lib/std/std.zig pass --- lib/std/c.zig | 1 + lib/std/fs/test.zig | 3 ++- lib/std/posix/test.zig | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 2fb62a3443433cb16f7d998af7a0b434fbd5a761..e9810aeb0912015c015dd68f1259cde477b1a60a 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -9690,6 +9690,7 @@ pub const NSIG = switch (native_os) { .illumos => 75, // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h#L42 .openbsd, .serenity => 33, + .dragonfly => 64, else => {}, }; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 608bc73e62408ce4b929e6c47d155c2309ad9dae..94cf3055e562c0cc077b19c0951578f5bdf05c66 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -821,7 +821,7 @@ test "file operations on directories" { try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name)); switch (native_os) { - .dragonfly, .netbsd => { + .netbsd => { // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732 const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited); testing.allocator.free(buf); @@ -1241,6 +1241,7 @@ test "createDirPath, put some files in it, deleteTreeMinStackSize" { test "createDirPath in a directory that no longer exists" { if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir + if (native_os == .dragonfly) return error.SkipZigTest; // DragonflyBSD does not produce error (hammer2 fs) const io = testing.io; diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 64845d15ee7b3b778a427756fb6ccb533243bfd8..6fdb980b1bb93ab2db7d1a404cb996c7cf3c24ac 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -364,9 +364,10 @@ test "getrlimit and setrlimit" { } test "sigrtmin/max" { - if (native_os == .wasi or native_os == .windows or native_os.isDarwin() or native_os == .openbsd) { - return error.SkipZigTest; - } + if (native_os.isDarwin() or switch (native_os) { + .wasi, .windows, .openbsd, .dragonfly => true, + else => false, + }) return error.SkipZigTest; try expect(posix.sigrtmin() >= 32); try expect(posix.sigrtmin() >= posix.system.sigrtmin()); @@ -397,7 +398,7 @@ fn reserved_signo(i: usize) bool { if (!builtin.link_libc) return false; const max = if (native_os == .netbsd) 32 else 31; if (i > max) return true; - if (native_os == .openbsd) return false; // no RT signals + if (native_os == .openbsd or native_os == .dragonfly) return false; // no RT signals return i < posix.sigrtmin(); } -- 2.54.0