Add sensible SIGTERM handling (propagate it to the child), etc.
This commit is contained in:
parent
92ec05ab67
commit
b0c867224f
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
|
@ -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 */);
|
||||
|
|
Loading…
Reference in New Issue