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        // This is an arbitrary limit that should be large enough.
56        const MAX_REBOOT_ARG_LEN: usize = 256;
57        current_task
58            .read_c_string_to_vec(UserCString::new(current_task, arg), MAX_REBOOT_ARG_LEN)?
59    } else {
60        FsString::default()
61    };
62
63    if current_task.kernel().is_shutting_down() {
64        log_debug!("Ignoring reboot() and parking caller, already shutting down.");
65        let event = InterruptibleEvent::new();
66        return current_task.block_until(event.begin_wait(), zx::MonotonicInstant::INFINITE);
67    }
68
69    let proxy = connect_to_protocol_sync::<fpower::AdminMarker>().or_else(|_| error!(EINVAL))?;
70
71    match cmd {
72        // CAD on/off commands turn Ctrl-Alt-Del keystroke on or off without halting the system.
73        LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(()),
74
75        // `kexec_load()` is not supported.
76        LINUX_REBOOT_CMD_KEXEC => error!(EINVAL),
77
78        // Suspend is not implemented.
79        LINUX_REBOOT_CMD_SW_SUSPEND => error!(EINVAL),
80
81        LINUX_REBOOT_CMD_HALT | LINUX_REBOOT_CMD_POWER_OFF => {
82            log_info!("Powering off");
83            let options = fpower::ShutdownOptions {
84                action: Some(fpower::ShutdownAction::Poweroff),
85                reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
86                ..Default::default()
87            };
88
89            match proxy.shutdown(&options, zx::MonotonicInstant::INFINITE) {
90                Ok(_) => {
91                    // System is rebooting... wait until runtime ends.
92                    zx::MonotonicInstant::INFINITE.sleep();
93                }
94                Err(e) => {
95                    return panic_or_error(
96                        current_task.kernel(),
97                        errno!(EINVAL, format!("Failed to power off, status: {e}")),
98                    );
99                }
100            }
101            Ok(())
102        }
103
104        LINUX_REBOOT_CMD_RESTART | LINUX_REBOOT_CMD_RESTART2 => {
105            let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect();
106
107            let reboot_result = if reboot_args.contains(&&b"bootloader"[..]) {
108                log_info!("Rebooting to bootloader");
109                let options = fpower::ShutdownOptions {
110                    action: Some(fpower::ShutdownAction::RebootToBootloader),
111                    reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
112                    ..Default::default()
113                };
114                proxy.shutdown(&options, zx::MonotonicInstant::INFINITE)
115            } else if reboot_args.contains(&&b"recovery"[..]) {
116                log_info!("Rebooting to recovery...");
117                let options = fpower::ShutdownOptions {
118                    action: Some(fpower::ShutdownAction::RebootToRecovery),
119                    reasons: Some(vec![fpower::ShutdownReason::StarnixContainerNoReason]),
120                    ..Default::default()
121                };
122                proxy.shutdown(&options, zx::MonotonicInstant::INFINITE)
123            } else {
124                // TODO(https://391585107): Loop through all the arguments and
125                // generate a list of shutdown reasons.
126
127                let shutdown_reason =
128                    if let Some(arg) = reboot_args.iter().find(|arg| arg.ends_with(b"-failed")) {
129                        let process_name =
130                            String::from_utf8_lossy(arg.strip_suffix(b"-failed").unwrap());
131                        // This log message is load-bearing server-side as it's used to
132                        // extract the critical process responsible for the reboot.
133                        // Please notify //src/developer/forensics/OWNERS upon changing.
134                        log_info!("Android critical process '{}' failed, rebooting", process_name);
135                        fpower::ShutdownReason::AndroidCriticalProcessFailure
136                    } else if reboot_args.contains(&&b"ota_update"[..])
137                        || reboot_args.contains(&&b"System update during setup"[..])
138                    {
139                        fpower::ShutdownReason::SystemUpdate
140                    } else if reboot_args.contains(&&b"shell"[..]) {
141                        fpower::ShutdownReason::DeveloperRequest
142                    } else if reboot_args.contains(&&b"RescueParty"[..])
143                        || reboot_args.contains(&&b"rescueparty"[..])
144                    {
145                        fpower::ShutdownReason::AndroidRescueParty
146                    } else if reboot_args.contains(&&b"userrequested"[..]) {
147                        fpower::ShutdownReason::UserRequest
148                    } else if reboot_args == [b""]
149                    // args empty? splitting "" returns [""], not []
150                    {
151                        fpower::ShutdownReason::StarnixContainerNoReason
152                    } else {
153                        log_warn!("Unknown reboot args: {arg_bytes:?}");
154                        track_stub!(
155                            TODO("https://fxbug.dev/322874610"),
156                            "unknown reboot args, see logs for strings"
157                        );
158                        fpower::ShutdownReason::AndroidUnexpectedReason
159                    };
160
161                log_info!("Rebooting... reason: {:?}", shutdown_reason);
162                proxy.shutdown(
163                    &fpower::ShutdownOptions {
164                        action: Some(fpower::ShutdownAction::Reboot),
165                        reasons: Some(vec![shutdown_reason]),
166                        ..Default::default()
167                    },
168                    zx::MonotonicInstant::INFINITE,
169                )
170            };
171
172            match reboot_result {
173                Ok(Ok(())) => {
174                    // System is rebooting... wait until runtime ends.
175                    zx::MonotonicInstant::INFINITE.sleep();
176                }
177                Ok(Err(e)) => {
178                    return panic_or_error(
179                        current_task.kernel(),
180                        errno!(
181                            EINVAL,
182                            format!("Failed to reboot, status: {}", zx::Status::from_raw(e))
183                        ),
184                    );
185                }
186                Err(e) => {
187                    return panic_or_error(
188                        current_task.kernel(),
189                        errno!(EINVAL, format!("Failed to reboot, FIDL error: {e}")),
190                    );
191                }
192            }
193            Ok(())
194        }
195
196        _ => error!(EINVAL),
197    }
198}
199
200#[cfg(target_arch = "aarch64")]
201mod arch32 {
202    pub use super::sys_reboot as sys_arch32_reboot;
203}
204
205#[cfg(target_arch = "aarch64")]
206pub use arch32::*;