@@ -8,8 +8,10 @@ use super::receiver_manager::Receiver;
88use super :: signal_handler_manager:: chain_signal_handler;
99use crate :: crash_info:: Metadata ;
1010use crate :: shared:: configuration:: CrashtrackerConfiguration ;
11+ use crate :: StackTrace ;
1112use libc:: { c_void, siginfo_t, ucontext_t} ;
1213use libdd_common:: timeout:: TimeoutManager ;
14+ use std:: os:: unix:: { io:: FromRawFd , net:: UnixStream } ;
1315use std:: panic;
1416use std:: panic:: PanicHookInfo ;
1517use std:: ptr;
@@ -301,6 +303,121 @@ fn handle_posix_signal_impl(
301303 Ok ( ( ) )
302304}
303305
306+ /// Gets a clone of the current metadata, if set.
307+ /// Unlike the signal handler path, this reads without consuming the stored value.
308+ ///
309+ /// SAFETY:
310+ /// This function must not be called concurrently with `update_metadata`.
311+ fn get_metadata ( ) -> Option < ( crate :: crash_info:: Metadata , String ) > {
312+ let ptr = METADATA . load ( SeqCst ) ;
313+ if ptr. is_null ( ) {
314+ None
315+ } else {
316+ // Safety: ptr was created by Box::into_raw in update_metadata
317+ let ( metadata, metadata_string) = unsafe { & * ptr } ;
318+ Some ( ( metadata. clone ( ) , metadata_string. clone ( ) ) )
319+ }
320+ }
321+
322+ /// Gets a clone of the current config, if set.
323+ /// Unlike the signal handler path, this reads without consuming the stored value.
324+ ///
325+ /// SAFETY:
326+ /// This function must not be called concurrently with `update_config`.
327+ fn get_config ( ) -> Option < (
328+ crate :: shared:: configuration:: CrashtrackerConfiguration ,
329+ String ,
330+ ) > {
331+ let ptr = CONFIG . load ( SeqCst ) ;
332+ if ptr. is_null ( ) {
333+ None
334+ } else {
335+ // Safety: ptr was created by Box::into_raw in update_config
336+ let ( config, config_string) = unsafe { & * ptr } ;
337+ Some ( ( config. clone ( ) , config_string. clone ( ) ) )
338+ }
339+ }
340+
341+ /// This function is designed to be when a program is at a terminal state
342+ /// and the application wants to report an unhandled exception to the crashtracker
343+ ///
344+ /// Preconditions:
345+ /// - The crashtracker must be started
346+ /// - The stacktrace must be valid
347+ ///
348+ /// This function will spawn the receiver process and call an emit function to pipe over
349+ /// the crash data. We don't use the collector process because we are not in a signal handler
350+ /// Rather, we call emit_crashreport directly and pipe over data to the receiver
351+ pub fn report_unhandled_exception (
352+ exception_type : Option < & str > ,
353+ exception_message : Option < & str > ,
354+ stacktrace : StackTrace ,
355+ ) -> Result < ( ) , CrashHandlerError > {
356+ let Some ( ( config, config_str) ) = get_config ( ) else {
357+ return Err ( CrashHandlerError :: NoConfig ) ;
358+ } ;
359+ let Some ( ( _metadata, metadata_str) ) = get_metadata ( ) else {
360+ return Err ( CrashHandlerError :: NoMetadata ) ;
361+ } ;
362+
363+ // Turn crashtracker off to prevent a recursive crash report emission
364+ // We do not turn it back on because this function is not intended to be used as
365+ // a recurring mechanism to report exceptions. We expect the application to exit
366+ // after
367+ disable ( ) ;
368+
369+ let unix_socket_path = config. unix_socket_path ( ) . as_deref ( ) . unwrap_or_default ( ) ;
370+
371+ let receiver = if unix_socket_path. is_empty ( ) {
372+ Receiver :: spawn_from_stored_config ( ) ?
373+ } else {
374+ Receiver :: from_socket ( unix_socket_path) ?
375+ } ;
376+
377+ let timeout_manager = TimeoutManager :: new ( config. timeout ( ) ) ;
378+
379+ let pid = unsafe { libc:: getpid ( ) } ;
380+ let tid = libdd_common:: threading:: get_current_thread_id ( ) as libc:: pid_t ;
381+
382+ let error_type_str = exception_type. unwrap_or ( "<unknown>" ) ;
383+ let error_message_str = exception_message. unwrap_or ( "<no message>" ) ;
384+ let message = format ! (
385+ "Process was terminated due to an unhandled exception of type '{error_type_str}'. \
386+ Message: \" {error_message_str}\" "
387+ ) ;
388+
389+ let message_ptr = Box :: into_raw ( Box :: new ( message) ) ;
390+
391+ // Duplicate the socket fd so we can poll for receiver completion after we close the write end.
392+ // UnixStream::from_raw_fd takes ownership of uds_fd, so we need a separate fd to poll.
393+ let poll_fd = unsafe { libc:: dup ( receiver. handle . uds_fd ) } ;
394+ let receiver_pid = receiver. handle . pid ;
395+
396+ {
397+ let mut unix_stream = unsafe { UnixStream :: from_raw_fd ( receiver. handle . uds_fd ) } ;
398+ let _ = super :: emitters:: emit_crashreport (
399+ & mut unix_stream,
400+ & config,
401+ & config_str,
402+ & metadata_str,
403+ message_ptr,
404+ None ,
405+ Some ( stacktrace) ,
406+ None ,
407+ pid,
408+ tid,
409+ ) ;
410+ // unix_stream is dropped here, closing the write end of the socket.
411+ // This signals EOF to the receiver so it can finish writing the crash report.
412+ }
413+
414+ // Wait for the receiver to signal it is done (POLLHUP on the dup'd fd), then reap it.
415+ let finish_handle = super :: process_handle:: ProcessHandle :: new ( poll_fd, receiver_pid) ;
416+ finish_handle. finish ( & timeout_manager) ;
417+ unsafe { libc:: close ( poll_fd) } ;
418+
419+ Ok ( ( ) )
420+ }
304421#[ cfg( test) ]
305422mod tests {
306423 use super :: * ;
0 commit comments