authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-04-13 16:15:16-07:00
committergravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-04-30 20:32:04-07:00
logd16079d79a6b7963db05253b6a3fe57a7e19d813
treec3f0ce970d9c87c28c45d81a5df1e8c5e021a94d
parent298b1886b2b35b168589026d075d14206d6e3430

posix.zig: export sigset_t and matching operations from system

Unify the C library sigset_t and Linux native sigset_t and the accessor operations. Add tests that the various sigset_t operations are working. And clean up existing tests a bit.

2 files changed, 209 insertions(+), 41 deletions(-)

lib/std/posix.zig+63-16
...@@ -86,6 +86,7 @@ pub const MREMAP = system.MREMAP;...@@ -86,6 +86,7 @@ pub const MREMAP = system.MREMAP;
86pub const MSF = system.MSF;86pub const MSF = system.MSF;
87pub const MSG = system.MSG;87pub const MSG = system.MSG;
88pub const NAME_MAX = system.NAME_MAX;88pub const NAME_MAX = system.NAME_MAX;
89pub const NSIG = system.NSIG;
89pub const O = system.O;90pub const O = system.O;
90pub const PATH_MAX = system.PATH_MAX;91pub const PATH_MAX = system.PATH_MAX;
91pub const POLL = system.POLL;92pub const POLL = system.POLL;
...@@ -129,7 +130,6 @@ pub const dl_phdr_info = system.dl_phdr_info;...@@ -129,7 +130,6 @@ pub const dl_phdr_info = system.dl_phdr_info;
129pub const empty_sigset = system.empty_sigset;130pub const empty_sigset = system.empty_sigset;
130pub const fd_t = system.fd_t;131pub const fd_t = system.fd_t;
131pub const file_obj = system.file_obj;132pub const file_obj = system.file_obj;
132pub const filled_sigset = system.filled_sigset;
133pub const gid_t = system.gid_t;133pub const gid_t = system.gid_t;
134pub const ifreq = system.ifreq;134pub const ifreq = system.ifreq;
135pub const ino_t = system.ino_t;135pub const ino_t = system.ino_t;
...@@ -678,7 +678,7 @@ pub fn abort() noreturn {...@@ -678,7 +678,7 @@ pub fn abort() noreturn {
678 raise(SIG.ABRT) catch {};678 raise(SIG.ABRT) catch {};
679679
680 // Disable all signal handlers.680 // Disable all signal handlers.
681 sigprocmask(SIG.BLOCK, &linux.all_mask, null);681 sigprocmask(SIG.BLOCK, &linux.filled_sigset, null);
682682
683 // Only one thread may proceed to the rest of abort().683 // Only one thread may proceed to the rest of abort().
684 if (!builtin.single_threaded) {684 if (!builtin.single_threaded) {
...@@ -698,7 +698,8 @@ pub fn abort() noreturn {...@@ -698,7 +698,8 @@ pub fn abort() noreturn {
698698
699 _ = linux.tkill(linux.gettid(), SIG.ABRT);699 _ = linux.tkill(linux.gettid(), SIG.ABRT);
700700
701 const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)};701 var sigabrtmask = empty_sigset;
702 sigaddset(&sigabrtmask, SIG.ABRT);
702 sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);703 sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
703704
704 // Beyond this point should be unreachable.705 // Beyond this point should be unreachable.
...@@ -723,18 +724,13 @@ pub fn raise(sig: u8) RaiseError!void {...@@ -723,18 +724,13 @@ pub fn raise(sig: u8) RaiseError!void {
723 }724 }
724725
725 if (native_os == .linux) {726 if (native_os == .linux) {
726 // https://git.musl-libc.org/cgit/musl/commit/?id=0bed7e0acfd34e3fb63ca0e4d99b7592571355a9727 // Block all signals so a `fork` (from a signal handler) between the gettid() and kill() syscalls
727 //728 // cannot trigger an extra, unexpected, inter-process signal. Signal paranoia inherited from Musl.
728 // Unlike musl, libc-less Zig std does not have any internal signals for implementation purposes, so we729 const filled = linux.sigfillset();
729 // need to block all signals on the assumption that any of them could potentially fork() in a handler.730 var orig: sigset_t = undefined;
730 var set: sigset_t = undefined;731 sigprocmask(SIG.BLOCK, &linux.filled_sigset, &orig);
731 sigprocmask(SIG.BLOCK, &linux.all_mask, &set);732 const rc = linux.tkill(linux.gettid(), sig);
732733 sigprocmask(SIG.SETMASK, &orig, null);
733 const tid = linux.gettid();
734 const rc = linux.tkill(tid, sig);
735
736 // restore signal mask
737 sigprocmask(SIG.SETMASK, &set, null);
738734
739 switch (errno(rc)) {735 switch (errno(rc)) {
740 .SUCCESS => return,736 .SUCCESS => return,
...@@ -5818,8 +5814,59 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {...@@ -5818,8 +5814,59 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
5818 }5814 }
5819}5815}
58205816
5817pub fn sigfillset(set: *sigset_t) void {
5818 if (builtin.link_libc) {
5819 switch (errno(system.sigfillset(set))) {
5820 .SUCCESS => return,
5821 else => unreachable,
5822 }
5823 }
5824 set.* = system.filled_sigset;
5825}
5826
5827pub fn sigemptyset(set: *sigset_t) void {
5828 if (builtin.link_libc) {
5829 switch (errno(system.sigemptyset(set))) {
5830 .SUCCESS => return,
5831 else => unreachable,
5832 }
5833 }
5834 set.* = system.empty_sigset;
5835}
5836
5837pub fn sigaddset(set: *sigset_t, sig: u8) void {
5838 if (builtin.link_libc) {
5839 switch (errno(system.sigaddset(set, sig))) {
5840 .SUCCESS => return,
5841 else => unreachable,
5842 }
5843 }
5844 system.sigaddset(set, sig);
5845}
5846
5847pub fn sigdelset(set: *sigset_t, sig: u8) void {
5848 if (builtin.link_libc) {
5849 switch (errno(system.sigdelset(set, sig))) {
5850 .SUCCESS => return,
5851 else => unreachable,
5852 }
5853 }
5854 system.sigdelset(set, sig);
5855}
5856
5857pub fn sigismember(set: *const sigset_t, sig: u8) bool {
5858 if (builtin.link_libc) {
5859 const rc = system.sigismember(set, sig);
5860 switch (errno(rc)) {
5861 .SUCCESS => return rc == 1,
5862 else => unreachable,
5863 }
5864 }
5865 return system.sigismember(set, sig);
5866}
5867
5821/// Examine and change a signal action.5868/// Examine and change a signal action.
5822pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {5869pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
5823 switch (errno(system.sigaction(sig, act, oact))) {5870 switch (errno(system.sigaction(sig, act, oact))) {
5824 .SUCCESS => return,5871 .SUCCESS => return,
5825 // EINVAL means the signal is either invalid or some signal that cannot have its action5872 // EINVAL means the signal is either invalid or some signal that cannot have its action
lib/std/posix/test.zig+146-25
...@@ -859,12 +859,64 @@ test "shutdown socket" {...@@ -859,12 +859,64 @@ test "shutdown socket" {
859 std.net.Stream.close(.{ .handle = sock });859 std.net.Stream.close(.{ .handle = sock });
860}860}
861861
862test "sigaction" {862test "sigset empty/full" {
863 if (native_os == .wasi or native_os == .windows)
864 return error.SkipZigTest;
865
866 var set: posix.sigset_t = undefined;
867
868 posix.sigemptyset(&set);
869 for (1..posix.NSIG) |i| {
870 try expectEqual(false, posix.sigismember(&set, @truncate(i)));
871 }
872
873 // The C library can reserve some (unnamed) signals, so can't check the full
874 // NSIG set is defined, but just test a couple:
875 posix.sigfillset(&set);
876 try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.USR1)));
877 try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.INT)));
878}
879
880// Some signals (32 - 34 on glibc/musl) are not allowed to be added to a
881// sigset by the C library, so avoid testing them.
882fn reserved_signo(i: usize) bool {
883 return builtin.link_libc and (i >= 32 and i <= 34);
884}
885
886test "sigset add/del" {
863 if (native_os == .wasi or native_os == .windows)887 if (native_os == .wasi or native_os == .windows)
864 return error.SkipZigTest;888 return error.SkipZigTest;
865889
866 // https://github.com/ziglang/zig/issues/7427890 var sigset = posix.empty_sigset;
867 if (native_os == .linux and builtin.target.cpu.arch == .x86)891
892 // See that none are set, then set each one, see that they're all set, then
893 // remove them all, and then see that none are set.
894 for (1..posix.NSIG) |i| {
895 try expectEqual(false, posix.sigismember(&sigset, @truncate(i)));
896 }
897 for (1..posix.NSIG) |i| {
898 if (!reserved_signo(i)) {
899 posix.sigaddset(&sigset, @truncate(i));
900 }
901 }
902 for (1..posix.NSIG) |i| {
903 if (!reserved_signo(i)) {
904 try expectEqual(true, posix.sigismember(&sigset, @truncate(i)));
905 }
906 try expectEqual(false, posix.sigismember(&posix.empty_sigset, @truncate(i)));
907 }
908 for (1..posix.NSIG) |i| {
909 if (!reserved_signo(i)) {
910 posix.sigdelset(&sigset, @truncate(i));
911 }
912 }
913 for (1..posix.NSIG) |i| {
914 try expectEqual(false, posix.sigismember(&sigset, @truncate(i)));
915 }
916}
917
918test "sigaction" {
919 if (native_os == .wasi or native_os == .windows)
868 return error.SkipZigTest;920 return error.SkipZigTest;
869921
870 // https://github.com/ziglang/zig/issues/15381922 // https://github.com/ziglang/zig/issues/15381
...@@ -872,21 +924,20 @@ test "sigaction" {...@@ -872,21 +924,20 @@ test "sigaction" {
872 return error.SkipZigTest;924 return error.SkipZigTest;
873 }925 }
874926
927 const test_signo = posix.SIG.USR1;
928
875 const S = struct {929 const S = struct {
876 var handler_called_count: u32 = 0;930 var handler_called_count: u32 = 0;
877931
878 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {932 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
879 _ = ctx_ptr;933 _ = ctx_ptr;
880 // Check that we received the correct signal.934 // Check that we received the correct signal.
881 switch (native_os) {935 const info_sig = switch (native_os) {
882 .netbsd => {936 .netbsd => info.info.signo,
883 if (sig == posix.SIG.USR1 and sig == info.info.signo)937 else => info.signo,
884 handler_called_count += 1;938 };
885 },939 if (sig == test_signo and sig == info_sig) {
886 else => {940 handler_called_count += 1;
887 if (sig == posix.SIG.USR1 and sig == info.signo)
888 handler_called_count += 1;
889 },
890 }941 }
891 }942 }
892 };943 };
...@@ -899,39 +950,109 @@ test "sigaction" {...@@ -899,39 +950,109 @@ test "sigaction" {
899 var old_sa: posix.Sigaction = undefined;950 var old_sa: posix.Sigaction = undefined;
900951
901 // Install the new signal handler.952 // Install the new signal handler.
902 posix.sigaction(posix.SIG.USR1, &sa, null);953 posix.sigaction(test_signo, &sa, null);
903954
904 // Check that we can read it back correctly.955 // Check that we can read it back correctly.
905 posix.sigaction(posix.SIG.USR1, null, &old_sa);956 posix.sigaction(test_signo, null, &old_sa);
906 try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);957 try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
907 try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);958 try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
908959
909 // Invoke the handler.960 // Invoke the handler.
910 try posix.raise(posix.SIG.USR1);961 try posix.raise(test_signo);
911 try testing.expect(S.handler_called_count == 1);962 try testing.expectEqual(1, S.handler_called_count);
912963
913 // Check if passing RESETHAND correctly reset the handler to SIG_DFL964 // Check if passing RESETHAND correctly reset the handler to SIG_DFL
914 posix.sigaction(posix.SIG.USR1, null, &old_sa);965 posix.sigaction(test_signo, null, &old_sa);
915 try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);966 try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
916967
917 // Reinstall the signal w/o RESETHAND and re-raise968 // Reinstall the signal w/o RESETHAND and re-raise
918 sa.flags = posix.SA.SIGINFO;969 sa.flags = posix.SA.SIGINFO;
919 posix.sigaction(posix.SIG.USR1, &sa, null);970 posix.sigaction(test_signo, &sa, null);
920 try posix.raise(posix.SIG.USR1);971 try posix.raise(test_signo);
921 try testing.expect(S.handler_called_count == 2);972 try testing.expectEqual(2, S.handler_called_count);
922973
923 // Now set the signal to ignored974 // Now set the signal to ignored
924 sa.handler = .{ .handler = posix.SIG.IGN };975 sa.handler = .{ .handler = posix.SIG.IGN };
925 sa.flags = 0;976 sa.flags = 0;
926 posix.sigaction(posix.SIG.USR1, &sa, null);977 posix.sigaction(test_signo, &sa, null);
927978
928 // Re-raise to ensure handler is actually ignored979 // Re-raise to ensure handler is actually ignored
929 try posix.raise(posix.SIG.USR1);980 try posix.raise(test_signo);
930 try testing.expect(S.handler_called_count == 2);981 try testing.expectEqual(2, S.handler_called_count);
931982
932 // Ensure that ignored state is returned when querying983 // Ensure that ignored state is returned when querying
933 posix.sigaction(posix.SIG.USR1, null, &old_sa);984 posix.sigaction(test_signo, null, &old_sa);
934 try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?);985 try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler);
986}
987
988test "sigset_t bits" {
989 if (native_os == .wasi or native_os == .windows)
990 return error.SkipZigTest;
991
992 const S = struct {
993 var expected_sig: i32 = undefined;
994 var handler_called_count: u32 = 0;
995
996 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
997 _ = ctx_ptr;
998
999 const info_sig = switch (native_os) {
1000 .netbsd => info.info.signo,
1001 else => info.signo,
1002 };
1003 if (sig == expected_sig and sig == info_sig) {
1004 handler_called_count += 1;
1005 }
1006 }
1007 };
1008
1009 const self_pid = posix.system.getpid();
1010
1011 // To check that sigset_t mapping matches kernel (think u32/u64
1012 // mismatches on big-endian), try sending a blocked signal to make
1013 // sure the mask matches the signal.
1014 inline for ([_]usize{ posix.SIG.INT, posix.SIG.USR1, 62, 94, 126 }) |test_signo| {
1015 if (test_signo >= posix.NSIG) continue;
1016
1017 S.expected_sig = test_signo;
1018 S.handler_called_count = 0;
1019
1020 var sa: posix.Sigaction = .{
1021 .handler = .{ .sigaction = &S.handler },
1022 .mask = posix.empty_sigset,
1023 .flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
1024 };
1025 var old_sa: posix.Sigaction = undefined;
1026
1027 // Install the new signal handler.
1028 posix.sigaction(test_signo, &sa, &old_sa);
1029
1030 // block the signal and see that its delayed until unblocked
1031 var block_one = posix.empty_sigset;
1032 _ = posix.sigaddset(&block_one, test_signo);
1033 posix.sigprocmask(posix.SIG.BLOCK, &block_one, null);
1034
1035 // qemu maps target signals to host signals 1-to-1, so targets
1036 // with more signals than the host will fail to send the signal.
1037 const rc = posix.system.kill(self_pid, test_signo);
1038 switch (posix.errno(rc)) {
1039 .SUCCESS => {
1040 // See that the signal is blocked, then unblocked
1041 try testing.expectEqual(0, S.handler_called_count);
1042 posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
1043 try testing.expectEqual(1, S.handler_called_count);
1044 },
1045 .INVAL => {
1046 // Signal won't get delviered. Just clean up.
1047 posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
1048 try testing.expectEqual(0, S.handler_called_count);
1049 },
1050 else => |errno| return posix.unexpectedErrno(errno),
1051 }
1052
1053 // Restore original handler
1054 posix.sigaction(test_signo, &old_sa, null);
1055 }
935}1056}
9361057
937test "dup & dup2" {1058test "dup & dup2" {