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::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
115fn 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
135pub 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}