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" {