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.
This commit is contained in:
Pat Tullmann
2025-04-13 16:15:16 -07:00
parent 298b1886b2
commit d16079d79a
2 changed files with 209 additions and 41 deletions

View File

@@ -86,6 +86,7 @@ pub const MREMAP = system.MREMAP;
pub const MSF = system.MSF;
pub const MSG = system.MSG;
pub const NAME_MAX = system.NAME_MAX;
pub const NSIG = system.NSIG;
pub const O = system.O;
pub const PATH_MAX = system.PATH_MAX;
pub const POLL = system.POLL;
@@ -129,7 +130,6 @@ pub const dl_phdr_info = system.dl_phdr_info;
pub const empty_sigset = system.empty_sigset;
pub const fd_t = system.fd_t;
pub const file_obj = system.file_obj;
pub const filled_sigset = system.filled_sigset;
pub const gid_t = system.gid_t;
pub const ifreq = system.ifreq;
pub const ino_t = system.ino_t;
@@ -678,7 +678,7 @@ pub fn abort() noreturn {
raise(SIG.ABRT) catch {};
// Disable all signal handlers.
sigprocmask(SIG.BLOCK, &linux.all_mask, null);
sigprocmask(SIG.BLOCK, &linux.filled_sigset, null);
// Only one thread may proceed to the rest of abort().
if (!builtin.single_threaded) {
@@ -698,7 +698,8 @@ pub fn abort() noreturn {
_ = linux.tkill(linux.gettid(), SIG.ABRT);
const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)};
var sigabrtmask = empty_sigset;
sigaddset(&sigabrtmask, SIG.ABRT);
sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
// Beyond this point should be unreachable.
@@ -723,18 +724,13 @@ pub fn raise(sig: u8) RaiseError!void {
}
if (native_os == .linux) {
// https://git.musl-libc.org/cgit/musl/commit/?id=0bed7e0acfd34e3fb63ca0e4d99b7592571355a9
//
// Unlike musl, libc-less Zig std does not have any internal signals for implementation purposes, so we
// need to block all signals on the assumption that any of them could potentially fork() in a handler.
var set: sigset_t = undefined;
sigprocmask(SIG.BLOCK, &linux.all_mask, &set);
const tid = linux.gettid();
const rc = linux.tkill(tid, sig);
// restore signal mask
sigprocmask(SIG.SETMASK, &set, null);
// Block all signals so a `fork` (from a signal handler) between the gettid() and kill() syscalls
// cannot trigger an extra, unexpected, inter-process signal. Signal paranoia inherited from Musl.
const filled = linux.sigfillset();
var orig: sigset_t = undefined;
sigprocmask(SIG.BLOCK, &linux.filled_sigset, &orig);
const rc = linux.tkill(linux.gettid(), sig);
sigprocmask(SIG.SETMASK, &orig, null);
switch (errno(rc)) {
.SUCCESS => return,
@@ -5818,8 +5814,59 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
}
}
pub fn sigfillset(set: *sigset_t) void {
if (builtin.link_libc) {
switch (errno(system.sigfillset(set))) {
.SUCCESS => return,
else => unreachable,
}
}
set.* = system.filled_sigset;
}
pub fn sigemptyset(set: *sigset_t) void {
if (builtin.link_libc) {
switch (errno(system.sigemptyset(set))) {
.SUCCESS => return,
else => unreachable,
}
}
set.* = system.empty_sigset;
}
pub fn sigaddset(set: *sigset_t, sig: u8) void {
if (builtin.link_libc) {
switch (errno(system.sigaddset(set, sig))) {
.SUCCESS => return,
else => unreachable,
}
}
system.sigaddset(set, sig);
}
pub fn sigdelset(set: *sigset_t, sig: u8) void {
if (builtin.link_libc) {
switch (errno(system.sigdelset(set, sig))) {
.SUCCESS => return,
else => unreachable,
}
}
system.sigdelset(set, sig);
}
pub fn sigismember(set: *const sigset_t, sig: u8) bool {
if (builtin.link_libc) {
const rc = system.sigismember(set, sig);
switch (errno(rc)) {
.SUCCESS => return rc == 1,
else => unreachable,
}
}
return system.sigismember(set, sig);
}
/// Examine and change a signal action.
pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
switch (errno(system.sigaction(sig, act, oact))) {
.SUCCESS => return,
// EINVAL means the signal is either invalid or some signal that cannot have its action

View File

@@ -859,12 +859,64 @@ test "shutdown socket" {
std.net.Stream.close(.{ .handle = sock });
}
test "sigaction" {
test "sigset empty/full" {
if (native_os == .wasi or native_os == .windows)
return error.SkipZigTest;
// https://github.com/ziglang/zig/issues/7427
if (native_os == .linux and builtin.target.cpu.arch == .x86)
var set: posix.sigset_t = undefined;
posix.sigemptyset(&set);
for (1..posix.NSIG) |i| {
try expectEqual(false, posix.sigismember(&set, @truncate(i)));
}
// The C library can reserve some (unnamed) signals, so can't check the full
// NSIG set is defined, but just test a couple:
posix.sigfillset(&set);
try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.USR1)));
try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.INT)));
}
// Some signals (32 - 34 on glibc/musl) are not allowed to be added to a
// sigset by the C library, so avoid testing them.
fn reserved_signo(i: usize) bool {
return builtin.link_libc and (i >= 32 and i <= 34);
}
test "sigset add/del" {
if (native_os == .wasi or native_os == .windows)
return error.SkipZigTest;
var sigset = posix.empty_sigset;
// See that none are set, then set each one, see that they're all set, then
// remove them all, and then see that none are set.
for (1..posix.NSIG) |i| {
try expectEqual(false, posix.sigismember(&sigset, @truncate(i)));
}
for (1..posix.NSIG) |i| {
if (!reserved_signo(i)) {
posix.sigaddset(&sigset, @truncate(i));
}
}
for (1..posix.NSIG) |i| {
if (!reserved_signo(i)) {
try expectEqual(true, posix.sigismember(&sigset, @truncate(i)));
}
try expectEqual(false, posix.sigismember(&posix.empty_sigset, @truncate(i)));
}
for (1..posix.NSIG) |i| {
if (!reserved_signo(i)) {
posix.sigdelset(&sigset, @truncate(i));
}
}
for (1..posix.NSIG) |i| {
try expectEqual(false, posix.sigismember(&sigset, @truncate(i)));
}
}
test "sigaction" {
if (native_os == .wasi or native_os == .windows)
return error.SkipZigTest;
// https://github.com/ziglang/zig/issues/15381
@@ -872,21 +924,20 @@ test "sigaction" {
return error.SkipZigTest;
}
const test_signo = posix.SIG.USR1;
const S = struct {
var handler_called_count: u32 = 0;
fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
_ = ctx_ptr;
// Check that we received the correct signal.
switch (native_os) {
.netbsd => {
if (sig == posix.SIG.USR1 and sig == info.info.signo)
handler_called_count += 1;
},
else => {
if (sig == posix.SIG.USR1 and sig == info.signo)
handler_called_count += 1;
},
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;
}
}
};
@@ -899,39 +950,109 @@ test "sigaction" {
var old_sa: posix.Sigaction = undefined;
// Install the new signal handler.
posix.sigaction(posix.SIG.USR1, &sa, null);
posix.sigaction(test_signo, &sa, null);
// Check that we can read it back correctly.
posix.sigaction(posix.SIG.USR1, null, &old_sa);
posix.sigaction(test_signo, null, &old_sa);
try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
// Invoke the handler.
try posix.raise(posix.SIG.USR1);
try testing.expect(S.handler_called_count == 1);
try posix.raise(test_signo);
try testing.expectEqual(1, S.handler_called_count);
// Check if passing RESETHAND correctly reset the handler to SIG_DFL
posix.sigaction(posix.SIG.USR1, null, &old_sa);
posix.sigaction(test_signo, null, &old_sa);
try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
// Reinstall the signal w/o RESETHAND and re-raise
sa.flags = posix.SA.SIGINFO;
posix.sigaction(posix.SIG.USR1, &sa, null);
try posix.raise(posix.SIG.USR1);
try testing.expect(S.handler_called_count == 2);
posix.sigaction(test_signo, &sa, null);
try posix.raise(test_signo);
try testing.expectEqual(2, S.handler_called_count);
// Now set the signal to ignored
sa.handler = .{ .handler = posix.SIG.IGN };
sa.flags = 0;
posix.sigaction(posix.SIG.USR1, &sa, null);
posix.sigaction(test_signo, &sa, null);
// Re-raise to ensure handler is actually ignored
try posix.raise(posix.SIG.USR1);
try testing.expect(S.handler_called_count == 2);
try posix.raise(test_signo);
try testing.expectEqual(2, S.handler_called_count);
// Ensure that ignored state is returned when querying
posix.sigaction(posix.SIG.USR1, null, &old_sa);
try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?);
posix.sigaction(test_signo, null, &old_sa);
try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler);
}
test "sigset_t bits" {
if (native_os == .wasi or native_os == .windows)
return error.SkipZigTest;
const S = struct {
var expected_sig: i32 = undefined;
var handler_called_count: u32 = 0;
fn handler(sig: i32, info: *const 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 (sig == expected_sig and sig == info_sig) {
handler_called_count += 1;
}
}
};
const self_pid = 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.
inline for ([_]usize{ posix.SIG.INT, posix.SIG.USR1, 62, 94, 126 }) |test_signo| {
if (test_signo >= posix.NSIG) continue;
S.expected_sig = test_signo;
S.handler_called_count = 0;
var sa: posix.Sigaction = .{
.handler = .{ .sigaction = &S.handler },
.mask = posix.empty_sigset,
.flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
};
var old_sa: posix.Sigaction = undefined;
// Install the new signal handler.
posix.sigaction(test_signo, &sa, &old_sa);
// block the signal and see that its delayed until unblocked
var block_one = posix.empty_sigset;
_ = posix.sigaddset(&block_one, test_signo);
posix.sigprocmask(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 = posix.system.kill(self_pid, test_signo);
switch (posix.errno(rc)) {
.SUCCESS => {
// See that the signal is blocked, then unblocked
try testing.expectEqual(0, S.handler_called_count);
posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
try testing.expectEqual(1, S.handler_called_count);
},
.INVAL => {
// Signal won't get delviered. Just clean up.
posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
try testing.expectEqual(0, S.handler_called_count);
},
else => |errno| return posix.unexpectedErrno(errno),
}
// Restore original handler
posix.sigaction(test_signo, &old_sa, null);
}
}
test "dup & dup2" {