pgear/src/daem.c

291 lines
8.2 KiB
C

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "daem.h"
#include "logr.h"
#include "opts.h"
/* For use by the parent during daemonization. The parent process's
signal handler updates the outcome to communicate whether the child
setup completed normally.
*/
#define CHILD_UNKNOWN -1
#define CHILD_OK 0
#define CHILD_FAILED 1
#define CHILD_TIMEOUT 2
volatile sig_atomic_t child_outcome = CHILD_UNKNOWN;
/* For use by the child during daemonization (set after the fork() and
before the kernel reparents the child to init). The parent_pid is
the target for the child's signal indicating the outcome of the
child's setup.
*/
pid_t parent_pid = 0;
/* The signals that designate the outcomes of the child daemonizing.
*/
#define SIG_CHILD_OK SIGUSR1
#define SIG_CHILD_FAILED SIGUSR2
/*---------------------------------------------------------------------------*/
/* Close the given stream or exit.
*/
void
close_or_die (FILE *stream)
{
if (EOF == fclose(stream))
{
logg_exit("fclose failed: %s\n", strerror(errno));
}
return;
}
/*---------------------------------------------------------------------------*/
/* Handler for use by the parent during daemonization.
*/
void
catch_child_signal_or_timeout (int signum)
{
switch (signum)
{
case SIG_CHILD_OK:
child_outcome = CHILD_OK;
break;
case SIG_CHILD_FAILED:
child_outcome = CHILD_FAILED;
break;
case SIGALRM:
child_outcome = CHILD_TIMEOUT;
break;
default:
assert(false); /* Should be unreachable. */
logg_exit("bug: unexpected signal `%s'.", strsignal(signum));
}
return;
}
/*---------------------------------------------------------------------------*/
/* Set a handler to anticipate an outcome of the child completing its
setup.
*/
void
establish_handler (int signum)
{
struct sigaction sa;
sa.sa_handler = catch_child_signal_or_timeout;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (-1 == sigaction(signum, &sa, NULL))
{
logg_exit("failed sigaction for `%s': %s\n",
strsignal(signum), strerror(errno));
}
return;
}
/*---------------------------------------------------------------------------*/
/* As the parent process, wait for the child to signal that its setup
completed. This function doesn't return.
*/
void
parent_wait_for_child (sigset_t *old_mask)
{
logg(3, "parent waiting for child...\n");
/* Establish a handler for each anticipated outcome of child setup.
* Include a timeout unless the user asked for a zero timeout.
*/
establish_handler(SIG_CHILD_OK);
establish_handler(SIG_CHILD_FAILED);
if (0 != opts.timeout_sec)
{
establish_handler(SIGALRM);
alarm(opts.timeout_sec);
logg(3, "set alarm for %d sec.\n", opts.timeout_sec);
}
/* Wait for something to happen.
*/
while (CHILD_UNKNOWN == child_outcome) sigsuspend(old_mask);
switch (child_outcome)
{
case CHILD_OK:
logg(3, "child setup complete, quitting now.\n");
exit(EXIT_SUCCESS);
case CHILD_FAILED:
logg_exit("failed child setup.\n");
exit(2); // placates compiler warning
case CHILD_TIMEOUT:
logg(0, "timeout waiting for child setup.\n");
/* Distinguish timeout from a definite failure. */
exit(2);
default:
assert(false); /* Should be unreachable. */
logg_exit("bug: unexpected child outcome %d.\n", child_outcome);
}
assert(false); /* Should be unreachable. */
logg_exit("bug: parent wait escaped.\n");
}
/*---------------------------------------------------------------------------*/
/* As the child, send to the parent the signal designated as meaning
"I hit a problem and am exiting with failure".
*/
void
send_signal_child_failed (void)
{
logg(3, "%s about to signal `%s' to parent `%d'...\n",
__func__, strsignal(SIG_CHILD_FAILED), parent_pid);
if (-1 == kill(parent_pid, SIG_CHILD_FAILED))
{
/* Perhaps the parent process gave up waiting. Log and continue.
*/
logg(0, "%s failed to signal `%s' to `%d': %s\n",
__func__, strsignal(SIG_CHILD_FAILED),
parent_pid, strerror(errno));
}
return;
}
/*---------------------------------------------------------------------------*/
/* As the child, send to the parent the signal designated as meaning
"I'm OK".
*/
void
send_signal_child_ok (void)
{
logg(3, "%s about to signal `%s' to parent `%d'...\n",
__func__, strsignal(SIG_CHILD_OK), parent_pid);
/* Clear the exit trigger that would send an "I'm not OK" signal to
the parent, since we expect the parent to die imminently.
*/
atexit_fp = NULL;
if (-1 == kill(parent_pid, SIG_CHILD_OK))
{
/* Perhaps the parent process gave up waiting. Log and continue.
*/
logg(0, "%s failed to signal `%s' to `%d': %s\n",
__func__, strsignal(SIG_CHILD_OK),
parent_pid, strerror(errno));
}
return;
}
/*---------------------------------------------------------------------------*/
/* Reopen the given stream from /dev/null or exit.
*/
void
reopen_or_die (const char* opentype, FILE *stream)
{
if (NULL == freopen("/dev/null", opentype, stream))
{
logg_exit("freopen failed: %s\n", strerror(errno));
}
return;
}
/*---------------------------------------------------------------------------*/
void
daemonize (void)
{
/* Function is only to be called if self is not already daemonized.
*/
assert(1 < getppid());
logg(2, "starting to daemonize...\n");
/* Before forking, temporarily block SIGUSR[12] so an instance
of either is kept in the queue. This is in case the child
process sends its "I'm OK" or its "oh noes" signal before the
parent has its handlers ready.
*/
sigset_t old_mask, tmp_mask;
sigemptyset(&tmp_mask);
sigaddset(&tmp_mask, SIG_CHILD_OK);
sigaddset(&tmp_mask, SIG_CHILD_FAILED);
if (-1 == sigprocmask(SIG_BLOCK, &tmp_mask, &old_mask))
{
logg_exit("failed sigprocmask block, %s line %d: %s\n",
__FILE__, __LINE__, strerror(errno));
}
/* At this point, any SIGUSR[12] will be held until after the
parent_wait_for_child() call below.
*/
pid_t pid = fork();
if (pid < 0)
{
logg_exit("fork failed on %s\n", strerror(errno));
}
else if (pid > 0)
{
/* This is the soon-not-wanted parent process.
Listen for SIGUSR[12] from the child before quitting.
*/
parent_wait_for_child(&old_mask);
}
logg(3, "fork child continuing here.\n");
/* If the child exits for some (probably bad) reason before it
sends its "I'm OK" signal, then this will send the "I'm not
OK" signal to the parent on exit().
*/
atexit_fp = send_signal_child_failed;
/* Remove the temporary block on handling SIGUSR[12] since the
block is only for the parent.
*/
if (-1 == sigprocmask(SIG_UNBLOCK, &tmp_mask, NULL))
{
logg_exit("failed sigprocmask unblock, %s line %d: %s\n",
__FILE__, __LINE__, strerror(errno));
}
/* Remember our parent for signalling later. */
parent_pid = getppid();
/* Create a new session detached from its former parent. */
pid_t sid = setsid();
if (sid < 0)
{
logg_exit("setsid failed on %s\n", strerror(errno));
}
/* Change the current working directory so as not to obstruct a
umount or an rmdir.
*/
if (chdir("/") < 0)
{
logg_exit("chdir(\"/\") failed on %s\n", strerror(errno));
}
/* Close the standard streams... */
logg(3, "closing stdin...\n");
close_or_die(stdin);
logg(3, "closing stdout...\n");
close_or_die(stdout);
logg(3, "closing stderr...\n");
close_or_die(stderr);
/* ...and reopen them from /dev/null. */
logg(3, "reopening stdin...\n");
reopen_or_die("r", stdin);
logg(3, "reopening stdout...\n");
reopen_or_die("w", stdout);
logg(3, "reopening stderr...\n");
reopen_or_die("w", stderr);
logg(1, "daemonized.\n");
return;
}
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/