Files
diagd/crates/core/src/scanner/crashes.rs
T

214 lines
9.4 KiB
Rust

use crate::models::CrashReportInfo;
use std::path::Path;
use std::process::Command;
pub struct CrashScanner;
impl CrashScanner {
pub fn scan() -> Vec<CrashReportInfo> {
let mut crashes = Vec::new();
#[cfg(target_os = "linux")]
{
Self::scan_linux_coredumps(&mut crashes);
Self::scan_linux_var_crash(&mut crashes);
Self::scan_linux_dmesg_segfaults(&mut crashes);
}
#[cfg(target_os = "windows")]
{
Self::scan_windows_minidumps(&mut crashes);
Self::scan_windows_event_crashes(&mut crashes);
}
#[cfg(target_os = "macos")]
{
Self::scan_macos_diagnostic_reports(&mut crashes);
}
// Generic fallback if empty (e.g. BSD or systems without coredumpctl)
if crashes.is_empty() {
Self::scan_generic_system_logs(&mut crashes);
}
// Limit to 20 most recent crashes
crashes.truncate(20);
crashes
}
#[cfg(target_os = "linux")]
fn scan_linux_coredumps(crashes: &mut Vec<CrashReportInfo>) {
if let Ok(output) = Command::new("coredumpctl")
.args(["list", "--no-legend", "-n", "10"])
.output()
{
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 {
let time = parts[0..parts.len().saturating_sub(6)].join(" ");
let pid = parts.iter().find_map(|p| p.parse::<u32>().ok());
let signal = parts.iter().find(|p| p.starts_with("SIG") || **p == "11" || **p == "6").copied().unwrap_or("SIGSEGV");
let exe = parts.last().unwrap_or(&"unknown");
crashes.push(CrashReportInfo {
timestamp: if time.is_empty() { "Недавний".to_string() } else { time },
process_name: exe.to_string(),
pid,
signal_or_code: signal.to_string(),
fault_module: None,
stack_trace_snippet: format!("Core dump сохранен systemd-coredump для исполняемого файла {}", exe),
dump_path: Some(format!("coredumpctl info {}", pid.unwrap_or(0))),
});
}
}
}
}
#[cfg(target_os = "linux")]
fn scan_linux_var_crash(crashes: &mut Vec<CrashReportInfo>) {
let crash_dir = Path::new("/var/crash");
if let Ok(entries) = std::fs::read_dir(crash_dir) {
for entry in entries.flatten() {
let path = entry.path();
let filename = path.file_name().unwrap_or_default().to_string_lossy().to_string();
if filename.ends_with(".crash") {
let proc_name = filename.trim_end_matches(".crash").to_string();
let metadata = std::fs::metadata(&path).ok();
let time_str = metadata
.and_then(|m| m.modified().ok())
.map(|t| format!("{:?}", t))
.unwrap_or_else(|| "Архивный".to_string());
crashes.push(CrashReportInfo {
timestamp: time_str,
process_name: proc_name,
pid: None,
signal_or_code: "Crash Dump".to_string(),
fault_module: None,
stack_trace_snippet: format!("Дамп аварийного завершения в /var/crash/{}", filename),
dump_path: Some(path.to_string_lossy().to_string()),
});
}
}
}
}
#[cfg(target_os = "linux")]
fn scan_linux_dmesg_segfaults(crashes: &mut Vec<CrashReportInfo>) {
// Поиск segfaults в dmesg через неинтерактивный вызов
if let Ok(output) = Command::new("dmesg").args(["-T", "-l", "err,warn"]).output() {
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
if line.contains("segfault at") || line.contains("general protection fault") {
let mut proc = "kernel".to_string();
let mut fault_mod = None;
if let Some(idx) = line.find("[") {
if let Some(_end) = line[idx..].find("]") {
let candidate = line[..idx].trim().split_whitespace().last().unwrap_or("");
if !candidate.is_empty() {
proc = candidate.to_string();
}
}
}
if let Some(in_idx) = line.find(" in ") {
let after_in = &line[in_idx + 4..];
let mod_name = after_in.split_whitespace().next().unwrap_or("");
if !mod_name.is_empty() {
fault_mod = Some(mod_name.to_string());
}
}
crashes.push(CrashReportInfo {
timestamp: "Из dmesg".to_string(),
process_name: proc,
pid: None,
signal_or_code: "SIGSEGV (Segmentation Fault)".to_string(),
fault_module: fault_mod,
stack_trace_snippet: line.trim().to_string(),
dump_path: None,
});
}
}
}
}
#[cfg(target_os = "windows")]
fn scan_windows_minidumps(crashes: &mut Vec<CrashReportInfo>) {
let minidump_dir = Path::new(r"C:\Windows\Minidump");
if let Ok(entries) = std::fs::read_dir(minidump_dir) {
for entry in entries.flatten() {
let path = entry.path();
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
if name.ends_with(".dmp") {
crashes.push(CrashReportInfo {
timestamp: "BSOD Crash Dump".to_string(),
process_name: "ntoskrnl.exe (Kernel BSOD)".to_string(),
pid: Some(4),
signal_or_code: "BUGCHECK_DUMP".to_string(),
fault_module: Some("Kernel".to_string()),
stack_trace_snippet: format!("Дамп аварийного синего экрана смерти: {}", path.display()),
dump_path: Some(path.to_string_lossy().to_string()),
});
}
}
}
}
#[cfg(target_os = "windows")]
fn scan_windows_event_crashes(crashes: &mut Vec<CrashReportInfo>) {
let ps_cmd = "Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Application Error'} -MaxEvents 5 -ErrorAction SilentlyContinue | Select-Object TimeCreated, Message | ConvertTo-Json -Compress";
if let Ok(output) = Command::new("powershell").args(["-NoProfile", "-NonInteractive", "-Command", ps_cmd]).output() {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&output.stdout)) {
let items = match val {
serde_json::Value::Array(a) => a,
serde_json::Value::Object(_) => vec![val],
_ => vec![],
};
for item in items {
let msg = item["Message"].as_str().unwrap_or("");
let time = item["TimeCreated"].as_str().unwrap_or("Недавно");
crashes.push(CrashReportInfo {
timestamp: time.to_string(),
process_name: "Application Error".to_string(),
pid: None,
signal_or_code: "0xC0000005 (Access Violation)".to_string(),
fault_module: None,
stack_trace_snippet: msg.trim().to_string(),
dump_path: None,
});
}
}
}
}
#[cfg(target_os = "macos")]
fn scan_macos_diagnostic_reports(crashes: &mut Vec<CrashReportInfo>) {
let reports_dir = Path::new("/Library/Logs/DiagnosticReports");
if let Ok(entries) = std::fs::read_dir(reports_dir) {
for entry in entries.flatten() {
let path = entry.path();
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
if name.ends_with(".ips") || name.ends_with(".crash") {
let proc = name.split(['_', '-']).next().unwrap_or(&name).to_string();
crashes.push(CrashReportInfo {
timestamp: "Diagnostic Report".to_string(),
process_name: proc,
pid: None,
signal_or_code: "EXC_BAD_ACCESS / Crash".to_string(),
fault_module: None,
stack_trace_snippet: format!("macOS Crash Report: {}", path.display()),
dump_path: Some(path.to_string_lossy().to_string()),
});
}
}
}
}
fn scan_generic_system_logs(_crashes: &mut Vec<CrashReportInfo>) {
// Fallback placeholder
}
}