1use std::{
2 path::{Path, PathBuf},
3 process,
4 sync::Arc,
5};
6
7pub mod client;
8pub mod server;
9
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12 #[error(transparent)]
13 IO(#[from] std::io::Error),
14 #[error(transparent)]
15 CrashHandler(#[from] crash_handler::Error),
16 #[error(transparent)]
17 Minidumper(#[from] minidumper::Error),
18}
19
20pub struct ClientHandle {
21 client: Arc<minidumper::Client>,
22 _handler: crash_handler::CrashHandler,
23 _child: process::Child,
24}
25
26impl ClientHandle {
27 pub fn send_message(&self, kind: u32, buf: impl AsRef<[u8]>) -> Result<(), Error> {
28 self.client.send_message(kind, buf).map_err(Error::from)
29 }
30}
31
32pub type OnMinidump = Box<dyn Fn(Vec<u8>, &Path) + Send + Sync + 'static>;
33pub type OnMessage = Box<dyn Fn(u32, Vec<u8>) + Send + Sync + 'static>;
34
35pub struct MinidumperChild {
36 crashes_dir: PathBuf,
37 server_stale_timeout: u64,
38 client_connect_timeout: u64,
39 server_arg: String,
40 on_minidump: Option<OnMinidump>,
41 on_message: Option<OnMessage>,
42}
43
44impl Default for MinidumperChild {
45 fn default() -> Self {
46 Self {
47 crashes_dir: std::env::temp_dir().join("Crashes"),
48 server_stale_timeout: 5000,
49 client_connect_timeout: 3000,
50 server_arg: "--crash-reporter-server".to_string(),
51 on_minidump: None,
52 on_message: None,
53 }
54 }
55}
56
57impl MinidumperChild {
58 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
59 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn is_crash_reporter_process(&self) -> bool {
64 std::env::args().any(|arg| arg.starts_with(&self.server_arg))
65 }
66
67 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
68 pub fn on_minidump<F>(mut self, on_minidump: F) -> Self
69 where
70 F: Fn(Vec<u8>, &Path) + Send + Sync + 'static,
71 {
72 self.on_minidump = Some(Box::new(on_minidump));
73 self
74 }
75
76 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
77 pub fn on_message<F>(mut self, on_message: F) -> Self
78 where
79 F: Fn(u32, Vec<u8>) + Send + Sync + 'static,
80 {
81 self.on_message = Some(Box::new(on_message));
82 self
83 }
84
85 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
86 pub fn with_crashes_dir(mut self, crashes_dir: PathBuf) -> Self {
87 self.crashes_dir = crashes_dir;
88 self
89 }
90
91 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
92 pub fn with_server_stale_timeout(mut self, server_stale_timeout: u64) -> Self {
93 self.server_stale_timeout = server_stale_timeout;
94 self
95 }
96
97 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
98 pub fn with_client_connect_timeout(mut self, client_connect_timeout: u64) -> Self {
99 self.client_connect_timeout = client_connect_timeout;
100 self
101 }
102
103 #[must_use = "You should call spawn() or the crash reporter won't be enabled"]
104 pub fn with_server_arg(mut self, server_arg: String) -> Self {
105 self.server_arg = server_arg;
106 self
107 }
108
109 #[must_use = "The return value of spawn() should not be dropped until the program exits"]
110 pub fn spawn(self) -> Result<ClientHandle, Error> {
111 if self.on_minidump.is_none() && self.on_message.is_none() {
112 panic!("You should set one of 'on_minidump' or 'on_message'");
113 }
114
115 let server_socket = std::env::args()
116 .find(|arg| arg.starts_with(&self.server_arg))
117 .and_then(|arg| arg.split('=').last().map(|arg| arg.to_string()));
118
119 if let Some(socket_name) = server_socket {
120 server::start(
121 &socket_name,
122 self.crashes_dir,
123 self.server_stale_timeout,
124 self.on_minidump,
125 self.on_message,
126 )?;
127
128 std::process::exit(0);
131 } else {
132 let socket_name = make_socket_name(uuid::Uuid::new_v4());
135
136 std::env::current_exe()
137 .and_then(|current_exe| {
138 process::Command::new(current_exe)
139 .arg(format!("{}={}", &self.server_arg, socket_name))
140 .spawn()
141 })
142 .map_err(Error::from)
143 .and_then(|server_process| {
144 client::start(
145 &socket_name,
146 self.client_connect_timeout,
147 server_process.id(),
148 self.server_stale_timeout / 2,
149 )
150 .map(|(client, handler)| ClientHandle {
151 client,
152 _handler: handler,
153 _child: server_process,
154 })
155 })
156 }
157 }
158}
159
160pub fn make_socket_name(session_id: uuid::Uuid) -> String {
161 if cfg!(any(target_os = "linux", target_os = "android")) {
162 format!("temp-socket-{}", session_id.simple())
163 } else {
164 let mut td = std::env::temp_dir();
169 td.push(format!("temp-socket-{}", session_id.simple()));
170 td.to_string_lossy().to_string()
171 }
172}