diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 24f58e1c685b..2213bd9b1166 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -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); diff --git a/lib/std/c.zig b/lib/std/c.zig index 35d175f0b9aa..d5560c3f6fa2 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -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; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index ac083ec9f2d9..74d79842be25 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -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 diff --git a/lib/std/debug.zig b/lib/std/debug.zig index d2b17ed30dbe..eb7cf793f2d7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -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); diff --git a/lib/std/os/emscripten.zig b/lib/std/os/emscripten.zig index ad7a3f65c1ce..0e07a8afb77f 100644 --- a/lib/std/os/emscripten.zig +++ b/lib/std/os/emscripten.zig @@ -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, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 00390c0800d7..83bf8e101346 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -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; -const sigset_len = @typeInfo(sigset_t).array.len; +/// 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; -/// Empty set to initialize sigset_t instances from. -pub const empty_sigset: sigset_t = [_]u32{0} ** sigset_len; +const SigsetElement = c_ulong; -pub const filled_sigset: sigset_t = [_]u32{0x7fff_ffff} ++ [_]u32{0} ** (sigset_len - 1); +const sigset_len = @typeInfo(sigset_t).array.len; -pub const all_mask: sigset_t = [_]u32{0xffff_ffff} ** sigset_len; +/// Zig's version of sigemptyset. Returns initialized sigset_t. +pub fn sigemptyset() sigset_t { + return [_]SigsetElement{0} ** sigset_len; +} -fn sigset_bit_index(sig: usize) struct { word: usize, mask: u32 } { +/// Zig's version of sigfillset. Returns initalized sigset_t. +pub fn sigfillset() sigset_t { + return [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len; +} + +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 { - flags: c_uint, - handler: k_sigaction_funcs.handler, - mask: [4]c_ulong, - restorer: k_sigaction_funcs.restorer, - }, - .mips64, .mips64el => extern struct { + .mips, .mipsel, .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 { diff --git a/lib/std/os/linux/aarch64.zig b/lib/std/os/linux/aarch64.zig index 649e474310fc..ed40145e1bbb 100644 --- a/lib/std/os/linux/aarch64.zig +++ b/lib/std/os/linux/aarch64.zig @@ -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, }; diff --git a/lib/std/os/linux/arm.zig b/lib/std/os/linux/arm.zig index 8f83bb60a1ba..8fea30a5da8b 100644 --- a/lib/std/os/linux/arm.zig +++ b/lib/std/os/linux/arm.zig @@ -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, }; diff --git a/lib/std/os/linux/loongarch64.zig b/lib/std/os/linux/loongarch64.zig index 9ee8fe229c2a..50c4664ac821 100644 --- a/lib/std/os/linux/loongarch64.zig +++ b/lib/std/os/linux/loongarch64.zig @@ -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, }; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index 443a32769ab5..450cb27c7ec4 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -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, }; diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index bb1032fb21ad..b0a14ff3cb6e 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -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, }; diff --git a/lib/std/os/linux/s390x.zig b/lib/std/os/linux/s390x.zig index 70cf53f6fd08..614c33d076f5 100644 --- a/lib/std/os/linux/s390x.zig +++ b/lib/std/os/linux/s390x.zig @@ -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 { diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 594b11b7accc..34df73fcb160 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -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 diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index fa6f8223297b..7f5deff480d3 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -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" { diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index cef038820616..41e1ec7d997d 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -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, }; diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 90147876c817..7ef86fdcf888 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -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" ); } diff --git a/lib/std/os/plan9.zig b/lib/std/os/plan9.zig index 1882eda47696..b910e26c7134 100644 --- a/lib/std/os/plan9.zig +++ b/lib/std/os/plan9.zig @@ -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 { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index ad65cf205ded..f414aa801b96 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -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 diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index e27f8971d7e6..c886ed6153fb 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -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; + + 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; - // https://github.com/ziglang/zig/issues/7427 - if (native_os == .linux and builtin.target.cpu.arch == .x86) + 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" { diff --git a/lib/std/start.zig b/lib/std/start.zig index 9072c97c0254..6495db1c51a6 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -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); diff --git a/src/crash_report.zig b/src/crash_report.zig index 431381fce4e5..6f043c3bfb79 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -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); } diff --git a/test/standalone/sigpipe/build.zig b/test/standalone/sigpipe/build.zig index 8e1cb5e1227f..de06e1df1553 100644 --- a/test/standalone/sigpipe/build.zig +++ b/test/standalone/sigpipe/build.zig @@ -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);