diff --git a/lib/src/metadata.rs b/lib/src/metadata.rs index 0d8ccca..ebbffa2 100644 --- a/lib/src/metadata.rs +++ b/lib/src/metadata.rs @@ -14,7 +14,7 @@ use crate::cper::CperSectionBody; use crate::source::CrashLogSource; /// Crash Log Metadata -#[derive(Default)] +#[derive(Default, Clone)] pub struct Metadata { /// Name of the computer where the Crash Log has been extracted from. pub computer: Option, @@ -28,6 +28,7 @@ pub struct Metadata { } /// Crash Log Extraction Time +#[derive(Clone)] pub struct Time { pub year: u16, pub month: u8, diff --git a/lib/src/source/event_log/win.rs b/lib/src/source/event_log/win.rs index 87c7123..8db86a3 100644 --- a/lib/src/source/event_log/win.rs +++ b/lib/src/source/event_log/win.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: MIT use crate::CrashLog; -use crate::metadata; +use crate::metadata::{Metadata, Time}; use std::alloc::{Layout, alloc, dealloc}; +use std::collections::{BTreeMap, HashMap}; use std::ffi::c_void; use std::ops::{Deref, Drop}; use std::path::Path; @@ -138,30 +139,85 @@ fn evt_render_values(context: &EvtHandle, event: &EvtHandle) -> Result Result { - let mut time = SYSTEMTIME::default(); - unsafe { +#[derive(Default)] +struct EvtRecord { + id: u32, + metadata: Metadata, + chunks: BTreeMap>, +} + +impl EvtRecord { + fn new(id: u32) -> Self { + Self { + id, + ..Default::default() + } + } + + fn add_chunk(&mut self, seq_num: u32, chunk: &[u8]) { + if self.chunks.insert(seq_num, chunk.to_vec()).is_some() { + log::warn!( + "Chunk {seq_num} of the record {} is defined several times in the Event Log.", + self.id + ); + } + } + + fn set_time(&mut self, filetimeval: u64) { + let mut time = SYSTEMTIME::default(); let filetime = FILETIME { - dwHighDateTime: (filetime.Anonymous.FileTimeVal >> 32) as u32, - dwLowDateTime: (filetime.Anonymous.FileTimeVal & 0xFFFFFFFF) as u32, + dwHighDateTime: (filetimeval >> 32) as u32, + dwLowDateTime: (filetimeval & 0xFFFFFFFF) as u32, + }; + let res = unsafe { + FileTimeToSystemTime(&filetime as *const FILETIME, &mut time as *mut SYSTEMTIME) }; - FileTimeToSystemTime(&filetime as *const FILETIME, &mut time as *mut SYSTEMTIME)? - } - Ok(metadata::Metadata { - time: Some(metadata::Time { + if let Err(err) = res { + log::warn!( + "Failed to convert filetime to system time for event record {}: {err}", + self.id + ); + return; + } + + self.metadata.time = Some(Time { year: time.wYear, month: time.wMonth as u8, day: time.wDay as u8, hour: time.wHour as u8, minute: time.wMinute as u8, - }), - computer: unsafe { computer.Anonymous.StringVal.to_string().ok() }, - ..Default::default() - }) + }); + } + + fn set_computer(&mut self, computer: PCWSTR) { + self.metadata.computer = unsafe { computer.to_string().ok() }; + } + + fn into_crashlog(self) -> Option { + let mut binary = Vec::new(); + + for (i, (seq_num, chunk)) in self.chunks.into_iter().enumerate() { + if i as u32 != seq_num { + log::warn!( + "Event record {} is incomplete. Chunk {i} is missing.", + self.id + ); + return None; + } + + binary.extend(chunk); + } + + let mut crashlog = CrashLog::from_slice(&binary) + .inspect_err(|err| { + log::warn!("Error while decoding Crash Log read from Event Logs: {err}") + }) + .ok()?; + + crashlog.metadata = self.metadata.clone(); + Some(crashlog) + } } fn query_crashlogs(path: PCWSTR, query: PCWSTR, query_flags: u32) -> Result> { @@ -171,6 +227,8 @@ fn query_crashlogs(path: PCWSTR, query: PCWSTR, query_flags: u32) -> Result Result(values[0].Anonymous.BinaryVal, values[0].Count as usize) }; - - match CrashLog::from_slice(binary) { - Ok(mut crashlog) => { - crashlog.metadata = metadata_from_evt_values(values[1], values[2])?; - crashlogs.push(crashlog) - } - Err(err) => { - log::warn!("Error while decoding Crash Log read from Event Logs: {err}") - } + let record_id = unsafe { values[1].Anonymous.UInt32Val }; + let seq_num = unsafe { values[2].Anonymous.UInt32Val }; + let filetimeval = unsafe { values[3].Anonymous.FileTimeVal }; + let computer = unsafe { values[4].Anonymous.StringVal }; + + records.entry(record_id).or_insert_with(|| { + let mut record = EvtRecord::new(record_id); + record.set_time(filetimeval); + record.set_computer(computer); + record + }); + + if let Some(record) = records.get_mut(&record_id) { + record.add_chunk(seq_num, binary); } } } - Ok(crashlogs) + Ok(records + .into_values() + .filter_map(|record| record.into_crashlog()) + .collect()) } pub(super) fn extract_crashlogs(path: Option<&Path>) -> Result> {