Skip to main content

starnix_core/syscalls/
reboot.rs

1// Copyright 2025 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 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        // This is an arbitrary limit that should be large enough.
58        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        // CAD on/off commands turn Ctrl-Alt-Del keystroke on or off without halting the system.
75        LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(()),
76
77        // `kexec_load()` is not supported.
78        LINUX_REBOOT_CMD_KEXEC => error!(EINVAL),
79
80        // Suspend is not implemented.
81        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                    // System is rebooting... wait until runtime ends.
96                    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                    // System is rebooting... wait until runtime ends.
144                    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    // TODO(https://fxbug.dev/391585107): Loop through all the arguments and
171    // generate a list of shutdown reasons.
172    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        // This log message is load-bearing server-side as it's used to
175        // extract the critical process responsible for the reboot.
176        // Please notify //src/developer/forensics/OWNERS upon changing.
177        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    // args empty? splitting "" returns [""], not []
197    {
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}