master
1const std = @import("std");
2const builtin = @import("builtin");
3
4const native_os = builtin.target.os.tag;
5
6pub 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
15fn 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
82fn 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}