291 lines
8.2 KiB
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;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/*---------------------------------------------------------------------------*/
|