diff options
Diffstat (limited to 'src/start-stop-daemon.c')
| -rw-r--r-- | src/start-stop-daemon.c | 1047 | 
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); +} | 
