From 51654aea877d1da92f7f2ad2c6c781c73e08d7f0 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sun, 13 Apr 2025 15:54:56 -0700 Subject: [PATCH 1/7] c.zig: glibc/musl export 1024-bit sigset_t Export the sigset_t ops (sigaddset, etc) from the C library. Don't rely on the linux.zig defintions (which will be defined to use the kernel ABI). Move Darwin sigset and NSIG declarations into darwin.zig. Remove extraneous (?) sigaddset. The C library sigaddset can reject some signals being added, so need to defer to it. --- lib/std/c.zig | 53 ++++++++++++++++++++++++++++---------------- lib/std/c/darwin.zig | 9 ++++---- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 35d175f0b9..d5560c3f6f 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -3115,6 +3115,21 @@ pub const SYS = switch (native_os) { .linux => linux.SYS, else => void, }; + +/// A common format for the Sigaction struct across a variety of Linux flavors. +const common_linux_Sigaction = extern struct { + pub const handler_fn = *align(1) const fn (i32) callconv(.c) void; + pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void; + + handler: extern union { + handler: ?handler_fn, + sigaction: ?sigaction_fn, + }, + mask: sigset_t, + flags: c_uint, + restorer: ?*const fn () callconv(.c) void = null, // C library will fill this in +}; + /// Renamed from `sigaction` to `Sigaction` to avoid conflict with function name. pub const Sigaction = switch (native_os) { .linux => switch (native_arch) { @@ -3123,7 +3138,7 @@ pub const Sigaction = switch (native_os) { .mips64, .mips64el, => if (builtin.target.abi.isMusl()) - linux.Sigaction + common_linux_Sigaction else if (builtin.target.ptrBitWidth() == 64) extern struct { pub const handler_fn = *align(1) const fn (i32) callconv(.c) void; pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void; @@ -3160,8 +3175,8 @@ pub const Sigaction = switch (native_os) { flags: c_uint, restorer: ?*const fn () callconv(.c) void = null, mask: sigset_t, - } else linux.Sigaction, - else => linux.Sigaction, + } else common_linux_Sigaction, + else => common_linux_Sigaction, }, .emscripten => emscripten.Sigaction, .netbsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct { @@ -4518,27 +4533,18 @@ pub const siginfo_t = switch (native_os) { else => void, }; pub const sigset_t = switch (native_os) { - .linux => linux.sigset_t, + .linux => [1024 / @bitSizeOf(c_ulong)]c_ulong, // glibc and musl present a 1024-bit sigset_t, while kernel's is 128-bit or less. .emscripten => emscripten.sigset_t, // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L19 - .openbsd, .macos, .ios, .tvos, .watchos, .visionos, .serenity => u32, + .openbsd, .serenity => u32, + .macos, .ios, .tvos, .watchos, .visionos => darwin.sigset_t, .dragonfly, .netbsd, .solaris, .illumos, .freebsd => extern struct { __bits: [SIG.WORDS]u32, }, .haiku => u64, else => u0, }; -pub const empty_sigset: sigset_t = switch (native_os) { - .linux => linux.empty_sigset, - .emscripten => emscripten.empty_sigset, - .dragonfly, .netbsd, .solaris, .illumos, .freebsd => .{ .__bits = [_]u32{0} ** SIG.WORDS }, - else => 0, -}; -pub const filled_sigset = switch (native_os) { - .linux => linux.filled_sigset, - .haiku => ~@as(sigset_t, 0), - else => 0, -}; + pub const sigval = switch (native_os) { .linux => linux.sigval, // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L22-L25 @@ -6665,7 +6671,7 @@ pub const timezone = switch (native_os) { }; pub const ucontext_t = switch (native_os) { - .linux => linux.ucontext_t, + .linux => linux.ucontext_t, // std.os.linux.ucontext_t is currently glibc-compatible, but it should probably not be. .emscripten => emscripten.ucontext_t, .macos, .ios, .tvos, .watchos, .visionos => extern struct { onstack: c_int, @@ -9596,6 +9602,7 @@ pub const NSIG = switch (native_os) { .windows => 23, .haiku => 65, .netbsd, .freebsd => 32, + .macos => darwin.NSIG, .solaris, .illumos => 75, // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h#L42 .openbsd, .serenity => 33, @@ -10345,6 +10352,11 @@ pub const sigfillset = switch (native_os) { else => private.sigfillset, }; +pub const sigaddset = private.sigaddset; +pub const sigemptyset = private.sigemptyset; +pub const sigdelset = private.sigdelset; +pub const sigismember = private.sigismember; + pub const sigprocmask = switch (native_os) { .netbsd => private.__sigprocmask14, else => private.sigprocmask, @@ -11025,7 +11037,6 @@ pub const pthread_attr_set_qos_class_np = darwin.pthread_attr_set_qos_class_np; pub const pthread_get_qos_class_np = darwin.pthread_get_qos_class_np; pub const pthread_set_qos_class_self_np = darwin.pthread_set_qos_class_self_np; pub const ptrace = darwin.ptrace; -pub const sigaddset = darwin.sigaddset; pub const task_for_pid = darwin.task_for_pid; pub const task_get_exception_ports = darwin.task_get_exception_ports; pub const task_info = darwin.task_info; @@ -11148,7 +11159,11 @@ const private = struct { extern "c" fn sched_yield() c_int; extern "c" fn sendfile(out_fd: fd_t, in_fd: fd_t, offset: ?*off_t, count: usize) isize; extern "c" fn sigaction(sig: c_int, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int; - extern "c" fn sigfillset(set: ?*sigset_t) void; + extern "c" fn sigdelset(set: ?*sigset_t, signo: c_int) c_int; + extern "c" fn sigaddset(set: ?*sigset_t, signo: c_int) c_int; + extern "c" fn sigfillset(set: ?*sigset_t) c_int; + extern "c" fn sigemptyset(set: ?*sigset_t) c_int; + extern "c" fn sigismember(set: ?*const sigset_t, signo: c_int) c_int; extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int; extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int; extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index ac083ec9f2..74d79842be 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -10,7 +10,6 @@ const mode_t = std.c.mode_t; const off_t = std.c.off_t; const pid_t = std.c.pid_t; const pthread_attr_t = std.c.pthread_attr_t; -const sigset_t = std.c.sigset_t; const timespec = std.c.timespec; const sf_hdtr = std.c.sf_hdtr; @@ -840,9 +839,11 @@ pub extern "c" fn sendfile( flags: u32, ) c_int; -pub fn sigaddset(set: *sigset_t, signo: u5) void { - set.* |= @as(u32, 1) << (signo - 1); -} +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74 +pub const sigset_t = u32; + +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/signal.h#L76 +pub const NSIG = 32; pub const qos_class_t = enum(c_uint) { /// highest priority QOS class for critical tasks From 298b1886b2b35b168589026d075d14206d6e3430 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sun, 13 Apr 2025 16:14:25 -0700 Subject: [PATCH 2/7] std.os.linux: export kernel-sized sigset_t and operations The kernel ABI sigset_t is smaller than the glibc one. Define the right-sized sigset_t and fixup the sigaction() wrapper to leverage it. The Sigaction wrapper here is not an ABI, so relax it (drop the "extern" and the "restorer" fields), the existing `k_sigaction` is the ABI sigaction struct. Linux defines `sigset_t` with a c_ulong, so it can be 32-bit or 64-bit, depending on the platform. This can make a difference on big-endian systems. Patch up `ucontext_t` so that this change doesn't impact its layout. AFAICT, its currently the glibc layout. --- lib/std/os/linux.zig | 69 ++++++++++++++++++++----------------- lib/std/os/linux/test.zig | 47 +++++++++++++++++++++---- lib/std/os/linux/x86_64.zig | 14 ++++++-- 3 files changed, 88 insertions(+), 42 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 00390c0800..3e5c808f56 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1745,8 +1745,9 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?* return syscall4(.rt_sigprocmask, flags, @intFromPtr(set), @intFromPtr(oldset), NSIG / 8); } -pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize { - assert(sig >= 1); +pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize { + assert(sig > 0); + assert(sig < NSIG); assert(sig != SIG.KILL); assert(sig != SIG.STOP); @@ -1755,14 +1756,15 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact const mask_size = @sizeOf(@TypeOf(ksa.mask)); if (act) |new| { + // Zig needs to install our arch restorer function with any signal handler, so + // must copy the Sigaction struct const restorer_fn = if ((new.flags & SA.SIGINFO) != 0) &restore_rt else &restore; ksa = k_sigaction{ .handler = new.handler.handler, .flags = new.flags | SA.RESTORER, - .mask = undefined, + .mask = new.mask, .restorer = @ptrCast(restorer_fn), }; - @memcpy(@as([*]u8, @ptrCast(&ksa.mask))[0..mask_size], @as([*]const u8, @ptrCast(&new.mask))); } const ksa_arg = if (act != null) @intFromPtr(&ksa) else 0; @@ -1777,8 +1779,8 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact if (oact) |old| { old.handler.handler = oldksa.handler; - old.flags = @as(c_uint, @truncate(oldksa.flags)); - @memcpy(@as([*]u8, @ptrCast(&old.mask))[0..mask_size], @as([*]const u8, @ptrCast(&oldksa.mask))); + old.flags = oldksa.flags; + old.mask = oldksa.mask; } return 0; @@ -1786,25 +1788,31 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact const usize_bits = @typeInfo(usize).int.bits; -pub const sigset_t = [1024 / 32]u32; +/// Defined as one greater than the largest defined signal number. +pub const NSIG = if (is_mips) 128 else 65; + +/// Linux kernel's sigset_t. This is logically 64-bit on most +/// architectures, but 128-bit on MIPS. Contrast with the 1024-bit +/// sigset_t exported by the glibc and musl library ABIs. +pub const sigset_t = [(NSIG - 1 + 7) / @bitSizeOf(SigsetElement)]SigsetElement; + +const SigsetElement = c_ulong; const sigset_len = @typeInfo(sigset_t).array.len; -/// Empty set to initialize sigset_t instances from. -pub const empty_sigset: sigset_t = [_]u32{0} ** sigset_len; +/// Empty set to initialize sigset_t instances from. No need for `sigemptyset`. +pub const empty_sigset: sigset_t = [_]SigsetElement{0} ** sigset_len; -pub const filled_sigset: sigset_t = [_]u32{0x7fff_ffff} ++ [_]u32{0} ** (sigset_len - 1); +/// Filled set to initialize sigset_t instances from. No need for `sigfillset`. +pub const filled_sigset: sigset_t = [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len; -pub const all_mask: sigset_t = [_]u32{0xffff_ffff} ** sigset_len; - -fn sigset_bit_index(sig: usize) struct { word: usize, mask: u32 } { +fn sigset_bit_index(sig: usize) struct { word: usize, mask: SigsetElement } { assert(sig > 0); assert(sig < NSIG); const bit = sig - 1; - const shift = @as(u5, @truncate(bit % 32)); return .{ - .word = bit / 32, - .mask = @as(u32, 1) << shift, + .word = bit / @bitSizeOf(SigsetElement), + .mask = @as(SigsetElement, 1) << @truncate(bit % @bitSizeOf(SigsetElement)), }; } @@ -5479,38 +5487,33 @@ pub const TFD = switch (native_arch) { }, }; -/// NSIG is the total number of signals defined. -/// As signal numbers are sequential, NSIG is one greater than the largest defined signal number. -pub const NSIG = if (is_mips) 128 else 65; - const k_sigaction_funcs = struct { const handler = ?*align(1) const fn (i32) callconv(.c) void; const restorer = *const fn () callconv(.c) void; }; +/// Kernel sigaction struct, as expected by the `rt_sigaction` syscall. Includes restorer. pub const k_sigaction = switch (native_arch) { - .mips, .mipsel => extern struct { + .mips, .mipsel, .mips64, .mips64el => extern struct { flags: c_uint, handler: k_sigaction_funcs.handler, - mask: [4]c_ulong, - restorer: k_sigaction_funcs.restorer, - }, - .mips64, .mips64el => extern struct { - flags: c_uint, - handler: k_sigaction_funcs.handler, - mask: [2]c_ulong, + mask: sigset_t, restorer: k_sigaction_funcs.restorer, }, else => extern struct { handler: k_sigaction_funcs.handler, flags: c_ulong, restorer: k_sigaction_funcs.restorer, - mask: [2]c_uint, + mask: sigset_t, }, }; +/// Kernel Sigaction wrapper for the actual ABI `k_sigaction`. The Zig +/// linux.zig wrapper library still does some pre-processing on +/// sigaction() calls (to add the `restorer` field). +/// /// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall. -pub const Sigaction = extern struct { +pub const Sigaction = struct { pub const handler_fn = *align(1) const fn (i32) callconv(.c) void; pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void; @@ -5519,8 +5522,10 @@ pub const Sigaction = extern struct { sigaction: ?sigaction_fn, }, mask: sigset_t, - flags: c_uint, - restorer: ?*const fn () callconv(.c) void = null, + flags: switch (native_arch) { + .mips, .mipsel, .mips64, .mips64el => c_uint, + else => c_ulong, + }, }; pub const SFD = struct { diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index fa6f822329..097c5a388c 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -126,6 +126,8 @@ test "fadvise" { } test "sigset_t" { + std.debug.assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8)); + var sigset = linux.empty_sigset; // See that none are set, then set each one, see that they're all set, then @@ -138,6 +140,7 @@ test "sigset_t" { } for (1..linux.NSIG) |i| { try expectEqual(linux.sigismember(&sigset, @truncate(i)), true); + try expectEqual(linux.sigismember(&linux.filled_sigset, @truncate(i)), true); try expectEqual(linux.sigismember(&linux.empty_sigset, @truncate(i)), false); } for (1..linux.NSIG) |i| { @@ -147,22 +150,52 @@ test "sigset_t" { try expectEqual(linux.sigismember(&sigset, @truncate(i)), false); } + // Kernel sigset_t is either 2+ 32-bit values or 1+ 64-bit value(s). + const sigset_len = @typeInfo(linux.sigset_t).array.len; + const sigset_elemis64 = 64 == @bitSizeOf(@typeInfo(linux.sigset_t).array.child); + linux.sigaddset(&sigset, 1); try expectEqual(sigset[0], 1); - try expectEqual(sigset[1], 0); + if (sigset_len > 1) { + try expectEqual(sigset[1], 0); + } linux.sigaddset(&sigset, 31); try expectEqual(sigset[0], 0x4000_0001); - try expectEqual(sigset[1], 0); + if (sigset_len > 1) { + try expectEqual(sigset[1], 0); + } linux.sigaddset(&sigset, 36); - try expectEqual(sigset[0], 0x4000_0001); - try expectEqual(sigset[1], 0x8); + if (sigset_elemis64) { + try expectEqual(sigset[0], 0x8_4000_0001); + } else { + try expectEqual(sigset[0], 0x4000_0001); + try expectEqual(sigset[1], 0x8); + } linux.sigaddset(&sigset, 64); - try expectEqual(sigset[0], 0x4000_0001); - try expectEqual(sigset[1], 0x8000_0008); - try expectEqual(sigset[2], 0); + if (sigset_elemis64) { + try expectEqual(sigset[0], 0x8000_0008_4000_0001); + } else { + try expectEqual(sigset[0], 0x4000_0001); + try expectEqual(sigset[1], 0x8000_0008); + } +} + +test "filled_sigset" { + // unlike the C library, all the signals are set in the kernel-level fillset + const sigset = linux.filled_sigset; + for (1..linux.NSIG) |i| { + try expectEqual(linux.sigismember(&sigset, @truncate(i)), true); + } +} + +test "empty_sigset" { + const sigset = linux.empty_sigset; + for (1..linux.NSIG) |i| { + try expectEqual(linux.sigismember(&sigset, @truncate(i)), false); + } } test "sysinfo" { diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 90147876c8..7ef86fdcf8 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -369,13 +369,21 @@ pub const mcontext_t = extern struct { reserved1: [8]usize = undefined, }; +/// ucontext_t is part of the state pushed on the stack by the kernel for +/// a signal handler. And also a subset of the state returned from the +/// makecontext/getcontext/swapcontext POSIX APIs. +/// +/// Currently this structure matches the glibc/musl layout. It contains a +/// 1024-bit signal mask, and `fpregs_mem`. This structure should be +/// split into one for the kernel ABI and c.zig should define a glibc/musl +/// compatible structure. pub const ucontext_t = extern struct { flags: usize, link: ?*ucontext_t, stack: stack_t, mcontext: mcontext_t, - sigmask: sigset_t, - fpregs_mem: [64]usize, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask. + fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t }; fn gpRegisterOffset(comptime reg_index: comptime_int) usize { @@ -455,7 +463,7 @@ fn getContextInternal() callconv(.naked) usize { [stack_offset] "i" (@offsetOf(ucontext_t, "stack")), [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), [sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")), - [sigset_size] "i" (linux.NSIG / 8), + [sigset_size] "i" (@sizeOf(sigset_t)), : "cc", "memory", "rax", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11" ); } From d16079d79a6b7963db05253b6a3fe57a7e19d813 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sun, 13 Apr 2025 16:15:16 -0700 Subject: [PATCH 3/7] posix.zig: export sigset_t and matching operations from system Unify the C library sigset_t and Linux native sigset_t and the accessor operations. Add tests that the various sigset_t operations are working. And clean up existing tests a bit. --- lib/std/posix.zig | 79 +++++++++++++++---- lib/std/posix/test.zig | 171 +++++++++++++++++++++++++++++++++++------ 2 files changed, 209 insertions(+), 41 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index ad65cf205d..69ecc6dd58 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -86,6 +86,7 @@ pub const MREMAP = system.MREMAP; pub const MSF = system.MSF; pub const MSG = system.MSG; pub const NAME_MAX = system.NAME_MAX; +pub const NSIG = system.NSIG; pub const O = system.O; pub const PATH_MAX = system.PATH_MAX; pub const POLL = system.POLL; @@ -129,7 +130,6 @@ pub const dl_phdr_info = system.dl_phdr_info; pub const empty_sigset = system.empty_sigset; pub const fd_t = system.fd_t; pub const file_obj = system.file_obj; -pub const filled_sigset = system.filled_sigset; pub const gid_t = system.gid_t; pub const ifreq = system.ifreq; pub const ino_t = system.ino_t; @@ -678,7 +678,7 @@ pub fn abort() noreturn { raise(SIG.ABRT) catch {}; // Disable all signal handlers. - sigprocmask(SIG.BLOCK, &linux.all_mask, null); + sigprocmask(SIG.BLOCK, &linux.filled_sigset, null); // Only one thread may proceed to the rest of abort(). if (!builtin.single_threaded) { @@ -698,7 +698,8 @@ pub fn abort() noreturn { _ = linux.tkill(linux.gettid(), SIG.ABRT); - const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)}; + var sigabrtmask = empty_sigset; + sigaddset(&sigabrtmask, SIG.ABRT); sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); // Beyond this point should be unreachable. @@ -723,18 +724,13 @@ pub fn raise(sig: u8) RaiseError!void { } if (native_os == .linux) { - // https://git.musl-libc.org/cgit/musl/commit/?id=0bed7e0acfd34e3fb63ca0e4d99b7592571355a9 - // - // Unlike musl, libc-less Zig std does not have any internal signals for implementation purposes, so we - // need to block all signals on the assumption that any of them could potentially fork() in a handler. - var set: sigset_t = undefined; - sigprocmask(SIG.BLOCK, &linux.all_mask, &set); - - const tid = linux.gettid(); - const rc = linux.tkill(tid, sig); - - // restore signal mask - sigprocmask(SIG.SETMASK, &set, null); + // Block all signals so a `fork` (from a signal handler) between the gettid() and kill() syscalls + // cannot trigger an extra, unexpected, inter-process signal. Signal paranoia inherited from Musl. + const filled = linux.sigfillset(); + var orig: sigset_t = undefined; + sigprocmask(SIG.BLOCK, &linux.filled_sigset, &orig); + const rc = linux.tkill(linux.gettid(), sig); + sigprocmask(SIG.SETMASK, &orig, null); switch (errno(rc)) { .SUCCESS => return, @@ -5818,8 +5814,59 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { } } +pub fn sigfillset(set: *sigset_t) void { + if (builtin.link_libc) { + switch (errno(system.sigfillset(set))) { + .SUCCESS => return, + else => unreachable, + } + } + set.* = system.filled_sigset; +} + +pub fn sigemptyset(set: *sigset_t) void { + if (builtin.link_libc) { + switch (errno(system.sigemptyset(set))) { + .SUCCESS => return, + else => unreachable, + } + } + set.* = system.empty_sigset; +} + +pub fn sigaddset(set: *sigset_t, sig: u8) void { + if (builtin.link_libc) { + switch (errno(system.sigaddset(set, sig))) { + .SUCCESS => return, + else => unreachable, + } + } + system.sigaddset(set, sig); +} + +pub fn sigdelset(set: *sigset_t, sig: u8) void { + if (builtin.link_libc) { + switch (errno(system.sigdelset(set, sig))) { + .SUCCESS => return, + else => unreachable, + } + } + system.sigdelset(set, sig); +} + +pub fn sigismember(set: *const sigset_t, sig: u8) bool { + if (builtin.link_libc) { + const rc = system.sigismember(set, sig); + switch (errno(rc)) { + .SUCCESS => return rc == 1, + else => unreachable, + } + } + return system.sigismember(set, sig); +} + /// Examine and change a signal action. -pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void { +pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void { switch (errno(system.sigaction(sig, act, oact))) { .SUCCESS => return, // EINVAL means the signal is either invalid or some signal that cannot have its action diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index e27f8971d7..75ed74dc75 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -859,12 +859,64 @@ test "shutdown socket" { std.net.Stream.close(.{ .handle = sock }); } -test "sigaction" { +test "sigset empty/full" { if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; - // https://github.com/ziglang/zig/issues/7427 - if (native_os == .linux and builtin.target.cpu.arch == .x86) + var set: posix.sigset_t = undefined; + + posix.sigemptyset(&set); + for (1..posix.NSIG) |i| { + try expectEqual(false, posix.sigismember(&set, @truncate(i))); + } + + // The C library can reserve some (unnamed) signals, so can't check the full + // NSIG set is defined, but just test a couple: + posix.sigfillset(&set); + try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.USR1))); + try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.INT))); +} + +// Some signals (32 - 34 on glibc/musl) are not allowed to be added to a +// sigset by the C library, so avoid testing them. +fn reserved_signo(i: usize) bool { + return builtin.link_libc and (i >= 32 and i <= 34); +} + +test "sigset add/del" { + if (native_os == .wasi or native_os == .windows) + return error.SkipZigTest; + + var sigset = posix.empty_sigset; + + // See that none are set, then set each one, see that they're all set, then + // remove them all, and then see that none are set. + for (1..posix.NSIG) |i| { + try expectEqual(false, posix.sigismember(&sigset, @truncate(i))); + } + for (1..posix.NSIG) |i| { + if (!reserved_signo(i)) { + posix.sigaddset(&sigset, @truncate(i)); + } + } + for (1..posix.NSIG) |i| { + if (!reserved_signo(i)) { + try expectEqual(true, posix.sigismember(&sigset, @truncate(i))); + } + try expectEqual(false, posix.sigismember(&posix.empty_sigset, @truncate(i))); + } + for (1..posix.NSIG) |i| { + if (!reserved_signo(i)) { + posix.sigdelset(&sigset, @truncate(i)); + } + } + for (1..posix.NSIG) |i| { + try expectEqual(false, posix.sigismember(&sigset, @truncate(i))); + } +} + +test "sigaction" { + if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15381 @@ -872,21 +924,20 @@ test "sigaction" { return error.SkipZigTest; } + const test_signo = posix.SIG.USR1; + const S = struct { var handler_called_count: u32 = 0; fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void { _ = ctx_ptr; // Check that we received the correct signal. - switch (native_os) { - .netbsd => { - if (sig == posix.SIG.USR1 and sig == info.info.signo) - handler_called_count += 1; - }, - else => { - if (sig == posix.SIG.USR1 and sig == info.signo) - handler_called_count += 1; - }, + const info_sig = switch (native_os) { + .netbsd => info.info.signo, + else => info.signo, + }; + if (sig == test_signo and sig == info_sig) { + handler_called_count += 1; } } }; @@ -899,39 +950,109 @@ test "sigaction" { var old_sa: posix.Sigaction = undefined; // Install the new signal handler. - posix.sigaction(posix.SIG.USR1, &sa, null); + posix.sigaction(test_signo, &sa, null); // Check that we can read it back correctly. - posix.sigaction(posix.SIG.USR1, null, &old_sa); + posix.sigaction(test_signo, null, &old_sa); try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?); try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0); // Invoke the handler. - try posix.raise(posix.SIG.USR1); - try testing.expect(S.handler_called_count == 1); + try posix.raise(test_signo); + try testing.expectEqual(1, S.handler_called_count); // Check if passing RESETHAND correctly reset the handler to SIG_DFL - posix.sigaction(posix.SIG.USR1, null, &old_sa); + posix.sigaction(test_signo, null, &old_sa); try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler); // Reinstall the signal w/o RESETHAND and re-raise sa.flags = posix.SA.SIGINFO; - posix.sigaction(posix.SIG.USR1, &sa, null); - try posix.raise(posix.SIG.USR1); - try testing.expect(S.handler_called_count == 2); + posix.sigaction(test_signo, &sa, null); + try posix.raise(test_signo); + try testing.expectEqual(2, S.handler_called_count); // Now set the signal to ignored sa.handler = .{ .handler = posix.SIG.IGN }; sa.flags = 0; - posix.sigaction(posix.SIG.USR1, &sa, null); + posix.sigaction(test_signo, &sa, null); // Re-raise to ensure handler is actually ignored - try posix.raise(posix.SIG.USR1); - try testing.expect(S.handler_called_count == 2); + try posix.raise(test_signo); + try testing.expectEqual(2, S.handler_called_count); // Ensure that ignored state is returned when querying - posix.sigaction(posix.SIG.USR1, null, &old_sa); - try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?); + posix.sigaction(test_signo, null, &old_sa); + try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler); +} + +test "sigset_t bits" { + if (native_os == .wasi or native_os == .windows) + return error.SkipZigTest; + + const S = struct { + var expected_sig: i32 = undefined; + var handler_called_count: u32 = 0; + + fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void { + _ = ctx_ptr; + + const info_sig = switch (native_os) { + .netbsd => info.info.signo, + else => info.signo, + }; + if (sig == expected_sig and sig == info_sig) { + handler_called_count += 1; + } + } + }; + + const self_pid = posix.system.getpid(); + + // To check that sigset_t mapping matches kernel (think u32/u64 + // mismatches on big-endian), try sending a blocked signal to make + // sure the mask matches the signal. + inline for ([_]usize{ posix.SIG.INT, posix.SIG.USR1, 62, 94, 126 }) |test_signo| { + if (test_signo >= posix.NSIG) continue; + + S.expected_sig = test_signo; + S.handler_called_count = 0; + + var sa: posix.Sigaction = .{ + .handler = .{ .sigaction = &S.handler }, + .mask = posix.empty_sigset, + .flags = posix.SA.SIGINFO | posix.SA.RESETHAND, + }; + var old_sa: posix.Sigaction = undefined; + + // Install the new signal handler. + posix.sigaction(test_signo, &sa, &old_sa); + + // block the signal and see that its delayed until unblocked + var block_one = posix.empty_sigset; + _ = posix.sigaddset(&block_one, test_signo); + posix.sigprocmask(posix.SIG.BLOCK, &block_one, null); + + // qemu maps target signals to host signals 1-to-1, so targets + // with more signals than the host will fail to send the signal. + const rc = posix.system.kill(self_pid, test_signo); + switch (posix.errno(rc)) { + .SUCCESS => { + // See that the signal is blocked, then unblocked + try testing.expectEqual(0, S.handler_called_count); + posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null); + try testing.expectEqual(1, S.handler_called_count); + }, + .INVAL => { + // Signal won't get delviered. Just clean up. + posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null); + try testing.expectEqual(0, S.handler_called_count); + }, + else => |errno| return posix.unexpectedErrno(errno), + } + + // Restore original handler + posix.sigaction(test_signo, &old_sa, null); + } } test "dup & dup2" { From f0aefa625b5b0d0b3612cfba9bd25c825a71818f Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Tue, 22 Apr 2025 09:31:14 -0700 Subject: [PATCH 4/7] posix: remove empty_sigset When linking a libc, Zig should defer to the C library for sigset operations. The pre-filled constants signal sets (empty_sigset, filled_sigset) are not compatible with C library initialization, so remove them and use the runtime `sigemptyset` and `sigfillset` methods to initialize any sigset. --- lib/std/Progress.zig | 3 ++- lib/std/debug.zig | 6 ++++-- lib/std/posix.zig | 7 +++---- lib/std/posix/test.zig | 15 +++++++++------ lib/std/start.zig | 5 +++-- src/crash_report.zig | 4 ++-- test/standalone/sigpipe/build.zig | 5 +++-- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 24f58e1c68..3e5f29c54e 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -412,9 +412,10 @@ pub fn start(options: Options) Node { if (have_sigwinch) { var act: posix.Sigaction = .{ .handler = .{ .sigaction = handleSigWinch }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = (posix.SA.SIGINFO | posix.SA.RESTART), }; + posix.sigemptyset(&act.mask); posix.sigaction(posix.SIG.WINCH, &act, null); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index d2b17ed30d..c0b360fda3 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1389,9 +1389,10 @@ pub fn attachSegfaultHandler() void { } var act = posix.Sigaction{ .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), }; + posix.sigemptyset(&act.mask); updateSegfaultHandler(&act); } @@ -1406,9 +1407,10 @@ fn resetSegfaultHandler() void { } var act = posix.Sigaction{ .handler = .{ .handler = posix.SIG.DFL }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = 0, }; + posix.sigemptyset(&act.mask); updateSegfaultHandler(&act); } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 69ecc6dd58..1113224da8 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -127,7 +127,6 @@ pub const timerfd_clockid_t = system.timerfd_clockid_t; pub const cpu_set_t = system.cpu_set_t; pub const dev_t = system.dev_t; pub const dl_phdr_info = system.dl_phdr_info; -pub const empty_sigset = system.empty_sigset; pub const fd_t = system.fd_t; pub const file_obj = system.file_obj; pub const gid_t = system.gid_t; @@ -691,14 +690,14 @@ pub fn abort() noreturn { // Install default handler so that the tkill below will terminate. const sigact = Sigaction{ .handler = .{ .handler = SIG.DFL }, - .mask = empty_sigset, + .mask = linux.empty_sigset, .flags = 0, }; sigaction(SIG.ABRT, &sigact, null); _ = linux.tkill(linux.gettid(), SIG.ABRT); - var sigabrtmask = empty_sigset; + var sigabrtmask = linux.empty_sigset; sigaddset(&sigabrtmask, SIG.ABRT); sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); @@ -5831,7 +5830,7 @@ pub fn sigemptyset(set: *sigset_t) void { else => unreachable, } } - set.* = system.empty_sigset; + set.* = mem.zeroes(sigset_t); } pub fn sigaddset(set: *sigset_t, sig: u8) void { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 75ed74dc75..7be60be88d 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -887,7 +887,8 @@ test "sigset add/del" { if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; - var sigset = posix.empty_sigset; + var sigset: posix.sigset_t = undefined; + posix.sigemptyset(&sigset); // See that none are set, then set each one, see that they're all set, then // remove them all, and then see that none are set. @@ -903,7 +904,6 @@ test "sigset add/del" { if (!reserved_signo(i)) { try expectEqual(true, posix.sigismember(&sigset, @truncate(i))); } - try expectEqual(false, posix.sigismember(&posix.empty_sigset, @truncate(i))); } for (1..posix.NSIG) |i| { if (!reserved_signo(i)) { @@ -944,9 +944,10 @@ test "sigaction" { var sa: posix.Sigaction = .{ .handler = .{ .sigaction = &S.handler }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = posix.SA.SIGINFO | posix.SA.RESETHAND, }; + posix.sigemptyset(&sa.mask); var old_sa: posix.Sigaction = undefined; // Install the new signal handler. @@ -1019,17 +1020,19 @@ test "sigset_t bits" { var sa: posix.Sigaction = .{ .handler = .{ .sigaction = &S.handler }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = posix.SA.SIGINFO | posix.SA.RESETHAND, }; + posix.sigemptyset(&sa.mask); var old_sa: posix.Sigaction = undefined; // Install the new signal handler. posix.sigaction(test_signo, &sa, &old_sa); // block the signal and see that its delayed until unblocked - var block_one = posix.empty_sigset; - _ = posix.sigaddset(&block_one, test_signo); + var block_one: posix.sigset_t = undefined; + posix.sigemptyset(&block_one); + posix.sigaddset(&block_one, test_signo); posix.sigprocmask(posix.SIG.BLOCK, &block_one, null); // qemu maps target signals to host signals 1-to-1, so targets diff --git a/lib/std/start.zig b/lib/std/start.zig index 9072c97c02..a7421f80f7 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -745,13 +745,14 @@ fn maybeIgnoreSigpipe() void { if (have_sigpipe_support and !std.options.keep_sigpipe) { const posix = std.posix; - const act: posix.Sigaction = .{ + var act: posix.Sigaction = .{ // Set handler to a noop function instead of `SIG.IGN` to prevent // leaking signal disposition to a child process. .handler = .{ .handler = noopSigHandler }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = 0, }; + posix.sigemptyset(&act.mask); posix.sigaction(posix.SIG.PIPE, &act, null); } } diff --git a/src/crash_report.zig b/src/crash_report.zig index 431381fce4..6e207ca74a 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -177,10 +177,10 @@ pub fn attachSegfaultHandler() void { } var act: posix.Sigaction = .{ .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), }; - + posix.sigemptyset(&act.mask); debug.updateSegfaultHandler(&act); } diff --git a/test/standalone/sigpipe/build.zig b/test/standalone/sigpipe/build.zig index 8e1cb5e122..da2c765182 100644 --- a/test/standalone/sigpipe/build.zig +++ b/test/standalone/sigpipe/build.zig @@ -16,11 +16,12 @@ pub fn build(b: *std.build.Builder) !void { // This test runs "breakpipe" as a child process and that process // depends on inheriting a SIGPIPE disposition of "default". { - const act = posix.Sigaction{ + var act = posix.Sigaction{ .handler = .{ .handler = posix.SIG.DFL }, - .mask = posix.empty_sigset, + .mask = undefined, .flags = 0, }; + posix.sigemptyset(&act.mask); try posix.sigaction(posix.SIG.PIPE, &act, null); } From 120c4789c31c066f15fe414a5b32cdc7e80a065c Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Tue, 22 Apr 2025 14:19:06 -0700 Subject: [PATCH 5/7] sigset_t: sigemptyset() and sigfillset() are functions that return sigset_t By returning an initialized sigset (instead of taking the set as an output parameter), these functions can be used to directly initialize the `mask` parameter of a `Sigaction` instance. --- lib/std/Progress.zig | 5 ++--- lib/std/debug.zig | 11 ++++------ lib/std/os/emscripten.zig | 4 +++- lib/std/os/linux.zig | 12 +++++++---- lib/std/os/linux/test.zig | 12 +++++------ lib/std/os/plan9.zig | 5 ++++- lib/std/posix.zig | 29 ++++++++++++++----------- lib/std/posix/test.zig | 35 ++++++++++++++----------------- lib/std/start.zig | 5 ++--- src/crash_report.zig | 5 ++--- test/standalone/sigpipe/build.zig | 5 ++--- 11 files changed, 65 insertions(+), 63 deletions(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 3e5f29c54e..2213bd9b11 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -410,12 +410,11 @@ pub fn start(options: Options) Node { } if (have_sigwinch) { - var act: posix.Sigaction = .{ + const act: posix.Sigaction = .{ .handler = .{ .sigaction = handleSigWinch }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = (posix.SA.SIGINFO | posix.SA.RESTART), }; - posix.sigemptyset(&act.mask); posix.sigaction(posix.SIG.WINCH, &act, null); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index c0b360fda3..eb7cf793f2 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1387,13 +1387,11 @@ pub fn attachSegfaultHandler() void { windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); return; } - var act = posix.Sigaction{ + const act = posix.Sigaction{ .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), }; - posix.sigemptyset(&act.mask); - updateSegfaultHandler(&act); } @@ -1405,12 +1403,11 @@ fn resetSegfaultHandler() void { } return; } - var act = posix.Sigaction{ + const act = posix.Sigaction{ .handler = .{ .handler = posix.SIG.DFL }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = 0, }; - posix.sigemptyset(&act.mask); updateSegfaultHandler(&act); } diff --git a/lib/std/os/emscripten.zig b/lib/std/os/emscripten.zig index ad7a3f65c1..0e07a8afb7 100644 --- a/lib/std/os/emscripten.zig +++ b/lib/std/os/emscripten.zig @@ -560,7 +560,9 @@ pub const Sigaction = extern struct { }; pub const sigset_t = [1024 / 32]u32; -pub const empty_sigset = [_]u32{0} ** @typeInfo(sigset_t).array.len; +pub fn sigemptyset() sigset_t { + return [_]u32{0} ** @typeInfo(sigset_t).array.len; +} pub const siginfo_t = extern struct { signo: i32, errno: i32, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 3e5c808f56..ee44bf072b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1800,11 +1800,15 @@ const SigsetElement = c_ulong; const sigset_len = @typeInfo(sigset_t).array.len; -/// Empty set to initialize sigset_t instances from. No need for `sigemptyset`. -pub const empty_sigset: sigset_t = [_]SigsetElement{0} ** sigset_len; +/// Zig's version of sigemptyset. Returns initialized sigset_t. +pub fn sigemptyset() sigset_t { + return [_]SigsetElement{0} ** sigset_len; +} -/// Filled set to initialize sigset_t instances from. No need for `sigfillset`. -pub const filled_sigset: sigset_t = [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len; +/// Zig's version of sigfillset. Returns initalized sigset_t. +pub fn sigfillset() sigset_t { + return [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len; +} fn sigset_bit_index(sig: usize) struct { word: usize, mask: SigsetElement } { assert(sig > 0); diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 097c5a388c..7f5deff480 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -128,7 +128,7 @@ test "fadvise" { test "sigset_t" { std.debug.assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8)); - var sigset = linux.empty_sigset; + var sigset = linux.sigemptyset(); // See that none are set, then set each one, see that they're all set, then // remove them all, and then see that none are set. @@ -140,8 +140,6 @@ test "sigset_t" { } for (1..linux.NSIG) |i| { try expectEqual(linux.sigismember(&sigset, @truncate(i)), true); - try expectEqual(linux.sigismember(&linux.filled_sigset, @truncate(i)), true); - try expectEqual(linux.sigismember(&linux.empty_sigset, @truncate(i)), false); } for (1..linux.NSIG) |i| { linux.sigdelset(&sigset, @truncate(i)); @@ -183,16 +181,16 @@ test "sigset_t" { } } -test "filled_sigset" { +test "sigfillset" { // unlike the C library, all the signals are set in the kernel-level fillset - const sigset = linux.filled_sigset; + const sigset = linux.sigfillset(); for (1..linux.NSIG) |i| { try expectEqual(linux.sigismember(&sigset, @truncate(i)), true); } } -test "empty_sigset" { - const sigset = linux.empty_sigset; +test "sigemptyset" { + const sigset = linux.sigemptyset(); for (1..linux.NSIG) |i| { try expectEqual(linux.sigismember(&sigset, @truncate(i)), false); } diff --git a/lib/std/os/plan9.zig b/lib/std/os/plan9.zig index 1882eda476..b910e26c71 100644 --- a/lib/std/os/plan9.zig +++ b/lib/std/os/plan9.zig @@ -182,7 +182,6 @@ pub const SIG = struct { pub const TTOU = 20; }; pub const sigset_t = c_long; -pub const empty_sigset = 0; pub const siginfo_t = c_long; // TODO plan9 doesn't have sigaction_fn. Sigaction is not a union, but we include it here to be compatible. pub const Sigaction = extern struct { @@ -199,6 +198,10 @@ pub const Sigaction = extern struct { pub const AT = struct { pub const FDCWD = -100; // we just make up a constant; FDCWD and openat don't actually exist in plan9 }; +// Plan 9 doesn't do signals. This is just needed to get through start.zig. +pub fn sigemptyset() sigset_t { + return 0; +} // TODO implement sigaction // right now it is just a shim to allow using start.zig code pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 1113224da8..f414aa801b 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -677,7 +677,8 @@ pub fn abort() noreturn { raise(SIG.ABRT) catch {}; // Disable all signal handlers. - sigprocmask(SIG.BLOCK, &linux.filled_sigset, null); + const filledset = linux.sigfillset(); + sigprocmask(SIG.BLOCK, &filledset, null); // Only one thread may proceed to the rest of abort(). if (!builtin.single_threaded) { @@ -690,14 +691,14 @@ pub fn abort() noreturn { // Install default handler so that the tkill below will terminate. const sigact = Sigaction{ .handler = .{ .handler = SIG.DFL }, - .mask = linux.empty_sigset, + .mask = sigemptyset(), .flags = 0, }; sigaction(SIG.ABRT, &sigact, null); _ = linux.tkill(linux.gettid(), SIG.ABRT); - var sigabrtmask = linux.empty_sigset; + var sigabrtmask = sigemptyset(); sigaddset(&sigabrtmask, SIG.ABRT); sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); @@ -727,7 +728,7 @@ pub fn raise(sig: u8) RaiseError!void { // cannot trigger an extra, unexpected, inter-process signal. Signal paranoia inherited from Musl. const filled = linux.sigfillset(); var orig: sigset_t = undefined; - sigprocmask(SIG.BLOCK, &linux.filled_sigset, &orig); + sigprocmask(SIG.BLOCK, &filled, &orig); const rc = linux.tkill(linux.gettid(), sig); sigprocmask(SIG.SETMASK, &orig, null); @@ -5813,24 +5814,28 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { } } -pub fn sigfillset(set: *sigset_t) void { +/// Return a filled sigset_t. +pub fn sigfillset() sigset_t { if (builtin.link_libc) { - switch (errno(system.sigfillset(set))) { - .SUCCESS => return, + var set: sigset_t = undefined; + switch (errno(system.sigfillset(&set))) { + .SUCCESS => return set, else => unreachable, } } - set.* = system.filled_sigset; + return system.sigfillset(); } -pub fn sigemptyset(set: *sigset_t) void { +/// Return an empty sigset_t. +pub fn sigemptyset() sigset_t { if (builtin.link_libc) { - switch (errno(system.sigemptyset(set))) { - .SUCCESS => return, + var set: sigset_t = undefined; + switch (errno(system.sigemptyset(&set))) { + .SUCCESS => return set, else => unreachable, } } - set.* = mem.zeroes(sigset_t); + return system.sigemptyset(); } pub fn sigaddset(set: *sigset_t, sig: u8) void { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 7be60be88d..c886ed6153 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -863,17 +863,15 @@ test "sigset empty/full" { if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; - var set: posix.sigset_t = undefined; - - posix.sigemptyset(&set); + var set: posix.sigset_t = posix.sigemptyset(); for (1..posix.NSIG) |i| { try expectEqual(false, posix.sigismember(&set, @truncate(i))); } // The C library can reserve some (unnamed) signals, so can't check the full // NSIG set is defined, but just test a couple: - posix.sigfillset(&set); - try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.USR1))); + set = posix.sigfillset(); + try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.CHLD))); try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.INT))); } @@ -887,8 +885,7 @@ test "sigset add/del" { if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; - var sigset: posix.sigset_t = undefined; - posix.sigemptyset(&sigset); + var sigset: posix.sigset_t = posix.sigemptyset(); // See that none are set, then set each one, see that they're all set, then // remove them all, and then see that none are set. @@ -924,7 +921,7 @@ test "sigaction" { return error.SkipZigTest; } - const test_signo = posix.SIG.USR1; + const test_signo = posix.SIG.URG; // URG only because it is ignored by default in debuggers const S = struct { var handler_called_count: u32 = 0; @@ -944,10 +941,10 @@ test "sigaction" { var sa: posix.Sigaction = .{ .handler = .{ .sigaction = &S.handler }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = posix.SA.SIGINFO | posix.SA.RESETHAND, }; - posix.sigemptyset(&sa.mask); + var old_sa: posix.Sigaction = undefined; // Install the new signal handler. @@ -1009,29 +1006,29 @@ test "sigset_t bits" { const self_pid = posix.system.getpid(); - // To check that sigset_t mapping matches kernel (think u32/u64 - // mismatches on big-endian), try sending a blocked signal to make - // sure the mask matches the signal. - inline for ([_]usize{ posix.SIG.INT, posix.SIG.USR1, 62, 94, 126 }) |test_signo| { + // To check that sigset_t mapping matches kernel (think u32/u64 mismatches on + // big-endian), try sending a blocked signal to make sure the mask matches the + // signal. (Send URG and CHLD because they're ignored by default in the + // debugger, vs. USR1 or other named signals) + inline for ([_]usize{ posix.SIG.URG, posix.SIG.CHLD, 62, 94, 126 }) |test_signo| { if (test_signo >= posix.NSIG) continue; S.expected_sig = test_signo; S.handler_called_count = 0; - var sa: posix.Sigaction = .{ + const sa: posix.Sigaction = .{ .handler = .{ .sigaction = &S.handler }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = posix.SA.SIGINFO | posix.SA.RESETHAND, }; - posix.sigemptyset(&sa.mask); + var old_sa: posix.Sigaction = undefined; // Install the new signal handler. posix.sigaction(test_signo, &sa, &old_sa); // block the signal and see that its delayed until unblocked - var block_one: posix.sigset_t = undefined; - posix.sigemptyset(&block_one); + var block_one: posix.sigset_t = posix.sigemptyset(); posix.sigaddset(&block_one, test_signo); posix.sigprocmask(posix.SIG.BLOCK, &block_one, null); diff --git a/lib/std/start.zig b/lib/std/start.zig index a7421f80f7..6495db1c51 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -745,14 +745,13 @@ fn maybeIgnoreSigpipe() void { if (have_sigpipe_support and !std.options.keep_sigpipe) { const posix = std.posix; - var act: posix.Sigaction = .{ + const act: posix.Sigaction = .{ // Set handler to a noop function instead of `SIG.IGN` to prevent // leaking signal disposition to a child process. .handler = .{ .handler = noopSigHandler }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = 0, }; - posix.sigemptyset(&act.mask); posix.sigaction(posix.SIG.PIPE, &act, null); } } diff --git a/src/crash_report.zig b/src/crash_report.zig index 6e207ca74a..6f043c3bfb 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -175,12 +175,11 @@ pub fn attachSegfaultHandler() void { _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); return; } - var act: posix.Sigaction = .{ + const act: posix.Sigaction = .{ .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), }; - posix.sigemptyset(&act.mask); debug.updateSegfaultHandler(&act); } diff --git a/test/standalone/sigpipe/build.zig b/test/standalone/sigpipe/build.zig index da2c765182..de06e1df15 100644 --- a/test/standalone/sigpipe/build.zig +++ b/test/standalone/sigpipe/build.zig @@ -16,12 +16,11 @@ pub fn build(b: *std.build.Builder) !void { // This test runs "breakpipe" as a child process and that process // depends on inheriting a SIGPIPE disposition of "default". { - var act = posix.Sigaction{ + const act = posix.Sigaction{ .handler = .{ .handler = posix.SIG.DFL }, - .mask = undefined, + .mask = posix.sigemptyset(), .flags = 0, }; - posix.sigemptyset(&act.mask); try posix.sigaction(posix.SIG.PIPE, &act, null); } From cf989374e986462fef70fe4c1d0f5869b194148d Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Thu, 24 Apr 2025 22:02:53 -0700 Subject: [PATCH 6/7] linux: update `sigmask` in every arch `ucontext_t` All the existing code that manipulates `ucontext_t` expects there to be a glibc-compatible sigmask (1024-bit). The `ucontext_t` struct need to be cleaned up so the glibc-dependent format is only used when linking glibc/musl library, but that is a more involved change. In practice, no Zig code looks at the sigset field contents, so it just needs to be the right size. --- lib/std/os/linux/aarch64.zig | 2 +- lib/std/os/linux/arm.zig | 2 +- lib/std/os/linux/loongarch64.zig | 3 +-- lib/std/os/linux/powerpc.zig | 2 +- lib/std/os/linux/powerpc64.zig | 2 +- lib/std/os/linux/s390x.zig | 2 +- lib/std/os/linux/sparc64.zig | 2 +- lib/std/os/linux/x86.zig | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/std/os/linux/aarch64.zig b/lib/std/os/linux/aarch64.zig index 649e474310..ed40145e1b 100644 --- a/lib/std/os/linux/aarch64.zig +++ b/lib/std/os/linux/aarch64.zig @@ -289,7 +289,7 @@ pub const ucontext_t = extern struct { flags: usize, link: ?*ucontext_t, stack: stack_t, - sigmask: sigset_t, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask mcontext: mcontext_t, }; diff --git a/lib/std/os/linux/arm.zig b/lib/std/os/linux/arm.zig index 8f83bb60a1..8fea30a5da 100644 --- a/lib/std/os/linux/arm.zig +++ b/lib/std/os/linux/arm.zig @@ -337,7 +337,7 @@ pub const ucontext_t = extern struct { link: ?*ucontext_t, stack: stack_t, mcontext: mcontext_t, - sigmask: sigset_t, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask regspace: [64]u64, }; diff --git a/lib/std/os/linux/loongarch64.zig b/lib/std/os/linux/loongarch64.zig index 9ee8fe229c..50c4664ac8 100644 --- a/lib/std/os/linux/loongarch64.zig +++ b/lib/std/os/linux/loongarch64.zig @@ -264,8 +264,7 @@ pub const ucontext_t = extern struct { flags: c_ulong, link: ?*ucontext_t, stack: stack_t, - sigmask: sigset_t, - _pad: [1024 / 8 - @sizeOf(sigset_t)]u8, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask mcontext: mcontext_t, }; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index 443a32769a..450cb27c7e 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -341,7 +341,7 @@ pub const ucontext_t = extern struct { stack: stack_t, pad: [7]i32, regs: *mcontext_t, - sigmask: sigset_t, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask pad2: [3]i32, mcontext: mcontext_t, }; diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index bb1032fb21..b0a14ff3cb 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -337,7 +337,7 @@ pub const ucontext_t = extern struct { flags: u32, link: ?*ucontext_t, stack: stack_t, - sigmask: sigset_t, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask mcontext: mcontext_t, }; diff --git a/lib/std/os/linux/s390x.zig b/lib/std/os/linux/s390x.zig index 70cf53f6fd..614c33d076 100644 --- a/lib/std/os/linux/s390x.zig +++ b/lib/std/os/linux/s390x.zig @@ -273,7 +273,7 @@ pub const ucontext_t = extern struct { link: ?*ucontext_t, stack: stack_t, mcontext: mcontext_t, - sigmask: sigset_t, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask }; pub const mcontext_t = extern struct { diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 594b11b7ac..34df73fcb1 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -454,7 +454,7 @@ pub const ucontext_t = extern struct { sigmask: u64, mcontext: mcontext_t, stack: stack_t, - sigset: sigset_t, + sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask }; /// TODO diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index cef0388206..41e1ec7d99 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -350,7 +350,7 @@ pub const ucontext_t = extern struct { link: ?*ucontext_t, stack: stack_t, mcontext: mcontext_t, - sigmask: sigset_t, + sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask regspace: [64]u64, }; From a55ecd7532e7ae015afe5cb5bc16b89376a4bf56 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Mon, 28 Apr 2025 22:29:26 -0700 Subject: [PATCH 7/7] std.os.linux: Fix MIPS signal numbers Dunno why the MIPS signal numbers are different, or why Zig had them already special cased, but wrong. We have the technology to test these constants. We should use it. --- lib/std/os/linux.zig | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index ee44bf072b..83bf8e1013 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -3499,6 +3499,7 @@ pub const SIG = if (is_mips) struct { pub const UNBLOCK = 2; pub const SETMASK = 3; + // https://github.com/torvalds/linux/blob/ca91b9500108d4cf083a635c2e11c884d5dd20ea/arch/mips/include/uapi/asm/signal.h#L25 pub const HUP = 1; pub const INT = 2; pub const QUIT = 3; @@ -3506,33 +3507,32 @@ pub const SIG = if (is_mips) struct { pub const TRAP = 5; pub const ABRT = 6; pub const IOT = ABRT; - pub const BUS = 7; + pub const EMT = 7; pub const FPE = 8; pub const KILL = 9; - pub const USR1 = 10; + pub const BUS = 10; pub const SEGV = 11; - pub const USR2 = 12; + pub const SYS = 12; pub const PIPE = 13; pub const ALRM = 14; pub const TERM = 15; - pub const STKFLT = 16; - pub const CHLD = 17; - pub const CONT = 18; - pub const STOP = 19; - pub const TSTP = 20; - pub const TTIN = 21; - pub const TTOU = 22; - pub const URG = 23; - pub const XCPU = 24; - pub const XFSZ = 25; - pub const VTALRM = 26; - pub const PROF = 27; - pub const WINCH = 28; - pub const IO = 29; - pub const POLL = 29; - pub const PWR = 30; - pub const SYS = 31; - pub const UNUSED = SIG.SYS; + pub const USR1 = 16; + pub const USR2 = 17; + pub const CHLD = 18; + pub const PWR = 19; + pub const WINCH = 20; + pub const URG = 21; + pub const IO = 22; + pub const POLL = IO; + pub const STOP = 23; + pub const TSTP = 24; + pub const CONT = 25; + pub const TTIN = 26; + pub const TTOU = 27; + pub const VTALRM = 28; + pub const PROF = 29; + pub const XCPU = 30; + pub const XFZ = 31; pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize)); pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);