aboutsummaryrefslogtreecommitdiff
path: root/src/start-stop-daemon.c
diff options
context:
space:
mode:
authorRoy Marples <roy@marples.name>2007-04-05 11:18:42 +0000
committerRoy Marples <roy@marples.name>2007-04-05 11:18:42 +0000
commit5af58b45146ab5253ca964738f4e45287bf963d4 (patch)
tree68d3a9a61fa55dd7fe273db776c375f797edaa5b /src/start-stop-daemon.c
Rewrite the core parts in C. We now provide librc so other programs can
query runlevels, services and state without using bash. We also provide libeinfo so other programs can easily use our informational functions. As such, we have dropped the requirement of using bash as the init script shell. We now use /bin/sh and have strived to make the scripts as portable as possible. Shells that work are bash and dash. busybox works provided you disable s-s-d. If you have WIPE_TMP set to yes in conf.d/bootmisc you should disable find too. zsh and ksh do not work at this time. Networking support is currently being re-vamped also as it was heavily bash array based. As such, a new config format is available like so config_eth0="1.2.3.4/24 5.6.7.8/16" or like so config_eth0="'1.2.3.4 netmask 255.255.255.0' '5.6.7.8 netmask 255.255.0.0'" We will still support the old bash array format provided that /bin/sh IS a link it bash. ChangeLog for baselayout-1 can be found in our SVN repo.
Diffstat (limited to 'src/start-stop-daemon.c')
-rw-r--r--src/start-stop-daemon.c1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/src/start-stop-daemon.c b/src/start-stop-daemon.c
new file mode 100644
index 00000000..e5dae783
--- /dev/null
+++ b/src/start-stop-daemon.c
@@ -0,0 +1,1047 @@
+/*
+ start-stop-daemon
+ Starts, stops, tests and signals daemons
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+
+ This is essentially a ground up re-write of Debians
+ start-stop-daemon for cleaner code and to integrate into our RC
+ system so we can monitor daemons a little.
+ */
+
+#define POLL_INTERVAL 20000
+#define START_WAIT 100000
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/termios.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+
+/* We are not supporting authentication conversations */
+static struct pam_conv conv = { NULL, NULL} ;
+#endif
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+typedef struct schedulelist
+{
+ enum
+ {
+ schedule_timeout,
+ schedule_signal,
+ schedule_goto,
+ schedule_forever
+ } type;
+ int value;
+ struct schedulelist *gotolist;
+ struct schedulelist *next;
+} schedulelist_t;
+static schedulelist_t *schedule;
+
+static char *progname;
+static char *changeuser;
+static char **newenv;
+
+extern char **environ;
+
+static void free_schedulelist (schedulelist_t **list)
+{
+ schedulelist_t *here;
+ schedulelist_t *next;
+
+ for (here = *list; here; here = next)
+ {
+ next = here->next;
+ free (here);
+ }
+
+ *list = NULL;
+}
+
+static void cleanup (void)
+{
+ if (changeuser)
+ free (changeuser);
+
+ if (schedule)
+ free_schedulelist (&schedule);
+
+ if (newenv)
+ rc_strlist_free (newenv);
+}
+
+static int parse_signal (const char *sig)
+{
+ typedef struct signalpair
+ {
+ const char *name;
+ int signal;
+ } signalpair_t;
+
+ static const signalpair_t signallist[] = {
+ { "ABRT", SIGABRT },
+ { "ALRM", SIGALRM },
+ { "FPE", SIGFPE },
+ { "HUP", SIGHUP },
+ { "ILL", SIGILL },
+ { "INT", SIGINT },
+ { "KILL", SIGKILL },
+ { "PIPE", SIGPIPE },
+ { "QUIT", SIGQUIT },
+ { "SEGV", SIGSEGV },
+ { "TERM", SIGTERM },
+ { "USR1", SIGUSR1 },
+ { "USR2", SIGUSR2 },
+ { "CHLD", SIGCHLD },
+ { "CONT", SIGCONT },
+ { "STOP", SIGSTOP },
+ { "TSTP", SIGTSTP },
+ { "TTIN", SIGTTIN },
+ { "TTOU", SIGTTOU }
+ };
+
+ unsigned int i = 0;
+ char *s;
+
+ if (! sig || strlen (sig) == 0)
+ return (-1);
+
+ if (sscanf (sig, "%u", &i) == 1)
+ {
+ if (i > 0 && i < sizeof (signallist) / sizeof (signallist[0]))
+ return (i);
+ eerrorx ("%s: `%s' is not a valid signal", progname, sig);
+ }
+
+ if (strncmp (sig, "SIG", 3) == 0)
+ s = (char *) sig + 3;
+ else
+ s = NULL;
+
+ for (i = 0; i < sizeof (signallist) / sizeof (signallist[0]); i++)
+ if (strcmp (sig, signallist[i].name) == 0 ||
+ (s && strcmp (s, signallist[i].name) == 0))
+ return (signallist[i].signal);
+
+ eerrorx ("%s: `%s' is not a valid signal", progname, sig);
+}
+
+static void parse_schedule_item (schedulelist_t *item, const char *string)
+{
+ const char *after_hyph;
+ int sig;
+
+ if (strcmp (string,"forever") == 0)
+ item->type = schedule_forever;
+ else if (isdigit (string[0]))
+ {
+ item->type = schedule_timeout;
+ errno = 0;
+ if (sscanf (string, "%d", &item->value) != 1)
+ eerrorx ("%s: invalid timeout value in schedule `%s'", progname,
+ string);
+ }
+ else if ((after_hyph = string + (string[0] == '-')) &&
+ ((sig = parse_signal (after_hyph)) != -1))
+ {
+ item->type = schedule_signal;
+ item->value = (int) sig;
+ }
+ else
+ eerrorx ("%s: invalid schedule item `%s'", progname, string);
+}
+
+static void parse_schedule (const char *string, int default_signal)
+{
+ char buffer[20];
+ const char *slash;
+ int count = 0;
+ schedulelist_t *repeatat = NULL;
+ ptrdiff_t len;
+ schedulelist_t *next;
+
+ if (string)
+ for (slash = string; *slash; slash++)
+ if (*slash == '/')
+ count++;
+
+ if (schedule)
+ free_schedulelist (&schedule);
+
+ schedule = rc_xmalloc (sizeof (schedulelist_t));
+ schedule->gotolist = NULL;
+
+ if (count == 0)
+ {
+ schedule->type = schedule_signal;
+ schedule->value = default_signal;
+ schedule->next = rc_xmalloc (sizeof (schedulelist_t));
+ next = schedule->next;
+ next->type = schedule_timeout;
+ next->gotolist = NULL;
+ if (string)
+ {
+ if (sscanf (string, "%d", &next->value) != 1)
+ eerrorx ("%s: invalid timeout value in schedule", progname);
+ }
+ else
+ next->value = 5;
+ next->next = NULL;
+
+ return;
+ }
+
+ next = schedule;
+ while (string != NULL)
+ {
+ if ((slash = strchr (string, '/')))
+ len = slash - string;
+ else
+ len = strlen (string);
+
+ if (len >= (ptrdiff_t) sizeof (buffer))
+ eerrorx ("%s: invalid schedule item, far too long", progname);
+
+ memcpy (buffer, string, len);
+ buffer[len] = 0;
+ string = slash ? slash + 1 : NULL;
+
+ parse_schedule_item (next, buffer);
+ if (next->type == schedule_forever)
+ {
+ if (repeatat)
+ eerrorx ("%s: invalid schedule, `forever' appears more than once",
+ progname);
+
+ repeatat = next;
+ continue;
+ }
+
+ if (string)
+ {
+ next->next = rc_xmalloc (sizeof (schedulelist_t));
+ next = next->next;
+ next->gotolist = NULL;
+ }
+ }
+
+ if (repeatat)
+ {
+ next->next = rc_xmalloc (sizeof (schedulelist_t));
+ next = next->next;
+ next->type = schedule_goto;
+ next->value = 0;
+ next->gotolist = repeatat;
+ }
+
+ next->next = NULL;
+ return;
+}
+
+static pid_t get_pid (const char *pidfile, bool quiet)
+{
+ FILE *fp;
+ pid_t pid;
+
+ if (! pidfile)
+ return (-1);
+
+ if ((fp = fopen (pidfile, "r")) == NULL)
+ {
+ if (! quiet)
+ eerror ("%s: fopen `%s': %s", progname, pidfile, strerror (errno));
+ return (-1);
+ }
+
+ if (fscanf (fp, "%d", &pid) != 1)
+ {
+ if (! quiet)
+ eerror ("%s: no pid found in `%s'", progname, pidfile);
+ fclose (fp);
+ return (-1);
+ }
+ fclose (fp);
+
+ return (pid);
+}
+
+/* return number of processed killed, -1 on error */
+static int do_stop (const char *exec, const char *cmd,
+ const char *pidfile, uid_t uid,int sig,
+ bool quiet, bool verbose, bool test)
+{
+ pid_t *pids;
+ bool killed;
+ int nkilled = 0;
+ pid_t pid = 0;
+ int i;
+
+ if (pidfile)
+ if ((pid = get_pid (pidfile, quiet)) == -1)
+ return (quiet ? 0 : -1);
+
+ if ((pids = rc_find_pids (exec, cmd, uid, pid)) == NULL)
+ return (0);
+
+ for (i = 0; pids[i]; i++)
+ {
+ if (test)
+ {
+ if (! quiet)
+ einfo ("Would send signal %d to PID %d", sig, pids[i]);
+ nkilled++;
+ continue;
+ }
+
+ if (verbose)
+ ebegin ("Sending signal %d to PID %d", sig, pids[i]);
+ errno = 0;
+ killed = (kill (pids[i], sig) == 0 || errno == ESRCH ? true : false);
+ if (! killed)
+ {
+ if (! quiet)
+ eerror ("%s: failed to send signal %d to PID %d: %s",
+ progname, sig, pids[i], strerror (errno));
+ if (verbose)
+ eend (1, NULL);
+ nkilled = -1;
+ }
+ else
+ {
+ if (verbose)
+ eend (0, NULL);
+ if (nkilled != -1)
+ nkilled++;
+ }
+ }
+
+ free (pids);
+ return (nkilled);
+}
+
+static int run_stop_schedule (const char *exec, const char *cmd,
+ const char *pidfile, uid_t uid,
+ bool quiet, bool verbose, bool test)
+{
+ schedulelist_t *item = schedule;
+ int nkilled = 0;
+ int tkilled = 0;
+ int nrunning = 0;
+ struct timeval tv;
+ struct timeval now;
+ struct timeval stopat;
+
+ if (verbose)
+ {
+ if (pidfile)
+ einfo ("Will stop PID in pidfile `%s'", pidfile);
+ if (uid)
+ einfo ("Will stop processes owned by UID %d", uid);
+ if (exec)
+ einfo ("Will stop processes of `%s'", exec);
+ if (cmd)
+ einfo ("Will stop processes called `%s'", cmd);
+ }
+
+ while (item)
+ {
+ switch (item->type)
+ {
+ case schedule_goto:
+ item = item->gotolist;
+ continue;
+
+ case schedule_signal:
+ nrunning = 0;
+ nkilled = do_stop (exec, cmd, pidfile, uid, item->value,
+ quiet, verbose, test);
+ if (nkilled == 0)
+ {
+ if (tkilled == 0)
+ {
+ if (! quiet)
+ eerror ("%s: no matching processes found", progname);
+ }
+ return (tkilled);
+ }
+ else if (nkilled == -1)
+ return (0);
+
+ tkilled += nkilled;
+ break;
+ case schedule_timeout:
+ if (item->value < 1)
+ {
+ item = NULL;
+ break;
+ }
+
+ if (gettimeofday (&stopat, NULL) != 0)
+ {
+ eerror ("%s: gettimeofday: %s", progname, strerror (errno));
+ return (0);
+ }
+
+ stopat.tv_sec += item->value;
+ while (1)
+ {
+ if ((nrunning = do_stop (exec, cmd, pidfile,
+ uid, 0, true, false, true)) == 0)
+ return (true);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = POLL_INTERVAL;
+ if (select (0, 0, 0, 0, &tv) < 0)
+ {
+ if (errno == EINTR)
+ eerror ("%s: caught an interupt", progname);
+ else
+ eerror ("%s: select: %s", progname, strerror (errno));
+ return (0);
+ }
+
+ if (gettimeofday (&now, NULL) != 0)
+ {
+ eerror ("%s: gettimeofday: %s", progname, strerror (errno));
+ return (0);
+ }
+ if (timercmp (&now, &stopat, >))
+ break;
+ }
+ break;
+
+ default:
+ eerror ("%s: invalid schedule item `%d'", progname, item->type);
+ return (0);
+ }
+
+ if (item)
+ item = item->next;
+ }
+
+ if (test || (tkilled > 0 && nrunning == 0))
+ return (nkilled);
+
+ if (! quiet)
+ {
+ if (nrunning == 1)
+ eerror ("%s: %d process refused to stop", progname, nrunning);
+ else
+ eerror ("%s: %d process(es) refused to stop", progname, nrunning);
+ }
+
+ return (-nrunning);
+}
+
+static void handle_signal (int sig)
+{
+ int pid;
+ int status;
+ int serrno = errno;
+
+ switch (sig)
+ {
+ case SIGINT:
+ case SIGTERM:
+ case SIGQUIT:
+ eerrorx ("%s: caught signal %d, aborting", progname, sig);
+
+ case SIGCHLD:
+ while (1)
+ {
+ if ((pid = waitpid (-1, &status, WNOHANG)) < 0)
+ {
+ if (errno != ECHILD)
+ eerror ("%s: waitpid: %s", progname, strerror (errno));
+ break;
+ }
+ }
+ break;
+
+ default:
+ eerror ("%s: caught unknown signal %d", progname, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+int main (int argc, char **argv)
+{
+ int devnull_fd = -1;
+
+#ifdef TIOCNOTTY
+ int tty_fd = -1;
+#endif
+#ifdef HAVE_PAM
+ pam_handle_t *pamh = NULL;
+ int pamr;
+#endif
+
+ static struct option longopts[] = {
+ { "stop", 0, NULL, 'K'},
+ { "nicelevel", 1, NULL, 'N'},
+ { "retry", 1, NULL, 'R'},
+ { "start", 0, NULL, 'S'},
+ { "background", 0, NULL, 'b'},
+ { "chuid", 1, NULL, 'c'},
+ { "chdir", 1, NULL, 'd'},
+ { "group", 1, NULL, 'g'},
+ { "make-pidfile", 0, NULL, 'm'},
+ { "name", 1, NULL, 'n'},
+ { "oknodo", 0, NULL, 'o'},
+ { "pidfile", 1, NULL, 'p'},
+ { "quiet", 0, NULL, 'q'},
+ { "signal", 1, NULL, 's'},
+ { "test", 0, NULL, 't'},
+ { "user", 1, NULL, 'u'},
+ { "chroot", 1, NULL, 'r'},
+ { "verbose", 0, NULL, 'v'},
+ { "exec", 1, NULL, 'x'},
+ { "stdout", 1, NULL, '1'},
+ { "stderr", 1, NULL, '2'},
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+ bool start = false;
+ bool stop = false;
+ bool oknodo = false;
+ bool test = false;
+ bool quiet = false;
+ bool verbose = false;
+ char *exec = NULL;
+ char *cmd = NULL;
+ char *pidfile = NULL;
+ int sig = SIGTERM;
+ uid_t uid = 0;
+ int nicelevel = 0;
+ bool background = false;
+ bool makepidfile = false;
+ uid_t ch_uid = 0;
+ gid_t ch_gid = 0;
+ char *ch_root = NULL;
+ char *ch_dir = NULL;
+ int tid = 0;
+ char *redirect_stderr = NULL;
+ char *redirect_stdout = NULL;
+ int stdout_fd;
+ int stderr_fd;
+ pid_t pid;
+ struct timeval tv;
+ int i;
+ char *svcname = getenv ("SVCNAME");
+ char *env;
+
+ progname = argv[0];
+ atexit (cleanup);
+
+ signal (SIGINT, handle_signal);
+ signal (SIGQUIT, handle_signal);
+ signal (SIGTERM, handle_signal);
+
+ while ((c = getopt_long (argc, argv,
+ "KN:R:Sbc:d:g:mn:op:qs:tu:r:vx:1:2:",
+ longopts, (int *) 0)) != -1)
+ switch (c)
+ {
+ case 'K': /* --stop */
+ stop = true;
+ break;
+
+ case 'N': /* --nice */
+ if (sscanf (optarg, "%d", &nicelevel) != 1)
+ eerrorx ("%s: invalid nice level `%s'", progname, optarg);
+ break;
+
+ case 'R': /* --retry <schedule>|<timeout> */
+ parse_schedule (optarg, sig);
+ break;
+
+ case 'S': /* --start */
+ start = true;
+ break;
+
+ case 'b': /* --background */
+ background = true;
+ break;
+
+ case 'c': /* --chuid <username>|<uid> */
+ /* we copy the string just in case we need the
+ * argument later. */
+ {
+ char *p = optarg;
+ char *cu = strsep (&p, ":");
+ changeuser = strdup (cu);
+ if (sscanf (cu, "%d", &tid) != 1)
+ {
+ struct passwd *pw = getpwnam (cu);
+ if (! pw)
+ eerrorx ("%s: user `%s' not found", progname, cu);
+ ch_uid = pw->pw_uid;
+ }
+ else
+ ch_uid = tid;
+ if (p)
+ {
+ char *cg = strsep (&p, ":");
+ if (sscanf (cg, "%d", &tid) != 1)
+ {
+ struct group *gr = getgrnam (cg);
+ if (! gr)
+ eerrorx ("%s: group `%s' not found", progname, cg);
+ ch_gid = gr->gr_gid;
+ }
+ else
+ ch_gid = tid;
+ }
+ }
+ break;
+
+ case 'd': /* --chdir /new/dir */
+ ch_dir = optarg;
+ break;
+
+ case 'g': /* --group <group>|<gid> */
+ if (sscanf (optarg, "%d", &tid) != 1)
+ {
+ struct group *gr = getgrnam (optarg);
+ if (! gr)
+ eerrorx ("%s: group `%s' not found", progname, optarg);
+ ch_gid = gr->gr_gid;
+ }
+ else
+ ch_gid = tid;
+ break;
+
+ case 'm': /* --make-pidfile */
+ makepidfile = true;
+ break;
+
+ case 'n': /* --name <process-name> */
+ cmd = optarg;
+ break;
+
+ case 'o': /* --oknodo */
+ oknodo = true;
+ break;
+
+ case 'p': /* --pidfile <pid-file> */
+ pidfile = optarg;
+ break;
+
+ case 'q': /* --quiet */
+ quiet = true;
+ break;
+
+ case 's': /* --signal <signal> */
+ sig = parse_signal (optarg);
+ break;
+
+ case 't': /* --test */
+ test = true;
+ break;
+
+ case 'u': /* --user <username>|<uid> */
+ if (sscanf (optarg, "%d", &tid) != 1)
+ {
+ struct passwd *pw = getpwnam (optarg);
+ if (! pw)
+ eerrorx ("%s: user `%s' not found", progname, optarg);
+ uid = pw->pw_uid;
+ }
+ else
+ uid = tid;
+ break;
+
+ case 'r': /* --chroot /new/root */
+ ch_root = optarg;
+ break;
+
+ case 'v': /* --verbose */
+ verbose = true;
+ break;
+
+ case 'x': /* --exec <executable> */
+ exec = optarg;
+ break;
+
+ case '1': /* --stdout /path/to/stdout.lgfile */
+ redirect_stdout = optarg;
+ break;
+
+ case '2': /* --stderr /path/to/stderr.logfile */
+ redirect_stderr = optarg;
+ break;
+
+ default:
+ exit (EXIT_FAILURE);
+ }
+
+ /* Respect RC as well as how we are called */
+ if (rc_is_env ("RC_QUIET", "yes") && ! verbose)
+ quiet = true;
+
+ if (start == stop)
+ eerrorx ("%s: need one of --start or --stop", progname);
+
+ if (start && ! exec)
+ eerrorx ("%s: --start needs --exec", progname);
+
+ if (stop && ! exec && ! pidfile && ! cmd && ! uid)
+ eerrorx ("%s: --stop needs --exec, --pidfile, --name or --user", progname);
+
+ if (makepidfile && ! pidfile)
+ eerrorx ("%s: --make-pidfile is only relevant with --pidfile", progname);
+
+ if (background && ! start)
+ eerrorx ("%s: --background is only relevant with --start", progname);
+
+ if ((redirect_stdout || redirect_stderr) && ! background)
+ eerrorx ("%s: --stdout and --stderr are only relevant with --background",
+ progname);
+
+ argc -= optind;
+ argv += optind;
+
+ /* Validate that the binary rc_exists if we are starting */
+ if (exec && start)
+ {
+ char *tmp;
+ if (ch_root)
+ tmp = rc_strcatpaths (ch_root, exec, NULL);
+ else
+ tmp = exec;
+ if (! rc_is_file (tmp))
+ {
+ eerror ("%s: %s does not exist", progname, tmp);
+ if (ch_root)
+ free (tmp);
+ exit (EXIT_FAILURE);
+ }
+ if (ch_root)
+ free (tmp);
+ }
+
+ if (stop)
+ {
+ int result;
+
+ if (! schedule)
+ {
+ if (test || oknodo)
+ parse_schedule ("0", sig);
+ else
+ parse_schedule (NULL, sig);
+ }
+
+ result = run_stop_schedule (exec, cmd, pidfile, uid, quiet, verbose, test);
+ if (test || oknodo)
+ return (result > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ if (result < 1)
+ exit (result == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+
+ if (pidfile && rc_is_file (pidfile))
+ unlink (pidfile);
+
+ if (svcname)
+ rc_set_service_daemon (svcname, exec, cmd, pidfile, false);
+
+ exit (EXIT_SUCCESS);
+ }
+
+ if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0)
+ eerrorx ("%s: %s is already running", progname, exec);
+
+ if (test)
+ {
+ if (quiet)
+ exit (EXIT_SUCCESS);
+
+ einfon ("Would start %s", exec);
+ while (argc-- > 0)
+ printf("%s ", *argv++);
+ printf ("\n");
+ eindent ();
+ if (ch_uid != 0)
+ einfo ("as user %d", ch_uid);
+ if (ch_gid != 0)
+ einfo ("as group %d", ch_gid);
+ if (ch_root)
+ einfo ("in root `%s'", ch_root);
+ if (ch_dir)
+ einfo ("in dir `%s'", ch_dir);
+ if (nicelevel != 0)
+ einfo ("with a priority of %d", nicelevel);
+ eoutdent ();
+ exit (EXIT_SUCCESS);
+ }
+
+ /* Ensure this is unset, so if the daemon does /etc/init.d/foo
+ Then we filter the environment accordingly */
+ unsetenv ("RC_SOFTLEVEL");
+
+ if (verbose)
+ {
+ ebegin ("Detaching to start `%s'", exec);
+ eindent ();
+ }
+
+ if (background)
+ signal (SIGCHLD, handle_signal);
+
+ *--argv = exec;
+ if ((pid = fork ()) == -1)
+ eerrorx ("%s: fork: %s", progname, strerror (errno));
+
+ /* Child process - lets go! */
+ if (pid == 0)
+ {
+ pid_t mypid = getpid ();
+
+#ifdef TIOCNOTTY
+ tty_fd = open("/dev/tty", O_RDWR);
+#endif
+
+ devnull_fd = open("/dev/null", O_RDWR);
+
+ if (nicelevel)
+ {
+ if (setpriority (PRIO_PROCESS, mypid, nicelevel) == -1)
+ eerrorx ("%s: setpritory %d: %s", progname, nicelevel,
+ strerror(errno));
+ }
+
+ if (ch_root && chroot (ch_root) < 0)
+ eerrorx ("%s: chroot `%s': %s", progname, ch_root, strerror (errno));
+
+ if (ch_dir && chdir (ch_dir) < 0)
+ eerrorx ("%s: chdir `%s': %s", progname, ch_dir, strerror (errno));
+
+ if (makepidfile && pidfile)
+ {
+ FILE *fp = fopen (pidfile, "w");
+ if (! fp)
+ eerrorx ("%s: fopen `%s': %s", progname, pidfile, strerror
+ (errno));
+ fprintf (fp, "%d\n", mypid);
+ fclose (fp);
+ }
+
+#ifdef HAVE_PAM
+ if (changeuser != NULL)
+ pamr = pam_start ("start-stop-daemon", changeuser, &conv, &pamh);
+ else
+ pamr = pam_start ("start-stop-daemon", "nobody", &conv, &pamh);
+
+ if (pamr == PAM_SUCCESS)
+ pamr = pam_authenticate (pamh, PAM_SILENT);
+ if (pamr == PAM_SUCCESS)
+ pamr = pam_acct_mgmt (pamh, PAM_SILENT);
+ if (pamr == PAM_SUCCESS)
+ pamr = pam_open_session (pamh, PAM_SILENT);
+ if (pamr != PAM_SUCCESS)
+ eerrorx ("%s: pam error: %s", progname, pam_strerror(pamh, pamr));
+#endif
+
+ if ((ch_gid) && setgid(ch_gid))
+ eerrorx ("%s: unable to set groupid to %d", progname, ch_gid);
+ if (changeuser && ch_gid)
+ if (initgroups (changeuser, ch_gid))
+ eerrorx ("%s: initgroups (%s, %d)", progname, changeuser, ch_gid);
+ if (ch_uid && setuid (ch_uid))
+ eerrorx ("%s: unable to set userid to %d", progname, ch_uid);
+ else
+ {
+ struct passwd *passwd = getpwuid (ch_uid);
+ if (passwd)
+ {
+ unsetenv ("HOME");
+ if (passwd->pw_dir)
+ setenv ("HOME", passwd->pw_dir, 1);
+ unsetenv ("USER");
+ if (passwd->pw_name)
+ setenv ("USER", passwd->pw_name, 1);
+ }
+ }
+
+ /* Close any fd's to the passwd database */
+ endpwent ();
+
+#ifdef TIOCNOTTY
+ ioctl(tty_fd, TIOCNOTTY, 0);
+ close(tty_fd);
+#endif
+
+ /* Clean the environment of any RC_ variables */
+ STRLIST_FOREACH (environ, env, i)
+ if (env && strncmp (env, "RC_", 3) != 0)
+ {
+ /* For the path character, remove the rcscript bin dir from it */
+ if (strncmp (env, "PATH=" RC_LIBDIR "bin:",
+ strlen ("PATH=" RC_LIBDIR "bin:")) == 0)
+ {
+ char *path = env;
+ char *newpath;
+ int len;
+ path += strlen ("PATH=" RC_LIBDIR "bin:");
+ len = sizeof (char *) * strlen (path) + 6;
+ newpath = rc_xmalloc (len);
+ snprintf (newpath, len, "PATH=%s", path);
+ newenv = rc_strlist_add (newenv, newpath);
+ free (newpath);
+ }
+ else
+ newenv = rc_strlist_add (newenv, env);
+ }
+
+ umask (022);
+
+ stdout_fd = devnull_fd;
+ stderr_fd = devnull_fd;
+ if (redirect_stdout)
+ {
+ if ((stdout_fd = open (redirect_stdout, O_WRONLY | O_CREAT | O_APPEND,
+ S_IRUSR | S_IWUSR)) == -1)
+ eerrorx ("%s: unable to open the logfile for stdout `%s': %s",
+ progname, redirect_stdout, strerror (errno));
+ }
+ if (redirect_stderr)
+ {
+ if ((stderr_fd = open (redirect_stderr, O_WRONLY | O_CREAT | O_APPEND,
+ S_IRUSR | S_IWUSR)) == -1)
+ eerrorx ("%s: unable to open the logfile for stderr `%s': %s",
+ progname, redirect_stderr, strerror (errno));
+ }
+
+ dup2 (devnull_fd, STDIN_FILENO);
+ if (background)
+ {
+ dup2 (stdout_fd, STDOUT_FILENO);
+ dup2 (stderr_fd, STDERR_FILENO);
+ }
+
+ for (i = getdtablesize () - 1; i >= 3; --i)
+ close(i);
+
+ setsid ();
+
+ execve (exec, argv, newenv);
+#ifdef HAVE_PAM
+ if (pamr == PAM_SUCCESS)
+ pam_close_session (pamh, PAM_SILENT);
+#endif
+ eerrorx ("%s: failed to exec `%s': %s", progname, exec, strerror (errno));
+ }
+
+ /* Parent process */
+ if (! background)
+ {
+ /* As we're not backgrounding the process, wait for our pid to return */
+ int status = 0;
+ int savepid = pid;
+
+ errno = 0;
+ do
+ {
+ pid = waitpid (savepid, &status, 0);
+ if (pid < 1)
+ {
+ eerror ("waitpid %d: %s", savepid, strerror (errno));
+ return (-1);
+ }
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ if (! WIFEXITED (status) || WEXITSTATUS (status) != 0)
+ {
+ if (! quiet)
+ eerrorx ("%s: failed to started `%s'", progname, exec);
+ exit (EXIT_FAILURE);
+ }
+
+ pid = savepid;
+ }
+
+ /* Wait a little bit and check that process is still running
+ We do this as some badly written daemons fork and then barf */
+ if (START_WAIT > 0)
+ {
+ struct timeval stopat;
+ struct timeval now;
+
+ if (gettimeofday (&stopat, NULL) != 0)
+ eerrorx ("%s: gettimeofday: %s", progname, strerror (errno));
+
+ stopat.tv_usec += START_WAIT;
+ while (1)
+ {
+ bool alive = false;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = POLL_INTERVAL;
+ if (select (0, 0, 0, 0, &tv) < 0)
+ {
+ /* Let our signal handler handle the interupt */
+ if (errno != EINTR)
+ eerrorx ("%s: select: %s", progname, strerror (errno));
+ }
+
+ if (gettimeofday (&now, NULL) != 0)
+ eerrorx ("%s: gettimeofday: %s", progname, strerror (errno));
+
+ /* This is knarly.
+ If we backgrounded then we know the exact pid.
+ Otherwise if we have a pidfile then it *may* know the exact pid.
+ Failing that, we'll have to query processes.
+ We sleep first as some programs like ntp like to fork, and write
+ their pidfile a LONG time later. */
+ if (background)
+ {
+ if (kill (pid, 0) == 0)
+ alive = true;
+ }
+ else
+ {
+ if (pidfile && rc_exists (pidfile))
+ {
+ if (do_stop (NULL, NULL, pidfile, uid, 0, true, false, true) > 0)
+ alive = true;
+ }
+ else
+ {
+ if (do_stop (exec, cmd, NULL, uid, 0, true, false, true) > 0)
+ alive = true;
+ }
+ }
+
+ if (! alive)
+ eerrorx ("%s: %s died", progname, exec);
+
+ if (timercmp (&now, &stopat, >))
+ break;
+ }
+ }
+
+ if (svcname)
+ rc_set_service_daemon (svcname, exec, cmd, pidfile, true);
+
+ exit (EXIT_SUCCESS);
+}