/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2005 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see .
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static char *progname; /* The name of this program (for error messages) */
static char **box_arglist;
static int min_restart_delay = 60; /* in seconds */
static pid_t child_box; /* used in main_loop, available to signal handlers */
static char *pidfile; /* The name of the pidfile to use. NULL if no pidfile */
static int use_extra_args = 1; /* Add "extra_arguments" list to argv? */
/* Extra arguments to pass to the box */
static char *extra_arguments[] = {
"-v", "4", /* Minimal output on stderr, goes to /dev/null anyway */
};
#define NUM_EXTRA ((int) (sizeof(extra_arguments) / sizeof(*extra_arguments)))
static void print_usage(FILE *stream)
{
fprintf(stream,
"Usage: %s [--pidfile PIDFILE] [--min-delay SECONDS] BOXPATH [boxoptions...]\n",
progname);
}
/* Create the argument list to pass to the box process, and put it
* in the box_arglist global variable. This is also the right place
* to add any standard arguments that we want to pass. */
static void build_box_arglist(char *boxfile, int argc, char **argv)
{
int i;
char **argp;
if (box_arglist) {
free(box_arglist);
}
/* one for the boxfile name itself, one for each extra argument,
* one for each normal argument, and one for the terminating NULL */
box_arglist = malloc((1 + NUM_EXTRA + argc + 1) * sizeof(*box_arglist));
if (!box_arglist) {
fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
exit(1);
}
/* Have argp walk down box_arglist and set each argument. */
argp = box_arglist;
*argp++ = boxfile;
if (use_extra_args) {
for (i = 0; i < NUM_EXTRA; i++) {
*argp++ = extra_arguments[i];
}
}
for (i = 0; i < argc; i++) {
*argp++ = argv[i];
}
*argp++ = (char *)NULL;
}
static void write_pidfile(void)
{
int fd;
FILE *f;
if (!pidfile)
return;
fd = open(pidfile, O_WRONLY|O_NOCTTY|O_TRUNC|O_CREAT, 0644);
if (fd < 0) {
fprintf(stderr, "%s: open: %s: %s\n", progname, pidfile, strerror(errno));
exit(1);
}
f = fdopen(fd, "w");
if (!f) {
fprintf(stderr, "%s: fdopen: %s\n", progname, strerror(errno));
exit(1);
}
fprintf(f, "%ld\n", (long)getpid());
if (fclose(f) < 0) {
fprintf(stderr, "%s: writing %s: %s\n", progname, pidfile, strerror(errno));
exit(1);
}
}
static void remove_pidfile(void)
{
if (!pidfile)
return;
unlink(pidfile);
}
/* Set 0 (stdin) to /dev/null, and 1 and 2 (stdout and stderr) to /dev/full
* if it's available and /dev/null otherwise. */
static void rebind_standard_streams(void)
{
int devnullfd;
int devfullfd;
devnullfd = open("/dev/null", O_RDONLY);
if (devnullfd < 0) {
fprintf(stderr, "%s: cannot open /dev/null: %s\n",
progname, strerror(errno));
exit(2);
}
devfullfd = open("/dev/full", O_WRONLY);
if (devfullfd < 0) {
devfullfd = devnullfd;
}
/* Alert: The dup on stderr is done last, so that the error message
* works regardless of which dup fails. */
if (dup2(devnullfd, 0) < 0 ||
dup2(devfullfd, 1) < 0 ||
dup2(devfullfd, 2) < 0) {
fprintf(stderr, "%s: dup2: %s\n", progname, strerror(errno));
exit(1);
}
}
/* Some code to determine the highest possible file descriptor number,
* so that we can close them all. */
static int open_max(void)
{
#ifdef OPEN_MAX
return OPEN_MAX;
#else
int max;
max = sysconf(_SC_OPEN_MAX);
if (max <= 0) {
return 1024; /* guess */
}
return max;
#endif
}
/* Close all file descriptors other than 0, 1, and 2. */
static void close_extra_files(void)
{
int max = open_max();
int fd;
for (fd = 3; fd < max; fd++) {
close(fd);
}
}
/* We received a signal that we should pass on to the child box.
* We ignore it ourselves. */
static void signal_transfer(int signum)
{
if (child_box > 0) {
kill(child_box, signum);
}
}
/* We received a signal that we should pass on to the child box,
* and then die from ourselves. It has to be a signal that
* terminates the process as its default action! */
static void signal_transfer_and_die(int signum)
{
/* First send it to the child process */
if (child_box > 0) {
kill(child_box, signum);
}
/* Prepare to die. Normally the atexit handler would take care
* of this when we exit(), but we're going to die from a signal. */
remove_pidfile();
/* Then send it to self. First set the default handler, to
* avoid catching the signal with this handler again. This
* is not a race, because it doesn't matter if we die from
* the signal we're going to send or from a different one. */
signal(signum, SIG_DFL);
kill(getpid(), signum);
}
static void setup_signals(void)
{
signal(SIGHUP, &signal_transfer);
signal(SIGINT, &signal_transfer_and_die);
signal(SIGQUIT, &signal_transfer_and_die);
signal(SIGTERM, &signal_transfer_and_die);
signal(SIGUSR1, &signal_transfer);
signal(SIGUSR2, &signal_transfer);
}
/* Fork off a box process and loop indefinitely, forking a new one
* every time it dies. */
static int main_loop(char *boxfile)
{
time_t next_fork = 0;
/* We can't report any errors here, because we are running
* as a daemon and we have no logfile of our own. So we
* exit with errno as the exit code, to offer a minimal clue. */
for (;;) {
/* Make sure we don't fork in an endless loop if something
* is drastically wrong. This code limits it to one
* per minute (or whatever min_restart_delay is set to). */
time_t this_time = time(NULL);
if (this_time <= next_fork) {
sleep(next_fork - this_time);
}
next_fork = this_time + min_restart_delay;
child_box = fork();
if (child_box < 0) {
return errno;
}
if (child_box == 0) {
/* child. exec the box */
execvp(boxfile, box_arglist);
exit(127);
}
while (waitpid(child_box, (int *)NULL, 0) != child_box) {
if (errno == ECHILD) {
/* Something went wrong... we don't know what,
* but we do know that our child does not
* exist. So restart it. */
break;
}
if (errno == EINTR) {
continue;
}
/* Something weird happened. */
return errno;
}
}
}
int main(int argc, char *argv[])
{
int i;
char *boxfile = NULL;
pid_t childpid;
progname = argv[0];
if (argc == 1) {
print_usage(stderr);
exit(2);
}
/* Parse the options meant for the wrapper, and get the name of
* the box to wrap. */
for (i = 1; i < argc && !boxfile; i++) {
if (strcmp(argv[i], "--pidfile") == 0) {
if (i+1 >= argc) {
fprintf(stderr, "Missing argument for option %s\n", argv[i]);
exit(2);
}
pidfile = argv[i+1];
i++;
} else if (strcmp(argv[i], "--min-delay") == 0) {
if (i+1 >= argc) {
fprintf(stderr, "Missing argument for option %s", argv[i]);
exit(2);
}
min_restart_delay = atoi(argv[i+1]);
i++;
} else if (strcmp(argv[i], "--no-extra-args") == 0) {
use_extra_args = 0;
} else if (argv[i][0] == '-') {
fprintf(stderr, "Unknown option %s\n", argv[i]);
exit(2);
} else {
boxfile = argv[i];
}
}
/* Check if we have everything */
if (!boxfile) {
print_usage(stderr);
exit(2);
}
/* The remaining arguments should be passed to the box */
build_box_arglist(boxfile, argc - i, argv + i);
/* Ready to rock. Begin daemonization. */
/* Fork a child process and have the parent exit.
* This makes us run in the background. */
childpid = fork();
if (childpid < 0) {
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
exit(1);
}
if (childpid != 0) {
exit(0); /* parent exits immediately */
}
/* The child continues here. Now call setsid() to disconnect
* from our terminal and from the parent's session and process
* group. */
if (setsid() < 0) {
fprintf(stderr, "%s: setsid: %s\n", progname, strerror(errno));
exit(1);
}
/* Change to the root directory, so that we don't keep a
* file descriptor open on an unknown directory. */
if (chdir("/") < 0) {
fprintf(stderr, "%s: chdir to root: %s\n", progname, strerror(errno));
exit(1);
}
atexit(remove_pidfile);
write_pidfile();
/* Set the umask to a known value, rather than inheriting
* an unknown one. */
umask(077);
/* Leave file descriptors 0, 1, and 2 pointing to harmless
* places, and close all other file descriptors. */
rebind_standard_streams();
close_extra_files();
setup_signals();
return main_loop(boxfile);
}