starnix_modules_syscalls/
reboot.rs1use std::sync::Arc;
6
7use bstr::ByteSlice;
8use fidl_fuchsia_hardware_power_statecontrol as fpower;
9use fuchsia_component::client::connect_to_protocol;
10use linux_uapi::{
11 LINUX_REBOOT_CMD_CAD_OFF, LINUX_REBOOT_CMD_CAD_ON, LINUX_REBOOT_CMD_HALT,
12 LINUX_REBOOT_CMD_KEXEC, LINUX_REBOOT_CMD_POWER_OFF, LINUX_REBOOT_CMD_RESTART,
13 LINUX_REBOOT_CMD_RESTART2, LINUX_REBOOT_CMD_SW_SUSPEND,
14};
15use starnix_core::mm::MemoryAccessorExt;
16use starnix_core::security;
17use starnix_core::task::{CurrentTask, ExitStatus};
18use starnix_core::vfs::FsString;
19use starnix_logging::{log_debug, log_error, log_info, log_warn, track_stub};
20use starnix_sync::{InterruptibleEvent, Mutex};
21use starnix_uapi::auth::CAP_SYS_BOOT;
22use starnix_uapi::errors::{EINTR, Errno};
23use starnix_uapi::signals::SigSet;
24use starnix_uapi::user_address::{UserAddress, UserCString};
25use starnix_uapi::{
26 LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_MAGIC2A, LINUX_REBOOT_MAGIC2B,
27 LINUX_REBOOT_MAGIC2C, errno, error,
28};
29
30pub fn sys_reboot(
31 current_task: &mut CurrentTask,
32 magic: u32,
33 magic2: u32,
34 cmd: u32,
35 arg: UserAddress,
36) -> Result<(), Errno> {
37 if magic != LINUX_REBOOT_MAGIC1
38 || (magic2 != LINUX_REBOOT_MAGIC2
39 && magic2 != LINUX_REBOOT_MAGIC2A
40 && magic2 != LINUX_REBOOT_MAGIC2B
41 && magic2 != LINUX_REBOOT_MAGIC2C)
42 {
43 return error!(EINVAL);
44 }
45 security::check_task_capable(current_task, CAP_SYS_BOOT)?;
46
47 let arg_bytes = if matches!(cmd, LINUX_REBOOT_CMD_RESTART2)
48 || (matches!(cmd, LINUX_REBOOT_CMD_POWER_OFF) && !arg.is_null())
49 {
50 const MAX_REBOOT_ARG_LEN: usize = 256;
52 current_task
53 .read_c_string_to_vec(UserCString::new(current_task, arg), MAX_REBOOT_ARG_LEN)?
54 } else {
55 FsString::default()
56 };
57
58 if current_task.kernel().is_shutting_down() {
59 log_debug!("Ignoring reboot() and parking caller, already shutting down.");
60 let event = InterruptibleEvent::new();
61 return current_task.block_until(event.begin_wait(), zx::MonotonicInstant::INFINITE);
62 }
63
64 match cmd {
65 LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(()),
67
68 LINUX_REBOOT_CMD_KEXEC => error!(EINVAL),
70
71 LINUX_REBOOT_CMD_SW_SUSPEND => error!(EINVAL),
73
74 LINUX_REBOOT_CMD_HALT | LINUX_REBOOT_CMD_POWER_OFF => {
75 log_info!("Powering off");
76 let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect();
77 let shutdown_reason = parse_shutdown_reason(&reboot_args, &arg_bytes);
78 let options = fpower::ShutdownOptions {
79 action: Some(fpower::ShutdownAction::Poweroff),
80 reasons: Some(vec![shutdown_reason]),
81 ..Default::default()
82 };
83 shutdown_and_block(current_task, options, "sys_reboot_poweroff")
84 }
85
86 LINUX_REBOOT_CMD_RESTART | LINUX_REBOOT_CMD_RESTART2 => {
87 let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect();
88
89 let options = if reboot_args.contains(&&b"bootloader"[..]) {
90 log_info!("Rebooting to bootloader");
91 fpower::ShutdownOptions {
92 action: Some(fpower::ShutdownAction::RebootToBootloader),
93 reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
94 ..Default::default()
95 }
96 } else if reboot_args.contains(&&b"recovery"[..]) {
97 log_info!("Rebooting to recovery...");
98 fpower::ShutdownOptions {
99 action: Some(fpower::ShutdownAction::RebootToRecovery),
100 reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
101 ..Default::default()
102 }
103 } else {
104 let shutdown_reason = parse_shutdown_reason(&reboot_args, &arg_bytes);
105 log_info!("Rebooting... reason: {:?}", shutdown_reason);
106 fpower::ShutdownOptions {
107 action: Some(fpower::ShutdownAction::Reboot),
108 reasons: Some(vec![shutdown_reason]),
109 ..Default::default()
110 }
111 };
112 shutdown_and_block(current_task, options, "sys_reboot_reboot")
113 }
114
115 _ => error!(EINVAL),
116 }
117}
118
119fn shutdown_and_block(
120 current_task: &mut CurrentTask,
121 options: fpower::ShutdownOptions,
122 debug_name: &'static str,
123) -> Result<(), Errno> {
124 let event = InterruptibleEvent::new();
125 let event_clone = event.clone();
126 let error_state = Arc::new(Mutex::new(None));
127 let error_state_clone = error_state.clone();
128
129 let kernel = current_task.kernel().clone();
130 kernel.kthreads.spawn_future(
131 move || async move {
132 match connect_to_protocol::<fpower::AdminMarker>() {
133 Ok(proxy) => match proxy.shutdown(&options).await {
134 Ok(Ok(())) => {}
135 Ok(Err(status)) => {
136 log_error!(
137 "sys_reboot: proxy.shutdown async ({debug_name}) returned error: {}",
138 zx::Status::err_from_raw(status)
139 );
140 error_state_clone.lock().replace(errno!(EIO));
141 event_clone.notify();
142 }
143 Err(e) => {
144 log_error!("sys_reboot: proxy.shutdown async ({debug_name}) failed: {e:?}");
145 error_state_clone.lock().replace(errno!(EIO));
146 event_clone.notify();
147 }
148 },
149 Err(e) => {
150 log_error!(
151 "sys_reboot: failed to connect to Admin async ({debug_name}): {e:?}"
152 );
153 error_state_clone.lock().replace(errno!(ENOTSUP));
154 event_clone.notify();
155 }
156 }
157 },
158 debug_name,
159 );
160
161 let result = current_task.wait_with_temporary_mask(!SigSet::default(), |current_task| {
166 current_task.block_until(event.begin_wait(), zx::MonotonicInstant::INFINITE)
167 });
168
169 if let Some(err) = error_state.lock().take() {
170 return Err(err);
171 }
172
173 if let Err(err) = result {
174 if err.code == EINTR {
175 current_task.thread_group().kill(ExitStatus::Exit(0), None);
184 return Ok(());
185 }
186
187 return Err(err);
188 }
189
190 Ok(())
191}
192
193fn parse_shutdown_reason(reboot_args: &[&[u8]], arg_bytes: &FsString) -> fpower::ShutdownReason {
194 if let Some(arg) = reboot_args.iter().find(|arg| arg.ends_with(b"-failed")) {
197 let process_name = String::from_utf8_lossy(arg.strip_suffix(b"-failed").unwrap());
198 log_info!("Android critical process '{}' failed, rebooting", process_name);
202 fpower::ShutdownReason::AndroidCriticalProcessFailure
203 } else if reboot_args.contains(&&b"ota_update"[..])
204 || reboot_args.contains(&&b"System update during setup"[..])
205 {
206 fpower::ShutdownReason::SystemUpdate
207 } else if reboot_args.contains(&&b"shell"[..]) {
208 fpower::ShutdownReason::DeveloperRequest
209 } else if reboot_args.contains(&&b"RescueParty"[..])
210 || reboot_args.contains(&&b"rescueparty"[..])
211 {
212 fpower::ShutdownReason::AndroidRescueParty
213 } else if reboot_args.contains(&&b"userrequested"[..]) {
214 fpower::ShutdownReason::UserRequest
215 } else if reboot_args.contains(&&b"thermal"[..]) {
216 fpower::ShutdownReason::HighTemperature
217 } else if reboot_args.contains(&&b"battery"[..]) {
218 fpower::ShutdownReason::BatteryDrained
219 } else if reboot_args == [b""]
220 {
222 fpower::ShutdownReason::StarnixContainerNoReason
223 } else {
224 log_warn!("Unknown reboot args: {arg_bytes:?}");
225 track_stub!(
226 TODO("https://fxbug.dev/322874610"),
227 "unknown reboot args, see logs for strings"
228 );
229 fpower::ShutdownReason::AndroidUnexpectedReason
230 }
231}
232
233#[cfg(target_arch = "aarch64")]
234mod arch32 {
235 pub use super::sys_reboot as sys_arch32_reboot;
236}
237
238#[cfg(target_arch = "aarch64")]
239pub use arch32::*;
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 fn check_parse_shutdown_reason(args: &str, expected: fpower::ShutdownReason) {
246 let bytes = FsString::from(args.as_bytes().to_vec());
247 let split_args: Vec<_> = bytes.split_str(b",").collect();
248 let reason = super::parse_shutdown_reason(&split_args, &bytes);
249 assert_eq!(reason, expected, "Failed for args: {}", args);
250 }
251
252 #[test]
253 fn parse_shutdown_reason_thermal() {
254 check_parse_shutdown_reason("shutdown,thermal", fpower::ShutdownReason::HighTemperature);
255 }
256
257 #[test]
258 fn parse_shutdown_reason_battery() {
259 check_parse_shutdown_reason("shutdown,battery", fpower::ShutdownReason::BatteryDrained);
260 }
261
262 #[test]
263 fn parse_shutdown_reason_thermal_and_battery() {
264 check_parse_shutdown_reason(
265 "shutdown,thermal,battery",
266 fpower::ShutdownReason::HighTemperature,
267 );
268 }
269}