Commit d16079d79a
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;
+
+ 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;
- // https://github.com/ziglang/zig/issues/7427
- if (native_os == .linux and builtin.target.cpu.arch == .x86)
+ 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" {
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