detcore/signal_restart.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! What a syscall interrupted for a caught signal gives the guest, by Linux's
10//! x86 table (A5 design 16.6 and D40, D43).
11//!
12//! A wait that Detcore ends because a caught signal is pending ends with one
13//! of the kernel's private restart codes. On ptrace the kernel applies the
14//! table itself when it builds the handler's frame. Where Detcore ends the
15//! wait and the kernel never sees the code (an in-guest backend under its
16//! delivery gate, `Config::in_guest_signal_delivery`), Detcore applies the
17//! same table, with the action of the first caught delivery at that return,
18//! and the backend only writes the registers. Ignored instances ahead of it
19//! are discarded without touching the registers, and a later frame at the
20//! same return sees the result the first one left (`-EINTR`, or the call's
21//! number to run again), which is no longer a restart code.
22
23/// The kernel's private restart codes, as negative results
24/// (include/linux/errno.h v6.12 lines 14-18,
25/// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/errno.h?h=v6.12#n14>).
26pub const ERESTARTSYS: i64 = -512;
27/// See [`ERESTARTSYS`].
28pub const ERESTARTNOINTR: i64 = -513;
29/// See [`ERESTARTSYS`].
30pub const ERESTARTNOHAND: i64 = -514;
31/// See [`ERESTARTSYS`].
32pub const ERESTART_RESTARTBLOCK: i64 = -516;
33
34/// What the guest's syscall does once the caught signal's frame is built.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum CaughtSignalReturn {
37 /// The call returns this raw result to the guest: `-EINTR` in place of a
38 /// restart code, or the call's own result, which is not one.
39 Returns(i64),
40 /// The call runs again once the handler returns: the result register
41 /// holds the syscall's number again and the instruction pointer moves
42 /// back over the `syscall` instruction, so the guest executes it again.
43 Restarts,
44}
45
46/// The syscall's outcome when a caught signal whose action has `sa_flags` is
47/// delivered at its return with raw result `result` (an errno as its
48/// negative): Linux's `handle_signal`, arch/x86/kernel/signal.c v6.12 lines
49/// 264-282,
50/// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/x86/kernel/signal.c?h=v6.12#n264>:
51/// - `-ERESTART_RESTARTBLOCK` and `-ERESTARTNOHAND` return `-EINTR`;
52/// - `-ERESTARTSYS` returns `-EINTR` without `SA_RESTART`, and restarts
53/// with it;
54/// - `-ERESTARTNOINTR` restarts, whatever the flags;
55/// - any other result is returned as it is.
56///
57/// Only for the first caught delivery at the return. With no handler at all,
58/// the kernel restarts instead (lines 343-357): `-ERESTARTNOHAND`,
59/// `-ERESTARTSYS` and `-ERESTARTNOINTR` run the same call again, and
60/// `-ERESTART_RESTARTBLOCK` runs `restart_syscall` (lines 353-356), not the
61/// same call. Neither that case nor a default-fatal delivery, which ends the
62/// process, comes here.
63pub fn caught_signal_return(result: i64, sa_flags: u64) -> CaughtSignalReturn {
64 const EINTR: i64 = -(libc::EINTR as i64);
65 let sa_restart = libc::SA_RESTART as u32 as u64;
66 match result {
67 ERESTART_RESTARTBLOCK | ERESTARTNOHAND => CaughtSignalReturn::Returns(EINTR),
68 ERESTARTSYS if sa_flags & sa_restart == 0 => CaughtSignalReturn::Returns(EINTR),
69 ERESTARTSYS | ERESTARTNOINTR => CaughtSignalReturn::Restarts,
70 other => CaughtSignalReturn::Returns(other),
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 const EINTR: i64 = -(libc::EINTR as i64);
79 const SA_RESTART: u64 = libc::SA_RESTART as u32 as u64;
80 /// Flags beside `SA_RESTART`, which change no row.
81 const OTHER_FLAGS: u64 = (libc::SA_SIGINFO | libc::SA_ONSTACK | libc::SA_NODEFER) as u64;
82
83 #[test]
84 fn erestart_restartblock_returns_eintr_with_or_without_sa_restart() {
85 for flags in [0, SA_RESTART, OTHER_FLAGS | SA_RESTART] {
86 assert_eq!(
87 caught_signal_return(ERESTART_RESTARTBLOCK, flags),
88 CaughtSignalReturn::Returns(EINTR)
89 );
90 }
91 }
92
93 #[test]
94 fn erestartnohand_returns_eintr_with_or_without_sa_restart() {
95 for flags in [0, SA_RESTART, OTHER_FLAGS | SA_RESTART] {
96 assert_eq!(
97 caught_signal_return(ERESTARTNOHAND, flags),
98 CaughtSignalReturn::Returns(EINTR)
99 );
100 }
101 }
102
103 #[test]
104 fn erestartsys_without_sa_restart_returns_eintr() {
105 for flags in [0, OTHER_FLAGS] {
106 assert_eq!(
107 caught_signal_return(ERESTARTSYS, flags),
108 CaughtSignalReturn::Returns(EINTR)
109 );
110 }
111 }
112
113 #[test]
114 fn erestartsys_with_sa_restart_restarts() {
115 for flags in [SA_RESTART, OTHER_FLAGS | SA_RESTART] {
116 assert_eq!(
117 caught_signal_return(ERESTARTSYS, flags),
118 CaughtSignalReturn::Restarts
119 );
120 }
121 }
122
123 #[test]
124 fn erestartnointr_restarts_with_or_without_sa_restart() {
125 for flags in [0, SA_RESTART, OTHER_FLAGS] {
126 assert_eq!(
127 caught_signal_return(ERESTARTNOINTR, flags),
128 CaughtSignalReturn::Restarts
129 );
130 }
131 }
132
133 /// Results that are not restart codes, including the neighbours of the
134 /// codes (515 is `ENOIOCTLCMD`, 517 `EPROBE_DEFER`, both kernel-private
135 /// too) and `EINTR` itself, are returned as they are.
136 #[test]
137 fn any_other_result_is_returned_unchanged() {
138 for result in [0, 1, 4096, EINTR, -(libc::EAGAIN as i64), -511, -515, -517] {
139 for flags in [0, SA_RESTART] {
140 assert_eq!(
141 caught_signal_return(result, flags),
142 CaughtSignalReturn::Returns(result)
143 );
144 }
145 }
146 }
147}