Skip to main content

starnix_core/signals/
signal_handling.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::arch::signal_handling::{
6    RED_ZONE_SIZE, SIG_STACK_SIZE, SignalStackFrame, align_stack_pointer, restore_registers,
7};
8use crate::mm::{MemoryAccessor, MemoryAccessorExt};
9use crate::ptrace::StopState;
10use crate::signals::{KernelSignal, KernelSignalInfo, SignalDetail, SignalInfo, SignalState};
11use crate::task::{
12    ArchExtendedPstateStorage, CurrentTask, ExitStatus, Task, TaskFlags, TaskWriteGuard,
13    ThreadState, Waiter,
14};
15use starnix_logging::{log_info, log_trace, log_warn};
16use starnix_registers::{RegisterState, RegisterStorageEnum};
17use starnix_syscalls::SyscallResult;
18use starnix_types::arch::ArchWidth;
19use starnix_uapi::errors::{EINTR, ERESTART_RESTARTBLOCK, Errno};
20use starnix_uapi::resource_limits::Resource;
21use starnix_uapi::signals::{
22    SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIO, SIGKILL,
23    SIGPIPE, SIGPROF, SIGPWR, SIGQUIT, SIGSEGV, SIGSTKFLT, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP,
24    SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ,
25    SigSet, sigaltstack_contains_pointer,
26};
27use starnix_uapi::user_address::{ArchSpecific, UserAddress};
28use starnix_uapi::{
29    SA_NODEFER, SA_ONSTACK, SA_RESETHAND, SA_SIGINFO, SIG_DFL, SIG_IGN, errno, error, sigaction_t,
30};
31
32/// Indicates where in the signal queue a signal should go.  Signals
33/// can jump the queue when being injected by tools like ptrace.
34#[derive(PartialEq)]
35enum SignalPriority {
36    First,
37    Last,
38}
39
40// `send_signal*()` calls below may fail only for real-time signals (with EAGAIN). They are
41// expected to succeed for all other signals.
42
43/// Enqueues `signal_info` at the front of `task`'s signal queue and wakes `task`.
44///
45/// Callers must not hold `ThreadGroupMutableStateLock`, as delivering `SIGKILL` or waking the
46/// thread group acquires the target thread group's write lock.
47pub fn send_signal_first(task: &Task, task_state: TaskWriteGuard<'_>, signal_info: SignalInfo) {
48    send_signal_prio(
49        task,
50        task_state,
51        signal_info.into(),
52        SignalPriority::First,
53        /* force_wake = */ true,
54    )
55    .expect("send_signal(SignalPriority::First) is not expected to fail")
56}
57
58/// Sends a standard (non-real-time) `signal_info` to `task`.
59///
60/// Callers must not hold `ThreadGroupMutableStateLock`, as delivering `SIGKILL` or waking the
61/// thread group acquires the target thread group's write lock.
62pub fn send_standard_signal(task: &Task, signal_info: SignalInfo) {
63    debug_assert!(!signal_info.signal.is_real_time());
64    let state = task.write();
65    send_signal_prio(
66        task,
67        state,
68        signal_info.into(),
69        SignalPriority::Last,
70        /* force_wake = */ false,
71    )
72    .expect("send_signal(SignalPriority::Last) is not expected to fail for standard signals.")
73}
74
75/// Sends `signal_info` to `task`.
76///
77/// May only fail for real-time signals (with `EAGAIN` when `RLIMIT_SIGPENDING` is reached) and
78/// always succeeds for standard signals.
79///
80/// Callers must not hold `ThreadGroupMutableStateLock`, as delivering `SIGKILL` or waking the
81/// thread group acquires the target thread group's write lock.
82pub fn send_signal(task: &Task, signal_info: SignalInfo) -> Result<(), Errno> {
83    let state = task.write();
84    send_signal_prio(
85        task,
86        state,
87        signal_info.into(),
88        SignalPriority::Last,
89        /* force_wake = */ false,
90    )
91}
92
93pub fn send_freeze_signal(task: &Task, waiter: Waiter) -> Result<(), Errno> {
94    let state = task.write();
95    send_signal_prio(
96        task,
97        state,
98        KernelSignalInfo::Freeze(waiter),
99        SignalPriority::First,
100        /* force_wake = */ true,
101    )
102}
103
104fn send_signal_prio(
105    task: &Task,
106    mut task_state: TaskWriteGuard<'_>,
107    kernel_signal_info: KernelSignalInfo,
108    prio: SignalPriority,
109    force_wake: bool,
110) -> Result<(), Errno> {
111    let (signal_info, signal, is_masked, was_masked, is_real_time, sigaction, action) =
112        match kernel_signal_info {
113            KernelSignalInfo::User(ref user_signal_info) => {
114                if user_signal_info.signal == SIGKILL {
115                    drop(task_state);
116                    // Synchronously transition the thread group to Exiting and interrupt all
117                    // member tasks.
118                    // TODO(https://fxbug.dev/573607334): Immediately transition the thread group
119                    // to Exiting for other unblocked default-fatal signals
120                    // (DeliveryAction::Terminate when not ptraced).
121                    task.thread_group().write().force_exit_for_sigkill(user_signal_info.clone());
122                    return Ok(());
123                }
124                let signal = user_signal_info.signal;
125                let is_masked = task_state.is_signal_masked(signal);
126                let was_masked = task_state.is_signal_masked_by_saved_mask(signal);
127                let sigaction = task.get_signal_action(signal);
128                let action = action_for_signal(user_signal_info, sigaction);
129                (
130                    Some(user_signal_info.clone()),
131                    Some(signal),
132                    is_masked,
133                    was_masked,
134                    signal.is_real_time(),
135                    Some(sigaction),
136                    Some(action),
137                )
138            }
139            KernelSignalInfo::Freeze(_) => (None, None, false, false, false, None, None),
140        };
141
142    if is_real_time && prio != SignalPriority::First {
143        if task_state.pending_signal_count()
144            >= task.thread_group().get_rlimit(Resource::SIGPENDING) as usize
145        {
146            return error!(EAGAIN);
147        }
148    }
149
150    // If the signal is ignored then it doesn't need to be queued, except the following 2 cases:
151    //  1. The signal is blocked by the current or the original mask. The signal may be unmasked
152    //     later, see `SigtimedwaitTest.IgnoredUnmaskedSignal` gvisor test.
153    //  2. The task is ptraced. In this case we want to queue the signal for signal-delivery-stop.
154    let is_queued = action.is_none()
155        || action != Some(DeliveryAction::Ignore)
156        || is_masked
157        || was_masked
158        || task_state.is_ptraced();
159    if is_queued {
160        match kernel_signal_info {
161            KernelSignalInfo::User(ref signal_info) => {
162                if prio == SignalPriority::First {
163                    task_state.enqueue_signal_front(signal_info.clone());
164                } else {
165                    task_state.enqueue_signal(signal_info.clone());
166                }
167                task_state.set_flags(TaskFlags::SIGNALS_AVAILABLE, true);
168            }
169            KernelSignalInfo::Freeze(waiter) => {
170                task_state.enqueue_kernel_signal(KernelSignal::Freeze(waiter))
171            }
172        }
173    }
174
175    drop(task_state);
176    if is_queued
177        && !is_masked
178        && action.map_or_else(|| true, |action| action.must_interrupt(sigaction))
179    {
180        // Wake the task. Note that any potential signal handler will be executed before
181        // the task returns from the suspend (from the perspective of user space).
182        task.interrupt();
183    }
184
185    // Unstop the process for SIGCONT. Also unstop for forced waking, which the only caller
186    // of send_signal_first wants it to do.
187    // TODO: Give SIGSTOP similar immediate-effect treatment.
188    if signal == Some(SIGCONT) || force_wake {
189        task.thread_group().set_stopped(
190            StopState::Waking,
191            signal_info,
192            /* finalize_only = */ false,
193        );
194        task.write().set_stopped(
195            StopState::Waking,
196            /* siginfo = */ None,
197            /* current_task = */ None,
198            /* event = */ None,
199        );
200    }
201
202    Ok(())
203}
204
205/// Represents the action to take when signal is delivered.
206///
207/// See https://man7.org/linux/man-pages/man7/signal.7.html.
208#[derive(Debug, PartialEq)]
209pub enum DeliveryAction {
210    Ignore,
211    CallHandler,
212    Terminate,
213    CoreDump,
214    Stop,
215    Continue,
216}
217
218impl DeliveryAction {
219    /// Returns whether the target task must be interrupted to execute the action.
220    ///
221    /// The task is not interrupted for the Continue action, nor for a signal that is ignored by
222    /// default. A signal explicitly ignored with `SIG_IGN` does interrupt: it is only queued when a
223    /// task blocks it or is ptraced, and both of those cases need the task to wake up.
224    pub fn must_interrupt(&self, sigaction: Option<sigaction_t>) -> bool {
225        match *self {
226            Self::Continue => false,
227            Self::Ignore => sigaction.map_or(false, |sa| sa.sa_handler == SIG_IGN),
228            _ => true,
229        }
230    }
231}
232
233pub fn action_for_signal(siginfo: &SignalInfo, sigaction: sigaction_t) -> DeliveryAction {
234    let handler = if siginfo.force && sigaction.sa_handler == SIG_IGN {
235        SIG_DFL
236    } else {
237        sigaction.sa_handler
238    };
239    match handler {
240        SIG_DFL => match siginfo.signal {
241            SIGCHLD | SIGURG | SIGWINCH => DeliveryAction::Ignore,
242            sig if sig.is_real_time() => DeliveryAction::Ignore,
243            SIGHUP | SIGINT | SIGKILL | SIGPIPE | SIGALRM | SIGTERM | SIGUSR1 | SIGUSR2
244            | SIGPROF | SIGVTALRM | SIGSTKFLT | SIGIO | SIGPWR => DeliveryAction::Terminate,
245            SIGQUIT | SIGILL | SIGABRT | SIGFPE | SIGSEGV | SIGBUS | SIGSYS | SIGTRAP | SIGXCPU
246            | SIGXFSZ => DeliveryAction::CoreDump,
247            SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => DeliveryAction::Stop,
248            SIGCONT => DeliveryAction::Continue,
249            _ => panic!("Unknown signal"),
250        },
251        SIG_IGN => DeliveryAction::Ignore,
252        _ => DeliveryAction::CallHandler,
253    }
254}
255
256/// Dequeues and handles a pending signal for `current_task`.
257pub fn dequeue_signal(current_task: &mut CurrentTask) {
258    let &mut CurrentTask { ref task, ref mut thread_state, .. } = current_task;
259    if !task.should_check_for_pending_signals() {
260        return;
261    }
262
263    let mut task_state = task.write();
264    // This code is occasionally executed as the task is stopping. Stopping /
265    // stopped threads should not get signals.
266    if task.load_stopped().is_stopping_or_stopped() {
267        return;
268    }
269
270    // If there is a kernel signal needs to handle, deliver the signal right away.
271    let kernel_signal = task_state.take_kernel_signal();
272    let siginfo = if kernel_signal.is_some() { None } else { task_state.take_any_signal() };
273    prepare_to_restart_syscall(
274        thread_state,
275        siginfo.as_ref().map(|siginfo| task.thread_group().signal_actions.get(siginfo.signal)),
276    );
277
278    if let Some(ref siginfo) = siginfo {
279        if task_state.ptrace_on_signal_consume() && siginfo.signal != SIGKILL {
280            // Indicate we will be stopping for ptrace at the next opportunity.
281            // Whether you actually deliver the signal is now up to ptrace, so
282            // we can return.
283            task_state.set_stopped(
284                StopState::SignalDeliveryStopping,
285                Some(siginfo.clone()),
286                None,
287                None,
288            );
289            return;
290        }
291    }
292
293    // A syscall may have been waiting with a temporary mask which should be used to dequeue the
294    // signal, but after the signal has been dequeued the old mask should be restored.
295    task_state.restore_signal_mask();
296
297    if let Some(kernel_signal) = kernel_signal {
298        let KernelSignal::Freeze(waiter) = kernel_signal;
299        drop(task_state);
300
301        waiter.freeze(current_task);
302    } else if let Some(ref siginfo) = siginfo {
303        if let SignalDetail::Timer { timer } = &siginfo.detail {
304            timer.on_signal_delivered();
305        }
306        if let Some(status) = deliver_signal(
307            &task,
308            current_task.thread_state.arch_width(),
309            task_state,
310            siginfo.clone(),
311            &mut current_task.thread_state.registers,
312            &current_task.thread_state.extended_pstate,
313            None,
314        ) {
315            current_task.kill_thread_group(status);
316        }
317    };
318}
319
320fn deliver_signal(
321    task: &Task,
322    arch_width: ArchWidth,
323    mut task_state: TaskWriteGuard<'_>,
324    mut siginfo: SignalInfo,
325    registers: &mut RegisterState<RegisterStorageEnum>,
326    extended_pstate: &ArchExtendedPstateStorage,
327    restricted_exception: Option<zx::ExceptionReport>,
328) -> Option<ExitStatus> {
329    loop {
330        let signal = siginfo.signal;
331        let mut sigaction = task.thread_group().signal_actions.get(signal);
332        let masked_signals = task_state.signal_mask();
333        // From the `signal.7` man page:
334        //
335        // If a process is ignoring a signal that is generated as a consequence of
336        // a hardware exception (e.g., SIGSEGV, SIGBUS, SIGFPE, SIGILL), or is
337        // blocking the signal, the behavior is undefined, unless the signal was
338        // generated by kill(2), sigqueue(3), or raise(3).
339        //
340        // Linux behaviour is to unmask the signal and reset its disposition to
341        // `SIG_DFL`.
342        if siginfo.force && (masked_signals.has_signal(signal) || sigaction.sa_handler == SIG_IGN) {
343            task_state.set_signal_mask(masked_signals & !SigSet::from(signal));
344            sigaction = sigaction_t::default();
345            task.thread_group().signal_actions.set(signal, sigaction);
346        }
347
348        let action = action_for_signal(&siginfo, sigaction);
349        log_trace!("handling signal {:?} with action {:?}", siginfo, action);
350        match action {
351            DeliveryAction::Ignore => {}
352            DeliveryAction::CallHandler => {
353                // Reset the signal handler before dispatching if `SA_RESETHAND` was set, so the
354                // disposition is restored to `SIG_DFL` even if frame setup or handler entry faults.
355                if sigaction.sa_flags & (SA_RESETHAND as u64) != 0 {
356                    let new_sigaction = sigaction_t {
357                        sa_handler: SIG_DFL,
358                        sa_flags: sigaction.sa_flags & !(SA_RESETHAND as u64),
359                        ..sigaction
360                    };
361                    task.thread_group().signal_actions.set(signal, new_sigaction);
362                }
363                if let Err(err) = dispatch_signal_handler(
364                    task,
365                    arch_width,
366                    registers,
367                    extended_pstate,
368                    task_state.signals_mut(),
369                    siginfo,
370                    sigaction,
371                ) {
372                    log_warn!("failed to deliver signal {:?}: {:?}", signal, err);
373                    if signal == SIGSEGV {
374                        task.thread_group().signal_actions.set(SIGSEGV, sigaction_t::default());
375                    }
376                    siginfo = SignalInfo::forced(SIGSEGV);
377                    continue;
378                }
379            }
380            DeliveryAction::Terminate => {
381                // Release the signals lock. [`ThreadGroup::exit`] sends signals to threads which
382                // will include this one and cause a deadlock re-acquiring the signals lock.
383                drop(task_state);
384                return Some(ExitStatus::Kill(siginfo));
385            }
386            DeliveryAction::CoreDump => {
387                task_state.set_flags(TaskFlags::DUMP_ON_EXIT, true);
388                drop(task_state);
389                if let Some(exception) = restricted_exception {
390                    log_info!(
391                        registers:?=registers,
392                        exception:?=exception;
393                        // LINT.IfChange(restricted_mode_core_dump_tefmo)
394                        "Restricted mode exception caused core dump",
395                        // LINT.ThenChange(//tools/testing/tefmocheck/string_in_log_check.go:restricted_mode_core_dump_tefmo)
396                    );
397                    if let SignalDetail::SigFault { addr } = siginfo.detail {
398                        if let Ok(mm) = task.mm() {
399                            mm.log_memory_map(task, UserAddress::from(addr));
400                        }
401                    }
402                }
403                return Some(ExitStatus::CoreDump(siginfo));
404            }
405            DeliveryAction::Stop => {
406                drop(task_state);
407                task.thread_group().set_stopped(StopState::GroupStopping, Some(siginfo), false);
408            }
409            DeliveryAction::Continue => {
410                // Nothing to do. Effect already happened when the signal was raised.
411            }
412        };
413        break;
414    }
415    None
416}
417
418/// Delivers a synchronous kernel signal ([`SignalInfo`]) immediately, unmasking it and resetting
419/// its disposition to [`SIG_DFL`] if it is currently blocked or [`SIG_IGN`].
420///
421/// If `restricted_exception` is [`Some`], the [`zx::ExceptionReport`] and task memory map are
422/// logged when the signal results in a core dump.
423pub fn force_signal(
424    current_task: &mut CurrentTask,
425    mut siginfo: SignalInfo,
426    restricted_exception: Option<zx::ExceptionReport>,
427) {
428    siginfo.force = true;
429    let mut task_state = current_task.task.write();
430    if task_state.ptrace_on_signal_consume() && siginfo.signal != SIGKILL {
431        task_state.set_stopped(
432            StopState::SignalDeliveryStopping,
433            Some(siginfo),
434            Some(current_task),
435            /* event = */ None,
436        );
437        return;
438    }
439
440    if let Some(status) = deliver_signal(
441        &current_task.task,
442        current_task.thread_state.arch_width(),
443        task_state,
444        siginfo,
445        &mut current_task.thread_state.registers,
446        &current_task.thread_state.extended_pstate,
447        restricted_exception,
448    ) {
449        current_task.kill_thread_group(status);
450    }
451}
452
453/// Prepares `current` state to execute the signal handler stored in `action`.
454///
455/// This function stores the state required to restore after the signal handler on the stack.
456fn dispatch_signal_handler(
457    task: &Task,
458    arch_width: ArchWidth,
459    registers: &mut RegisterState<RegisterStorageEnum>,
460    extended_pstate: &ArchExtendedPstateStorage,
461    signal_state: &mut SignalState,
462    siginfo: SignalInfo,
463    action: sigaction_t,
464) -> Result<(), Errno> {
465    let stack_pointer_register = registers.stack_pointer_register();
466    let on_sigaltstack = signal_state.alt_stack.is_some_and(|sigaltstack| {
467        sigaltstack_contains_pointer(&sigaltstack, stack_pointer_register)
468    });
469    let main_stack = stack_pointer_register.checked_sub(RED_ZONE_SIZE);
470    let stack_bottom = if (action.sa_flags & SA_ONSTACK as u64) != 0 {
471        match signal_state.alt_stack {
472            Some(sigaltstack) => {
473                // Only install the sigaltstack if the stack pointer is not already in it.
474                if on_sigaltstack {
475                    main_stack
476                } else {
477                    // Since the stack grows down, the size is added to the ss_sp when
478                    // calculating the "bottom" of the stack.
479                    // Use the main stack if sigaltstack overflows.
480                    sigaltstack
481                        .ss_sp
482                        .addr
483                        .checked_add(sigaltstack.ss_size)
484                        .map(|sp| sp as u64)
485                        .or(main_stack)
486                }
487            }
488            None => main_stack,
489        }
490    } else {
491        main_stack
492    }
493    .ok_or_else(|| errno!(EINVAL))?;
494
495    let stack_pointer =
496        align_stack_pointer(stack_bottom.checked_sub(SIG_STACK_SIZE as u64).ok_or_else(|| {
497            errno!(
498                EINVAL,
499                format!(
500                    "Subtracting SIG_STACK_SIZE ({}) from stack bottom ({}) overflowed",
501                    SIG_STACK_SIZE, stack_bottom
502                )
503            )
504        })?);
505
506    // Check that if the stack pointer is inside altstack, the entire signal stack is inside
507    // altstack.
508    if let Some(alt_stack) = signal_state.alt_stack {
509        if sigaltstack_contains_pointer(&alt_stack, stack_pointer)
510            != (on_sigaltstack || sigaltstack_contains_pointer(&alt_stack, stack_bottom))
511        {
512            return error!(EINVAL);
513        }
514    }
515
516    let signal_stack_frame = SignalStackFrame::new(
517        task,
518        arch_width,
519        registers,
520        extended_pstate,
521        signal_state,
522        &siginfo,
523        action,
524        UserAddress::from(stack_pointer),
525    )?;
526
527    // Write the signal stack frame at the updated stack pointer.
528    task.write_memory(UserAddress::from(stack_pointer), signal_stack_frame.as_bytes())?;
529
530    let mut mask: SigSet = action.sa_mask.into();
531    if action.sa_flags & (SA_NODEFER as u64) == 0 {
532        mask = mask | siginfo.signal.into();
533    }
534
535    // Preserve the existing mask when handling a nested signal
536    signal_state.set_mask(mask | signal_state.mask());
537
538    registers.set_stack_pointer_register(stack_pointer);
539    registers.set_arg0_register(siginfo.signal.number() as u64);
540    if (action.sa_flags & SA_SIGINFO as u64) != 0 {
541        registers.set_arg1_register(
542            stack_pointer + memoffset::offset_of!(SignalStackFrame, siginfo_bytes) as u64,
543        );
544        registers.set_arg2_register(
545            stack_pointer + memoffset::offset_of!(SignalStackFrame, context) as u64,
546        );
547    }
548    registers.set_instruction_pointer_register(action.sa_handler.addr);
549    registers.reset_flags(); // TODO(https://fxbug.dev/413070731): Verify and update the logic in resetting the flags.
550
551    Ok(())
552}
553
554pub fn restore_from_signal_handler(current_task: &mut CurrentTask) -> Result<(), Errno> {
555    // Read the signal stack frame from memory.
556    let signal_frame_address = UserAddress::from(align_stack_pointer(
557        current_task.thread_state.registers.stack_pointer_register(),
558    ));
559    let signal_stack_bytes =
560        current_task.read_memory_to_array::<SIG_STACK_SIZE>(signal_frame_address)?;
561
562    // Grab the registers state from the stack frame.
563    let signal_stack_frame = SignalStackFrame::from_bytes(signal_stack_bytes);
564    restore_registers(current_task, &signal_stack_frame, signal_frame_address)?;
565
566    // Restore the stored signal mask.
567    current_task
568        .write()
569        .set_signal_mask(signal_stack_frame.get_signal_mask(current_task.is_arch32()));
570
571    Ok(())
572}
573
574/// Maybe adjust a task's registers to restart a syscall once the task switches back to userspace,
575/// based on whether the task previously had a syscall return with an error code indicating that a
576/// restart was required.
577pub fn prepare_to_restart_syscall(
578    thread_state: &mut ThreadState<RegisterStorageEnum>,
579    sigaction: Option<sigaction_t>,
580) {
581    // Taking the value ensures each syscall is only considered for restart once.
582    let Some(err) = thread_state.restart_code.take() else {
583        // Don't interact with register state at all unless other kernel code indicates that we may
584        // need to restart.
585        return;
586    };
587
588    if err.should_restart(sigaction) {
589        // This error code is returned for system calls that need restart_syscall() to adjust time
590        // related arguments when the syscall is restarted. Other syscall restarts can be dispatched
591        // directly to the original syscall implementation.
592        if err == ERESTART_RESTARTBLOCK {
593            thread_state.registers.prepare_for_custom_restart();
594        } else {
595            thread_state.registers.restore_original_return_register();
596        }
597
598        // TODO(https://fxbug.dev/388051291) figure out whether Linux relies on registers here
599        thread_state.registers.rewind_syscall_instruction();
600    } else {
601        thread_state.registers.set_return_register(EINTR.return_value());
602    }
603}
604
605pub fn sys_restart_syscall(current_task: &mut CurrentTask) -> Result<SyscallResult, Errno> {
606    match current_task.thread_state.syscall_restart_func.take() {
607        Some(f) => f(current_task),
608        None => {
609            // This may indicate a bug where a syscall returns ERESTART_RESTARTBLOCK without
610            // setting a restart func. But it can also be triggered by userspace, e.g. by directly
611            // calling restart_syscall or injecting an ERESTART_RESTARTBLOCK error through ptrace.
612            log_warn!("restart_syscall called, but nothing to restart");
613            error!(EINTR)
614        }
615    }
616}
617
618/// Test utilities for signal handling.
619#[cfg(test)]
620pub(crate) mod testing {
621    use super::*;
622    use crate::testing::AutoReleasableTask;
623    use std::ops::DerefMut as _;
624
625    pub(crate) fn dequeue_signal_for_test(current_task: &mut AutoReleasableTask) {
626        dequeue_signal(current_task.deref_mut());
627    }
628}