From b0c867224fb1991216e632109d6b0cf28eabf6d2 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Sat, 26 Jul 2008 20:54:14 +0000 Subject: [PATCH] Add sensible SIGTERM handling (propagate it to the child), etc. --- src/daemonitor/600_run_program.c | 78 +++++++++++++++++++++++ src/daemonitor/700_signal.c | 103 +++++++++++++++++++++++++++++++ src/daemonitor/999_main.c | 3 + 3 files changed, 184 insertions(+) create mode 100644 src/daemonitor/700_signal.c diff --git a/src/daemonitor/600_run_program.c b/src/daemonitor/600_run_program.c index a5c7630..638eaf1 100644 --- a/src/daemonitor/600_run_program.c +++ b/src/daemonitor/600_run_program.c @@ -28,6 +28,22 @@ void run_program(const char* exe_path, char** argv); +/* program_pid + * Set to (pid_t)0 if there is no currently active program, otherwise set to the PID of the running + * child process. This is intended for use in signal handlers. + */ +extern pid_t program_pid; + + + +/* sigterm_received + * Set by a signal handler if SIGTERM is received while program_pid is set, this flag causes + * run_program() to shut the child process down. + */ +volatile int sigterm_received = 0; + + + /* close_file_descriptors() * Closes all file descriptors but stdin, stdout and stderr. Should be called whether we are * daemonising or not. @@ -55,6 +71,25 @@ void crash(void) +/* program_pid, program_pid_lock() + * Used to record the PID of the child process for signal handlers. 0 means there is no child + * process running. The lock function actually blocks/unblocks those signals whose handlers would + * examine program_pid; they can be used to avoid races. + */ +pid_t program_pid = 0; + +void program_pid_lock(int lock) +{ + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, SIGTERM); + + sigprocmask(lock ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0); +} + + + /* run_program() * fork() then wait(), logging the results of each step. */ @@ -62,23 +97,34 @@ void run_program(const char* exe_path, char** argv) { pid_t pid; siginfo_t si; + sigset_t ss; + int i; + + program_pid_lock(1); pid = fork(); /* deal with errors */ if(pid == (pid_t)-1) { + program_pid_lock(0); LOG(LOG_ERR, "Could not create new child process (%m)."); return; } /* child -- execute new process */ if(pid == 0) { + /* clear the child's signal mask */ + sigemptyset(&ss); + sigprocmask(SIG_SETMASK, &ss, 0); + execve(exe_path, argv, child_environ); LOG(LOG_ERR, "Could not execute `%s' (%m).", exe_path); crash(); } /* parent -- wait(2) on child */ + program_pid = pid; + program_pid_lock(0); LOG(LOG_NOTICE, "Created child process with PID %lu.", (unsigned long)pid); wait_again: @@ -87,6 +133,7 @@ void run_program(const char* exe_path, char** argv) if(waitid(P_PID, pid, &si, WEXITED) == -1) { switch(errno) { case EINTR: + if(sigterm_received) goto sigterm; break; /* signal processed */ default: @@ -120,6 +167,37 @@ void run_program(const char* exe_path, char** argv) LOG(LOG_WARNING, "Received si_code 0x%X from waitid(); ignoring it.", si.si_code); goto wait_again; } + + /* deal with SIGTERM, avoiding race */ + program_pid_lock(1); + program_pid = 0; + program_pid_lock(0); + if(sigterm_received) exit(0); + return; + + sigterm: + /* SIGTERM was received, send SIGTERM to the child */ + LOG(LOG_NOTICE, "Termination signal received, passing it to child."); + kill(program_pid, SIGTERM); + + /* wait for up to 3 seconds for it to exit */ + si.si_pid = 0; + for(i = 0; i < 30; ++i) { + waitid(P_PID, pid, &si, WNOHANG | WEXITED); + if(si.si_pid) { + LOG(LOG_NOTICE, "Child has exited. Shutting down."); + exit(0); + } + + safe_sleep_fixed(0, 100 * 1000 * 1000); + } + + /* now send it a SIGKILL */ + LOG(LOG_WARNING, "Child process did not exit. Sending it a SIGKILL."); + errno = 0; + kill(program_pid, SIGKILL); + LOG(LOG_WARNING, "Result: %m."); + exit(0); } diff --git a/src/daemonitor/700_signal.c b/src/daemonitor/700_signal.c new file mode 100644 index 0000000..469c896 --- /dev/null +++ b/src/daemonitor/700_signal.c @@ -0,0 +1,103 @@ +/* daemonitor/src/daemonitor/700_signal.c + * + * (c)2007, Laurence Withers, . + * Released under the GNU GPLv3. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + + + +/* SIGNAL HANDLING ******************************************************************************** + * + * This file contains signal handlers and associated setup routines. Most signals are ignored. + * SIGTERM is translated into a SIGTERM of the child process, which we will kill within 3 seconds + * if it has not exited. + */ + + + +/* PUBLIC API ************************************************************************************/ + + + +/* signal_setup() + * Installs the signal handlers. + */ +void signal_setup(void); + + + +/* IMPLEMENTATION ********************************************************************************/ + + + +/* sigterm_handler() + * Handles receiving SIGTERM. If there is a child process, we set the SIGTERM flag, then return. + * Otherwise, we simply exit. + */ +void sigterm_handler(int signum __attribute__((unused))) +{ + if(program_pid) { + sigterm_received = 1; + return; + } + + /* uck -- we can't call exit() because it's not async-signal-safe, so we must call _exit() and + * clean up manually, with no logging */ + unlink(pidfile_filename); + exit(0); +} + + + +/* signal_setup() + * Block most signals. Install signal handlers for the remainder. + */ +void signal_setup(void) +{ + sigset_t ss; + struct sigaction sa; + + sigfillset(&ss); + memset(&sa, 0, sizeof(sa)); + + /* SIGKILL of course can't be blocked; neither can stop/continue */ + sigdelset(&ss, SIGKILL); + sigdelset(&ss, SIGSTOP); + sigdelset(&ss, SIGCONT); + + /* program error conditions -- don't block them or we won't crash when we should */ + sigdelset(&ss, SIGILL); + sigdelset(&ss, SIGFPE); + sigdelset(&ss, SIGSEGV); + sigdelset(&ss, SIGBUS); + + /* we'd like to deal with SIGTERM specially */ + sigdelset(&ss, SIGTERM); + + /* don't block SIGCHLD or wait(2) won't work */ + sigdelset(&ss, SIGCHLD); + + sigprocmask(SIG_SETMASK, &ss, 0); + + /* restore default signal dispositions in case we inherited SIG_IGN from parent */ + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCONT, &sa, 0); + sigaction(SIGILL, &sa, 0); + sigaction(SIGFPE, &sa, 0); + sigaction(SIGSEGV, &sa, 0); + sigaction(SIGBUS, &sa, 0); + sigaction(SIGCHLD, &sa, 0); + + /* install our own handler for SIGTERM */ + sa.sa_handler = sigterm_handler; + sigaction(SIGTERM, &sa, 0); +} + + + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4 +*/ diff --git a/src/daemonitor/999_main.c b/src/daemonitor/999_main.c index 9bfffda..7474a0e 100644 --- a/src/daemonitor/999_main.c +++ b/src/daemonitor/999_main.c @@ -139,6 +139,9 @@ int main(int argc, char* argv[]) } } + /* set up signals */ + signal_setup(); + /* run process */ while(1) { run_program(argv[optind] /* path */, argv + optind /* argv[], null terminated */);