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()
|
/* close_file_descriptors()
|
||||||
* Closes all file descriptors but stdin, stdout and stderr. Should be called whether we are
|
* Closes all file descriptors but stdin, stdout and stderr. Should be called whether we are
|
||||||
* daemonising or not.
|
* 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()
|
/* run_program()
|
||||||
* fork() then wait(), logging the results of each step.
|
* 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;
|
pid_t pid;
|
||||||
siginfo_t si;
|
siginfo_t si;
|
||||||
|
sigset_t ss;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
program_pid_lock(1);
|
||||||
|
|
||||||
pid = fork();
|
pid = fork();
|
||||||
|
|
||||||
/* deal with errors */
|
/* deal with errors */
|
||||||
if(pid == (pid_t)-1) {
|
if(pid == (pid_t)-1) {
|
||||||
|
program_pid_lock(0);
|
||||||
LOG(LOG_ERR, "Could not create new child process (%m).");
|
LOG(LOG_ERR, "Could not create new child process (%m).");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* child -- execute new process */
|
/* child -- execute new process */
|
||||||
if(pid == 0) {
|
if(pid == 0) {
|
||||||
|
/* clear the child's signal mask */
|
||||||
|
sigemptyset(&ss);
|
||||||
|
sigprocmask(SIG_SETMASK, &ss, 0);
|
||||||
|
|
||||||
execve(exe_path, argv, child_environ);
|
execve(exe_path, argv, child_environ);
|
||||||
LOG(LOG_ERR, "Could not execute `%s' (%m).", exe_path);
|
LOG(LOG_ERR, "Could not execute `%s' (%m).", exe_path);
|
||||||
crash();
|
crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* parent -- wait(2) on child */
|
/* parent -- wait(2) on child */
|
||||||
|
program_pid = pid;
|
||||||
|
program_pid_lock(0);
|
||||||
LOG(LOG_NOTICE, "Created child process with PID %lu.", (unsigned long)pid);
|
LOG(LOG_NOTICE, "Created child process with PID %lu.", (unsigned long)pid);
|
||||||
|
|
||||||
wait_again:
|
wait_again:
|
||||||
|
@ -87,6 +133,7 @@ void run_program(const char* exe_path, char** argv)
|
||||||
if(waitid(P_PID, pid, &si, WEXITED) == -1) {
|
if(waitid(P_PID, pid, &si, WEXITED) == -1) {
|
||||||
switch(errno) {
|
switch(errno) {
|
||||||
case EINTR:
|
case EINTR:
|
||||||
|
if(sigterm_received) goto sigterm;
|
||||||
break; /* signal processed */
|
break; /* signal processed */
|
||||||
|
|
||||||
default:
|
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);
|
LOG(LOG_WARNING, "Received si_code 0x%X from waitid(); ignoring it.", si.si_code);
|
||||||
goto wait_again;
|
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 */
|
/* run process */
|
||||||
while(1) {
|
while(1) {
|
||||||
run_program(argv[optind] /* path */, argv + optind /* argv[], null terminated */);
|
run_program(argv[optind] /* path */, argv + optind /* argv[], null terminated */);
|
||||||
|
|
Loading…
Reference in New Issue