lib/sys/src/signal.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const capabilities = @import("capabilities.zig");
4 const process = @import("process/root.zig");
5
6 const linux = std.os.linux;
7 const posix = std.posix;
8 const native_os = builtin.os.tag;
9
10 pub const required_capabilities = capabilities.host(&.{ .process, .signal });
11
12 const has_process_signals = switch (native_os) {
13 .windows, .wasi, .freestanding => false,
14 else => true,
15 };
16
17 pub const RawSignal = if (has_process_signals) posix.SIG else enum(c_int) {
18 HUP = 1,
19 INT = 2,
20 QUIT = 3,
21 BUS = 7,
22 KILL = 9,
23 SEGV = 11,
24 TSTP = 20,
25 PROF = 27,
26 WINCH = 28,
27 TERM = 15,
28 };
29 pub const SignalInfo = if (has_process_signals) posix.siginfo_t else opaque {};
30 pub const SignalAction = if (has_process_signals) posix.Sigaction else struct {};
31 pub const AlternateStack = if (has_process_signals) posix.stack_t else struct {
32 sp: ?*anyopaque = null,
33 size: usize = 0,
34 flags: c_uint = 0,
35 };
36 pub const AlternateStackPointer = if (has_process_signals) @TypeOf((@as(posix.stack_t, undefined)).sp) else ?*anyopaque;
37 pub const AlternateStackSize = if (has_process_signals) @TypeOf((@as(posix.stack_t, undefined)).size) else usize;
38 pub const AlternateStackError = error{
39 UnsupportedPlatform,
40 StackQueryFailed,
41 StackInstallFailed,
42 };
43 pub const SignalActionError = error{
44 UnsupportedPlatform,
45 InvalidSignal,
46 ActionFailed,
47 };
48 pub const SignalMaskError = error{
49 UnsupportedPlatform,
50 MaskFailed,
51 };
52 pub const SendError = error{
53 UnsupportedPlatform,
54 ProcessNotFound,
55 PermissionDenied,
56 SendFailed,
57 };
58 pub const ParentDeathError = error{
59 UnsupportedPlatform,
60 ParentChanged,
61 InvalidSignal,
62 ArmFailed,
63 };
64
65 pub const SignalPolicy = enum {
66 unsupported,
67 linux_syscall,
68 posix_host,
69 };
70
71 pub const HandlerOptions = struct {
72 reset: bool = false,
73 restart: bool = false,
74 };
75
76 pub const Handler = *const fn (RawSignal) callconv(.c) void;
77 pub const FaultHandler = *const fn (RawSignal, *const SignalInfo, ?*anyopaque) callconv(.c) void;
78
79 const interrupt_signal: RawSignal = if (has_process_signals) posix.SIG.INT else .INT;
80 const termination_signals: []const RawSignal = if (has_process_signals)
81 &.{ posix.SIG.TERM, posix.SIG.HUP }
82 else
83 &.{};
84
85 pub fn supportsProcessSignals() bool {
86 return has_process_signals;
87 }
88
89 pub fn signalPolicy() SignalPolicy {
90 return switch (native_os) {
91 .windows, .wasi, .freestanding => .unsupported,
92 .linux => .posix_host,
93 else => .posix_host,
94 };
95 }
96
97 pub fn faultAddress(signal: RawSignal, info: *const SignalInfo) ?usize {
98 if (comptime !has_process_signals) {
99 return null;
100 }
101
102 if (native_os == .linux and builtin.cpu.arch == .x86_64) {
103 const SI_KERNEL = 0x80;
104 if (signal == .SEGV and info.code == SI_KERNEL) return null;
105 }
106
107 return switch (native_os) {
108 .serenity,
109 .dragonfly,
110 .freebsd,
111 .driverkit,
112 .ios,
113 .maccatalyst,
114 .macos,
115 .tvos,
116 .visionos,
117 .watchos,
118 => @intFromPtr(info.addr),
119 .linux => @intFromPtr(info.fields.sigfault.addr),
120 .netbsd => @intFromPtr(info.info.reason.fault.addr),
121 .haiku,
122 .openbsd,
123 => @intFromPtr(info.data.fault.addr),
124 .illumos => @intFromPtr(info.reason.fault.addr),
125 else => comptime unreachable,
126 };
127 }
128
129 pub fn installBusFaultStackGrowthHandler() bool {
130 return has_process_signals and native_os != .linux;
131 }
132
133 pub fn segmentationFaultSignal() RawSignal {
134 if (comptime !has_process_signals) return .TERM;
135 return .SEGV;
136 }
137
138 pub fn busFaultSignal() RawSignal {
139 if (comptime !has_process_signals) return .TERM;
140 return .BUS;
141 }
142
143 pub fn isBusFaultSignal(signal: RawSignal) bool {
144 if (comptime !has_process_signals) return false;
145 return signal == .BUS;
146 }
147
148 pub fn stackFaultAction(handler: FaultHandler) SignalAction {
149 if (comptime !has_process_signals) {
150 return .{};
151 }
152 return .{
153 .handler = .{ .sigaction = handler },
154 .mask = emptySignalSet(),
155 .flags = posix.SA.SIGINFO | posix.SA.ONSTACK,
156 };
157 }
158
159 pub fn siginfoAction(handler: FaultHandler) SignalAction {
160 if (comptime !has_process_signals) {
161 return .{};
162 }
163 return .{
164 .handler = .{ .sigaction = handler },
165 .mask = emptySignalSet(),
166 .flags = posix.SA.SIGINFO,
167 };
168 }
169
170 pub fn handlerAction(handler: Handler, options: HandlerOptions) SignalAction {
171 if (comptime !has_process_signals) return .{};
172 var flags: c_uint = 0;
173 if (options.reset) flags |= posix.SA.RESETHAND;
174 if (options.restart) flags |= posix.SA.RESTART;
175 return .{
176 .handler = .{ .handler = handler },
177 .mask = emptySignalSet(),
178 .flags = flags,
179 };
180 }
181
182 pub fn installAction(signal: RawSignal, action: *const SignalAction, previous: ?*SignalAction) SignalActionError!void {
183 return switch (comptime signalPolicy()) {
184 .unsupported => error.UnsupportedPlatform,
185 .linux_syscall => installActionLinux(signal, action, previous),
186 .posix_host => installActionPosix(signal, action, previous),
187 };
188 }
189
190 pub fn dispatchPreviousAction(
191 action: SignalAction,
192 signal: RawSignal,
193 info: *const SignalInfo,
194 context: ?*anyopaque,
195 ) void {
196 if (comptime !has_process_signals) {
197 return;
198 }
199 if ((action.flags & posix.SA.SIGINFO) != 0) {
200 if (action.handler.sigaction) |handler| handler(signal, info, context);
201 } else if (action.handler.handler) |handler| {
202 const raw = @intFromPtr(handler);
203 if (raw > 1 and raw != std.math.maxInt(usize)) handler(signal);
204 }
205 }
206
207 pub fn alternateStackPointer(ptr: [*]u8) AlternateStackPointer {
208 return @ptrCast(ptr);
209 }
210
211 pub fn alternateStackSize(len: usize) AlternateStackSize {
212 return @intCast(len);
213 }
214
215 pub fn alternateStack(ptr: [*]u8, len: usize) AlternateStack {
216 return .{
217 .sp = alternateStackPointer(ptr),
218 .size = alternateStackSize(len),
219 .flags = 0,
220 };
221 }
222
223 pub fn readAlternateStack() AlternateStackError!AlternateStack {
224 return switch (comptime signalPolicy()) {
225 .unsupported => error.UnsupportedPlatform,
226 .linux_syscall => readAlternateStackLinux(),
227 .posix_host => readAlternateStackPosix(),
228 };
229 }
230
231 pub fn alternateStackIsInstalled(stack: AlternateStack) bool {
232 if (comptime !has_process_signals) return false;
233 return (stack.flags & posix.system.SS.DISABLE) == 0 and @intFromPtr(stack.sp) != 0;
234 }
235
236 pub fn installAlternateStack(stack: *const AlternateStack) AlternateStackError!void {
237 return switch (comptime signalPolicy()) {
238 .unsupported => error.UnsupportedPlatform,
239 .linux_syscall => installAlternateStackLinux(stack),
240 .posix_host => installAlternateStackPosix(stack),
241 };
242 }
243
244 pub fn installTerminationHandlers(handler: Handler) void {
245 if (comptime !has_process_signals) return;
246 for (termination_signals) |signal| {
247 installHandler(signal, handler, posix.SA.RESETHAND | posix.SA.NODEFER);
248 }
249 }
250
251 pub fn installInterruptHandler(handler: Handler) void {
252 if (comptime !has_process_signals) return;
253 installHandler(interrupt_signal, handler, 0);
254 }
255
256 pub fn restoreTerminationDefaults() void {
257 if (comptime !has_process_signals) return;
258 for (termination_signals) |signal| {
259 restoreDefault(signal);
260 }
261 }
262
263 pub fn restoreInterruptDefault() void {
264 if (comptime !has_process_signals) return;
265 restoreDefault(interrupt_signal);
266 }
267
268 pub fn raise(signal: RawSignal) void {
269 switch (comptime signalPolicy()) {
270 .unsupported => {},
271 .linux_syscall => raiseLinux(signal),
272 .posix_host => raisePosix(signal),
273 }
274 }
275
276 pub fn send(
277 process_id: process.ProcessId,
278 signal: RawSignal,
279 group: bool,
280 ) SendError!void {
281 if (process_id <= 1) return error.ProcessNotFound;
282 const target = if (group) -process_id else process_id;
283 return switch (comptime native_os) {
284 .windows, .wasi, .freestanding => error.UnsupportedPlatform,
285 .linux => switch (linux.errno(linux.kill(target, signal))) {
286 .SUCCESS => {},
287 .SRCH => error.ProcessNotFound,
288 .ACCES, .PERM => error.PermissionDenied,
289 else => error.SendFailed,
290 },
291 else => posix.kill(target, signal) catch |err| switch (err) {
292 error.ProcessNotFound => error.ProcessNotFound,
293 error.PermissionDenied => error.PermissionDenied,
294 else => error.SendFailed,
295 },
296 };
297 }
298
299 pub fn bindParentDeathSignal(
300 expected_parent: process.ProcessId,
301 signal: RawSignal,
302 ) ParentDeathError!void {
303 if (comptime native_os != .linux) return error.UnsupportedPlatform;
304 if (expected_parent <= 1 or
305 (process.parentProcessId() catch return error.UnsupportedPlatform) != expected_parent)
306 {
307 return error.ParentChanged;
308 }
309 const result = linux.prctl(
310 @backingInt(linux.PR.SET_PDEATHSIG),
311 @backingInt(signal),
312 0,
313 0,
314 0,
315 );
316 switch (linux.errno(result)) {
317 .SUCCESS => {},
318 .INVAL => return error.InvalidSignal,
319 else => return error.ArmFailed,
320 }
321 if ((process.parentProcessId() catch return error.UnsupportedPlatform) != expected_parent) {
322 return error.ParentChanged;
323 }
324 }
325
326 pub const InterruptMask = if (!has_process_signals) struct {
327 pub fn begin() !@This() {
328 return .{};
329 }
330
331 pub fn restore(self: *@This()) void {
332 _ = self;
333 }
334 } else struct {
335 old_mask: posix.sigset_t,
336 active: bool = true,
337
338 pub fn begin() !@This() {
339 var mask = emptySignalSet();
340 addSignalToSet(&mask, interrupt_signal);
341
342 var old_mask: posix.sigset_t = undefined;
343 try applySignalMask(posix.SIG.BLOCK, &mask, &old_mask);
344 return .{ .old_mask = old_mask };
345 }
346
347 pub fn restore(self: *@This()) void {
348 if (!self.active) return;
349 applySignalMask(posix.SIG.SETMASK, &self.old_mask, null) catch {};
350 self.active = false;
351 }
352 };
353
354 pub const BlockMask = if (!has_process_signals) struct {
355 pub fn begin(_: []const RawSignal) !@This() {
356 return .{};
357 }
358
359 pub fn restore(_: *@This()) void {}
360 } else struct {
361 old_mask: posix.sigset_t,
362 active: bool = true,
363
364 pub fn begin(signals: []const RawSignal) !@This() {
365 var mask = emptySignalSet();
366 for (signals) |signal| addSignalToSet(&mask, signal);
367 var old_mask: posix.sigset_t = undefined;
368 try applySignalMask(posix.SIG.BLOCK, &mask, &old_mask);
369 return .{ .old_mask = old_mask };
370 }
371
372 pub fn restore(self: *@This()) void {
373 if (!self.active) return;
374 applySignalMask(posix.SIG.SETMASK, &self.old_mask, null) catch {};
375 self.active = false;
376 }
377 };
378
379 pub fn unblock(signals: []const RawSignal) SignalMaskError!void {
380 if (comptime !has_process_signals) return error.UnsupportedPlatform;
381 var mask = emptySignalSet();
382 for (signals) |signal| addSignalToSet(&mask, signal);
383 try applySignalMask(posix.SIG.UNBLOCK, &mask, null);
384 }
385
386 fn installActionLinux(signal: RawSignal, action: *const SignalAction, previous: ?*SignalAction) SignalActionError!void {
387 if (comptime native_os != .linux) return error.UnsupportedPlatform;
388 return signalActionErrorFromLinuxErrno(linux.errno(linux.sigaction(signal, action, previous)));
389 }
390
391 fn installActionPosix(signal: RawSignal, action: *const SignalAction, previous: ?*SignalAction) SignalActionError!void {
392 if (comptime !has_process_signals) return error.UnsupportedPlatform;
393 posix.sigaction(signal, action, previous);
394 }
395
396 fn readAlternateStackLinux() AlternateStackError!AlternateStack {
397 if (comptime native_os != .linux) return error.UnsupportedPlatform;
398 var stack: AlternateStack = undefined;
399 switch (linux.errno(linux.sigaltstack(null, &stack))) {
400 .SUCCESS => return stack,
401 else => return error.StackQueryFailed,
402 }
403 }
404
405 fn readAlternateStackPosix() AlternateStackError!AlternateStack {
406 if (comptime !has_process_signals) return error.UnsupportedPlatform;
407 var stack: AlternateStack = undefined;
408 posix.sigaltstack(null, &stack) catch return error.StackQueryFailed;
409 return stack;
410 }
411
412 fn installAlternateStackLinux(stack: *const AlternateStack) AlternateStackError!void {
413 if (comptime native_os != .linux) return error.UnsupportedPlatform;
414 return alternateStackErrorFromLinuxErrno(linux.errno(linux.sigaltstack(stack, null)));
415 }
416
417 fn installAlternateStackPosix(stack: *const AlternateStack) AlternateStackError!void {
418 if (comptime !has_process_signals) return error.UnsupportedPlatform;
419 posix.sigaltstack(stack, null) catch return error.StackInstallFailed;
420 }
421
422 fn raiseLinux(signal: RawSignal) void {
423 if (comptime native_os != .linux) return;
424 const filled = linux.sigfillset();
425 var original: posix.sigset_t = undefined;
426 applySignalMask(posix.SIG.BLOCK, &filled, &original) catch return;
427 const rc = linux.tkill(process.currentThreadId() catch return, signal);
428 applySignalMask(posix.SIG.SETMASK, &original, null) catch {};
429 _ = linux.errno(rc);
430 }
431
432 fn raisePosix(signal: RawSignal) void {
433 if (comptime !has_process_signals) return;
434 posix.raise(signal) catch {};
435 }
436
437 fn emptySignalSet() posix.sigset_t {
438 return posix.sigemptyset();
439 }
440
441 fn addSignalToSet(set: *posix.sigset_t, signal: RawSignal) void {
442 posix.sigaddset(set, signal);
443 }
444
445 fn applySignalMask(flags: u32, set: ?*const posix.sigset_t, old_set: ?*posix.sigset_t) SignalMaskError!void {
446 return switch (comptime signalPolicy()) {
447 .unsupported => error.UnsupportedPlatform,
448 .linux_syscall => signalMaskLinux(flags, set, old_set),
449 .posix_host => signalMaskPosix(flags, set, old_set),
450 };
451 }
452
453 fn signalMaskLinux(flags: u32, set: ?*const posix.sigset_t, old_set: ?*posix.sigset_t) SignalMaskError!void {
454 if (comptime native_os != .linux) return error.UnsupportedPlatform;
455 switch (linux.errno(linux.sigprocmask(flags, set, old_set))) {
456 .SUCCESS => {},
457 else => return error.MaskFailed,
458 }
459 }
460
461 fn signalMaskPosix(flags: u32, set: ?*const posix.sigset_t, old_set: ?*posix.sigset_t) SignalMaskError!void {
462 if (comptime !has_process_signals) return error.UnsupportedPlatform;
463 posix.sigprocmask(flags, set, old_set);
464 }
465
466 fn signalActionErrorFromLinuxErrno(err: linux.E) SignalActionError!void {
467 return switch (err) {
468 .SUCCESS => {},
469 .INVAL => error.InvalidSignal,
470 else => error.ActionFailed,
471 };
472 }
473
474 fn alternateStackErrorFromLinuxErrno(err: linux.E) AlternateStackError!void {
475 return switch (err) {
476 .SUCCESS => {},
477 else => error.StackInstallFailed,
478 };
479 }
480
481 fn installHandler(signal: RawSignal, handler: Handler, flags: c_uint) void {
482 if (comptime !has_process_signals) return;
483 var act = posix.Sigaction{
484 .handler = .{ .handler = handler },
485 .mask = switch (native_os) {
486 .macos => 0,
487 else => emptySignalSet(),
488 },
489 .flags = flags,
490 };
491 installAction(signal, &act, null) catch {};
492 }
493
494 fn restoreDefault(signal: RawSignal) void {
495 if (comptime !has_process_signals) return;
496 var act = posix.Sigaction{
497 .handler = .{ .handler = posix.SIG.DFL },
498 .mask = switch (native_os) {
499 .macos => 0,
500 else => emptySignalSet(),
501 },
502 .flags = 0,
503 };
504 installAction(signal, &act, null) catch {};
505 }
506
507 test "process signal support is queryable" {
508 _ = supportsProcessSignals();
509 }
510
511 test "signal policy is explicit" {
512 const expected: SignalPolicy = switch (native_os) {
513 .windows, .wasi, .freestanding => .unsupported,
514 .linux => .posix_host,
515 else => .posix_host,
516 };
517 try std.testing.expectEqual(expected, signalPolicy());
518 }
519
520 test "linux signal syscall errno mapping is explicit" {
521 if (comptime native_os != .linux) return;
522 try signalActionErrorFromLinuxErrno(.SUCCESS);
523 try std.testing.expectError(error.InvalidSignal, signalActionErrorFromLinuxErrno(.INVAL));
524 try std.testing.expectError(error.ActionFailed, signalActionErrorFromLinuxErrno(.IO));
525 try alternateStackErrorFromLinuxErrno(.SUCCESS);
526 try std.testing.expectError(error.StackInstallFailed, alternateStackErrorFromLinuxErrno(.NOMEM));
527 }
528
529 test "interrupt mask is constructible" {
530 var mask = try InterruptMask.begin();
531 mask.restore();
532 }
533
534 test "owned signals can be explicitly unblocked" {
535 if (comptime !has_process_signals) return;
536 var inherited = try BlockMask.begin(&.{.HUP});
537 defer inherited.restore();
538 var observed: posix.sigset_t = undefined;
539 try applySignalMask(posix.SIG.BLOCK, null, &observed);
540 try std.testing.expect(posix.sigismember(&observed, .HUP));
541
542 try unblock(&.{.HUP});
543 try applySignalMask(posix.SIG.BLOCK, null, &observed);
544 try std.testing.expect(!posix.sigismember(&observed, .HUP));
545 }