| // Copyright 2025 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use bstr::ByteSlice; |
| use fuchsia_component::client::connect_to_protocol_sync; |
| use linux_uapi::{ |
| LINUX_REBOOT_CMD_CAD_OFF, LINUX_REBOOT_CMD_CAD_ON, LINUX_REBOOT_CMD_HALT, |
| LINUX_REBOOT_CMD_KEXEC, LINUX_REBOOT_CMD_POWER_OFF, LINUX_REBOOT_CMD_RESTART, |
| LINUX_REBOOT_CMD_RESTART2, LINUX_REBOOT_CMD_SW_SUSPEND, |
| }; |
| use starnix_logging::{log_debug, log_info, log_warn, track_stub}; |
| use starnix_sync::{InterruptibleEvent, Locked, Unlocked}; |
| use starnix_uapi::auth::CAP_SYS_BOOT; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::user_address::{UserAddress, UserCString}; |
| use starnix_uapi::{ |
| LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_MAGIC2A, LINUX_REBOOT_MAGIC2B, |
| LINUX_REBOOT_MAGIC2C, errno, error, |
| }; |
| use {fidl_fuchsia_hardware_power_statecontrol as fpower, fidl_fuchsia_recovery as frecovery}; |
| |
| use crate::device::android::bootloader_message_store::{ |
| AndroidBootloaderMessageStore, BootloaderMessage, |
| }; |
| use crate::mm::MemoryAccessorExt; |
| use crate::security; |
| use crate::task::{CurrentTask, Kernel}; |
| use crate::vfs::FsString; |
| |
| #[track_caller] |
| fn panic_or_error(kernel: &Kernel, errno: Errno) -> Result<(), Errno> { |
| if kernel.features.error_on_failed_reboot { |
| return Err(errno); |
| } |
| panic!("Fatal: {errno:?}"); |
| } |
| |
| pub fn sys_reboot( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| magic: u32, |
| magic2: u32, |
| cmd: u32, |
| arg: UserAddress, |
| ) -> Result<(), Errno> { |
| if magic != LINUX_REBOOT_MAGIC1 |
| || (magic2 != LINUX_REBOOT_MAGIC2 |
| && magic2 != LINUX_REBOOT_MAGIC2A |
| && magic2 != LINUX_REBOOT_MAGIC2B |
| && magic2 != LINUX_REBOOT_MAGIC2C) |
| { |
| return error!(EINVAL); |
| } |
| security::check_task_capable(current_task, CAP_SYS_BOOT)?; |
| |
| let arg_bytes = if matches!(cmd, LINUX_REBOOT_CMD_RESTART2) { |
| // This is an arbitrary limit that should be large enough. |
| const MAX_REBOOT_ARG_LEN: usize = 256; |
| current_task |
| .read_c_string_to_vec(UserCString::new(current_task, arg), MAX_REBOOT_ARG_LEN)? |
| } else { |
| FsString::default() |
| }; |
| |
| if current_task.kernel().is_shutting_down() { |
| log_debug!("Ignoring reboot() and parking caller, already shutting down."); |
| let event = InterruptibleEvent::new(); |
| return current_task.block_until(event.begin_wait(), zx::MonotonicInstant::INFINITE); |
| } |
| |
| let proxy = connect_to_protocol_sync::<fpower::AdminMarker>().or_else(|_| error!(EINVAL))?; |
| |
| match cmd { |
| // CAD on/off commands turn Ctrl-Alt-Del keystroke on or off without halting the system. |
| LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(()), |
| |
| // `kexec_load()` is not supported. |
| LINUX_REBOOT_CMD_KEXEC => error!(EINVAL), |
| |
| // Suspend is not implemented. |
| LINUX_REBOOT_CMD_SW_SUSPEND => error!(EINVAL), |
| |
| LINUX_REBOOT_CMD_HALT | LINUX_REBOOT_CMD_POWER_OFF => { |
| log_info!("Powering off"); |
| let options = fpower::ShutdownOptions { |
| action: Some(fpower::ShutdownAction::Poweroff), |
| ..Default::default() |
| }; |
| |
| match proxy.shutdown(&options, zx::MonotonicInstant::INFINITE) { |
| Ok(_) => { |
| // System is rebooting... wait until runtime ends. |
| zx::MonotonicInstant::INFINITE.sleep(); |
| } |
| Err(e) => { |
| return panic_or_error( |
| current_task.kernel(), |
| errno!(EINVAL, format!("Failed to power off, status: {e}")), |
| ); |
| } |
| } |
| Ok(()) |
| } |
| |
| LINUX_REBOOT_CMD_RESTART | LINUX_REBOOT_CMD_RESTART2 => { |
| let reboot_args: Vec<_> = arg_bytes.split_str(b",").collect(); |
| |
| let reboot_result = if reboot_args.contains(&&b"bootloader"[..]) { |
| log_info!("Rebooting to bootloader"); |
| let options = fpower::ShutdownOptions { |
| action: Some(fpower::ShutdownAction::RebootToBootloader), |
| ..Default::default() |
| }; |
| proxy.shutdown(&options, zx::MonotonicInstant::INFINITE) |
| } else if reboot_args.contains(&&b"recovery"[..]) { |
| // Read the bootloader message from the misc partition to determine whether the |
| // device is rebooting to perform an FDR. |
| if let Some(store) = |
| current_task.kernel().expando.peek::<AndroidBootloaderMessageStore>() |
| { |
| match store.read_bootloader_message() { |
| Ok(BootloaderMessage::BootRecovery(args)) => { |
| if args.iter().any(|arg| arg == "--wipe_data") { |
| let factory_reset_proxy = |
| connect_to_protocol_sync::<frecovery::FactoryResetMarker>() |
| .or_else(|_| error!(EINVAL))?; |
| // NB: This performs a reboot for us. |
| log_info!("Initiating factory data reset..."); |
| match factory_reset_proxy.reset(zx::MonotonicInstant::INFINITE) { |
| Ok(_) => { |
| // System is rebooting... wait until runtime ends. |
| zx::MonotonicInstant::INFINITE.sleep(); |
| } |
| Err(e) => { |
| return panic_or_error( |
| current_task.kernel(), |
| errno!( |
| EINVAL, |
| format!("Failed to reboot for FDR, status: {e}") |
| ), |
| ); |
| } |
| } |
| } |
| } |
| // In all other cases, fall through to a regular reboot. |
| Ok(_) => log_info!("Boot message not recognized!"), |
| Err(e) => log_warn!("Failed to read boot message: {e}"), |
| } |
| } |
| log_info!("Rebooting to recovery..."); |
| let options = fpower::ShutdownOptions { |
| action: Some(fpower::ShutdownAction::RebootToRecovery), |
| ..Default::default() |
| }; |
| proxy.shutdown(&options, zx::MonotonicInstant::INFINITE) |
| } else { |
| // TODO(https://391585107): Loop through all the arguments and |
| // generate a list of shutdown reasons. |
| |
| let shutdown_reason = |
| if let Some(arg) = reboot_args.iter().find(|arg| arg.ends_with(b"-failed")) { |
| let process_name = |
| String::from_utf8_lossy(arg.strip_suffix(b"-failed").unwrap()); |
| // This log message is load-bearing server-side as it's used to |
| // extract the critical process responsible for the reboot. |
| // Please notify //src/developer/forensics/OWNERS upon changing. |
| log_info!("Android critical process '{}' failed, rebooting", process_name); |
| fpower::ShutdownReason::AndroidCriticalProcessFailure |
| } else if reboot_args.contains(&&b"ota_update"[..]) |
| || reboot_args.contains(&&b"System update during setup"[..]) |
| { |
| fpower::ShutdownReason::SystemUpdate |
| } else if reboot_args.contains(&&b"shell"[..]) { |
| fpower::ShutdownReason::DeveloperRequest |
| } else if reboot_args.contains(&&b"RescueParty"[..]) |
| || reboot_args.contains(&&b"rescueparty"[..]) |
| { |
| fpower::ShutdownReason::AndroidRescueParty |
| } else if reboot_args.contains(&&b"userrequested"[..]) { |
| fpower::ShutdownReason::UserRequest |
| } else if reboot_args == [b""] |
| // args empty? splitting "" returns [""], not [] |
| { |
| fpower::ShutdownReason::StarnixContainerNoReason |
| } else { |
| log_warn!("Unknown reboot args: {arg_bytes:?}"); |
| track_stub!( |
| TODO("https://fxbug.dev/322874610"), |
| "unknown reboot args, see logs for strings" |
| ); |
| fpower::ShutdownReason::AndroidUnexpectedReason |
| }; |
| |
| log_info!("Rebooting... reason: {:?}", shutdown_reason); |
| proxy.shutdown( |
| &fpower::ShutdownOptions { |
| action: Some(fpower::ShutdownAction::Reboot), |
| reasons: Some(vec![shutdown_reason]), |
| ..Default::default() |
| }, |
| zx::MonotonicInstant::INFINITE, |
| ) |
| }; |
| |
| match reboot_result { |
| Ok(Ok(())) => { |
| // System is rebooting... wait until runtime ends. |
| zx::MonotonicInstant::INFINITE.sleep(); |
| } |
| Ok(Err(e)) => { |
| return panic_or_error( |
| current_task.kernel(), |
| errno!( |
| EINVAL, |
| format!("Failed to reboot, status: {}", zx::Status::from_raw(e)) |
| ), |
| ); |
| } |
| Err(e) => { |
| return panic_or_error( |
| current_task.kernel(), |
| errno!(EINVAL, format!("Failed to reboot, FIDL error: {e}")), |
| ); |
| } |
| } |
| Ok(()) |
| } |
| |
| _ => error!(EINVAL), |
| } |
| } |
| |
| #[cfg(target_arch = "aarch64")] |
| mod arch32 { |
| pub use super::sys_reboot as sys_arch32_reboot; |
| } |
| |
| #[cfg(target_arch = "aarch64")] |
| pub use arch32::*; |