Skip to content

Commit cfcee06

Browse files
committed
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 the 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 sigset_t is 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.
1 parent ac838ef commit cfcee06

File tree

4 files changed

+91
-42
lines changed

4 files changed

+91
-42
lines changed

lib/std/os/linux.zig

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1748-
pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
1749-
assert(sig >= 1);
1748+
pub 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,34 +1779,40 @@ 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;
17851787
}
17861788

17871789
const usize_bits = @typeInfo(usize).int.bits;
17881790

1789-
pub const sigset_t = [1024 / 32]u32;
1791+
/// Defined as one greater than the largest defined signal number.
1792+
pub const NSIG = if (is_mips) 128 else 65;
17901793

1791-
const 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.
1797+
pub const sigset_t = [(NSIG - 1 + 7) / @bitSizeOf(SigsetElement)]SigsetElement;
17921798

1793-
/// Empty set to initialize sigset_t instances from.
1794-
pub const empty_sigset: sigset_t = [_]u32{0} ** sigset_len;
1799+
const SigsetElement = c_ulong;
17951800

1796-
pub const filled_sigset: sigset_t = [_]u32{0x7fff_ffff} ++ [_]u32{0} ** (sigset_len - 1);
1801+
const sigset_len = @typeInfo(sigset_t).array.len;
1802+
1803+
/// Empty set to initialize sigset_t instances from. No need for `sigemptyset`.
1804+
pub const empty_sigset: sigset_t = [_]SigsetElement{0} ** sigset_len;
17971805

1798-
pub const all_mask: sigset_t = [_]u32{0xffff_ffff} ** sigset_len;
1806+
/// Filled set to initialize sigset_t instances from. No need for `sigfillset`.
1807+
pub const filled_sigset: sigset_t = [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len;
17991808

1800-
fn sigset_bit_index(sig: usize) struct { word: usize, mask: u32 } {
1809+
fn 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.
5484-
pub const NSIG = if (is_mips) 128 else 65;
5485-
54865490
const 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.
54915496
pub 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.
5513-
pub const Sigaction = extern struct {
5516+
pub 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

55265531
pub const SFD = struct {
@@ -5900,6 +5905,8 @@ else
59005905
size: usize,
59015906
};
59025907

5908+
// Not clear the i32 padding is always correct with 64-bit pointers.
5909+
// See https://github.com/ziglang/zig/issues/19754
59035910
pub const sigval = extern union {
59045911
int: i32,
59055912
ptr: *anyopaque,

lib/std/os/linux/loongarch64.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ pub const mcontext_t = extern struct {
260260
extcontext: [0]u64 align(16),
261261
};
262262

263+
/// I think this is trying to be a glibc-compatible ucontext_t (with the extra padding)
263264
pub const ucontext_t = extern struct {
264265
flags: c_ulong,
265266
link: ?*ucontext_t,

lib/std/os/linux/test.zig

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ test "fadvise" {
126126
}
127127

128128
test "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+
186+
test "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+
194+
test "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

168201
test "sysinfo" {

lib/std/os/linux/x86_64.zig

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
372380
pub 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

381389
fn 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
}

0 commit comments

Comments
 (0)