1use 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
14const STARTED_ONCE: &str = "component-started-once";
16static HAS_STARNIX_SESSION_RESTARTED: OnceCell<bool> = OnceCell::new();
19static ANDROID_BOOTREASON: OnceCell<Result<String, Error>> = OnceCell::new();
21
22const LRIP_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::INFINITE;
24
25async 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
45pub 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
55pub async fn update_android_bootreason(
58 dir: Option<fio::DirectoryProxy>,
59 android_provided_bootreason: Option<String>,
60) -> Result<String, Error> {
61 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 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::RootJobTermination) => "kernel_panic",
90 Some(RebootReason::UserRequest) => "reboot,userrequested",
91 Some(RebootReason::UserRequestDeviceStuck) => "reboot,userrequested",
92 Some(RebootReason::UserHardReset) => "reboot,longkey,s2",
93 Some(RebootReason::DeveloperRequest) => "reboot,shell",
94 Some(RebootReason::RetrySystemUpdate) => "reboot,ota",
95 Some(RebootReason::HighTemperature) => "shutdown,thermal",
96 Some(RebootReason::SessionFailure) => "kernel_panic",
97 Some(RebootReason::SysmgrFailure) => "kernel_panic",
98 Some(RebootReason::FactoryDataReset) => "reboot,factory_reset",
99 Some(RebootReason::CriticalComponentFailure) => "kernel_panic",
100 Some(RebootReason::ZbiSwap) => "reboot,normal",
101 Some(RebootReason::SystemUpdate) => "reboot,ota",
102 Some(RebootReason::NetstackMigration) => "reboot,normal",
103 Some(RebootReason::AndroidUnexpectedReason) => "reboot,normal",
104 Some(RebootReason::AndroidNoReason) => "reboot",
105 Some(RebootReason::AndroidRescueParty) => "reboot,rescueparty",
106 Some(RebootReason::AndroidCriticalProcessFailure) => "reboot,userspace_failed",
107 Some(RebootReason::BatteryDrained) => "shutdown,battery",
108 Some(RebootReason::__SourceBreaking { .. }) => "reboot,normal",
109 None => "reboot,unknown",
110 };
111 Ok(bootreason.to_string())
112}
113
114fn get_reboot_reason() -> Option<RebootReason> {
116 let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>().ok();
117 let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
118 let reboot_info = reboot_info_proxy?.get(deadline);
119 match reboot_info {
120 Ok(info) => match info.reason {
121 Some(r) => Some(r),
122 None => {
123 info!("Failed to get the reboot reason.");
124 Some(RebootReason::unknown())
125 }
126 },
127 Err(e) => {
128 info!("Failed to get the reboot info: {:?}", e);
129 Some(RebootReason::unknown())
130 }
131 }
132}
133
134pub fn get_console_ramoops() -> Option<Vec<u8>> {
139 debug!("Getting console-ramoops contents");
140 if HAS_STARNIX_SESSION_RESTARTED.get().copied().unwrap_or(false) {
141 return Some(format!("Last Reboot Reason: Starnix Crash\n").as_bytes().to_vec());
142 }
143 match ANDROID_BOOTREASON.get() {
144 Some(Ok(reason)) => match reason.as_str() {
145 "kernel_panic" | "watchdog" | "watchdog,sw" => Some(
146 format!("Last Reboot Reason: {:?}\n", get_reboot_reason()?).as_bytes().to_vec(),
147 ),
148 _ => None,
149 },
150 Some(Err(e)) => {
151 info!("Failed to get android bootreason for console_ramoops: {:?}", e);
152 None
153 }
154 None => {
155 info!("Android bootreason not initialized.");
156 None
157 }
158 }
159}