minidumper_child/
lib.rs

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            // We force exit so that the app code after here does not run in the
129            // crash reporter process.
130            std::process::exit(0);
131        } else {
132            // We use a unique socket name because we don't share the crash reporter
133            // processes between different instances of the app.
134            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        // For platforms without abstract uds, put the pipe in the
165        // temporary directory so that the OS can clean it up, rather than
166        // polluting the cwd due to annoying file deletion problems,
167        // particularly on Windows
168        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}