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 fuchsia_component::client::connect_to_protocol_sync;
7use linux_uapi::{
8    LINUX_REBOOT_CMD_CAD_OFF, LINUX_REBOOT_CMD_CAD_ON, LINUX_REBOOT_CMD_HALT,
9    LINUX_REBOOT_CMD_KEXEC, LINUX_REBOOT_CMD_POWER_OFF, LINUX_REBOOT_CMD_RESTART,
10    LINUX_REBOOT_CMD_RESTART2, LINUX_REBOOT_CMD_SW_SUSPEND,
11};
12use starnix_logging::{log_debug, log_info, log_warn, track_stub};
13use starnix_sync::{InterruptibleEvent, Locked, Unlocked};
14use starnix_uapi::auth::CAP_SYS_BOOT;
15use starnix_uapi::errors::Errno;
16use starnix_uapi::user_address::{UserAddress, UserCString};
17use starnix_uapi::{
18    LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_MAGIC2A, LINUX_REBOOT_MAGIC2B,
19    LINUX_REBOOT_MAGIC2C, errno, error,
20};
21use {fidl_fuchsia_hardware_power_statecontrol as fpower, fidl_fuchsia_recovery as frecovery};
22
23use crate::device::android::bootloader_message_store::{
24    AndroidBootloaderMessageStore, BootloaderMessage,
25};
26use crate::mm::MemoryAccessorExt;
27use crate::security;
28use crate::task::{CurrentTask, Kernel};
29use crate::vfs::FsString;
30
31#[track_caller]
32fn panic_or_error(kernel: &Kernel, errno: Errno) -> Result<(), Errno> {
33    if kernel.features.error_on_failed_reboot {
34        return Err(errno);
35    }
36    panic!("Fatal: {errno:?}");
37}
38
39pub fn sys_reboot(
40    _locked: &mut Locked<Unlocked>,
41    current_task: &CurrentTask,
42    magic: u32,
43    magic2: u32,
44    cmd: u32,
45    arg: UserAddress,
46) -> Result<(), Errno> {
47    if magic != LINUX_REBOOT_MAGIC1
48        || (magic2 != LINUX_REBOOT_MAGIC2
49            && magic2 != LINUX_REBOOT_MAGIC2A
50            && magic2 != LINUX_REBOOT_MAGIC2B
51            && magic2 != LINUX_REBOOT_MAGIC2C)
52    {
53        return error!(EINVAL);
54    }
55    security::check_task_capable(current_task, CAP_SYS_BOOT)?;
56
57    let arg_bytes = if matches!(cmd, LINUX_REBOOT_CMD_RESTART2) {
58        // This is an arbitrary limit that should be large enough.
59        const MAX_REBOOT_ARG_LEN: usize = 256;
60        current_task
61            .read_c_string_to_vec(UserCString::new(current_task, arg), MAX_REBOOT_ARG_LEN)?
62    } else {
63        FsString::default()
64    };
65
66    if current_task.kernel().is_shutting_down() {
67        log_debug!("Ignoring reboot() and parking caller, already shutting down.");
68        let event = InterruptibleEvent::new();
69        return current_task.block_until(event.begin_wait(), zx::MonotonicInstant::INFINITE);
70    }
71
72    let proxy = connect_to_protocol_sync::<fpower::AdminMarker>().or_else(|_| error!(EINVAL))?;
73
74    match cmd {
75        // CAD on/off commands turn Ctrl-Alt-Del keystroke on or off without halting the system.
76        LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(()),
77
78        // `kexec_load()` is not supported.
79        LINUX_REBOOT_CMD_KEXEC => error!(EINVAL),
80
81        // Suspend is not implemented.
82        LINUX_REBOOT_CMD_SW_SUSPEND => error!(EINVAL),
83
84        LINUX_REBOOT_CMD_HALT | LINUX_REBOOT_CMD_POWER_OFF => {
85            log_info!("Powering off");
86            let options = fpower::ShutdownOptions {
87                action: Some(fpower::ShutdownAction::Poweroff),
88                ..Default::default()
89            };
90
91            match proxy.shutdown(&options, zx::MonotonicInstant::INFINITE) {
92                Ok(_) => {
93                    // System is rebooting... wait until runtime ends.
94                    zx::MonotonicInstant::INFINITE.sleep();
95                }
96                Err(e) => {
97                    return panic_or_error(
98                        current_task.kernel(),
99                        errno!(EINVAL, format!("Failed to power off, status: {e}")),
100                    );
101                }
102            }
103            Ok(())
104        }
105
106        LINUX_REBOOT_CMD_RESTART | LINUX_REBOOT_CMD_RESTART2 => {
107            let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect();
108
109            let reboot_result = if reboot_args.contains(&&b"bootloader"[..]) {
110                log_info!("Rebooting to bootloader");
111                let options = fpower::ShutdownOptions {
112                    action: Some(fpower::ShutdownAction::RebootToBootloader),
113                    ..Default::default()
114                };
115                proxy.shutdown(&options, zx::MonotonicInstant::INFINITE)
116            } else if reboot_args.contains(&&b"recovery"[..]) {
117                // Read the bootloader message from the misc partition to determine whether the
118                // device is rebooting to perform an FDR.
119                if let Some(store) =
120                    current_task.kernel().expando.peek::<AndroidBootloaderMessageStore>()
121                {
122                    match store.read_bootloader_message() {
123                        Ok(BootloaderMessage::BootRecovery(args)) => {
124                            if args.iter().any(|arg| arg == "--wipe_data") {
125                                let factory_reset_proxy =
126                                    connect_to_protocol_sync::<frecovery::FactoryResetMarker>()
127                                        .or_else(|_| error!(EINVAL))?;
128                                // NB: This performs a reboot for us.
129                                log_info!("Initiating factory data reset...");
130                                match factory_reset_proxy.reset(zx::MonotonicInstant::INFINITE) {
131                                    Ok(_) => {
132                                        // System is rebooting... wait until runtime ends.
133                                        zx::MonotonicInstant::INFINITE.sleep();
134                                    }
135                                    Err(e) => {
136                                        return panic_or_error(
137                                            current_task.kernel(),
138                                            errno!(
139                                                EINVAL,
140                                                format!("Failed to reboot for FDR, status: {e}")
141                                            ),
142                                        );
143                                    }
144                                }
145                            }
146                        }
147                        // In all other cases, fall through to a regular reboot.
148                        Ok(_) => log_info!("Boot message not recognized!"),
149                        Err(e) => log_warn!("Failed to read boot message: {e}"),
150                    }
151                }
152                log_info!("Rebooting to recovery...");
153                let options = fpower::ShutdownOptions {
154                    action: Some(fpower::ShutdownAction::RebootToRecovery),
155                    ..Default::default()
156                };
157                proxy.shutdown(&options, zx::MonotonicInstant::INFINITE)
158            } else {
159                // TODO(https://391585107): Loop through all the arguments and
160                // generate a list of shutdown reasons.
161
162                let shutdown_reason =
163                    if let Some(arg) = reboot_args.iter().find(|arg| arg.ends_with(b"-failed")) {
164                        let process_name =
165                            String::from_utf8_lossy(arg.strip_suffix(b"-failed").unwrap());
166                        // This log message is load-bearing server-side as it's used to
167                        // extract the critical process responsible for the reboot.
168                        // Please notify //src/developer/forensics/OWNERS upon changing.
169                        log_info!("Android critical process '{}' failed, rebooting", process_name);
170                        fpower::ShutdownReason::AndroidCriticalProcessFailure
171                    } else if reboot_args.contains(&&b"ota_update"[..])
172                        || reboot_args.contains(&&b"System update during setup"[..])
173                    {
174                        fpower::ShutdownReason::SystemUpdate
175                    } else if reboot_args.contains(&&b"shell"[..]) {
176                        fpower::ShutdownReason::DeveloperRequest
177                    } else if reboot_args.contains(&&b"RescueParty"[..])
178                        || reboot_args.contains(&&b"rescueparty"[..])
179                    {
180                        fpower::ShutdownReason::AndroidRescueParty
181                    } else if reboot_args == [b""] // args empty? splitting "" returns [""], not []
182                    || reboot_args.contains(&&b"userrequested"[..])
183                    {
184                        fpower::ShutdownReason::UserRequest
185                    } else {
186                        log_warn!("Unknown reboot args: {arg_bytes:?}");
187                        track_stub!(
188                            TODO("https://fxbug.dev/322874610"),
189                            "unknown reboot args, see logs for strings"
190                        );
191                        fpower::ShutdownReason::AndroidUnexpectedReason
192                    };
193
194                log_info!("Rebooting... reason: {:?}", shutdown_reason);
195                proxy.shutdown(
196                    &fpower::ShutdownOptions {
197                        action: Some(fpower::ShutdownAction::Reboot),
198                        reasons: Some(vec![shutdown_reason]),
199                        ..Default::default()
200                    },
201                    zx::MonotonicInstant::INFINITE,
202                )
203            };
204
205            match reboot_result {
206                Ok(Ok(())) => {
207                    // System is rebooting... wait until runtime ends.
208                    zx::MonotonicInstant::INFINITE.sleep();
209                }
210                Ok(Err(e)) => {
211                    return panic_or_error(
212                        current_task.kernel(),
213                        errno!(
214                            EINVAL,
215                            format!("Failed to reboot, status: {}", zx::Status::from_raw(e))
216                        ),
217                    );
218                }
219                Err(e) => {
220                    return panic_or_error(
221                        current_task.kernel(),
222                        errno!(EINVAL, format!("Failed to reboot, FIDL error: {e}")),
223                    );
224                }
225            }
226            Ok(())
227        }
228
229        _ => error!(EINVAL),
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::*;