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 35d175f0b9aa98aa1b26f32e1b373663859a50eb..d5560c3f6fa24a989fa7aebd68687b4011887f1f 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 ac083ec9f2d96f04820c6b952f377f1126298931..74d79842be25fe1afecb324a9accacc769697074 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 -- 2.54.0 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 00390c0800d70d8fba3026282e9a6f0df3b60209..3e5c808f56595420dfc14b3bbe84781f545dd3c3 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 fa6f8223297bf8388a566746de1c89a0ecc94cee..097c5a388c2687f20ed9d01c99dc4ffc23f41e0b 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 90147876c81761194abb33e4ade7543c7e1c60d6..7ef86fdcf888f6a3349dad9cae70c665d5c3dd25 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" ); } -- 2.54.0 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 | 173 ++++++++++++++++++++++++++++++++++------- 2 files changed, 210 insertions(+), 42 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index ad65cf205ded7ef093a62994b5590c2eb93003aa..69ecc6dd585ea3c59177901f199eaff8b35a3e4c 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 e27f8971d7e6f733866d12cd4f9dfdfdc2a2bff2..75ed74dc759207ab71c8952ebe0fedcf15931345 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -859,34 +859,85 @@ test "shutdown socket" { std.net.Stream.close(.{ .handle = sock }); } +test "sigset empty/full" { + if (native_os == .wasi or native_os == .windows) + return error.SkipZigTest; + + 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/7427 - if (native_os == .linux and builtin.target.cpu.arch == .x86) - return error.SkipZigTest; - // https://github.com/ziglang/zig/issues/15381 if (native_os == .macos and builtin.target.cpu.arch == .x86_64) { 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" { -- 2.54.0 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 24f58e1c685b49b16d33cc996b9d9faf3e507389..3e5f29c54e5893a490faf32bdaab30174c0c162e 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 d2b17ed30dbe5a49b6926dbd770919a0dd4b843b..c0b360fda320c79949efb45f2aad18816ec13955 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 69ecc6dd585ea3c59177901f199eaff8b35a3e4c..1113224da8f74d78c860e9af62f5fb16c6dca4ed 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 75ed74dc759207ab71c8952ebe0fedcf15931345..7be60be88dd0c63b719930ad6ba1a7b692197ecc 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 9072c97c0254d79499afd992c108899c0be1f3b4..a7421f80f7fef8a3d06d84ec32c3c9f2d2298ced 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 431381fce4e5b81dfc38f1578b2de5e883537a17..6e207ca74ab7cb053cef674b0b4a1dcf4a301fb3 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 8e1cb5e1227ffabb37e914ded4417065c8c4853f..da2c7651825da089eb6689e1a7f3826cd37bd7d0 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); } -- 2.54.0 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 3e5f29c54e5893a490faf32bdaab30174c0c162e..2213bd9b1166e6e9eccb90e84f8e673c8e798b65 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 c0b360fda320c79949efb45f2aad18816ec13955..eb7cf793f2d736135a1c54d032c5bf63f91ebaef 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 ad7a3f65c1cec3b3754006320bd13f5439c59240..0e07a8afb77f1d411f37c14030d2e548ed3d8c6f 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 3e5c808f56595420dfc14b3bbe84781f545dd3c3..ee44bf072bb8eb9ab39756d9e17ed61f34f058e9 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 097c5a388c2687f20ed9d01c99dc4ffc23f41e0b..7f5deff480d30c3400669f1731449ede235ddde4 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 1882eda47696fb80075ea5f50f00d22b85480a6e..b910e26c713483a69b4ad2a0acdbbda6c22e1766 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 1113224da8f74d78c860e9af62f5fb16c6dca4ed..f414aa801b964c4603fdd91f12204a8e6f6379b4 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 7be60be88dd0c63b719930ad6ba1a7b692197ecc..c886ed6153fb20e92d7900f6488bc63fb9a0f93f 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 a7421f80f7fef8a3d06d84ec32c3c9f2d2298ced..6495db1c51a67b77d82a8bda766e95774c59b321 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 6e207ca74ab7cb053cef674b0b4a1dcf4a301fb3..6f043c3bfb79df02a6b7bbcdcf1d2b20f4af9309 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 da2c7651825da089eb6689e1a7f3826cd37bd7d0..de06e1df1553386818349ee1fd3381d7ad0b9cdc 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); } -- 2.54.0 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 649e474310fcc50e896f13edf12e515bbbf58ad0..ed40145e1bbb9cfd7daa637c3df6237544823a56 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 8f83bb60a1bacc8177ec049e6cbaff93baaf89ca..8fea30a5da8bceea846c33c25e73351ecbb24270 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 9ee8fe229c2a80f709dea234f5b36b6fd6b78756..50c4664ac821dd4bbb07433c5a96e23aede4a38c 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 443a32769ab59b39942378f938720b1c087d6272..450cb27c7ec47a06bf99454fe3154eb0ec9a83e7 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 bb1032fb21ad526f22667f9873dcfc8db1ac87fc..b0a14ff3cb6e85f0b8bbe87bc29762aa50eb4211 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 70cf53f6fd085b0d422ffe28452997f12f56cacf..614c33d076f5047d96756be873efed23578d6565 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 594b11b7acccbd363e730111a5883fe3faeb47f1..34df73fcb1607d10ae11c4d7b117d681b935d694 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 cef038820616799bbb5dde7083c6530cfcf0fc83..41e1ec7d997da67aef12d5a14c555ecf82940cb6 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, }; -- 2.54.0 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 ee44bf072bb8eb9ab39756d9e17ed61f34f058e9..83bf8e10134686241808ebfdea4687e472b87846 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); -- 2.54.0