authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-04-13 16:14:25-07:00
committergravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-04-30 20:32:04-07:00
log298b1886b2b35b168589026d075d14206d6e3430
tree20316f3fc1a112b577dbc5ab825baffa574db2c0
parent51654aea877d1da92f7f2ad2c6c781c73e08d7f0

std.os.linux: export kernel-sized sigset_t and operations

The kernel ABI sigset_t is smaller than the glibc one. Define the right-sized sigset_t and fixup the sigaction() wrapper to leverage it. The Sigaction wrapper here is not an ABI, so relax it (drop the "extern" and the "restorer" fields), the existing `k_sigaction` is the ABI sigaction struct. Linux defines `sigset_t` with a c_ulong, so it can be 32-bit or 64-bit, depending on the platform. This can make a difference on big-endian systems. Patch up `ucontext_t` so that this change doesn't impact its layout. AFAICT, its currently the glibc layout.

3 files changed, 88 insertions(+), 42 deletions(-)

lib/std/os/linux.zig+37-32
......@@ -1745,8 +1745,9 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
17451745 return syscall4(.rt_sigprocmask, flags, @intFromPtr(set), @intFromPtr(oldset), NSIG / 8);
17461746}
17471747
1748pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
1749 assert(sig >= 1);
1748pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
1749 assert(sig > 0);
1750 assert(sig < NSIG);
17501751 assert(sig != SIG.KILL);
17511752 assert(sig != SIG.STOP);
17521753
......@@ -1755,14 +1756,15 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
17551756 const mask_size = @sizeOf(@TypeOf(ksa.mask));
17561757
17571758 if (act) |new| {
1759 // Zig needs to install our arch restorer function with any signal handler, so
1760 // must copy the Sigaction struct
17581761 const restorer_fn = if ((new.flags & SA.SIGINFO) != 0) &restore_rt else &restore;
17591762 ksa = k_sigaction{
17601763 .handler = new.handler.handler,
17611764 .flags = new.flags | SA.RESTORER,
1762 .mask = undefined,
1765 .mask = new.mask,
17631766 .restorer = @ptrCast(restorer_fn),
17641767 };
1765 @memcpy(@as([*]u8, @ptrCast(&ksa.mask))[0..mask_size], @as([*]const u8, @ptrCast(&new.mask)));
17661768 }
17671769
17681770 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
17771779
17781780 if (oact) |old| {
17791781 old.handler.handler = oldksa.handler;
1780 old.flags = @as(c_uint, @truncate(oldksa.flags));
1781 @memcpy(@as([*]u8, @ptrCast(&old.mask))[0..mask_size], @as([*]const u8, @ptrCast(&oldksa.mask)));
1782 old.flags = oldksa.flags;
1783 old.mask = oldksa.mask;
17821784 }
17831785
17841786 return 0;
......@@ -1786,25 +1788,31 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
17861788
17871789const usize_bits = @typeInfo(usize).int.bits;
17881790
1789pub const sigset_t = [1024 / 32]u32;
1791/// Defined as one greater than the largest defined signal number.
1792pub const NSIG = if (is_mips) 128 else 65;
17901793
1791const sigset_len = @typeInfo(sigset_t).array.len;
1794/// Linux kernel's sigset_t. This is logically 64-bit on most
1795/// architectures, but 128-bit on MIPS. Contrast with the 1024-bit
1796/// sigset_t exported by the glibc and musl library ABIs.
1797pub const sigset_t = [(NSIG - 1 + 7) / @bitSizeOf(SigsetElement)]SigsetElement;
17921798
1793/// Empty set to initialize sigset_t instances from.
1794pub const empty_sigset: sigset_t = [_]u32{0} ** sigset_len;
1799const SigsetElement = c_ulong;
17951800
1796pub const filled_sigset: sigset_t = [_]u32{0x7fff_ffff} ++ [_]u32{0} ** (sigset_len - 1);
1801const sigset_len = @typeInfo(sigset_t).array.len;
1802
1803/// Empty set to initialize sigset_t instances from. No need for `sigemptyset`.
1804pub const empty_sigset: sigset_t = [_]SigsetElement{0} ** sigset_len;
17971805
1798pub const all_mask: sigset_t = [_]u32{0xffff_ffff} ** sigset_len;
1806/// Filled set to initialize sigset_t instances from. No need for `sigfillset`.
1807pub const filled_sigset: sigset_t = [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len;
17991808
1800fn sigset_bit_index(sig: usize) struct { word: usize, mask: u32 } {
1809fn sigset_bit_index(sig: usize) struct { word: usize, mask: SigsetElement } {
18011810 assert(sig > 0);
18021811 assert(sig < NSIG);
18031812 const bit = sig - 1;
1804 const shift = @as(u5, @truncate(bit % 32));
18051813 return .{
1806 .word = bit / 32,
1807 .mask = @as(u32, 1) << shift,
1814 .word = bit / @bitSizeOf(SigsetElement),
1815 .mask = @as(SigsetElement, 1) << @truncate(bit % @bitSizeOf(SigsetElement)),
18081816 };
18091817}
18101818
......@@ -5479,38 +5487,33 @@ pub const TFD = switch (native_arch) {
54795487 },
54805488};
54815489
5482/// NSIG is the total number of signals defined.
5483/// As signal numbers are sequential, NSIG is one greater than the largest defined signal number.
5484pub const NSIG = if (is_mips) 128 else 65;
5485
54865490const k_sigaction_funcs = struct {
54875491 const handler = ?*align(1) const fn (i32) callconv(.c) void;
54885492 const restorer = *const fn () callconv(.c) void;
54895493};
54905494
5495/// Kernel sigaction struct, as expected by the `rt_sigaction` syscall. Includes restorer.
54915496pub const k_sigaction = switch (native_arch) {
5492 .mips, .mipsel => extern struct {
5497 .mips, .mipsel, .mips64, .mips64el => extern struct {
54935498 flags: c_uint,
54945499 handler: k_sigaction_funcs.handler,
5495 mask: [4]c_ulong,
5496 restorer: k_sigaction_funcs.restorer,
5497 },
5498 .mips64, .mips64el => extern struct {
5499 flags: c_uint,
5500 handler: k_sigaction_funcs.handler,
5501 mask: [2]c_ulong,
5500 mask: sigset_t,
55025501 restorer: k_sigaction_funcs.restorer,
55035502 },
55045503 else => extern struct {
55055504 handler: k_sigaction_funcs.handler,
55065505 flags: c_ulong,
55075506 restorer: k_sigaction_funcs.restorer,
5508 mask: [2]c_uint,
5507 mask: sigset_t,
55095508 },
55105509};
55115510
5511/// Kernel Sigaction wrapper for the actual ABI `k_sigaction`. The Zig
5512/// linux.zig wrapper library still does some pre-processing on
5513/// sigaction() calls (to add the `restorer` field).
5514///
55125515/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
5513pub const Sigaction = extern struct {
5516pub const Sigaction = struct {
55145517 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
55155518 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
55165519
......@@ -5519,8 +5522,10 @@ pub const Sigaction = extern struct {
55195522 sigaction: ?sigaction_fn,
55205523 },
55215524 mask: sigset_t,
5522 flags: c_uint,
5523 restorer: ?*const fn () callconv(.c) void = null,
5525 flags: switch (native_arch) {
5526 .mips, .mipsel, .mips64, .mips64el => c_uint,
5527 else => c_ulong,
5528 },
55245529};
55255530
55265531pub const SFD = struct {
lib/std/os/linux/test.zig+40-7
......@@ -126,6 +126,8 @@ test "fadvise" {
126126}
127127
128128test "sigset_t" {
129 std.debug.assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8));
130
129131 var sigset = linux.empty_sigset;
130132
131133 // See that none are set, then set each one, see that they're all set, then
......@@ -138,6 +140,7 @@ test "sigset_t" {
138140 }
139141 for (1..linux.NSIG) |i| {
140142 try expectEqual(linux.sigismember(&sigset, @truncate(i)), true);
143 try expectEqual(linux.sigismember(&linux.filled_sigset, @truncate(i)), true);
141144 try expectEqual(linux.sigismember(&linux.empty_sigset, @truncate(i)), false);
142145 }
143146 for (1..linux.NSIG) |i| {
......@@ -147,22 +150,52 @@ test "sigset_t" {
147150 try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
148151 }
149152
153 // Kernel sigset_t is either 2+ 32-bit values or 1+ 64-bit value(s).
154 const sigset_len = @typeInfo(linux.sigset_t).array.len;
155 const sigset_elemis64 = 64 == @bitSizeOf(@typeInfo(linux.sigset_t).array.child);
156
150157 linux.sigaddset(&sigset, 1);
151158 try expectEqual(sigset[0], 1);
152 try expectEqual(sigset[1], 0);
159 if (sigset_len > 1) {
160 try expectEqual(sigset[1], 0);
161 }
153162
154163 linux.sigaddset(&sigset, 31);
155164 try expectEqual(sigset[0], 0x4000_0001);
156 try expectEqual(sigset[1], 0);
165 if (sigset_len > 1) {
166 try expectEqual(sigset[1], 0);
167 }
157168
158169 linux.sigaddset(&sigset, 36);
159 try expectEqual(sigset[0], 0x4000_0001);
160 try expectEqual(sigset[1], 0x8);
170 if (sigset_elemis64) {
171 try expectEqual(sigset[0], 0x8_4000_0001);
172 } else {
173 try expectEqual(sigset[0], 0x4000_0001);
174 try expectEqual(sigset[1], 0x8);
175 }
161176
162177 linux.sigaddset(&sigset, 64);
163 try expectEqual(sigset[0], 0x4000_0001);
164 try expectEqual(sigset[1], 0x8000_0008);
165 try expectEqual(sigset[2], 0);
178 if (sigset_elemis64) {
179 try expectEqual(sigset[0], 0x8000_0008_4000_0001);
180 } else {
181 try expectEqual(sigset[0], 0x4000_0001);
182 try expectEqual(sigset[1], 0x8000_0008);
183 }
184}
185
186test "filled_sigset" {
187 // unlike the C library, all the signals are set in the kernel-level fillset
188 const sigset = linux.filled_sigset;
189 for (1..linux.NSIG) |i| {
190 try expectEqual(linux.sigismember(&sigset, @truncate(i)), true);
191 }
192}
193
194test "empty_sigset" {
195 const sigset = linux.empty_sigset;
196 for (1..linux.NSIG) |i| {
197 try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
198 }
166199}
167200
168201test "sysinfo" {
lib/std/os/linux/x86_64.zig+11-3
......@@ -369,13 +369,21 @@ pub const mcontext_t = extern struct {
369369 reserved1: [8]usize = undefined,
370370};
371371
372/// ucontext_t is part of the state pushed on the stack by the kernel for
373/// a signal handler. And also a subset of the state returned from the
374/// makecontext/getcontext/swapcontext POSIX APIs.
375///
376/// Currently this structure matches the glibc/musl layout. It contains a
377/// 1024-bit signal mask, and `fpregs_mem`. This structure should be
378/// split into one for the kernel ABI and c.zig should define a glibc/musl
379/// compatible structure.
372380pub const ucontext_t = extern struct {
373381 flags: usize,
374382 link: ?*ucontext_t,
375383 stack: stack_t,
376384 mcontext: mcontext_t,
377 sigmask: sigset_t,
378 fpregs_mem: [64]usize,
385 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask.
386 fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t
379387};
380388
381389fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
......@@ -455,7 +463,7 @@ fn getContextInternal() callconv(.naked) usize {
455463 [stack_offset] "i" (@offsetOf(ucontext_t, "stack")),
456464 [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
457465 [sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")),
458 [sigset_size] "i" (linux.NSIG / 8),
466 [sigset_size] "i" (@sizeOf(sigset_t)),
459467 : "cc", "memory", "rax", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11"
460468 );
461469}