Skip to content

Commit e56cfe5

Browse files
committed
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.
1 parent cfcee06 commit e56cfe5

File tree

2 files changed

+194
-41
lines changed

2 files changed

+194
-41
lines changed

lib/std/posix.zig

+64-16
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub const MREMAP = system.MREMAP;
8686
pub const MSF = system.MSF;
8787
pub const MSG = system.MSG;
8888
pub const NAME_MAX = system.NAME_MAX;
89+
pub const NSIG = system.NSIG;
8990
pub const O = system.O;
9091
pub const PATH_MAX = system.PATH_MAX;
9192
pub const POLL = system.POLL;
@@ -129,7 +130,6 @@ pub const dl_phdr_info = system.dl_phdr_info;
129130
pub const empty_sigset = system.empty_sigset;
130131
pub const fd_t = system.fd_t;
131132
pub const file_obj = system.file_obj;
132-
pub const filled_sigset = system.filled_sigset;
133133
pub const gid_t = system.gid_t;
134134
pub const ifreq = system.ifreq;
135135
pub const ino_t = system.ino_t;
@@ -678,7 +678,7 @@ pub fn abort() noreturn {
678678
raise(SIG.ABRT) catch {};
679679

680680
// Disable all signal handlers.
681-
sigprocmask(SIG.BLOCK, &linux.all_mask, null);
681+
sigprocmask(SIG.BLOCK, &linux.filled_sigset, null);
682682

683683
// Only one thread may proceed to the rest of abort().
684684
if (!builtin.single_threaded) {
@@ -698,7 +698,8 @@ pub fn abort() noreturn {
698698

699699
_ = 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);
702703
sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
703704

704705
// Beyond this point should be unreachable.
@@ -723,18 +724,14 @@ pub fn raise(sig: u8) RaiseError!void {
723724
}
724725

725726
if (native_os == .linux) {
726-
// https://git.musl-libc.org/cgit/musl/commit/?id=0bed7e0acfd34e3fb63ca0e4d99b7592571355a9
727-
//
728-
// Unlike musl, libc-less Zig std does not have any internal signals for implementation purposes, so we
729-
// need to block all signals on the assumption that any of them could potentially fork() in a handler.
730-
var set: sigset_t = undefined;
731-
sigprocmask(SIG.BLOCK, &linux.all_mask, &set);
732-
733-
const tid = linux.gettid();
734-
const rc = linux.tkill(tid, sig);
735-
736-
// restore signal mask
737-
sigprocmask(SIG.SETMASK, &set, null);
727+
// Block all signals so a `fork` (from a signal handler) between
728+
// the gettid() and kill() syscalls cannot trigger an extra,
729+
// unexpected, inter-process signal. Signal paranoia inherited
730+
// from Musl.
731+
var orig: sigset_t = undefined;
732+
sigprocmask(SIG.BLOCK, &linux.filled_sigset, &orig);
733+
const rc = linux.tkill(linux.gettid(), sig);
734+
sigprocmask(SIG.SETMASK, &orig, null);
738735

739736
switch (errno(rc)) {
740737
.SUCCESS => return,
@@ -5818,8 +5815,59 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
58185815
}
58195816
}
58205817

5818+
pub fn sigfillset(set: *sigset_t) void {
5819+
if (builtin.link_libc) {
5820+
switch (errno(system.sigfillset(set))) {
5821+
.SUCCESS => return,
5822+
else => unreachable,
5823+
}
5824+
}
5825+
set.* = system.filled_sigset;
5826+
}
5827+
5828+
pub fn sigemptyset(set: *sigset_t) void {
5829+
if (builtin.link_libc) {
5830+
switch (errno(system.sigemptyset(set))) {
5831+
.SUCCESS => return,
5832+
else => unreachable,
5833+
}
5834+
}
5835+
set.* = system.empty_sigset;
5836+
}
5837+
5838+
pub fn sigaddset(set: *sigset_t, sig: u8) void {
5839+
if (builtin.link_libc) {
5840+
switch (errno(system.sigaddset(set, sig))) {
5841+
.SUCCESS => return,
5842+
else => unreachable,
5843+
}
5844+
}
5845+
system.sigaddset(set, sig);
5846+
}
5847+
5848+
pub fn sigdelset(set: *sigset_t, sig: u8) void {
5849+
if (builtin.link_libc) {
5850+
switch (errno(system.sigdelset(set, sig))) {
5851+
.SUCCESS => return,
5852+
else => unreachable,
5853+
}
5854+
}
5855+
system.sigdelset(set, sig);
5856+
}
5857+
5858+
pub fn sigismember(set: *const sigset_t, sig: u8) bool {
5859+
if (builtin.link_libc) {
5860+
const rc = system.sigismember(set, sig);
5861+
switch (errno(rc)) {
5862+
.SUCCESS => return rc == 1,
5863+
else => unreachable,
5864+
}
5865+
}
5866+
return system.sigismember(set, sig);
5867+
}
5868+
58215869
/// Examine and change a signal action.
5822-
pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
5870+
pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
58235871
switch (errno(system.sigaction(sig, act, oact))) {
58245872
.SUCCESS => return,
58255873
// EINVAL means the signal is either invalid or some signal that cannot have its action

lib/std/posix/test.zig

+130-25
Original file line numberDiff line numberDiff line change
@@ -859,34 +859,85 @@ test "shutdown socket" {
859859
std.net.Stream.close(.{ .handle = sock });
860860
}
861861

862-
test "sigaction" {
862+
test "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.
882+
fn reserved_signo(i: usize) bool {
883+
return builtin.link_libc and (i >= 32 and i <= 34);
884+
}
885+
886+
test "sigset add/del" {
863887
if (native_os == .wasi or native_os == .windows)
864888
return error.SkipZigTest;
865889

866-
// https://github.com/ziglang/zig/issues/7427
867-
if (native_os == .linux and builtin.target.cpu.arch == .x86)
890+
var sigset = posix.empty_sigset;
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+
918+
test "sigaction" {
919+
if (native_os == .wasi or native_os == .windows)
868920
return error.SkipZigTest;
869921

870922
// https://github.com/ziglang/zig/issues/15381
871923
if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
872924
return error.SkipZigTest;
873925
}
874926

927+
const test_signo = posix.SIG.USR1;
928+
875929
const S = struct {
876930
var handler_called_count: u32 = 0;
877931

878932
fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
879933
_ = ctx_ptr;
880934
// Check that we received the correct signal.
881-
switch (native_os) {
882-
.netbsd => {
883-
if (sig == posix.SIG.USR1 and sig == info.info.signo)
884-
handler_called_count += 1;
885-
},
886-
else => {
887-
if (sig == posix.SIG.USR1 and sig == info.signo)
888-
handler_called_count += 1;
889-
},
935+
const info_sig = switch (native_os) {
936+
.netbsd => info.info.signo,
937+
else => info.signo,
938+
};
939+
if (sig == test_signo and sig == info_sig) {
940+
handler_called_count += 1;
890941
}
891942
}
892943
};
@@ -899,39 +950,93 @@ test "sigaction" {
899950
var old_sa: posix.Sigaction = undefined;
900951

901952
// Install the new signal handler.
902-
posix.sigaction(posix.SIG.USR1, &sa, null);
953+
posix.sigaction(test_signo, &sa, null);
903954

904955
// 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);
906957
try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
907958
try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
908959

909960
// Invoke the handler.
910-
try posix.raise(posix.SIG.USR1);
911-
try testing.expect(S.handler_called_count == 1);
961+
try posix.raise(test_signo);
962+
try testing.expectEqual(1, S.handler_called_count);
912963

913964
// 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);
915966
try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
916967

917968
// Reinstall the signal w/o RESETHAND and re-raise
918969
sa.flags = posix.SA.SIGINFO;
919-
posix.sigaction(posix.SIG.USR1, &sa, null);
920-
try posix.raise(posix.SIG.USR1);
921-
try testing.expect(S.handler_called_count == 2);
970+
posix.sigaction(test_signo, &sa, null);
971+
try posix.raise(test_signo);
972+
try testing.expectEqual(2, S.handler_called_count);
922973

923974
// Now set the signal to ignored
924975
sa.handler = .{ .handler = posix.SIG.IGN };
925976
sa.flags = 0;
926-
posix.sigaction(posix.SIG.USR1, &sa, null);
977+
posix.sigaction(test_signo, &sa, null);
927978

928979
// Re-raise to ensure handler is actually ignored
929-
try posix.raise(posix.SIG.USR1);
930-
try testing.expect(S.handler_called_count == 2);
980+
try posix.raise(test_signo);
981+
try testing.expectEqual(2, S.handler_called_count);
931982

932983
// Ensure that ignored state is returned when querying
933-
posix.sigaction(posix.SIG.USR1, null, &old_sa);
934-
try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?);
984+
posix.sigaction(test_signo, null, &old_sa);
985+
try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler);
986+
}
987+
988+
test "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+
// To check that sigset_t mapping matches kernel (think u32/u64
1010+
// mismatches on big-endian), try sending a blocked signal to make
1011+
// sure the mask matches the signal.
1012+
inline for ([_]usize{ posix.SIG.INT, posix.SIG.USR1, 62, 94, 126 }) |test_signo| {
1013+
if (test_signo >= posix.NSIG) continue;
1014+
1015+
S.expected_sig = test_signo;
1016+
S.handler_called_count = 0;
1017+
1018+
var sa: posix.Sigaction = .{
1019+
.handler = .{ .sigaction = &S.handler },
1020+
.mask = posix.empty_sigset,
1021+
.flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
1022+
};
1023+
var old_sa: posix.Sigaction = undefined;
1024+
1025+
// Install the new signal handler.
1026+
posix.sigaction(test_signo, &sa, &old_sa);
1027+
1028+
// block the signal and see that its delayed until unblocked
1029+
var block_one = posix.empty_sigset;
1030+
_ = posix.sigaddset(&block_one, test_signo);
1031+
posix.sigprocmask(posix.SIG.BLOCK, &block_one, null);
1032+
try posix.raise(test_signo);
1033+
try testing.expectEqual(0, S.handler_called_count);
1034+
posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
1035+
try testing.expectEqual(1, S.handler_called_count);
1036+
1037+
// Restore original handler
1038+
posix.sigaction(test_signo, &old_sa, null);
1039+
}
9351040
}
9361041

9371042
test "dup & dup2" {

0 commit comments

Comments
 (0)