Add sensible SIGTERM handling (propagate it to the child), etc.

This commit is contained in:
Laurence Withers 2008-07-26 20:54:14 +00:00
parent 92ec05ab67
commit b0c867224f
3 changed files with 184 additions and 0 deletions

View File

@ -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);
}

103
src/daemonitor/700_signal.c Normal file
View File

@ -0,0 +1,103 @@
/* daemonitor/src/daemonitor/700_signal.c
*
* (c)2007, Laurence Withers, <l@lwithers.me.uk>.
* 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
*/

View File

@ -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 */);