1fn crash_log_dir() -> std::path::PathBuf {
8 let home = std::env::var_os("HOME")
11 .map(std::path::PathBuf::from)
12 .unwrap_or_else(|| std::path::PathBuf::from("/tmp"));
13 home.join(".futu-opend-rs").join("crashes")
14}
15
16pub fn write_crash_log_file(info: &std::panic::PanicHookInfo<'_>) {
21 let dir = crash_log_dir();
22 if let Err(err) = std::fs::create_dir_all(&dir) {
23 eprintln!(
24 "futu-opend: failed to create crash log directory {}: {}",
25 dir.display(),
26 err
27 );
28 }
29
30 let now_secs = crash_log_unix_secs_or_zero();
35 let path = dir.join(format!("crash-{now_secs}.log"));
36
37 let location = info
38 .location()
39 .map(|l| format!("{}:{}", l.file(), l.line()))
40 .unwrap_or_else(|| "<unknown>".to_string());
41 let payload = info
42 .payload()
43 .downcast_ref::<&str>()
44 .copied()
45 .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.as_str()))
46 .unwrap_or("<non-string panic payload>");
47 let thread = std::thread::current()
48 .name()
49 .unwrap_or("<unnamed>")
50 .to_string();
51 let backtrace = std::backtrace::Backtrace::force_capture();
56
57 let body = format!(
58 "v1.4.97 P1-D-D crash report\n\
59 daemon_version: {}\n\
60 unix_timestamp: {}\n\
61 os: {}\n\
62 arch: {}\n\
63 thread: {}\n\
64 location: {}\n\
65 payload: {}\n\
66 backtrace:\n{}\n",
67 env!("CARGO_PKG_VERSION"),
68 now_secs,
69 std::env::consts::OS,
70 std::env::consts::ARCH,
71 thread,
72 location,
73 payload,
74 backtrace,
75 );
76 if let Err(err) = std::fs::write(&path, body) {
77 eprintln!(
78 "futu-opend: failed to write crash log {}: {}",
79 path.display(),
80 err
81 );
82 }
83}
84
85fn crash_log_unix_secs_or_zero() -> u64 {
86 match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
87 Ok(elapsed) => elapsed.as_secs(),
88 Err(err) => {
89 eprintln!(
90 "futu-opend: system wall clock is before UNIX_EPOCH while writing crash log; \
91 using zero timestamp fallback: {err}"
92 );
93 0
94 }
95 }
96}
97
98pub fn warn_if_previous_crash() {
102 let dir = crash_log_dir();
103 let Ok(entries) = std::fs::read_dir(&dir) else {
104 return;
105 };
106 let mut latest: Option<(std::time::SystemTime, std::path::PathBuf)> = None;
108 for entry in entries.flatten() {
109 let path = entry.path();
110 if !path
111 .file_name()
112 .and_then(|s| s.to_str())
113 .is_some_and(|s| s.starts_with("crash-") && s.ends_with(".log"))
114 {
115 continue;
116 }
117 let Ok(meta) = entry.metadata() else { continue };
118 let Ok(mtime) = meta.modified() else { continue };
119 if latest.as_ref().is_none_or(|(t, _)| mtime > *t) {
120 latest = Some((mtime, path));
121 }
122 }
123 if let Some((_, path)) = latest {
124 eprintln!(
126 "⚠️ v1.4.97 P1-D-D: previous crash log detected at {}",
127 path.display()
128 );
129 eprintln!(" inspect for last-known panic payload + backtrace.");
130 }
131}
132
133#[cfg(test)]
134mod tests;