Skip to main content

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