Skip to main content

libdd_crashtracker/shared/
configuration.rs

1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3use crate::{default_signals, shared::constants, signal_from_signum};
4use libdd_common::Endpoint;
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8/// Stacktrace collection occurs in the context of a crashing process.
9/// If the stack is sufficiently corruputed, it is possible (but unlikely),
10/// for stack trace collection itself to crash.
11/// We recommend fully enabling stacktrace collection, but having an environment
12/// variable to allow downgrading the collector.
13#[repr(C)]
14#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum StacktraceCollection {
16    /// Stacktrace collection occurs in the
17    Disabled,
18    WithoutSymbols,
19    /// This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information
20    /// and also unwind inlined functions. Enabling this feature will not only provide symbolic
21    /// details, but may also yield additional or less stack frames compared to other
22    /// configurations.
23    EnabledWithInprocessSymbols,
24    EnabledWithSymbolsInReceiver,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct CrashtrackerConfiguration {
29    // Paths to any additional files to track, if any
30    additional_files: Vec<String>,
31    create_alt_stack: bool,
32    // Whether to demangle symbol names in stack traces
33    demangle_names: bool,
34    endpoint: Option<Endpoint>,
35    resolve_frames: StacktraceCollection,
36    signals: Vec<i32>,
37    timeout: Duration,
38    unix_socket_path: Option<String>,
39    use_alt_stack: bool,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
43pub struct CrashtrackerReceiverConfig {
44    pub args: Vec<String>,
45    pub env: Vec<(String, String)>,
46    pub path_to_receiver_binary: String,
47    pub stderr_filename: Option<String>,
48    pub stdout_filename: Option<String>,
49}
50
51impl CrashtrackerReceiverConfig {
52    pub fn new(
53        args: Vec<String>,
54        env: Vec<(String, String)>,
55        path_to_receiver_binary: String,
56        stderr_filename: Option<String>,
57        stdout_filename: Option<String>,
58    ) -> anyhow::Result<Self> {
59        anyhow::ensure!(
60            stderr_filename.is_none() && stdout_filename.is_none()
61                || stderr_filename != stdout_filename,
62            "Can't give the same filename for stderr ({stderr_filename:?})
63        and stdout ({stdout_filename:?}), they will conflict with each other"
64        );
65
66        Ok(Self {
67            args,
68            env,
69            path_to_receiver_binary,
70            stderr_filename,
71            stdout_filename,
72        })
73    }
74}
75
76impl CrashtrackerConfiguration {
77    #[allow(clippy::too_many_arguments)]
78    pub fn new(
79        additional_files: Vec<String>,
80        create_alt_stack: bool,
81        use_alt_stack: bool,
82        endpoint: Option<Endpoint>,
83        resolve_frames: StacktraceCollection,
84        mut signals: Vec<i32>,
85        timeout: Option<Duration>,
86        unix_socket_path: Option<String>,
87        demangle_names: bool,
88    ) -> anyhow::Result<Self> {
89        // Requesting to create, but not use, the altstack is considered paradoxical.
90        anyhow::ensure!(
91            !create_alt_stack || use_alt_stack,
92            "Cannot create an altstack without using it"
93        );
94        let timeout = timeout.unwrap_or(constants::DD_CRASHTRACK_DEFAULT_TIMEOUT);
95        if signals.is_empty() {
96            signals = default_signals();
97        } else {
98            // Ensure we don't have double elements in the signals list.
99            let before_len = signals.len();
100            signals.sort();
101            signals.dedup();
102            anyhow::ensure!(
103                before_len == signals.len(),
104                "Signals contained duplicate elements"
105            );
106            // Ensure that all signal values translate to a valid signum
107            signals
108                .iter()
109                .try_for_each(|x| signal_from_signum(*x).map(|_| ()))?;
110        }
111
112        // Note:  don't check the receiver socket upfront, since a configuration can be interned
113        // before the receiver is started when using an async-receiver.
114        Ok(Self {
115            additional_files,
116            create_alt_stack,
117            use_alt_stack,
118            endpoint,
119            resolve_frames,
120            signals,
121            timeout,
122            unix_socket_path,
123            demangle_names,
124        })
125    }
126
127    pub fn additional_files(&self) -> &Vec<String> {
128        &self.additional_files
129    }
130
131    pub fn create_alt_stack(&self) -> bool {
132        self.create_alt_stack
133    }
134
135    pub fn use_alt_stack(&self) -> bool {
136        self.use_alt_stack
137    }
138
139    pub fn endpoint(&self) -> &Option<Endpoint> {
140        &self.endpoint
141    }
142
143    pub fn resolve_frames(&self) -> StacktraceCollection {
144        self.resolve_frames
145    }
146
147    pub fn signals(&self) -> &Vec<i32> {
148        &self.signals
149    }
150
151    pub fn timeout(&self) -> Duration {
152        self.timeout
153    }
154
155    pub fn unix_socket_path(&self) -> &Option<String> {
156        &self.unix_socket_path
157    }
158
159    pub fn demangle_names(&self) -> bool {
160        self.demangle_names
161    }
162
163    pub fn set_create_alt_stack(&mut self, create_alt_stack: bool) -> anyhow::Result<()> {
164        anyhow::ensure!(
165            !create_alt_stack || self.use_alt_stack,
166            "Cannot create an altstack without using it"
167        );
168        self.create_alt_stack = create_alt_stack;
169        Ok(())
170    }
171
172    pub fn set_use_alt_stack(&mut self, use_alt_stack: bool) -> anyhow::Result<()> {
173        anyhow::ensure!(
174            !self.create_alt_stack || use_alt_stack,
175            "Cannot create an altstack without using it"
176        );
177        self.use_alt_stack = use_alt_stack;
178        Ok(())
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::CrashtrackerReceiverConfig;
185
186    #[test]
187    fn test_receiver_config_new() -> anyhow::Result<()> {
188        let args = vec!["foo".to_string()];
189        let env = vec![
190            ("bar".to_string(), "baz".to_string()),
191            ("apple".to_string(), "banana".to_string()),
192        ];
193        let path_to_receiver_binary = "/tmp/crashtracker-receiver-binary".to_string();
194        let stderr_filename = None;
195        let stdout_filename = None;
196
197        let config = CrashtrackerReceiverConfig::new(
198            args.clone(),
199            env.clone(),
200            path_to_receiver_binary.clone(),
201            stderr_filename.clone(),
202            stdout_filename.clone(),
203        )?;
204        assert_eq!(config.args, args);
205        assert_eq!(config.env, env);
206        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
207        assert_eq!(config.stderr_filename, stderr_filename);
208        assert_eq!(config.stdout_filename, stdout_filename);
209
210        let stderr_filename = None;
211        let stdout_filename = Some("/tmp/stdout.txt".to_string());
212        let config = CrashtrackerReceiverConfig::new(
213            args.clone(),
214            env.clone(),
215            path_to_receiver_binary.clone(),
216            stderr_filename.clone(),
217            stdout_filename.clone(),
218        )?;
219        assert_eq!(config.args, args);
220        assert_eq!(config.env, env);
221        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
222        assert_eq!(config.stderr_filename, stderr_filename);
223        assert_eq!(config.stdout_filename, stdout_filename);
224
225        let stderr_filename = Some("/tmp/stderr.txt".to_string());
226        let stdout_filename = None;
227        let config = CrashtrackerReceiverConfig::new(
228            args.clone(),
229            env.clone(),
230            path_to_receiver_binary.clone(),
231            stderr_filename.clone(),
232            stdout_filename.clone(),
233        )?;
234        assert_eq!(config.args, args);
235        assert_eq!(config.env, env);
236        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
237        assert_eq!(config.stderr_filename, stderr_filename);
238        assert_eq!(config.stdout_filename, stdout_filename);
239
240        let stderr_filename = Some("/tmp/stderr.txt".to_string());
241        let stdout_filename = Some("/tmp/stdout.txt".to_string());
242        let config = CrashtrackerReceiverConfig::new(
243            args.clone(),
244            env.clone(),
245            path_to_receiver_binary.clone(),
246            stderr_filename.clone(),
247            stdout_filename.clone(),
248        )?;
249        assert_eq!(config.args, args);
250        assert_eq!(config.env, env);
251        assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
252        assert_eq!(config.stderr_filename, stderr_filename);
253        assert_eq!(config.stdout_filename, stdout_filename);
254
255        let stderr_filename = Some("/tmp/shared.txt".to_string());
256        let stdout_filename = Some("/tmp/shared.txt".to_string());
257        CrashtrackerReceiverConfig::new(
258            args.clone(),
259            env.clone(),
260            path_to_receiver_binary.clone(),
261            stderr_filename.clone(),
262            stdout_filename.clone(),
263        )
264        .unwrap_err();
265        Ok(())
266    }
267}