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