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