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
17/// Timeout for FIDL calls to LastRebootInfoProvider
18const LRIP_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::INFINITE;
19static ANDROID_BOOTREASON: OnceCell<Result<String, Error>> = OnceCell::new();
20
21/// Get an Android-compatible boot reason suitable to add to the cmdline or bootconfig.
22pub async fn get_android_bootreason(
23    dir: Option<fio::DirectoryProxy>,
24) -> &'static Result<String, Error> {
25    ANDROID_BOOTREASON.get_or_init(async || update_android_bootreason(dir).await).await
26}
27
28/// Update the Android bootreason.
29///
30/// Called only once when the Starnix kernel is initialized.
31/// If called more than once, it reports false Android crash cases to Pitot.
32async fn update_android_bootreason(dir: Option<fio::DirectoryProxy>) -> Result<String, Error> {
33    if let Some(dir) = dir {
34        match fuchsia_fs::directory::open_file(&dir, STARTED_ONCE, fio::Flags::FLAG_MUST_CREATE)
35            .await
36        {
37            Ok(_file) => (),
38            Err(OpenError::OpenError(Status::ALREADY_EXISTS)) => {
39                info!("Session restart observed, set android bootreason to kernel_panic.");
40                return Ok("kernel_panic".to_string());
41            }
42            Err(err) => {
43                info!(
44                    "Failed to generate the file with err {err:#?}. Continue with LastRebootInfo."
45                );
46            }
47        }
48    }
49
50    info!("Converting LastRebootInfo to an android-friendly bootreason.");
51    let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>()?;
52    let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
53    let reboot_info = reboot_info_proxy.get(deadline)?;
54
55    let bootreason = match reboot_info.reason {
56        Some(RebootReason::Unknown) => "reboot,unknown",
57        Some(RebootReason::Cold) => "reboot,cold",
58        Some(RebootReason::BriefPowerLoss) => "reboot,hard_reset",
59        Some(RebootReason::Brownout) => "reboot,undervoltage",
60        Some(RebootReason::KernelPanic) => "kernel_panic",
61        Some(RebootReason::SystemOutOfMemory) => "kernel_panic,oom",
62        Some(RebootReason::HardwareWatchdogTimeout) => "watchdog",
63        Some(RebootReason::SoftwareWatchdogTimeout) => "watchdog,sw",
64        Some(RebootReason::RootJobTermination) => "kernel_panic",
65        Some(RebootReason::UserRequest) => "reboot,userrequested",
66        Some(RebootReason::DeveloperRequest) => "reboot,shell",
67        Some(RebootReason::RetrySystemUpdate) => "reboot,ota",
68        Some(RebootReason::HighTemperature) => "shutdown,thermal",
69        Some(RebootReason::SessionFailure) => "kernel_panic",
70        Some(RebootReason::SysmgrFailure) => "kernel_panic",
71        Some(RebootReason::FactoryDataReset) => "reboot,factory_reset",
72        Some(RebootReason::CriticalComponentFailure) => "kernel_panic",
73        Some(RebootReason::ZbiSwap) => "reboot,normal",
74        Some(RebootReason::SystemUpdate) => "reboot,ota",
75        Some(RebootReason::NetstackMigration) => "reboot,normal",
76        Some(RebootReason::AndroidUnexpectedReason) => "reboot,normal",
77        Some(RebootReason::AndroidNoReason) => "reboot",
78        Some(RebootReason::AndroidRescueParty) => "reboot,rescueparty",
79        Some(RebootReason::AndroidCriticalProcessFailure) => "reboot,userspace_failed",
80        Some(RebootReason::__SourceBreaking { .. }) => "reboot,normal",
81        None => "reboot,unknown",
82    };
83    Ok(bootreason.to_string())
84}
85
86/// Get the last reboot reason code.
87fn get_reboot_reason() -> Option<RebootReason> {
88    let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>().ok();
89    let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
90    let reboot_info = reboot_info_proxy?.get(deadline);
91    match reboot_info {
92        Ok(info) => match info.reason {
93            Some(r) => Some(r),
94            None => {
95                info!("Failed to get the reboot reason.");
96                Some(RebootReason::unknown())
97            }
98        },
99        Err(e) => {
100            info!("Failed to get the reboot info: {:?}", e);
101            Some(RebootReason::unknown())
102        }
103    }
104}
105
106/// Get contents for the pstore/console-ramoops* file.
107///
108/// In Linux it contains a limited amount of some of the previous boot's kernel logs.
109/// The ramoops won't be created after a normal reboot.
110pub fn get_console_ramoops() -> Option<Vec<u8>> {
111    debug!("Getting console-ramoops contents");
112    match ANDROID_BOOTREASON.wait_blocking() {
113        Ok(reason) => match reason.as_str() {
114            "kernel_panic" | "watchdog" | "watchdog,sw" => Some(
115                format!("Last Reboot Reason: {:?}\n", get_reboot_reason()?).as_bytes().to_vec(),
116            ),
117            _ => None,
118        },
119        Err(e) => {
120            info!("Failed to get android bootreason for console_ramoops: {:?}", e);
121            None
122        }
123    }
124}