authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-09-08 23:04:42-07:00
committergravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-09-09 22:07:44-07:00
logbd4617033e09979398e65907a1d9c7f1b6e1d124
tree9702481c64dfe4c79f8fa628b234664d28fef481
parentca09629beeb899c418b16e5c180daf4d3f5ba3f9

standalone posix tests for sigaction

Fixes #24380

2 files changed, 153 insertions(+), 148 deletions(-)

lib/std/posix/test.zig-147
......@@ -703,153 +703,6 @@ test "sigset add/del" {
703703 }
704704}
705705
706test "sigaction" {
707 if (native_os == .wasi or native_os == .windows)
708 return error.SkipZigTest;
709
710 // https://github.com/ziglang/zig/issues/15381
711 if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
712 return error.SkipZigTest;
713 }
714
715 const test_signo = posix.SIG.URG; // URG only because it is ignored by default in debuggers
716
717 const S = struct {
718 var handler_called_count: u32 = 0;
719
720 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
721 _ = ctx_ptr;
722 // Check that we received the correct signal.
723 const info_sig = switch (native_os) {
724 .netbsd => info.info.signo,
725 else => info.signo,
726 };
727 if (sig == test_signo and sig == info_sig) {
728 handler_called_count += 1;
729 }
730 }
731 };
732
733 var sa: posix.Sigaction = .{
734 .handler = .{ .sigaction = &S.handler },
735 .mask = posix.sigemptyset(),
736 .flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
737 };
738
739 var old_sa: posix.Sigaction = undefined;
740
741 // Install the new signal handler.
742 posix.sigaction(test_signo, &sa, null);
743
744 // Check that we can read it back correctly.
745 posix.sigaction(test_signo, null, &old_sa);
746 try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
747 try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
748
749 // Invoke the handler.
750 try posix.raise(test_signo);
751 try testing.expectEqual(1, S.handler_called_count);
752
753 // Check if passing RESETHAND correctly reset the handler to SIG_DFL
754 posix.sigaction(test_signo, null, &old_sa);
755 try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
756
757 // Reinstall the signal w/o RESETHAND and re-raise
758 sa.flags = posix.SA.SIGINFO;
759 posix.sigaction(test_signo, &sa, null);
760 try posix.raise(test_signo);
761 try testing.expectEqual(2, S.handler_called_count);
762
763 // Now set the signal to ignored
764 sa.handler = .{ .handler = posix.SIG.IGN };
765 sa.flags = 0;
766 posix.sigaction(test_signo, &sa, null);
767
768 // Re-raise to ensure handler is actually ignored
769 try posix.raise(test_signo);
770 try testing.expectEqual(2, S.handler_called_count);
771
772 // Ensure that ignored state is returned when querying
773 posix.sigaction(test_signo, null, &old_sa);
774 try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler);
775}
776
777test "sigset_t bits" {
778 if (native_os == .wasi or native_os == .windows)
779 return error.SkipZigTest;
780
781 const NO_SIG: i32 = 0;
782
783 const S = struct {
784 var expected_sig: i32 = undefined;
785 var seen_sig: i32 = NO_SIG;
786
787 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
788 _ = ctx_ptr;
789
790 const info_sig = switch (native_os) {
791 .netbsd => info.info.signo,
792 else => info.signo,
793 };
794 if (seen_sig == NO_SIG and sig == expected_sig and sig == info_sig) {
795 seen_sig = sig;
796 }
797 }
798 };
799
800 // Assume this is a single-threaded process where the current thread has the
801 // 'pid' thread id. (The sigprocmask calls are thread-private state.)
802 const self_tid = posix.system.getpid();
803
804 // To check that sigset_t mapping matches kernel (think u32/u64 mismatches on
805 // big-endian), try sending a blocked signal to make sure the mask matches the
806 // signal. (Send URG and CHLD because they're ignored by default in the
807 // debugger, vs. USR1 or other named signals)
808 inline for ([_]i32{ posix.SIG.URG, posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
809 if (test_signo >= posix.NSIG) continue;
810
811 S.expected_sig = test_signo;
812 S.seen_sig = NO_SIG;
813
814 const sa: posix.Sigaction = .{
815 .handler = .{ .sigaction = &S.handler },
816 .mask = posix.sigemptyset(),
817 .flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
818 };
819
820 var old_sa: posix.Sigaction = undefined;
821
822 // Install the new signal handler.
823 posix.sigaction(test_signo, &sa, &old_sa);
824
825 // block the signal and see that its delayed until unblocked
826 var block_one: posix.sigset_t = posix.sigemptyset();
827 posix.sigaddset(&block_one, test_signo);
828 posix.sigprocmask(posix.SIG.BLOCK, &block_one, null);
829
830 // qemu maps target signals to host signals 1-to-1, so targets
831 // with more signals than the host will fail to send the signal.
832 const rc = posix.system.kill(self_tid, test_signo);
833 switch (posix.errno(rc)) {
834 .SUCCESS => {
835 // See that the signal is blocked, then unblocked
836 try testing.expectEqual(NO_SIG, S.seen_sig);
837 posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
838 try testing.expectEqual(test_signo, S.seen_sig);
839 },
840 .INVAL => {
841 // Signal won't get delviered. Just clean up.
842 posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
843 try testing.expectEqual(NO_SIG, S.seen_sig);
844 },
845 else => |errno| return posix.unexpectedErrno(errno),
846 }
847
848 // Restore original handler
849 posix.sigaction(test_signo, &old_sa, null);
850 }
851}
852
853706test "dup & dup2" {
854707 switch (native_os) {
855708 .linux, .solaris, .illumos => {},
test/standalone/posix/sigaction.zig+153-1
......@@ -1 +1,153 @@
1pub fn main() !void {}
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: i32, 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 NO_SIG: i32 = 0;
84
85 const S = struct {
86 var expected_sig: i32 = undefined;
87 var seen_sig: i32 = NO_SIG;
88
89 fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
90 _ = ctx_ptr;
91
92 const info_sig = switch (native_os) {
93 .netbsd => info.info.signo,
94 else => info.signo,
95 };
96 if (seen_sig == NO_SIG and sig == expected_sig and sig == info_sig) {
97 seen_sig = sig;
98 }
99 }
100 };
101
102 // Assume this is a single-threaded process where the current thread has the
103 // 'pid' thread id. (The sigprocmask calls are thread-private state.)
104 const self_tid = std.posix.system.getpid();
105
106 // To check that sigset_t mapping matches kernel (think u32/u64 mismatches on
107 // big-endian), try sending a blocked signal to make sure the mask matches the
108 // signal. (Send URG and CHLD because they're ignored by default in the
109 // debugger, vs. USR1 or other named signals)
110 inline for ([_]i32{ std.posix.SIG.URG, std.posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
111 if (test_signo >= std.posix.NSIG) continue;
112
113 S.expected_sig = test_signo;
114 S.seen_sig = NO_SIG;
115
116 const sa: std.posix.Sigaction = .{
117 .handler = .{ .sigaction = &S.handler },
118 .mask = std.posix.sigemptyset(),
119 .flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND,
120 };
121
122 var old_sa: std.posix.Sigaction = undefined;
123
124 // Install the new signal handler.
125 std.posix.sigaction(test_signo, &sa, &old_sa);
126
127 // block the signal and see that its delayed until unblocked
128 var block_one: std.posix.sigset_t = std.posix.sigemptyset();
129 std.posix.sigaddset(&block_one, test_signo);
130 std.posix.sigprocmask(std.posix.SIG.BLOCK, &block_one, null);
131
132 // qemu maps target signals to host signals 1-to-1, so targets
133 // with more signals than the host will fail to send the signal.
134 const rc = std.posix.system.kill(self_tid, test_signo);
135 switch (std.posix.errno(rc)) {
136 .SUCCESS => {
137 // See that the signal is blocked, then unblocked
138 try std.testing.expectEqual(NO_SIG, S.seen_sig);
139 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
140 try std.testing.expectEqual(test_signo, S.seen_sig);
141 },
142 .INVAL => {
143 // Signal won't get delviered. Just clean up.
144 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
145 try std.testing.expectEqual(NO_SIG, S.seen_sig);
146 },
147 else => |errno| return std.posix.unexpectedErrno(errno),
148 }
149
150 // Restore original handler
151 std.posix.sigaction(test_signo, &old_sa, null);
152 }
153}