Skip to main content

libdd_crashtracker/shared/configuration/
mod.rs

1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3//
4mod builder;
5pub use builder::CrashtrackerConfigurationBuilder;
6use core::time::Duration;
7use libdd_common::Endpoint;
8use serde::{Deserialize, Serialize};
9
10/// Stacktrace collection occurs in the context of a crashing process.
11/// If the stack is sufficiently corruputed, it is possible (but unlikely),
12/// for stack trace collection itself to crash.
13/// We recommend fully enabling stacktrace collection, but having an environment
14/// variable to allow downgrading the collector.
15#[repr(C)]
16#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
17pub enum StacktraceCollection {
18    #[default]
19    Disabled,
20    WithoutSymbols,
21    /// This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information
22    /// and also unwind inlined functions. Enabling this feature will not only provide symbolic
23    /// details, but may also yield additional or less stack frames compared to other
24    /// configurations.
25    EnabledWithInprocessSymbols,
26    EnabledWithSymbolsInReceiver,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct CrashtrackerConfiguration {
31    // Paths to any additional files to track, if any
32    additional_files: Vec<String>,
33    #[serde(default)]
34    collect_all_threads: bool,
35    create_alt_stack: bool,
36    // Whether to demangle symbol names in stack traces
37    demangle_names: bool,
38    endpoint: Option<Endpoint>,
39    #[serde(default = "default_max_threads")]
40    max_threads: usize,
41    resolve_frames: StacktraceCollection,
42    signals: Vec<i32>,
43    timeout: Duration,
44    unix_socket_path: Option<String>,
45    #[serde(skip, default = "default_unix_socket_connector_value")]
46    unix_socket_connector: fn(&str) -> std::os::fd::RawFd,
47    use_alt_stack: bool,
48}
49
50impl PartialEq for CrashtrackerConfiguration {
51    fn eq(&self, other: &Self) -> bool {
52        self.additional_files == other.additional_files
53            && self.collect_all_threads == other.collect_all_threads
54            && self.create_alt_stack == other.create_alt_stack
55            && self.demangle_names == other.demangle_names
56            && self.endpoint == other.endpoint
57            && self.max_threads == other.max_threads
58            && self.resolve_frames == other.resolve_frames
59            && self.signals == other.signals
60            && self.timeout == other.timeout
61            && self.unix_socket_path == other.unix_socket_path
62            && self.use_alt_stack == other.use_alt_stack
63    }
64}
65
66pub const fn default_max_threads() -> usize {
67    256
68}
69
70pub fn default_unix_socket_connector(unix_socket_path: &str) -> std::os::fd::RawFd {
71    use std::os::fd::IntoRawFd;
72    use std::os::unix::net::UnixStream;
73    #[cfg(target_os = "linux")]
74    let stream = if unix_socket_path.starts_with(['.', '/']) {
75        UnixStream::connect(unix_socket_path)
76    } else {
77        use std::os::linux::net::SocketAddrExt;
78        match std::os::unix::net::SocketAddr::from_abstract_name(unix_socket_path) {
79            Ok(addr) => UnixStream::connect_addr(&addr),
80            Err(e) => Err(e),
81        }
82    };
83    #[cfg(not(target_os = "linux"))]
84    let stream = UnixStream::connect(unix_socket_path);
85    match stream {
86        Ok(s) => s.into_raw_fd(),
87        Err(_) => -1,
88    }
89}
90
91fn default_unix_socket_connector_value() -> fn(&str) -> std::os::fd::RawFd {
92    default_unix_socket_connector
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
96pub struct CrashtrackerReceiverConfig {
97    pub args: Vec<String>,
98    pub env: Vec<(String, String)>,
99    pub path_to_receiver_binary: String,
100    pub stderr_filename: Option<String>,
101    pub stdout_filename: Option<String>,
102}
103
104impl CrashtrackerReceiverConfig {
105    pub fn new(
106        args: Vec<String>,
107        env: Vec<(String, String)>,
108        path_to_receiver_binary: String,
109        stderr_filename: Option<String>,
110        stdout_filename: Option<String>,
111    ) -> anyhow::Result<Self> {
112        anyhow::ensure!(
113            stderr_filename.is_none() && stdout_filename.is_none()
114                || stderr_filename != stdout_filename,
115            "Can't give the same filename for stderr ({stderr_filename:?})
116        and stdout ({stdout_filename:?}), they will conflict with each other"
117        );
118
119        Ok(Self {
120            args,
121            env,
122            path_to_receiver_binary,
123            stderr_filename,
124            stdout_filename,
125        })
126    }
127}
128
129impl CrashtrackerConfiguration {
130    pub fn builder() -> CrashtrackerConfigurationBuilder {
131        CrashtrackerConfigurationBuilder::default()
132    }
133
134    pub fn additional_files(&self) -> &Vec<String> {
135        &self.additional_files
136    }
137
138    pub fn collect_all_threads(&self) -> bool {
139        self.collect_all_threads
140    }
141
142    pub fn create_alt_stack(&self) -> bool {
143        self.create_alt_stack
144    }
145
146    pub fn max_threads(&self) -> usize {
147        self.max_threads
148    }
149
150    pub fn use_alt_stack(&self) -> bool {
151        self.use_alt_stack
152    }
153
154    pub(crate) fn endpoint(&self) -> &Option<Endpoint> {
155        &self.endpoint
156    }
157
158    pub fn resolve_frames(&self) -> StacktraceCollection {
159        self.resolve_frames
160    }
161
162    pub fn signals(&self) -> &Vec<i32> {
163        &self.signals
164    }
165
166    pub fn timeout(&self) -> Duration {
167        self.timeout
168    }
169
170    pub fn unix_socket_path(&self) -> &Option<String> {
171        &self.unix_socket_path
172    }
173
174    pub fn unix_socket_connector(&self) -> fn(&str) -> std::os::fd::RawFd {
175        self.unix_socket_connector
176    }
177
178    pub fn demangle_names(&self) -> bool {
179        self.demangle_names
180    }
181
182    pub fn set_collect_all_threads(&mut self, collect: bool) {
183        self.collect_all_threads = collect;
184    }
185
186    pub fn set_max_threads(&mut self, max: usize) {
187        self.max_threads = max;
188    }
189
190    pub fn set_create_alt_stack(&mut self, create_alt_stack: bool) -> anyhow::Result<()> {
191        anyhow::ensure!(
192            !create_alt_stack || self.use_alt_stack,
193            "Cannot create an altstack without using it"
194        );
195        self.create_alt_stack = create_alt_stack;
196        Ok(())
197    }
198
199    pub fn set_use_alt_stack(&mut self, use_alt_stack: bool) -> anyhow::Result<()> {
200        anyhow::ensure!(
201            !self.create_alt_stack || use_alt_stack,
202            "Cannot create an altstack without using it"
203        );
204        self.use_alt_stack = use_alt_stack;
205        Ok(())
206    }
207
208    pub fn set_unix_socket_path(&mut self, path: String) {
209        self.unix_socket_path = Some(path);
210    }
211
212    pub fn set_unix_socket_connector(&mut self, connector: fn(&str) -> std::os::fd::RawFd) {
213        self.unix_socket_connector = connector;
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::CrashtrackerReceiverConfig;
220
221    #[test]
222    fn test_receiver_config_new() -> anyhow::Result<()> {
223        let args = vec!["foo".to_string()];
224        let env = vec![
225            ("bar".to_string(), "baz".to_string()),
226            ("apple".to_string(), "banana".to_string()),
227        ];
228        let path_to_receiver_binary = "/tmp/crashtracker-receiver-binary".to_string();
229        let stderr_filename = None;
230        let stdout_filename = None;
231
232        let config = CrashtrackerReceiverConfig::new(
233            args.clone(),
234            env.clone(),
235            path_to_receiver_binary.clone(),
236            stderr_filename.clone(),
237            stdout_filename.clone(),
238        )?;
239        assert_eq!(config.args, args);
240        assert_eq!(config.env, env);
241        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
242        assert_eq!(config.stderr_filename, stderr_filename);
243        assert_eq!(config.stdout_filename, stdout_filename);
244
245        let stderr_filename = None;
246        let stdout_filename = Some("/tmp/stdout.txt".to_string());
247        let config = CrashtrackerReceiverConfig::new(
248            args.clone(),
249            env.clone(),
250            path_to_receiver_binary.clone(),
251            stderr_filename.clone(),
252            stdout_filename.clone(),
253        )?;
254        assert_eq!(config.args, args);
255        assert_eq!(config.env, env);
256        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
257        assert_eq!(config.stderr_filename, stderr_filename);
258        assert_eq!(config.stdout_filename, stdout_filename);
259
260        let stderr_filename = Some("/tmp/stderr.txt".to_string());
261        let stdout_filename = None;
262        let config = CrashtrackerReceiverConfig::new(
263            args.clone(),
264            env.clone(),
265            path_to_receiver_binary.clone(),
266            stderr_filename.clone(),
267            stdout_filename.clone(),
268        )?;
269        assert_eq!(config.args, args);
270        assert_eq!(config.env, env);
271        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
272        assert_eq!(config.stderr_filename, stderr_filename);
273        assert_eq!(config.stdout_filename, stdout_filename);
274
275        let stderr_filename = Some("/tmp/stderr.txt".to_string());
276        let stdout_filename = Some("/tmp/stdout.txt".to_string());
277        let config = CrashtrackerReceiverConfig::new(
278            args.clone(),
279            env.clone(),
280            path_to_receiver_binary.clone(),
281            stderr_filename.clone(),
282            stdout_filename.clone(),
283        )?;
284        assert_eq!(config.args, args);
285        assert_eq!(config.env, env);
286        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
287        assert_eq!(config.stderr_filename, stderr_filename);
288        assert_eq!(config.stdout_filename, stdout_filename);
289
290        let stderr_filename = Some("/tmp/shared.txt".to_string());
291        let stdout_filename = Some("/tmp/shared.txt".to_string());
292        CrashtrackerReceiverConfig::new(
293            args.clone(),
294            env.clone(),
295            path_to_receiver_binary.clone(),
296            stderr_filename.clone(),
297            stdout_filename.clone(),
298        )
299        .unwrap_err();
300        Ok(())
301    }
302}