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}