#include <time.h> #include <sys/types.h> #include <sys/resource.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <ctype.h> #include <fcntl.h> #include <netdb.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/times.h> /* for deadlock safety */ #include "merc.h" void db_dump args ( ( void ) ); extern volatile int alarm_threshold; extern volatile int last_checkpoint; void shutdown_wgrace args( ( void ) ); #if 1 /* Core dumps aren't created when the signal handler is used. * Thus as a debug tool these are removed. */ #if defined( DEBUG ) void init_signals() { signal( SIGPIPE, SIG_IGN ); } #else volatile sig_atomic_t fatal_error_in_process = 0; /* Copyover note: * Whenever a program is executed from exec (as in the case of copyover) * all signals from the previous execution of the mud return to their * SIG_DEF (default) signal handler, since copyover starts the mud * process all over again, init_signals will get called and properly * reset the signal handler. */ /** Function: signal_handler * Descr : Our signal handler, to shutdown gracefully (if possible) * : upon reciept of a signal signifying we should shutdown. * Returns : void * Syntax : (n/a) * Written : v1.0 11/98 * Author : Gary McNickle <gary@dharvest.com> */ void signal_handler( int sig ) { /* Since our 'shutdown_wgrace' is not reentrant, and may (likely) access * non-volatile global variables, be damn sure it's not called twice. */ if( fatal_error_in_process ) raise( sig ); switch( sig ) { case SIGBUS: case SIGTERM: case SIGABRT: case SIGSEGV: { fatal_error_in_process = 1; /* Yes, this IS a fatal error */ /* Log signal to bug log file */ bugf( "Caught deadly signal: %s.", (sig == SIGBUS) ? "SIGBUS" : (sig == SIGTERM) ? "SIGTERM" : (sig == SIGABRT) ? "SIGABRT" : (sig == SIGXCPU) ? "SIGXCPU" : "SIGSEGV" ); shutdown_wgrace(); /* Attempt a graceful shutdown */ raise( sig ); /* set return status of process */ break; } break; } } /** Function: init_signals * Descr : Initialize signals that we trap, setting up a handler for them. * Returns : void * Syntax : void * Written : v1.0 11/98 * Author : Gary McNickle <gary@dharvest.com> * Note : By default, signals sent to sighandler are blocked until the * : handler returns, but other signals are not blocked. We need * : to block any other signal we recieve that we normally trap, * : until we are done trying to be graceful... */ void init_signals() { struct sigaction sigact; sigset_t mask; signal( SIGPIPE, SIG_IGN ); /* NOTE: We inherit any current signal actions by default. * Don't install a signal handler for any ignored signals. */ sigaction( SIGBUS, NULL, &sigact ); if( sigact.sa_handler != SIG_IGN ) { sigact.sa_handler = signal_handler; sigemptyset( &mask ); /* block these signals to the handler while it's running */ sigaddset( &mask, SIGTERM ); sigaddset( &mask, SIGABRT ); sigaddset( &mask, SIGSEGV ); sigaddset( &mask, SIGPIPE ); sigaddset( &mask, SIGXCPU ); sigprocmask( SIG_BLOCK, &mask, NULL); /*JLR*/ sigact.sa_mask = mask; sigact.sa_flags = SA_RESETHAND; sigaction( SIGBUS, &sigact, NULL ); } else log_string( "Init: Signal SIGBUS ignored." ); sigaction( SIGTERM, NULL, &sigact ); if( sigact.sa_handler != SIG_IGN ) { sigact.sa_handler = signal_handler; sigemptyset( &mask ); /* block these signals to the handler while it's running */ sigaddset( &mask, SIGBUS ); sigaddset( &mask, SIGABRT ); sigaddset( &mask, SIGSEGV ); sigaddset( &mask, SIGPIPE ); sigaddset( &mask, SIGXCPU ); sigprocmask( SIG_BLOCK, &mask, NULL); /*JLR*/ sigact.sa_mask = mask; sigact.sa_flags = SA_RESETHAND; sigaction( SIGTERM, &sigact, NULL ); } else log_string( "Init: Signal SIGTERM ignored." ); sigaction( SIGABRT, NULL, &sigact ); if( sigact.sa_handler != SIG_IGN ) { sigact.sa_handler = signal_handler; sigemptyset( &mask ); /* block these signals to the handler while it's running */ sigaddset( &mask, SIGBUS ); sigaddset( &mask, SIGTERM ); sigaddset( &mask, SIGSEGV ); sigaddset( &mask, SIGPIPE ); sigaddset( &mask, SIGXCPU ); sigprocmask( SIG_BLOCK, &mask, NULL); /*JLR*/ sigact.sa_mask = mask; sigact.sa_flags = SA_RESETHAND; sigaction ( SIGABRT, &sigact, NULL ); } else log_string( "Init: Signal SIGABRT ignored." ); sigaction ( SIGSEGV, NULL, &sigact ); if( sigact.sa_handler != SIG_IGN ) { sigact.sa_handler = signal_handler; sigemptyset( &mask ); /* block these signals to the handler while it's running */ sigaddset( &mask, SIGBUS ); sigaddset( &mask, SIGTERM ); sigaddset( &mask, SIGABRT ); sigaddset( &mask, SIGPIPE ); sigaddset( &mask, SIGXCPU ); sigprocmask( SIG_BLOCK, &mask, NULL); /*JLR*/ sigact.sa_mask = mask; sigact.sa_flags = SA_RESETHAND; sigaction ( SIGSEGV, &sigact, NULL ); } else log_string("Init: Signal SIGSEGV ignored."); sigaction( SIGXCPU, NULL, &sigact ); if( sigact.sa_handler != SIG_IGN ) { sigact.sa_handler = signal_handler; sigemptyset( &mask ); /* block these signals to the handler while it's running */ sigaddset( &mask, SIGBUS ); sigaddset( &mask, SIGTERM ); sigaddset( &mask, SIGABRT ); sigaddset( &mask, SIGSEGV ); sigaddset( &mask, SIGPIPE ); sigprocmask( SIG_BLOCK, &mask, NULL ); /*JLR*/ sigact.sa_mask = mask; sigact.sa_flags = SA_RESETHAND; sigaction( SIGXCPU, &sigact, NULL ); } else log_string( "Init: Signal SIGXCPU ignored." ); log_string( "Init: Signals initialized." ); } /** Function: shutdown_wgrace * Descr : Upon receipt of a fatal signal, attempt a graceful shutdown. * Returns : void * Syntax : void * Written : v1.0 11/98 * Author : Gary McNickle <gary@dharvest.com> */ void shutdown_wgrace() { DESCRIPTOR_DATA *d,*d_next; db_dump( ); /* Notify players of impending crash, and save all pfiles */ for ( d = descriptor_list; d != NULL; d = d_next ) { if( d->character ) { if( d->connected == CON_PLAYING ) send_to_char( "\n\r-*- System Crash... Saving game data. -*-\n\r", d->character); do_save ( d->character, "" ); } d_next = d->next; close_socket( d ); } } #endif /************************************************************************** * CPU time safety checker. * This is based on code by Erwin S. Andreasen. * An alarm is set to call alarm_handler every ALARM_FREQUENCY seconds. * At this time, if the CPU usage exceeds the threshold value: * alarm_threshold, then the MUD will halt. * When booting, the threshold value is much larger. */ /* Returns the current amount of time used. */ int get_user_time( void ) { struct tms tx; times( &tx ); return (int)( tx.tms_utime * 10 / CLK_TCK ); } /* * This signal handler is called when the user timer triggers. Note that for * debugging purposes the exit is not called. This allows me to find where * the code is stuck in the debugger. */ void alarm_handler( int signo ) { int usage_now = get_user_time(); /* Has there gone alarm_threshold CPU seconds without alarm_update? */ if( usage_now - last_checkpoint > alarm_threshold ) { bug( "User CPU time limit exceeded.",0); signal( signo, SIG_DFL ); #if !defined( DEBUG ) exit( 1 ); #endif } last_checkpoint = usage_now; } /* * Install handler for the virtual timer. */ void install_alarm( void ) { struct sigaction sigact; struct itimerval itimer; last_checkpoint = get_user_time(); /* * Install the signal handler. */ sigact.sa_handler = alarm_handler; sigact.sa_flags = SA_RESTART; /* Restart interrupted system calls */ sigemptyset( &sigact.sa_mask ); if( sigaction( SIGVTALRM, &sigact, NULL ) < 0 ) { perror( "init_alarm_handler:sigaction" ); exit( 1 ); } /* * Start the timer, * the handler is called every ALARM_FREQUENCY CPU seconds. */ itimer.it_interval.tv_usec = 0; itimer.it_interval.tv_sec = ALARM_FREQUENCY; itimer.it_value.tv_usec = 0; itimer.it_value.tv_sec = ALARM_FREQUENCY; /* start the timer - in that many CPU seconds, alarm_handler will be called */ if( setitimer( ITIMER_VIRTUAL, &itimer, NULL ) < 0) { perror( "reset_itimer:setitimer" ); exit( 1 ); } log_string( "Init: The alarm has been set." ); } /* * This is called after boot_db to change the allowed amount of CPU time. */ void update_alarm( void ) { int now_time = get_user_time(); alarm_threshold = ALARM_RUNNING; logf( NULL, "Init: Boot took %d.%d user CPU seconds.", ( now_time - last_checkpoint ) / 10, ( now_time - last_checkpoint ) % 10 ); last_checkpoint = now_time; } #else void init_signals() { } #endif