starnix_modules_syscalls/
reboot.rs1use bstr::ByteSlice;
6use fidl_fuchsia_hardware_power_statecontrol as fpower;
7use fuchsia_component::client::connect_to_protocol_sync;
8use linux_uapi::{
9 LINUX_REBOOT_CMD_CAD_OFF, LINUX_REBOOT_CMD_CAD_ON, LINUX_REBOOT_CMD_HALT,
10 LINUX_REBOOT_CMD_KEXEC, LINUX_REBOOT_CMD_POWER_OFF, LINUX_REBOOT_CMD_RESTART,
11 LINUX_REBOOT_CMD_RESTART2, LINUX_REBOOT_CMD_SW_SUSPEND,
12};
13use starnix_logging::{log_debug, log_info, log_warn, track_stub};
14use starnix_sync::InterruptibleEvent;
15use starnix_uapi::auth::CAP_SYS_BOOT;
16use starnix_uapi::errors::Errno;
17use starnix_uapi::user_address::{UserAddress, UserCString};
18use starnix_uapi::{
19 LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_MAGIC2A, LINUX_REBOOT_MAGIC2B,
20 LINUX_REBOOT_MAGIC2C, errno, error,
21};
22
23use starnix_core::mm::MemoryAccessorExt;
24use starnix_core::security;
25use starnix_core::task::{CurrentTask, Kernel};
26use starnix_core::vfs::FsString;
27
28#[track_caller]
29fn panic_or_error(kernel: &Kernel, errno: Errno) -> Result<(), Errno> {
30 if kernel.features.error_on_failed_reboot {
31 return Err(errno);
32 }
33 panic!("Fatal: {errno:?}");
34}
35
36pub fn sys_reboot(
37 current_task: &CurrentTask,
38 magic: u32,
39 magic2: u32,
40 cmd: u32,
41 arg: UserAddress,
42) -> Result<(), Errno> {
43 if magic != LINUX_REBOOT_MAGIC1
44 || (magic2 != LINUX_REBOOT_MAGIC2
45 && magic2 != LINUX_REBOOT_MAGIC2A
46 && magic2 != LINUX_REBOOT_MAGIC2B
47 && magic2 != LINUX_REBOOT_MAGIC2C)
48 {
49 return error!(EINVAL);
50 }
51 security::check_task_capable(current_task, CAP_SYS_BOOT)?;
52
53 let arg_bytes = if matches!(cmd, LINUX_REBOOT_CMD_RESTART2)
54 || (matches!(cmd, LINUX_REBOOT_CMD_POWER_OFF) && !arg.is_null())
55 {
56 const MAX_REBOOT_ARG_LEN: usize = 256;
58 current_task
59 .read_c_string_to_vec(UserCString::new(current_task, arg), MAX_REBOOT_ARG_LEN)?
60 } else {
61 FsString::default()
62 };
63
64 if current_task.kernel().is_shutting_down() {
65 log_debug!("Ignoring reboot() and parking caller, already shutting down.");
66 let event = InterruptibleEvent::new();
67 return current_task.block_until(event.begin_wait(), zx::MonotonicInstant::INFINITE);
68 }
69
70 let proxy = connect_to_protocol_sync::<fpower::AdminMarker>().or_else(|_| error!(EINVAL))?;
71
72 match cmd {
73 LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(()),
75
76 LINUX_REBOOT_CMD_KEXEC => error!(EINVAL),
78
79 LINUX_REBOOT_CMD_SW_SUSPEND => error!(EINVAL),
81
82 LINUX_REBOOT_CMD_HALT | LINUX_REBOOT_CMD_POWER_OFF => {
83 log_info!("Powering off");
84 let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect();
85 let shutdown_reason = parse_shutdown_reason(&reboot_args, &arg_bytes);
86 let options = fpower::ShutdownOptions {
87 action: Some(fpower::ShutdownAction::Poweroff),
88 reasons: Some(vec![shutdown_reason]),
89 ..Default::default()
90 };
91
92 match proxy.shutdown(&options, zx::MonotonicInstant::INFINITE) {
93 Ok(_) => {
94 zx::MonotonicInstant::INFINITE.sleep();
96 }
97 Err(e) => {
98 return panic_or_error(
99 current_task.kernel(),
100 errno!(EINVAL, format!("Failed to power off, status: {e}")),
101 );
102 }
103 }
104 Ok(())
105 }
106
107 LINUX_REBOOT_CMD_RESTART | LINUX_REBOOT_CMD_RESTART2 => {
108 let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect();
109
110 let reboot_result = if reboot_args.contains(&&b"bootloader"[..]) {
111 log_info!("Rebooting to bootloader");
112 let options = fpower::ShutdownOptions {
113 action: Some(fpower::ShutdownAction::RebootToBootloader),
114 reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
115 ..Default::default()
116 };
117 proxy.shutdown(&options, zx::MonotonicInstant::INFINITE)
118 } else if reboot_args.contains(&&b"recovery"[..]) {
119 log_info!("Rebooting to recovery...");
120 let options = fpower::ShutdownOptions {
121 action: Some(fpower::ShutdownAction::RebootToRecovery),
122 reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
123 ..Default::default()
124 };
125 proxy.shutdown(&options, zx::MonotonicInstant::INFINITE)
126 } else {
127 let shutdown_reason = parse_shutdown_reason(&reboot_args, &arg_bytes);
128
129 log_info!("Rebooting... reason: {:?}", shutdown_reason);
130 proxy.shutdown(
131 &fpower::ShutdownOptions {
132 action: Some(fpower::ShutdownAction::Reboot),
133 reasons: Some(vec![shutdown_reason]),
134 ..Default::default()
135 },
136 zx::MonotonicInstant::INFINITE,
137 )
138 };
139
140 match reboot_result {
141 Ok(Ok(())) => {
142 zx::MonotonicInstant::INFINITE.sleep();
144 }
145 Ok(Err(e)) => {
146 return panic_or_error(
147 current_task.kernel(),
148 errno!(
149 EINVAL,
150 format!("Failed to reboot, status: {}", zx::Status::from_raw(e))
151 ),
152 );
153 }
154 Err(e) => {
155 return panic_or_error(
156 current_task.kernel(),
157 errno!(EINVAL, format!("Failed to reboot, FIDL error: {e}")),
158 );
159 }
160 }
161 Ok(())
162 }
163
164 _ => error!(EINVAL),
165 }
166}
167
168fn parse_shutdown_reason(reboot_args: &[&[u8]], arg_bytes: &FsString) -> fpower::ShutdownReason {
169 if let Some(arg) = reboot_args.iter().find(|arg| arg.ends_with(b"-failed")) {
172 let process_name = String::from_utf8_lossy(arg.strip_suffix(b"-failed").unwrap());
173 log_info!("Android critical process '{}' failed, rebooting", process_name);
177 fpower::ShutdownReason::AndroidCriticalProcessFailure
178 } else if reboot_args.contains(&&b"ota_update"[..])
179 || reboot_args.contains(&&b"System update during setup"[..])
180 {
181 fpower::ShutdownReason::SystemUpdate
182 } else if reboot_args.contains(&&b"shell"[..]) {
183 fpower::ShutdownReason::DeveloperRequest
184 } else if reboot_args.contains(&&b"RescueParty"[..])
185 || reboot_args.contains(&&b"rescueparty"[..])
186 {
187 fpower::ShutdownReason::AndroidRescueParty
188 } else if reboot_args.contains(&&b"userrequested"[..]) {
189 fpower::ShutdownReason::UserRequest
190 } else if reboot_args.contains(&&b"thermal"[..]) {
191 fpower::ShutdownReason::HighTemperature
192 } else if reboot_args.contains(&&b"battery"[..]) {
193 fpower::ShutdownReason::BatteryDrained
194 } else if reboot_args == [b""]
195 {
197 fpower::ShutdownReason::StarnixContainerNoReason
198 } else {
199 log_warn!("Unknown reboot args: {arg_bytes:?}");
200 track_stub!(
201 TODO("https://fxbug.dev/322874610"),
202 "unknown reboot args, see logs for strings"
203 );
204 fpower::ShutdownReason::AndroidUnexpectedReason
205 }
206}
207
208#[cfg(target_arch = "aarch64")]
209mod arch32 {
210 pub use super::sys_reboot as sys_arch32_reboot;
211}
212
213#[cfg(target_arch = "aarch64")]
214pub use arch32::*;
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn check_parse_shutdown_reason(args: &str, expected: fpower::ShutdownReason) {
221 let bytes = FsString::from(args.as_bytes().to_vec());
222 let split_args: Vec<_> = bytes.split_str(b",").collect();
223 let reason = super::parse_shutdown_reason(&split_args, &bytes);
224 assert_eq!(reason, expected, "Failed for args: {}", args);
225 }
226
227 #[test]
228 fn parse_shutdown_reason_thermal() {
229 check_parse_shutdown_reason("shutdown,thermal", fpower::ShutdownReason::HighTemperature);
230 }
231
232 #[test]
233 fn parse_shutdown_reason_battery() {
234 check_parse_shutdown_reason("shutdown,battery", fpower::ShutdownReason::BatteryDrained);
235 }
236
237 #[test]
238 fn parse_shutdown_reason_thermal_and_battery() {
239 check_parse_shutdown_reason(
240 "shutdown,thermal,battery",
241 fpower::ShutdownReason::HighTemperature,
242 );
243 }
244}