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 }