Merge pull request #23601 from rootbeer/sig-split

Split glibc and linux sigset_t ABIs and the accessor functions
This commit is contained in:
Alex Rønne Petersen 2025-05-01 21:29:23 +02:00 committed by GitHub
commit ad9cb40112
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 393 additions and 155 deletions

View file

@ -410,9 +410,9 @@ pub fn start(options: Options) Node {
}
if (have_sigwinch) {
var act: posix.Sigaction = .{
const act: posix.Sigaction = .{
.handler = .{ .sigaction = handleSigWinch },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = (posix.SA.SIGINFO | posix.SA.RESTART),
};
posix.sigaction(posix.SIG.WINCH, &act, null);

View file

@ -3115,6 +3115,21 @@ pub const SYS = switch (native_os) {
.linux => linux.SYS,
else => void,
};
/// A common format for the Sigaction struct across a variety of Linux flavors.
const common_linux_Sigaction = extern struct {
pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
handler: extern union {
handler: ?handler_fn,
sigaction: ?sigaction_fn,
},
mask: sigset_t,
flags: c_uint,
restorer: ?*const fn () callconv(.c) void = null, // C library will fill this in
};
/// Renamed from `sigaction` to `Sigaction` to avoid conflict with function name.
pub const Sigaction = switch (native_os) {
.linux => switch (native_arch) {
@ -3123,7 +3138,7 @@ pub const Sigaction = switch (native_os) {
.mips64,
.mips64el,
=> if (builtin.target.abi.isMusl())
linux.Sigaction
common_linux_Sigaction
else if (builtin.target.ptrBitWidth() == 64) extern struct {
pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
@ -3160,8 +3175,8 @@ pub const Sigaction = switch (native_os) {
flags: c_uint,
restorer: ?*const fn () callconv(.c) void = null,
mask: sigset_t,
} else linux.Sigaction,
else => linux.Sigaction,
} else common_linux_Sigaction,
else => common_linux_Sigaction,
},
.emscripten => emscripten.Sigaction,
.netbsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
@ -4518,27 +4533,18 @@ pub const siginfo_t = switch (native_os) {
else => void,
};
pub const sigset_t = switch (native_os) {
.linux => linux.sigset_t,
.linux => [1024 / @bitSizeOf(c_ulong)]c_ulong, // glibc and musl present a 1024-bit sigset_t, while kernel's is 128-bit or less.
.emscripten => emscripten.sigset_t,
// https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L19
.openbsd, .macos, .ios, .tvos, .watchos, .visionos, .serenity => u32,
.openbsd, .serenity => u32,
.macos, .ios, .tvos, .watchos, .visionos => darwin.sigset_t,
.dragonfly, .netbsd, .solaris, .illumos, .freebsd => extern struct {
__bits: [SIG.WORDS]u32,
},
.haiku => u64,
else => u0,
};
pub const empty_sigset: sigset_t = switch (native_os) {
.linux => linux.empty_sigset,
.emscripten => emscripten.empty_sigset,
.dragonfly, .netbsd, .solaris, .illumos, .freebsd => .{ .__bits = [_]u32{0} ** SIG.WORDS },
else => 0,
};
pub const filled_sigset = switch (native_os) {
.linux => linux.filled_sigset,
.haiku => ~@as(sigset_t, 0),
else => 0,
};
pub const sigval = switch (native_os) {
.linux => linux.sigval,
// https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L22-L25
@ -6665,7 +6671,7 @@ pub const timezone = switch (native_os) {
};
pub const ucontext_t = switch (native_os) {
.linux => linux.ucontext_t,
.linux => linux.ucontext_t, // std.os.linux.ucontext_t is currently glibc-compatible, but it should probably not be.
.emscripten => emscripten.ucontext_t,
.macos, .ios, .tvos, .watchos, .visionos => extern struct {
onstack: c_int,
@ -9596,6 +9602,7 @@ pub const NSIG = switch (native_os) {
.windows => 23,
.haiku => 65,
.netbsd, .freebsd => 32,
.macos => darwin.NSIG,
.solaris, .illumos => 75,
// https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h#L42
.openbsd, .serenity => 33,
@ -10345,6 +10352,11 @@ pub const sigfillset = switch (native_os) {
else => private.sigfillset,
};
pub const sigaddset = private.sigaddset;
pub const sigemptyset = private.sigemptyset;
pub const sigdelset = private.sigdelset;
pub const sigismember = private.sigismember;
pub const sigprocmask = switch (native_os) {
.netbsd => private.__sigprocmask14,
else => private.sigprocmask,
@ -11025,7 +11037,6 @@ pub const pthread_attr_set_qos_class_np = darwin.pthread_attr_set_qos_class_np;
pub const pthread_get_qos_class_np = darwin.pthread_get_qos_class_np;
pub const pthread_set_qos_class_self_np = darwin.pthread_set_qos_class_self_np;
pub const ptrace = darwin.ptrace;
pub const sigaddset = darwin.sigaddset;
pub const task_for_pid = darwin.task_for_pid;
pub const task_get_exception_ports = darwin.task_get_exception_ports;
pub const task_info = darwin.task_info;
@ -11148,7 +11159,11 @@ const private = struct {
extern "c" fn sched_yield() c_int;
extern "c" fn sendfile(out_fd: fd_t, in_fd: fd_t, offset: ?*off_t, count: usize) isize;
extern "c" fn sigaction(sig: c_int, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
extern "c" fn sigfillset(set: ?*sigset_t) void;
extern "c" fn sigdelset(set: ?*sigset_t, signo: c_int) c_int;
extern "c" fn sigaddset(set: ?*sigset_t, signo: c_int) c_int;
extern "c" fn sigfillset(set: ?*sigset_t) c_int;
extern "c" fn sigemptyset(set: ?*sigset_t) c_int;
extern "c" fn sigismember(set: ?*const sigset_t, signo: c_int) c_int;
extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int;

View file

@ -10,7 +10,6 @@ const mode_t = std.c.mode_t;
const off_t = std.c.off_t;
const pid_t = std.c.pid_t;
const pthread_attr_t = std.c.pthread_attr_t;
const sigset_t = std.c.sigset_t;
const timespec = std.c.timespec;
const sf_hdtr = std.c.sf_hdtr;
@ -840,9 +839,11 @@ pub extern "c" fn sendfile(
flags: u32,
) c_int;
pub fn sigaddset(set: *sigset_t, signo: u5) void {
set.* |= @as(u32, 1) << (signo - 1);
}
// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74
pub const sigset_t = u32;
// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/signal.h#L76
pub const NSIG = 32;
pub const qos_class_t = enum(c_uint) {
/// highest priority QOS class for critical tasks

View file

@ -1387,12 +1387,11 @@ pub fn attachSegfaultHandler() void {
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
return;
}
var act = posix.Sigaction{
const act = posix.Sigaction{
.handler = .{ .sigaction = handleSegfaultPosix },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND),
};
updateSegfaultHandler(&act);
}
@ -1404,9 +1403,9 @@ fn resetSegfaultHandler() void {
}
return;
}
var act = posix.Sigaction{
const act = posix.Sigaction{
.handler = .{ .handler = posix.SIG.DFL },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = 0,
};
updateSegfaultHandler(&act);

View file

@ -560,7 +560,9 @@ pub const Sigaction = extern struct {
};
pub const sigset_t = [1024 / 32]u32;
pub const empty_sigset = [_]u32{0} ** @typeInfo(sigset_t).array.len;
pub fn sigemptyset() sigset_t {
return [_]u32{0} ** @typeInfo(sigset_t).array.len;
}
pub const siginfo_t = extern struct {
signo: i32,
errno: i32,

View file

@ -1745,8 +1745,9 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
return syscall4(.rt_sigprocmask, flags, @intFromPtr(set), @intFromPtr(oldset), NSIG / 8);
}
pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
assert(sig >= 1);
pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
assert(sig > 0);
assert(sig < NSIG);
assert(sig != SIG.KILL);
assert(sig != SIG.STOP);
@ -1755,14 +1756,15 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
const mask_size = @sizeOf(@TypeOf(ksa.mask));
if (act) |new| {
// Zig needs to install our arch restorer function with any signal handler, so
// must copy the Sigaction struct
const restorer_fn = if ((new.flags & SA.SIGINFO) != 0) &restore_rt else &restore;
ksa = k_sigaction{
.handler = new.handler.handler,
.flags = new.flags | SA.RESTORER,
.mask = undefined,
.mask = new.mask,
.restorer = @ptrCast(restorer_fn),
};
@memcpy(@as([*]u8, @ptrCast(&ksa.mask))[0..mask_size], @as([*]const u8, @ptrCast(&new.mask)));
}
const ksa_arg = if (act != null) @intFromPtr(&ksa) else 0;
@ -1777,8 +1779,8 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
if (oact) |old| {
old.handler.handler = oldksa.handler;
old.flags = @as(c_uint, @truncate(oldksa.flags));
@memcpy(@as([*]u8, @ptrCast(&old.mask))[0..mask_size], @as([*]const u8, @ptrCast(&oldksa.mask)));
old.flags = oldksa.flags;
old.mask = oldksa.mask;
}
return 0;
@ -1786,25 +1788,35 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
const usize_bits = @typeInfo(usize).int.bits;
pub const sigset_t = [1024 / 32]u32;
/// Defined as one greater than the largest defined signal number.
pub const NSIG = if (is_mips) 128 else 65;
/// Linux kernel's sigset_t. This is logically 64-bit on most
/// architectures, but 128-bit on MIPS. Contrast with the 1024-bit
/// sigset_t exported by the glibc and musl library ABIs.
pub const sigset_t = [(NSIG - 1 + 7) / @bitSizeOf(SigsetElement)]SigsetElement;
const SigsetElement = c_ulong;
const sigset_len = @typeInfo(sigset_t).array.len;
/// Empty set to initialize sigset_t instances from.
pub const empty_sigset: sigset_t = [_]u32{0} ** sigset_len;
/// Zig's version of sigemptyset. Returns initialized sigset_t.
pub fn sigemptyset() sigset_t {
return [_]SigsetElement{0} ** sigset_len;
}
pub const filled_sigset: sigset_t = [_]u32{0x7fff_ffff} ++ [_]u32{0} ** (sigset_len - 1);
/// Zig's version of sigfillset. Returns initalized sigset_t.
pub fn sigfillset() sigset_t {
return [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len;
}
pub const all_mask: sigset_t = [_]u32{0xffff_ffff} ** sigset_len;
fn sigset_bit_index(sig: usize) struct { word: usize, mask: u32 } {
fn sigset_bit_index(sig: usize) struct { word: usize, mask: SigsetElement } {
assert(sig > 0);
assert(sig < NSIG);
const bit = sig - 1;
const shift = @as(u5, @truncate(bit % 32));
return .{
.word = bit / 32,
.mask = @as(u32, 1) << shift,
.word = bit / @bitSizeOf(SigsetElement),
.mask = @as(SigsetElement, 1) << @truncate(bit % @bitSizeOf(SigsetElement)),
};
}
@ -3487,6 +3499,7 @@ pub const SIG = if (is_mips) struct {
pub const UNBLOCK = 2;
pub const SETMASK = 3;
// https://github.com/torvalds/linux/blob/ca91b9500108d4cf083a635c2e11c884d5dd20ea/arch/mips/include/uapi/asm/signal.h#L25
pub const HUP = 1;
pub const INT = 2;
pub const QUIT = 3;
@ -3494,33 +3507,32 @@ pub const SIG = if (is_mips) struct {
pub const TRAP = 5;
pub const ABRT = 6;
pub const IOT = ABRT;
pub const BUS = 7;
pub const EMT = 7;
pub const FPE = 8;
pub const KILL = 9;
pub const USR1 = 10;
pub const BUS = 10;
pub const SEGV = 11;
pub const USR2 = 12;
pub const SYS = 12;
pub const PIPE = 13;
pub const ALRM = 14;
pub const TERM = 15;
pub const STKFLT = 16;
pub const CHLD = 17;
pub const CONT = 18;
pub const STOP = 19;
pub const TSTP = 20;
pub const TTIN = 21;
pub const TTOU = 22;
pub const URG = 23;
pub const XCPU = 24;
pub const XFSZ = 25;
pub const VTALRM = 26;
pub const PROF = 27;
pub const WINCH = 28;
pub const IO = 29;
pub const POLL = 29;
pub const PWR = 30;
pub const SYS = 31;
pub const UNUSED = SIG.SYS;
pub const USR1 = 16;
pub const USR2 = 17;
pub const CHLD = 18;
pub const PWR = 19;
pub const WINCH = 20;
pub const URG = 21;
pub const IO = 22;
pub const POLL = IO;
pub const STOP = 23;
pub const TSTP = 24;
pub const CONT = 25;
pub const TTIN = 26;
pub const TTOU = 27;
pub const VTALRM = 28;
pub const PROF = 29;
pub const XCPU = 30;
pub const XFZ = 31;
pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
@ -5479,38 +5491,33 @@ pub const TFD = switch (native_arch) {
},
};
/// NSIG is the total number of signals defined.
/// As signal numbers are sequential, NSIG is one greater than the largest defined signal number.
pub const NSIG = if (is_mips) 128 else 65;
const k_sigaction_funcs = struct {
const handler = ?*align(1) const fn (i32) callconv(.c) void;
const restorer = *const fn () callconv(.c) void;
};
/// Kernel sigaction struct, as expected by the `rt_sigaction` syscall. Includes restorer.
pub const k_sigaction = switch (native_arch) {
.mips, .mipsel => extern struct {
.mips, .mipsel, .mips64, .mips64el => extern struct {
flags: c_uint,
handler: k_sigaction_funcs.handler,
mask: [4]c_ulong,
restorer: k_sigaction_funcs.restorer,
},
.mips64, .mips64el => extern struct {
flags: c_uint,
handler: k_sigaction_funcs.handler,
mask: [2]c_ulong,
mask: sigset_t,
restorer: k_sigaction_funcs.restorer,
},
else => extern struct {
handler: k_sigaction_funcs.handler,
flags: c_ulong,
restorer: k_sigaction_funcs.restorer,
mask: [2]c_uint,
mask: sigset_t,
},
};
/// Kernel Sigaction wrapper for the actual ABI `k_sigaction`. The Zig
/// linux.zig wrapper library still does some pre-processing on
/// sigaction() calls (to add the `restorer` field).
///
/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
pub const Sigaction = extern struct {
pub const Sigaction = struct {
pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
@ -5519,8 +5526,10 @@ pub const Sigaction = extern struct {
sigaction: ?sigaction_fn,
},
mask: sigset_t,
flags: c_uint,
restorer: ?*const fn () callconv(.c) void = null,
flags: switch (native_arch) {
.mips, .mipsel, .mips64, .mips64el => c_uint,
else => c_ulong,
},
};
pub const SFD = struct {

View file

@ -289,7 +289,7 @@ pub const ucontext_t = extern struct {
flags: usize,
link: ?*ucontext_t,
stack: stack_t,
sigmask: sigset_t,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
mcontext: mcontext_t,
};

View file

@ -337,7 +337,7 @@ pub const ucontext_t = extern struct {
link: ?*ucontext_t,
stack: stack_t,
mcontext: mcontext_t,
sigmask: sigset_t,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
regspace: [64]u64,
};

View file

@ -264,8 +264,7 @@ pub const ucontext_t = extern struct {
flags: c_ulong,
link: ?*ucontext_t,
stack: stack_t,
sigmask: sigset_t,
_pad: [1024 / 8 - @sizeOf(sigset_t)]u8,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
mcontext: mcontext_t,
};

View file

@ -341,7 +341,7 @@ pub const ucontext_t = extern struct {
stack: stack_t,
pad: [7]i32,
regs: *mcontext_t,
sigmask: sigset_t,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
pad2: [3]i32,
mcontext: mcontext_t,
};

View file

@ -337,7 +337,7 @@ pub const ucontext_t = extern struct {
flags: u32,
link: ?*ucontext_t,
stack: stack_t,
sigmask: sigset_t,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
mcontext: mcontext_t,
};

View file

@ -273,7 +273,7 @@ pub const ucontext_t = extern struct {
link: ?*ucontext_t,
stack: stack_t,
mcontext: mcontext_t,
sigmask: sigset_t,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
};
pub const mcontext_t = extern struct {

View file

@ -454,7 +454,7 @@ pub const ucontext_t = extern struct {
sigmask: u64,
mcontext: mcontext_t,
stack: stack_t,
sigset: sigset_t,
sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
};
/// TODO

View file

@ -126,7 +126,9 @@ test "fadvise" {
}
test "sigset_t" {
var sigset = linux.empty_sigset;
std.debug.assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8));
var sigset = linux.sigemptyset();
// 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.
@ -138,7 +140,6 @@ test "sigset_t" {
}
for (1..linux.NSIG) |i| {
try expectEqual(linux.sigismember(&sigset, @truncate(i)), true);
try expectEqual(linux.sigismember(&linux.empty_sigset, @truncate(i)), false);
}
for (1..linux.NSIG) |i| {
linux.sigdelset(&sigset, @truncate(i));
@ -147,22 +148,52 @@ test "sigset_t" {
try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
}
// Kernel sigset_t is either 2+ 32-bit values or 1+ 64-bit value(s).
const sigset_len = @typeInfo(linux.sigset_t).array.len;
const sigset_elemis64 = 64 == @bitSizeOf(@typeInfo(linux.sigset_t).array.child);
linux.sigaddset(&sigset, 1);
try expectEqual(sigset[0], 1);
try expectEqual(sigset[1], 0);
if (sigset_len > 1) {
try expectEqual(sigset[1], 0);
}
linux.sigaddset(&sigset, 31);
try expectEqual(sigset[0], 0x4000_0001);
try expectEqual(sigset[1], 0);
if (sigset_len > 1) {
try expectEqual(sigset[1], 0);
}
linux.sigaddset(&sigset, 36);
try expectEqual(sigset[0], 0x4000_0001);
try expectEqual(sigset[1], 0x8);
if (sigset_elemis64) {
try expectEqual(sigset[0], 0x8_4000_0001);
} else {
try expectEqual(sigset[0], 0x4000_0001);
try expectEqual(sigset[1], 0x8);
}
linux.sigaddset(&sigset, 64);
try expectEqual(sigset[0], 0x4000_0001);
try expectEqual(sigset[1], 0x8000_0008);
try expectEqual(sigset[2], 0);
if (sigset_elemis64) {
try expectEqual(sigset[0], 0x8000_0008_4000_0001);
} else {
try expectEqual(sigset[0], 0x4000_0001);
try expectEqual(sigset[1], 0x8000_0008);
}
}
test "sigfillset" {
// unlike the C library, all the signals are set in the kernel-level fillset
const sigset = linux.sigfillset();
for (1..linux.NSIG) |i| {
try expectEqual(linux.sigismember(&sigset, @truncate(i)), true);
}
}
test "sigemptyset" {
const sigset = linux.sigemptyset();
for (1..linux.NSIG) |i| {
try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
}
}
test "sysinfo" {

View file

@ -350,7 +350,7 @@ pub const ucontext_t = extern struct {
link: ?*ucontext_t,
stack: stack_t,
mcontext: mcontext_t,
sigmask: sigset_t,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
regspace: [64]u64,
};

View file

@ -369,13 +369,21 @@ pub const mcontext_t = extern struct {
reserved1: [8]usize = undefined,
};
/// ucontext_t is part of the state pushed on the stack by the kernel for
/// a signal handler. And also a subset of the state returned from the
/// makecontext/getcontext/swapcontext POSIX APIs.
///
/// Currently this structure matches the glibc/musl layout. It contains a
/// 1024-bit signal mask, and `fpregs_mem`. This structure should be
/// split into one for the kernel ABI and c.zig should define a glibc/musl
/// compatible structure.
pub const ucontext_t = extern struct {
flags: usize,
link: ?*ucontext_t,
stack: stack_t,
mcontext: mcontext_t,
sigmask: sigset_t,
fpregs_mem: [64]usize,
sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask.
fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t
};
fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
@ -455,7 +463,7 @@ fn getContextInternal() callconv(.naked) usize {
[stack_offset] "i" (@offsetOf(ucontext_t, "stack")),
[sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
[sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")),
[sigset_size] "i" (linux.NSIG / 8),
[sigset_size] "i" (@sizeOf(sigset_t)),
: "cc", "memory", "rax", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11"
);
}

View file

@ -182,7 +182,6 @@ pub const SIG = struct {
pub const TTOU = 20;
};
pub const sigset_t = c_long;
pub const empty_sigset = 0;
pub const siginfo_t = c_long;
// TODO plan9 doesn't have sigaction_fn. Sigaction is not a union, but we include it here to be compatible.
pub const Sigaction = extern struct {
@ -199,6 +198,10 @@ pub const Sigaction = extern struct {
pub const AT = struct {
pub const FDCWD = -100; // we just make up a constant; FDCWD and openat don't actually exist in plan9
};
// Plan 9 doesn't do signals. This is just needed to get through start.zig.
pub fn sigemptyset() sigset_t {
return 0;
}
// TODO implement sigaction
// right now it is just a shim to allow using start.zig code
pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {

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;
@ -126,10 +127,8 @@ pub const timerfd_clockid_t = system.timerfd_clockid_t;
pub const cpu_set_t = system.cpu_set_t;
pub const dev_t = system.dev_t;
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 +677,8 @@ pub fn abort() noreturn {
raise(SIG.ABRT) catch {};
// Disable all signal handlers.
sigprocmask(SIG.BLOCK, &linux.all_mask, null);
const filledset = linux.sigfillset();
sigprocmask(SIG.BLOCK, &filledset, null);
// Only one thread may proceed to the rest of abort().
if (!builtin.single_threaded) {
@ -691,14 +691,15 @@ pub fn abort() noreturn {
// Install default handler so that the tkill below will terminate.
const sigact = Sigaction{
.handler = .{ .handler = SIG.DFL },
.mask = empty_sigset,
.mask = sigemptyset(),
.flags = 0,
};
sigaction(SIG.ABRT, &sigact, null);
_ = linux.tkill(linux.gettid(), SIG.ABRT);
const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)};
var sigabrtmask = sigemptyset();
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, &filled, &orig);
const rc = linux.tkill(linux.gettid(), sig);
sigprocmask(SIG.SETMASK, &orig, null);
switch (errno(rc)) {
.SUCCESS => return,
@ -5818,8 +5814,63 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
}
}
/// Return a filled sigset_t.
pub fn sigfillset() sigset_t {
if (builtin.link_libc) {
var set: sigset_t = undefined;
switch (errno(system.sigfillset(&set))) {
.SUCCESS => return set,
else => unreachable,
}
}
return system.sigfillset();
}
/// Return an empty sigset_t.
pub fn sigemptyset() sigset_t {
if (builtin.link_libc) {
var set: sigset_t = undefined;
switch (errno(system.sigemptyset(&set))) {
.SUCCESS => return set,
else => unreachable,
}
}
return system.sigemptyset();
}
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,61 @@ 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 = posix.sigemptyset();
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:
set = posix.sigfillset();
try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.CHLD)));
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.sigset_t = posix.sigemptyset();
// 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)));
}
}
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,66 +921,138 @@ test "sigaction" {
return error.SkipZigTest;
}
const test_signo = posix.SIG.URG; // URG only because it is ignored by default in debuggers
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;
}
}
};
var sa: posix.Sigaction = .{
.handler = .{ .sigaction = &S.handler },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
};
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. (Send URG and CHLD because they're ignored by default in the
// debugger, vs. USR1 or other named signals)
inline for ([_]usize{ posix.SIG.URG, posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
if (test_signo >= posix.NSIG) continue;
S.expected_sig = test_signo;
S.handler_called_count = 0;
const sa: posix.Sigaction = .{
.handler = .{ .sigaction = &S.handler },
.mask = posix.sigemptyset(),
.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.sigset_t = posix.sigemptyset();
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" {

View file

@ -749,7 +749,7 @@ fn maybeIgnoreSigpipe() void {
// Set handler to a noop function instead of `SIG.IGN` to prevent
// leaking signal disposition to a child process.
.handler = .{ .handler = noopSigHandler },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = 0,
};
posix.sigaction(posix.SIG.PIPE, &act, null);

View file

@ -175,12 +175,11 @@ pub fn attachSegfaultHandler() void {
_ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
return;
}
var act: posix.Sigaction = .{
const act: posix.Sigaction = .{
.handler = .{ .sigaction = handleSegfaultPosix },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND),
};
debug.updateSegfaultHandler(&act);
}

View file

@ -18,7 +18,7 @@ pub fn build(b: *std.build.Builder) !void {
{
const act = posix.Sigaction{
.handler = .{ .handler = posix.SIG.DFL },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = 0,
};
try posix.sigaction(posix.SIG.PIPE, &act, null);