#![forbid(unsafe_code)]
use std::path::Path;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CrashReceipt {
pub crash_point: String,
pub pre_hash: String,
pub post_hash: String,
pub recovered_hash: String,
pub admissible: bool,
pub fsck_clean: bool,
pub writable: bool,
pub store_dir: String,
pub revision: String,
pub kernel: String,
pub unix_secs: u64,
}
impl CrashReceipt {
pub fn current_revision() -> String {
std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default()
}
pub fn current_kernel() -> String {
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.map(|s| s.trim().to_string())
.unwrap_or_default()
}
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".into())
}
}
pub fn write_receipt(dir: &Path, name: &str, receipt: &CrashReceipt) -> std::io::Result<()> {
let path = dir.join(format!("{name}.json"));
crate::store::write_atomic(&path, receipt.to_json().as_bytes())
.map_err(|e| std::io::Error::other(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn receipt_json_roundtrip() {
let r = CrashReceipt {
crash_point: "AfterSuperblockFsync".into(),
pre_hash: "aa".into(),
post_hash: "bb".into(),
recovered_hash: "aa".into(),
admissible: true,
fsck_clean: true,
writable: true,
store_dir: "/tmp/x".into(),
revision: "deadbeef".into(),
kernel: "6.7".into(),
unix_secs: 0,
};
let json = r.to_json();
let back: CrashReceipt = serde_json::from_str(&json).unwrap();
assert_eq!(back, r);
let tmp = tempfile::TempDir::new().unwrap();
write_receipt(tmp.path(), "run-001", &r).unwrap();
assert!(tmp.path().join("run-001.json").exists());
}
}