| 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); |
| 3 | |
| 4 | const native_os = builtin.target.os.tag; |
| 5 | |
| 6 | pub fn main() !void { |
| 7 | if (native_os == .wasi or native_os == .windows) { |
| 8 | return; // no sigaction |
| 9 | } |
| 10 | |
| 11 | try test_sigaction(); |
| 12 | try test_sigset_bits(); |
| 13 | } |
| 14 | |
| 15 | fn test_sigaction() !void { |
| 16 | if (native_os == .macos and builtin.target.cpu.arch == .x86_64) { |
| 17 | return; // https://github.com/ziglang/zig/issues/15381 |
| 18 | } |
| 19 | |
| 20 | const test_signo: std.posix.SIG = .URG; // URG only because it is ignored by default in debuggers |
| 21 | |
| 22 | const S = struct { |
| 23 | var handler_called_count: u32 = 0; |
| 24 | |
| 25 | fn handler(sig: std.posix.SIG, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void { |
| 26 | _ = ctx_ptr; |
| 27 | // Check that we received the correct signal. |
| 28 | const info_sig = switch (native_os) { |
| 29 | .netbsd => info.info.signo, |
| 30 | else => info.signo, |
| 31 | }; |
| 32 | if (sig == test_signo and sig == info_sig) { |
| 33 | handler_called_count += 1; |
| 34 | } |
| 35 | } |
| 36 | }; |
| 37 | |
| 38 | var sa: std.posix.Sigaction = .{ |
| 39 | .handler = .{ .sigaction = &S.handler }, |
| 40 | .mask = std.posix.sigemptyset(), |
| 41 | .flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND, |
| 42 | }; |
| 43 | |
| 44 | var old_sa: std.posix.Sigaction = undefined; |
| 45 | |
| 46 | // Install the new signal handler. |
| 47 | std.posix.sigaction(test_signo, &sa, null); |
| 48 | |
| 49 | // Check that we can read it back correctly. |
| 50 | std.posix.sigaction(test_signo, null, &old_sa); |
| 51 | try std.testing.expectEqual(&S.handler, old_sa.handler.sigaction.?); |
| 52 | try std.testing.expect((old_sa.flags & std.posix.SA.SIGINFO) != 0); |
| 53 | |
| 54 | // Invoke the handler. |
| 55 | try std.posix.raise(test_signo); |
| 56 | try std.testing.expectEqual(1, S.handler_called_count); |
| 57 | |
| 58 | // Check if passing RESETHAND correctly reset the handler to SIG_DFL |
| 59 | std.posix.sigaction(test_signo, null, &old_sa); |
| 60 | try std.testing.expectEqual(std.posix.SIG.DFL, old_sa.handler.handler); |
| 61 | |
| 62 | // Reinstall the signal w/o RESETHAND and re-raise |
| 63 | sa.flags = std.posix.SA.SIGINFO; |
| 64 | std.posix.sigaction(test_signo, &sa, null); |
| 65 | try std.posix.raise(test_signo); |
| 66 | try std.testing.expectEqual(2, S.handler_called_count); |
| 67 | |
| 68 | // Now set the signal to ignored |
| 69 | sa.handler = .{ .handler = std.posix.SIG.IGN }; |
| 70 | sa.flags = 0; |
| 71 | std.posix.sigaction(test_signo, &sa, null); |
| 72 | |
| 73 | // Re-raise to ensure handler is actually ignored |
| 74 | try std.posix.raise(test_signo); |
| 75 | try std.testing.expectEqual(2, S.handler_called_count); |
| 76 | |
| 77 | // Ensure that ignored state is returned when querying |
| 78 | std.posix.sigaction(test_signo, null, &old_sa); |
| 79 | try std.testing.expectEqual(std.posix.SIG.IGN, old_sa.handler.handler); |
| 80 | } |
| 81 | |
| 82 | fn test_sigset_bits() !void { |
| 83 | const S = struct { |
| 84 | var expected_sig: std.posix.SIG = undefined; |
| 85 | var seen_sig: ?std.posix.SIG = null; |
| 86 | |
| 87 | fn handler(sig: std.posix.SIG, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void { |
| 88 | _ = ctx_ptr; |
| 89 | |
| 90 | const info_sig = switch (native_os) { |
| 91 | .netbsd => info.info.signo, |
| 92 | else => info.signo, |
| 93 | }; |
| 94 | if (seen_sig == null and sig == expected_sig and sig == info_sig) { |
| 95 | seen_sig = sig; |
| 96 | } |
| 97 | } |
| 98 | }; |
| 99 | |
| 100 | // Assume this is a single-threaded process where the current thread has the |
| 101 | // 'pid' thread id. (The sigprocmask calls are thread-private state.) |
| 102 | const self_tid = std.posix.system.getpid(); |
| 103 | |
| 104 | // To check that sigset_t mapping matches kernel (think u32/u64 mismatches on |
| 105 | // big-endian), try sending a blocked signal to make sure the mask matches the |
| 106 | // signal. (Send URG and CHLD because they're ignored by default in the |
| 107 | // debugger, vs. USR1 or other named signals) |
| 108 | inline for ([_]std.posix.SIG{ .URG, .CHLD }) |test_signo| { |
| 109 | S.expected_sig = test_signo; |
| 110 | S.seen_sig = null; |
| 111 | |
| 112 | const sa: std.posix.Sigaction = .{ |
| 113 | .handler = .{ .sigaction = &S.handler }, |
| 114 | .mask = std.posix.sigemptyset(), |
| 115 | .flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND, |
| 116 | }; |
| 117 | |
| 118 | var old_sa: std.posix.Sigaction = undefined; |
| 119 | |
| 120 | // Install the new signal handler. |
| 121 | std.posix.sigaction(test_signo, &sa, &old_sa); |
| 122 | |
| 123 | // block the signal and see that its delayed until unblocked |
| 124 | var block_one: std.posix.sigset_t = std.posix.sigemptyset(); |
| 125 | std.posix.sigaddset(&block_one, test_signo); |
| 126 | std.posix.sigprocmask(std.posix.SIG.BLOCK, &block_one, null); |
| 127 | |
| 128 | // qemu maps target signals to host signals 1-to-1, so targets |
| 129 | // with more signals than the host will fail to send the signal. |
| 130 | const rc = std.posix.system.kill(self_tid, test_signo); |
| 131 | switch (std.posix.errno(rc)) { |
| 132 | .SUCCESS => { |
| 133 | // See that the signal is blocked, then unblocked |
| 134 | try std.testing.expectEqual(null, S.seen_sig); |
| 135 | std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null); |
| 136 | try std.testing.expectEqual(test_signo, S.seen_sig); |
| 137 | }, |
| 138 | .INVAL => { |
| 139 | // Signal won't get delviered. Just clean up. |
| 140 | std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null); |
| 141 | try std.testing.expectEqual(null, S.seen_sig); |
| 142 | }, |
| 143 | else => |errno| return std.posix.unexpectedErrno(errno), |
| 144 | } |
| 145 | |
| 146 | // Restore original handler |
| 147 | std.posix.sigaction(test_signo, &old_sa, null); |
| 148 | } |
| 149 | } |