@@ -22,7 +22,8 @@ use std::ffi::{c_char, OsStr};
2222use std:: path:: Path ;
2323use std:: ptr:: null_mut;
2424use uuid:: Uuid ;
25-
25+ #[ cfg( unix) ]
26+ use datadog_sidecar:: crashtracker:: crashtracker_receiver_request_bytes;
2627pub use libdd_crashtracker_ffi:: * ;
2728pub use libdd_library_config_ffi:: * ;
2829pub use datadog_sidecar_ffi:: * ;
@@ -196,26 +197,144 @@ pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_agent_url(url: CharS
196197 }
197198}
198199
199- #[ no_mangle]
200+ /// Initialize crashtracking, selecting the receiver strategy for this process:
201+ /// - Linux, sidecar host (`master_pid == getpid()`): the in-process thread-mode sidecar can't
202+ /// serve its own crash, so spawn a fork+exec subprocess receiver (like the standalone daemon),
203+ /// resolving frames there since a crashing process can't reliably symbolize itself.
204+ /// - Linux, worker/collector: connect to the sidecar IPC socket and upgrade it to a crashtracker
205+ /// receiver on crash (`SOCK_SEQPACKET` + `enter_crashtracker_receiver`), streaming the report
206+ /// over that single socket and resolving frames in-process.
207+ /// - other unix (macOS): no sidecar upgrade; the default connector reaches the socket path.
208+ ///
209+ /// `master_pid` is the thread-mode master listener PID (0 if none): it keys the IPC socket and, on
210+ /// Linux, distinguishes the host from a worker.
211+ ///
212+ /// # Safety
213+ /// `endpoint` must point to a valid `Endpoint`; `metadata`'s borrowed strings/tags must outlive the
214+ /// call (they are copied into owned storage before it returns).
200215#[ cfg( unix) ]
201- pub unsafe extern "C" fn datadog_endpoint_as_crashtracker_config (
216+ #[ no_mangle]
217+ #[ allow( clippy:: missing_safety_doc) ]
218+ pub unsafe extern "C" fn datadog_crashtracker_init (
202219 endpoint : & Endpoint ,
203- callback : unsafe extern "C" fn ( EndpointConfig < ' _ > , * mut std:: ffi:: c_void ) ,
204- userdata : * mut std:: ffi:: c_void ,
205- ) {
206- let url_str = endpoint. url . to_string ( ) ;
207- unsafe {
208- callback (
209- EndpointConfig {
210- url : CharSlice :: from ( url_str. as_str ( ) ) ,
211- api_key : CharSlice :: from ( endpoint. api_key . as_deref ( ) . unwrap_or ( "" ) ) ,
212- test_token : CharSlice :: from ( endpoint. test_token . as_deref ( ) . unwrap_or ( "" ) ) ,
213- timeout : endpoint. timeout_ms ,
214- use_system_resolver : endpoint. use_system_resolver ,
215- } ,
216- userdata,
217- ) ;
220+ metadata : Metadata ,
221+ master_pid : i32 ,
222+ ) -> MaybeError {
223+ use libdd_crashtracker:: { CrashtrackerConfiguration , StacktraceCollection } ;
224+
225+ let result = ( || -> anyhow:: Result < ( ) > {
226+ let metadata: libdd_crashtracker:: Metadata = metadata. try_into ( ) ?;
227+
228+ let mut builder = CrashtrackerConfiguration :: builder ( )
229+ . collect_all_threads ( true )
230+ . timeout ( std:: time:: Duration :: from_millis ( 5000 ) )
231+ . endpoint_use_system_resolver ( endpoint. use_system_resolver )
232+ . endpoint_url ( & endpoint. url . to_string ( ) ) ;
233+ if let Some ( api_key) = endpoint. api_key . as_deref ( ) {
234+ builder = builder. endpoint_api_key ( api_key) ;
235+ }
236+ if let Some ( test_token) = endpoint. test_token . as_deref ( ) {
237+ builder = builder. endpoint_test_token ( test_token) ;
238+ }
239+ if endpoint. timeout_ms != 0 {
240+ builder = builder. endpoint_timeout_ms ( endpoint. timeout_ms ) ;
241+ }
242+
243+ #[ cfg( target_os = "linux" ) ]
244+ {
245+ // Worker/collector: open a fresh connection to the sidecar IPC socket, upgrade it with
246+ // the SEQPACKET connector, and resolve frames in-process.
247+ if master_pid == 0 || master_pid != std:: process:: id ( ) as i32 {
248+ let socket_path = datadog_sidecar:: crashtracker:: crashtracker_ipc_socket_path (
249+ master_pid as u32 ,
250+ datadog_sidecar:: config:: FromEnv :: ipc_mode ( ) ,
251+ ) ;
252+ // Prime the request bytes outside the crash handler so the connector never
253+ // allocates in signal context.
254+ let _ = crashtracker_receiver_request_bytes ( ) ;
255+ let config = builder
256+ . resolve_frames ( StacktraceCollection :: EnabledWithInprocessSymbols )
257+ . unix_socket_path ( socket_path. to_string_lossy ( ) . into_owned ( ) )
258+ . unix_socket_connector (
259+ datadog_sidecar:: crashtracker:: connect_to_sidecar_receiver,
260+ )
261+ . build ( ) ?;
262+ return libdd_crashtracker:: init (
263+ config,
264+ libdd_crashtracker:: CrashtrackerReceiverConfig :: default ( ) ,
265+ metadata,
266+ ) ;
267+ }
268+ // Thread-mode host: its in-process sidecar can't serve its own crash, so spawn a
269+ // transient fork+exec subprocess receiver and resolve frames there.
270+ let config = builder
271+ . resolve_frames ( StacktraceCollection :: EnabledWithSymbolsInReceiver )
272+ . build ( ) ?;
273+ let receiver_config =
274+ datadog_sidecar:: build_crashtracker_receiver_config ( None , None ) ?;
275+ libdd_crashtracker:: init ( config, receiver_config, metadata)
276+ }
277+
278+ // macOS can't open a fresh SOCK_SEQPACKET connection signal-safely, so reuse the
279+ // already-open sidecar fd and upgrade it at crash time (no-op if there's no connection).
280+ // The path is a placeholder the connector ignores; it just has to be non-empty so the
281+ // crashtracker takes the connector path.
282+ #[ cfg( target_os = "macos" ) ]
283+ {
284+ // Prime the request bytes outside the crash handler so the connector never
285+ // allocates in signal context.
286+ let _ = crashtracker_receiver_request_bytes ( ) ;
287+ let config = builder
288+ . resolve_frames ( StacktraceCollection :: EnabledWithInprocessSymbols )
289+ . unix_socket_path ( "datadog-sidecar-crashtracker" . to_string ( ) )
290+ . unix_socket_connector ( reuse_sidecar_fd_connector)
291+ . build ( ) ?;
292+ libdd_crashtracker:: init (
293+ config,
294+ libdd_crashtracker:: CrashtrackerReceiverConfig :: default ( ) ,
295+ metadata,
296+ )
297+ }
298+
299+ #[ cfg( not( any( target_os = "linux" , target_os = "macos" ) ) ) ]
300+ {
301+ let _ = ( master_pid, builder, metadata) ;
302+ Ok ( ( ) )
303+ }
304+ } ) ( ) ;
305+ match result {
306+ Ok ( ( ) ) => MaybeError :: None ,
307+ Err ( e) => {
308+ MaybeError :: Some ( Error :: from ( format ! ( "{e:?}" ) ) )
309+ }
310+ }
311+ }
312+
313+ /// On macos we cannot easily create a new signal safe connection to the sidecar, so we reuse the
314+ /// already open fd from datadog_sidecar_for_signal.
315+ #[ cfg( target_os = "macos" ) ]
316+ fn reuse_sidecar_fd_connector ( _unix_socket_path : & str ) -> std:: os:: fd:: RawFd {
317+ extern "C" {
318+ // Set by the sidecar connect path (sidecar.c) to the live transport for best-effort
319+ // signal-handler use; null when there is no connection. The transport pointer is stable
320+ // across transparent reconnects (only its inner sender is swapped), so reading the fd
321+ // through it stays current. Typed as an opaque pointer to keep the `extern` block FFI-safe;
322+ // cast to the real type below.
323+ static mut datadog_sidecar_for_signal: * mut std:: ffi:: c_void ;
324+ }
325+
326+ // Best-effort, signal context: read the transport pointer and get its current fd via
327+ // SidecarTransport::signal_fd (which uses get_mut, never locking). Going through the raw
328+ // pointer knowingly bypasses aliasing checks — the crashing thread is the only realistic
329+ // accessor.
330+ let transport = unsafe { datadog_sidecar_for_signal }
331+ as * mut datadog_sidecar:: service:: blocking:: SidecarTransport ;
332+ if transport. is_null ( ) {
333+ return -1 ;
218334 }
335+ let fd = unsafe { ( * transport) . as_raw_fd ( ) } ;
336+ let bytes = crashtracker_receiver_request_bytes ( ) ;
337+ let sent = unsafe { libc:: send ( dup, bytes. as_ptr ( ) as * const libc:: c_void , bytes. len ( ) , 0 ) } ;
219338}
220339
221340// Hack: Without this, the PECL build of the tracer does not contain the ddog_library_* functions
0 commit comments