diff --git a/app/Cargo.lock b/app/Cargo.lock index 5b46250..8928e98 100644 --- a/app/Cargo.lock +++ b/app/Cargo.lock @@ -177,6 +177,7 @@ version = "1.1.0" dependencies = [ "acpi", "cargo-emit", + "jiff", "log", "serde", "serde_json", @@ -220,6 +221,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", + "windows-link", ] [[package]] diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 9ac1f35..c1e82b3 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -201,6 +201,7 @@ dependencies = [ "acpi", "cargo-emit", "cbindgen", + "jiff", "log", "serde", "serde_json", @@ -223,6 +224,31 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-static" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "libc" version = "0.2.186" @@ -259,6 +285,21 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro2" version = "1.0.106" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 29adc75..8a21e73 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -47,7 +47,8 @@ serialize = [ "dep:serde_json", ] std = [ - "serde/std" + "serde/std", + "dep:jiff", ] [dev-dependencies] @@ -64,6 +65,16 @@ log = "0.4" version = "5" default-features = false +[dependencies.jiff] +version = "0.2" +default-features = false +features = [ + "std", + "tz-system", + "tzdb-zoneinfo", +] +optional = true + [dependencies.serde] version = "1" optional = true diff --git a/lib/src/metadata.rs b/lib/src/metadata.rs index ebbffa2..acadcbb 100644 --- a/lib/src/metadata.rs +++ b/lib/src/metadata.rs @@ -3,6 +3,8 @@ //! Information extracted alongside the Crash Log records. +mod time; + #[cfg(not(feature = "std"))] use alloc::vec::Vec; #[cfg(not(feature = "std"))] @@ -13,6 +15,8 @@ use std::fmt; use crate::cper::CperSectionBody; use crate::source::CrashLogSource; +pub use time::Time; + /// Crash Log Metadata #[derive(Default, Clone)] pub struct Metadata { @@ -27,41 +31,29 @@ pub struct Metadata { pub extra_cper_sections: Vec, } -/// Crash Log Extraction Time -#[derive(Clone)] -pub struct Time { - pub year: u16, - pub month: u8, - pub day: u8, - pub hour: u8, - pub minute: u8, -} - impl fmt::Display for Metadata { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match ( - self.computer.as_ref(), - self.source.as_ref(), - self.time.as_ref(), - ) { - (Some(computer), Some(source), Some(time)) => write!(f, "{computer}-{source}-{time}"), - (Some(computer), None, Some(time)) => write!(f, "{computer}-{time}"), - (None, None, Some(time)) => write!(f, "{time}"), - (None, Some(source), Some(time)) => write!(f, "{source}-{time}"), - (Some(computer), None, None) => write!(f, "{computer}"), - (Some(computer), Some(source), None) => write!(f, "{computer}-{source}"), - (None, Some(source), None) => write!(f, "{source}"), - (None, None, None) => write!(f, "unnamed"), + let mut sep = ""; + + if let Some(computer) = &self.computer { + write!(f, "{computer}")?; + sep = "-"; } - } -} -impl fmt::Display for Time { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{:04}-{:02}-{:02}-{:02}-{:02}", - self.year, self.month, self.day, self.hour, self.minute - ) + if let Some(source) = &self.source { + write!(f, "{sep}{source}")?; + sep = "-"; + } + + if let Some(time) = &self.time { + write!(f, "{sep}{time}")?; + sep = "-"; + } + + if sep.is_empty() { + write!(f, "unnamed")?; + } + + Ok(()) } } diff --git a/lib/src/metadata/time.rs b/lib/src/metadata/time.rs new file mode 100644 index 0000000..1c01b46 --- /dev/null +++ b/lib/src/metadata/time.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: MIT + +#[cfg(not(feature = "std"))] +use alloc::fmt; +#[cfg(feature = "std")] +use std::fmt; + +/// Crash Log Extraction Time, expressed in the local time of the system +#[derive(Clone)] +pub struct Time { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, + pub millisecond: u16, +} + +impl fmt::Display for Time { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:04}{:02}{:02}T{:02}{:02}{:02}.{:03}", + self.year, self.month, self.day, self.hour, self.minute, self.second, self.millisecond, + ) + } +} + +impl Time { + /// Returns the current [Time], or [None] if it cannot be determined + /// + /// The time is read from the operating system + #[cfg(feature = "std")] + pub fn now() -> Option { + use jiff::{Timestamp, tz}; + + let now = Timestamp::try_from(std::time::SystemTime::now()) + .inspect_err(|err| log::info!("Cannot get the current time: {err}")) + .ok()?; + + let tz = tz::TimeZone::system(); + + if tz == tz::TimeZone::unknown() { + log::warn!("Cannot determine the system time zone"); + } + + let local = now.to_zoned(tz).datetime(); + Some(Time { + year: local.year() as u16, + month: local.month() as u8, + day: local.day() as u8, + hour: local.hour() as u8, + minute: local.minute() as u8, + second: local.second() as u8, + millisecond: local.millisecond() as u16, + }) + } + + /// Returns the current [Time], or [None] if it cannot be determined + /// + /// The time is read from the UEFI runtime services + #[cfg(all(not(feature = "std"), target_os = "uefi"))] + pub fn now() -> Option { + let time = uefi::runtime::get_time() + .inspect_err(|err| log::info!("Cannot get RTC time: {err}")) + .ok()?; + + Some(Time { + year: time.year(), + month: time.month(), + day: time.day(), + hour: time.hour(), + minute: time.minute(), + second: time.second(), + millisecond: (time.nanosecond() / 1_000_000) as u16, + }) + } +} diff --git a/lib/src/source.rs b/lib/src/source.rs index b0086d4..e4ea8ce 100644 --- a/lib/src/source.rs +++ b/lib/src/source.rs @@ -198,7 +198,6 @@ impl CrashLogSource { crashlog.metadata.source = Some(self.clone()); } } - crashlogs } diff --git a/lib/src/source/acpi.rs b/lib/src/source/acpi.rs index 7b8616a..29a1a22 100644 --- a/lib/src/source/acpi.rs +++ b/lib/src/source/acpi.rs @@ -26,7 +26,7 @@ impl Acpi { #[cfg(target_os = "linux")] { match self.sysfs.extract() { - Ok(crashlogs) => return Ok(crashlogs), + Ok(crashlog) => return Ok(crashlog), Err(err) => log::error!("Cannot extract Crash Log from ACPI sysfs: {err}"), } } diff --git a/lib/src/source/acpi/efi.rs b/lib/src/source/acpi/efi.rs index 0fcaa33..0ebc148 100644 --- a/lib/src/source/acpi/efi.rs +++ b/lib/src/source/acpi/efi.rs @@ -82,16 +82,7 @@ pub(super) fn extract_crashlog() -> Result { crashlog.metadata = metadata::Metadata { computer: Some("efi".to_string()), - time: uefi::runtime::get_time() - .map(|time| metadata::Time { - year: time.year(), - month: time.month(), - day: time.day(), - hour: time.hour(), - minute: time.minute(), - }) - .inspect_err(|err| log::warn!("Cannot get time: {err}")) - .ok(), + time: metadata::Time::now(), ..Default::default() }; diff --git a/lib/src/source/acpi/sysfs.rs b/lib/src/source/acpi/sysfs.rs index 91c9a51..27227c8 100644 --- a/lib/src/source/acpi/sysfs.rs +++ b/lib/src/source/acpi/sysfs.rs @@ -4,6 +4,7 @@ use crate::CrashLog; use crate::bert::Berr; use crate::error::Error; +use crate::metadata::Time; use std::path::{Path, PathBuf}; pub(super) struct AcpiSysFs { @@ -54,7 +55,9 @@ impl AcpiSysFs { Berr::from_slice(&berr).ok_or(Error::InvalidBootErrorRecordRegion) })?; - CrashLog::from_berr(berr) + let mut crashlog = CrashLog::from_berr(berr)?; + crashlog.metadata.time = Time::now(); + Ok(crashlog) } } @@ -92,7 +95,10 @@ mod tests { let berr = Berr::from_crashlog(&crashlog); std::fs::write(berr_path, berr.to_bytes()).unwrap(); - let extracted_crashlog = acpi.extract().unwrap(); + let mut extracted_crashlog = acpi.extract().unwrap(); + assert!(extracted_crashlog.metadata.time.is_some()); + + extracted_crashlog.metadata.time = None; assert_eq!(crashlog.to_bytes(), extracted_crashlog.to_bytes()); } diff --git a/lib/src/source/event_log/win.rs b/lib/src/source/event_log/win.rs index d5409e9..247e2c6 100644 --- a/lib/src/source/event_log/win.rs +++ b/lib/src/source/event_log/win.rs @@ -187,6 +187,8 @@ impl EvtRecord { day: time.wDay as u8, hour: time.wHour as u8, minute: time.wMinute as u8, + second: time.wSecond as u8, + millisecond: time.wMilliseconds, }); } diff --git a/lib/src/source/pmt/sysfs.rs b/lib/src/source/pmt/sysfs.rs index 55e6c3c..9b8d903 100644 --- a/lib/src/source/pmt/sysfs.rs +++ b/lib/src/source/pmt/sysfs.rs @@ -5,6 +5,7 @@ use super::PmtDeviceId; use super::bdf::PciBdf; use crate::CrashLog; use crate::error::Error; +use crate::metadata::Time; use crate::region::Region; use crate::source::{Capabilities, Capability}; use std::collections::BTreeSet; @@ -212,7 +213,10 @@ impl PmtSysFs { for endpoint in self.get_endpoints(dev) { match endpoint.extract() { - Ok(crashlog) => crashlogs.push(crashlog), + Ok(mut crashlog) => { + crashlog.metadata.time = Time::now(); + crashlogs.push(crashlog); + } Err(Error::EmptyRegion) => (), Err(err) => return Err(err), } @@ -340,7 +344,9 @@ impl CrashLog { .flat_map(|crashlog| crashlog.regions) .collect(); - CrashLog::from_regions(regions) + let mut crashlog = CrashLog::from_regions(regions)?; + crashlog.metadata.time = Time::now(); + Ok(crashlog) } }