2022-05-14 02:36:12 +00:00
# include <unistd.h>
2024-06-17 22:09:30 +00:00
# include <cstring>
# include <cerrno>
# include <cstdlib>
# include <cstdio>
# include <cstdint>
# include <csignal>
2022-05-14 02:36:12 +00:00
# include <poll.h>
2022-05-29 22:44:27 +00:00
# include <sys/ioctl.h>
2022-07-14 03:05:59 +00:00
# include <sys/stat.h>
2024-06-08 20:30:39 +00:00
# include <sys/prctl.h>
2024-06-17 22:09:30 +00:00
# include <ctime>
# include <string>
2022-05-14 02:36:12 +00:00
# include <libreborn/libreborn.h>
# include "crash-report.h"
// Show Crash Report Dialog
# define DIALOG_TITLE "Crash Report"
# define CRASH_REPORT_DIALOG_WIDTH "640"
# define CRASH_REPORT_DIALOG_HEIGHT "480"
static void show_report ( const char * log_filename ) {
2022-10-01 05:37:20 +00:00
// Fork
pid_t pid = fork ( ) ;
if ( pid = = 0 ) {
// Child
setsid ( ) ;
2022-10-02 04:47:11 +00:00
ALLOC_CHECK ( freopen ( " /dev/null " , " w " , stdout ) ) ;
ALLOC_CHECK ( freopen ( " /dev/null " , " w " , stderr ) ) ;
ALLOC_CHECK ( freopen ( " /dev/null " , " r " , stdin ) ) ;
2022-10-01 05:37:20 +00:00
const char * command [ ] = {
" zenity " ,
" --title " , DIALOG_TITLE ,
" --name " , MCPI_APP_ID ,
" --width " , CRASH_REPORT_DIALOG_WIDTH ,
" --height " , CRASH_REPORT_DIALOG_HEIGHT ,
" --text-info " ,
2024-06-15 12:52:15 +00:00
" --text " , MCPI_APP_TITLE " has crashed! \n \n Need help? Consider asking on the <a href= \" " MCPI_DISCORD_INVITE " \" >Discord server</a>! <i>If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.</i> " ,
2022-10-01 05:37:20 +00:00
" --filename " , log_filename ,
" --no-wrap " ,
" --font " , " Monospace " ,
2022-10-07 03:19:43 +00:00
" --save-filename " , MCPI_VARIANT_NAME " -crash-report.log " ,
" --ok-label " , " Exit " ,
2022-10-01 05:37:20 +00:00
NULL
} ;
safe_execvpe ( command , ( const char * const * ) environ ) ;
}
2022-05-14 02:36:12 +00:00
}
// Exit Handler
2024-06-17 22:09:30 +00:00
static pid_t child_pid = - 1 ;
2022-05-14 02:36:12 +00:00
static void exit_handler ( __attribute__ ( ( unused ) ) int signal ) {
// Murder
2024-06-17 22:09:30 +00:00
kill ( child_pid , SIGTERM ) ;
2022-05-14 02:36:12 +00:00
}
2024-06-17 22:09:30 +00:00
// Log File
static std : : string log_filename ;
static int log_fd ;
static void setup_log_file ( ) {
// Get Log Directory
const std : : string home = std : : string ( getenv ( _MCPI_HOME_ENV ) ) + get_home_subdirectory_for_game_data ( ) ;
ensure_directory ( home . c_str ( ) ) ;
const std : : string logs = home + " /logs " ;
ensure_directory ( logs . c_str ( ) ) ;
// Get Timestamp
time_t raw_time ;
time ( & raw_time ) ;
2024-08-23 09:18:20 +00:00
const tm * time_info = localtime ( & raw_time ) ;
2024-06-17 22:09:30 +00:00
char time [ 512 ] ;
strftime ( time , 512 , " %Y-%m-%d " , time_info ) ;
// Get Log Filename
std : : string file ;
int num = 1 ;
do {
file = std : : string ( time ) + ' - ' + std : : to_string ( num ) + " .log " ;
log_filename = logs + ' / ' + file ;
num + + ;
} while ( access ( log_filename . c_str ( ) , F_OK ) ! = - 1 ) ;
// Create latest.log Symlink
const std : : string latest_log = logs + " /latest.log " ;
unlink ( latest_log . c_str ( ) ) ;
if ( symlink ( file . c_str ( ) , latest_log . c_str ( ) ) ! = 0 ) {
WARN ( " Unable To Create Latest Log Symlink: %s " , strerror ( errno ) ) ;
2022-10-07 03:19:43 +00:00
}
2024-06-17 22:09:30 +00:00
// Create File
log_fd = open ( log_filename . c_str ( ) , O_RDWR | O_CREAT , S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) ;
if ( log_fd = = - 1 ) {
2022-10-02 04:47:11 +00:00
ERR ( " Unable To Create Log File: %s " , strerror ( errno ) ) ;
}
2024-06-17 22:09:30 +00:00
reborn_set_log ( log_fd ) ;
}
// Setup
# define PIPE_READ 0
# define PIPE_WRITE 1
# define BUFFER_SIZE 1024
static void safe_write ( int fd , const void * buf , size_t size ) {
const ssize_t bytes_written = write ( fd , buf , size ) ;
if ( bytes_written < 0 ) {
ERR ( " Unable To Write Data: %s " , strerror ( errno ) ) ;
}
2022-10-02 04:47:11 +00:00
}
2022-05-14 02:36:12 +00:00
void setup_crash_report ( ) {
2024-06-17 22:09:30 +00:00
// Setup Logging
setup_log_file ( ) ;
2022-05-14 02:36:12 +00:00
// Store Output
int output_pipe [ 2 ] ;
safe_pipe2 ( output_pipe , 0 ) ;
int error_pipe [ 2 ] ;
safe_pipe2 ( error_pipe , 0 ) ;
2022-05-29 22:44:27 +00:00
int input_pipe [ 2 ] ;
safe_pipe2 ( input_pipe , 0 ) ;
2022-05-14 02:36:12 +00:00
// Fork
pid_t ret = fork ( ) ;
if ( ret = = - 1 ) {
ERR ( " Unable To Fork: %s " , strerror ( errno ) ) ;
} else if ( ret = = 0 ) {
// Child Process
// Pipe stdio
2022-05-29 22:44:27 +00:00
dup2 ( output_pipe [ PIPE_WRITE ] , STDOUT_FILENO ) ;
close ( output_pipe [ PIPE_READ ] ) ;
close ( output_pipe [ PIPE_WRITE ] ) ;
dup2 ( error_pipe [ PIPE_WRITE ] , STDERR_FILENO ) ;
close ( error_pipe [ PIPE_READ ] ) ;
close ( error_pipe [ PIPE_WRITE ] ) ;
dup2 ( input_pipe [ PIPE_READ ] , STDIN_FILENO ) ;
close ( input_pipe [ PIPE_READ ] ) ;
close ( input_pipe [ PIPE_WRITE ] ) ;
2022-05-14 02:36:12 +00:00
// Create New Process Group
setpgid ( 0 , 0 ) ;
2024-06-08 20:30:39 +00:00
// Kill Child If Parent Exits First
prctl ( PR_SET_PDEATHSIG , SIGKILL ) ;
2022-05-14 02:36:12 +00:00
// Continue Execution
} else {
// Install Signal Handlers
2024-06-17 22:09:30 +00:00
child_pid = ret ;
struct sigaction act_sigint = { } ;
2022-05-14 02:36:12 +00:00
act_sigint . sa_flags = SA_RESTART ;
act_sigint . sa_handler = & exit_handler ;
2024-06-17 22:09:30 +00:00
sigaction ( SIGINT , & act_sigint , nullptr ) ;
struct sigaction act_sigterm = { } ;
2022-05-14 02:36:12 +00:00
act_sigterm . sa_flags = SA_RESTART ;
act_sigterm . sa_handler = & exit_handler ;
2024-06-17 22:09:30 +00:00
sigaction ( SIGTERM , & act_sigterm , nullptr ) ;
2022-05-14 02:36:12 +00:00
// Close Unneeded File Descriptors
2022-05-29 22:44:27 +00:00
close ( output_pipe [ PIPE_WRITE ] ) ;
close ( error_pipe [ PIPE_WRITE ] ) ;
close ( input_pipe [ PIPE_READ ] ) ;
2022-05-14 02:36:12 +00:00
2022-09-22 21:43:21 +00:00
// Set Debug Tag
reborn_debug_tag = " (Crash Reporter) " ;
2022-05-14 02:36:12 +00:00
// Setup Polling
2024-06-17 22:09:30 +00:00
const int number_fds = 3 ;
pollfd poll_fds [ number_fds ] ;
2022-05-29 22:44:27 +00:00
poll_fds [ 0 ] . fd = output_pipe [ PIPE_READ ] ;
poll_fds [ 1 ] . fd = error_pipe [ PIPE_READ ] ;
poll_fds [ 2 ] . fd = STDIN_FILENO ;
2024-06-17 22:09:30 +00:00
for ( pollfd & poll_fd : poll_fds ) {
poll_fd . events = POLLIN ;
2022-05-14 02:36:12 +00:00
}
// Poll Data
2022-10-02 04:47:11 +00:00
int status ;
while ( waitpid ( ret , & status , WNOHANG ) ! = ret ) {
2024-06-17 22:09:30 +00:00
const int poll_ret = poll ( poll_fds , number_fds , - 1 ) ;
2022-05-14 02:36:12 +00:00
if ( poll_ret = = - 1 ) {
if ( errno = = EINTR ) {
continue ;
} else {
ERR ( " Unable To Poll Data: %s " , strerror ( errno ) ) ;
}
}
// Handle Data
2024-06-17 22:09:30 +00:00
for ( pollfd & poll_fd : poll_fds ) {
if ( poll_fd . revents ! = 0 ) {
if ( poll_fd . revents & POLLIN ) {
char buf [ BUFFER_SIZE ] ;
if ( poll_fd . fd = = STDIN_FILENO ) {
2022-05-29 22:44:27 +00:00
// Data Available From stdin
int bytes_available ;
if ( ioctl ( fileno ( stdin ) , FIONREAD , & bytes_available ) = = - 1 ) {
bytes_available = 0 ;
}
// Read
2024-06-17 22:09:30 +00:00
const ssize_t bytes_read = read ( poll_fd . fd , buf , BUFFER_SIZE ) ;
2022-05-29 22:44:27 +00:00
if ( bytes_read = = - 1 ) {
2024-05-14 05:23:16 +00:00
ERR ( " Unable To Read Input: %s " , strerror ( errno ) ) ;
2022-05-29 22:44:27 +00:00
}
// Write To Child
2024-06-17 22:09:30 +00:00
safe_write ( input_pipe [ PIPE_WRITE ] , buf , bytes_read ) ;
2022-05-29 22:44:27 +00:00
} else {
// Data Available From Child's stdout/stderr
2024-06-17 22:09:30 +00:00
const ssize_t bytes_read = read ( poll_fd . fd , buf , BUFFER_SIZE ) ;
2022-05-29 22:44:27 +00:00
if ( bytes_read = = - 1 ) {
ERR ( " Unable To Read Log Data: %s " , strerror ( errno ) ) ;
}
// Print To Terminal
2024-06-17 22:09:30 +00:00
safe_write ( poll_fd . fd = = output_pipe [ PIPE_READ ] ? STDOUT_FILENO : STDERR_FILENO , buf , bytes_read ) ;
2022-05-29 22:44:27 +00:00
// Write To log
2024-06-17 22:09:30 +00:00
safe_write ( reborn_get_log_fd ( ) , buf , bytes_read ) ;
2022-05-14 02:36:12 +00:00
}
} else {
// File Descriptor No Longer Accessible
2024-06-17 22:09:30 +00:00
poll_fd . events = 0 ;
2022-05-14 02:36:12 +00:00
}
}
}
}
2022-05-29 22:44:27 +00:00
2022-10-02 04:47:11 +00:00
// Close Pipes
close ( output_pipe [ PIPE_READ ] ) ;
close ( error_pipe [ PIPE_READ ] ) ;
close ( input_pipe [ PIPE_WRITE ] ) ;
2022-05-14 02:36:12 +00:00
// Check If Is Crash
2024-06-17 22:09:30 +00:00
const bool is_crash = ! is_exit_status_success ( status ) ;
2022-05-14 02:36:12 +00:00
// Log Exit Code To log If Crash
if ( is_crash ) {
// Create Exit Code Log Line
2024-06-17 22:09:30 +00:00
char * exit_status = nullptr ;
2022-05-15 17:51:28 +00:00
get_exit_status_string ( status , & exit_status ) ;
2024-06-17 22:09:30 +00:00
const std : : string exit_code_line = " [CRASH]: Terminated " + std : : string ( exit_status ) + ' \n ' ;
2022-05-15 17:51:28 +00:00
free ( exit_status ) ;
2022-05-14 02:36:12 +00:00
// Print Exit Code Log Line
2024-06-17 22:09:30 +00:00
safe_write ( STDERR_FILENO , exit_code_line . c_str ( ) , strlen ( exit_code_line . c_str ( ) ) ) ;
2022-05-14 02:36:12 +00:00
// Write Exit Code Log Line
2024-06-21 05:19:37 +00:00
safe_write ( reborn_get_log_fd ( ) , exit_code_line . c_str ( ) , strlen ( exit_code_line . c_str ( ) ) ) ;
2022-05-14 02:36:12 +00:00
}
2024-05-14 05:23:16 +00:00
// Close Log File
2024-06-17 22:09:30 +00:00
close ( log_fd ) ;
unsetenv ( _MCPI_LOG_FD_ENV ) ;
2022-05-14 02:36:12 +00:00
2022-05-29 22:44:27 +00:00
// Show Crash Log
2024-06-15 12:52:15 +00:00
if ( is_crash & & ! reborn_is_headless ( ) ) {
2024-06-17 22:09:30 +00:00
show_report ( log_filename . c_str ( ) ) ;
2022-05-14 02:36:12 +00:00
}
// Exit
exit ( WIFEXITED ( status ) ? WEXITSTATUS ( status ) : EXIT_FAILURE ) ;
}
}