Skip to main content

bootreason/
lib.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 anyhow::Error;
6use async_lock::OnceCell;
7use fidl_fuchsia_feedback::{LastRebootInfoProviderMarker, RebootReason};
8use fidl_fuchsia_io as fio;
9use fuchsia_component::client::connect_to_protocol_sync;
10use fuchsia_fs::node::OpenError;
11use log::{debug, info};
12use zx_status::Status;
13
14/// Temp file for the Starnix lifecycle detection
15const STARTED_ONCE: &str = "component-started-once";
16/// Starnix session restart indicator.
17/// True if the current Starnix session was restarted without a full reboot.
18static HAS_STARNIX_SESSION_RESTARTED: OnceCell<bool> = OnceCell::new();
19/// The Android boot reason of the current session.
20static ANDROID_BOOTREASON: OnceCell<Result<String, Error>> = OnceCell::new();
21
22/// Timeout for FIDL calls to LastRebootInfoProvider
23const LRIP_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::INFINITE;
24
25/// Determines whether the current session was restarted without a reboot.
26/// Returns true if the session was restarted, false if it is the initial session.
27async fn has_session_restarted(dir: Option<fio::DirectoryProxy>) -> bool {
28    match dir {
29        Some(dir) => {
30            match fuchsia_fs::directory::open_file(&dir, STARTED_ONCE, fio::Flags::FLAG_MUST_CREATE)
31                .await
32            {
33                Ok(_file) => false,
34                Err(OpenError::OpenError(Status::ALREADY_EXISTS)) => true,
35                Err(err) => {
36                    info!("Failed to generate the file with err {err:#?}.");
37                    false
38                }
39            }
40        }
41        None => false,
42    }
43}
44
45/// Get an Android-compatible boot reason suitable to add to the cmdline or bootconfig.
46pub async fn get_or_init_android_bootreason(
47    dir: Option<fio::DirectoryProxy>,
48    android_provided_bootreason: Option<String>,
49) -> &'static Result<String, Error> {
50    ANDROID_BOOTREASON
51        .get_or_init(async || update_android_bootreason(dir, android_provided_bootreason).await)
52        .await
53}
54
55/// Update the Android bootreason.
56/// Use get_or_init_android_bootreason to get the cached Android boot reason instead of this.
57pub async fn update_android_bootreason(
58    dir: Option<fio::DirectoryProxy>,
59    android_provided_bootreason: Option<String>,
60) -> Result<String, Error> {
61    // Set the Android bootreason to kernel_panic if the current session was restarted.
62    if *HAS_STARNIX_SESSION_RESTARTED.get_or_init(async || has_session_restarted(dir).await).await {
63        info!("Session restart observed, set android bootreason to kernel_panic.");
64        return Ok("kernel_panic".to_string());
65    }
66
67    // There are certain values from the Android bootloader that are more specific than
68    // what the Fuchsia platform knows so use that when relevant.
69    if let Some(reason) = &android_provided_bootreason {
70        if reason.starts_with("reboot,uvlo") || reason.starts_with("reboot,longkey") {
71            return Ok(reason.clone());
72        }
73    }
74
75    info!("Converting LastRebootInfo to an android-friendly bootreason.");
76    let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>()?;
77    let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
78    let reboot_info = reboot_info_proxy.get(deadline)?;
79
80    let bootreason = match reboot_info.reason {
81        Some(RebootReason::Unknown) => "reboot,unknown",
82        Some(RebootReason::Cold) => "reboot,cold",
83        Some(RebootReason::BriefPowerLoss) => "reboot,hard_reset",
84        Some(RebootReason::Brownout) => "reboot,undervoltage",
85        Some(RebootReason::KernelPanic) => "kernel_panic",
86        Some(RebootReason::SystemOutOfMemory) => "kernel_panic,oom",
87        Some(RebootReason::HardwareWatchdogTimeout) => "watchdog",
88        Some(RebootReason::SoftwareWatchdogTimeout) => "watchdog,sw",
89        Some(RebootReason::SuspensionFailure) => "kernel_panic",
90        Some(RebootReason::RootJobTermination) => "kernel_panic",
91        Some(RebootReason::UserRequest) => "reboot,userrequested",
92        Some(RebootReason::UserRequestDeviceStuck) => "reboot,userrequested",
93        Some(RebootReason::UserHardReset) => "reboot,longkey,s2",
94        Some(RebootReason::DeveloperRequest) => "reboot,shell",
95        Some(RebootReason::RetrySystemUpdate) => "reboot,ota",
96        Some(RebootReason::HighTemperature) => "shutdown,thermal",
97        Some(RebootReason::SessionFailure) => "kernel_panic",
98        Some(RebootReason::SysmgrFailure) => "kernel_panic",
99        Some(RebootReason::FactoryDataReset) => "reboot,factory_reset",
100        Some(RebootReason::CriticalComponentFailure) => "kernel_panic",
101        Some(RebootReason::ZbiSwap) => "reboot,normal",
102        Some(RebootReason::SystemUpdate) => "reboot,ota",
103        Some(RebootReason::NetstackMigration) => "reboot,normal",
104        Some(RebootReason::AndroidUnexpectedReason) => "reboot,normal",
105        Some(RebootReason::AndroidNoReason) => "reboot",
106        Some(RebootReason::AndroidRescueParty) => "reboot,rescueparty",
107        Some(RebootReason::AndroidCriticalProcessFailure) => "reboot,userspace_failed",
108        Some(RebootReason::BatteryDrained) => "shutdown,battery",
109        Some(RebootReason::__SourceBreaking { .. }) => "reboot,normal",
110        None => "reboot,unknown",
111    };
112    Ok(bootreason.to_string())
113}
114
115/// Get the last reboot reason code.
116fn get_reboot_reason() -> Option<RebootReason> {
117    let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>().ok();
118    let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
119    let reboot_info = reboot_info_proxy?.get(deadline);
120    match reboot_info {
121        Ok(info) => match info.reason {
122            Some(r) => Some(r),
123            None => {
124                info!("Failed to get the reboot reason.");
125                Some(RebootReason::unknown())
126            }
127        },
128        Err(e) => {
129            info!("Failed to get the reboot info: {:?}", e);
130            Some(RebootReason::unknown())
131        }
132    }
133}
134
135/// Get contents for the pstore/console-ramoops* file.
136///
137/// In Linux it contains a limited amount of some of the previous boot's kernel logs.
138/// The ramoops won't be created after a normal reboot.
139pub fn get_console_ramoops() -> Option<Vec<u8>> {
140    debug!("Getting console-ramoops contents");
141    if HAS_STARNIX_SESSION_RESTARTED.get().copied().unwrap_or(false) {
142        return Some(format!("Last Reboot Reason: Starnix Crash\n").as_bytes().to_vec());
143    }
144    match ANDROID_BOOTREASON.get() {
145        Some(Ok(reason)) => match reason.as_str() {
146            "kernel_panic" | "watchdog" | "watchdog,sw" => Some(
147                format!("Last Reboot Reason: {:?}\n", get_reboot_reason()?).as_bytes().to_vec(),
148            ),
149            _ => None,
150        },
151        Some(Err(e)) => {
152            info!("Failed to get android bootreason for console_ramoops: {:?}", e);
153            None
154        }
155        None => {
156            info!("Android bootreason not initialized.");
157            None
158        }
159    }
160}