openrc/src/rc/start-stop-daemon.c

1177 lines
27 KiB
C
Raw Normal View History

/*
start-stop-daemon
Starts, stops, tests and signals daemons
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.
*/
2008-01-14 10:35:22 +05:30
/*
* Copyright 2007-2008 Roy Marples <roy@marples.name>
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS 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 AUTHOR OR 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.
*/
/* nano seconds */
#define POLL_INTERVAL 20000000
#define WAIT_PIDFILE 500000000
#define START_WAIT 100000000
#define ONE_SECOND 1000000000
#include <sys/types.h>
#include <sys/ioctl.h>
#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 <limits.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_PAM
#include <security/pam_appl.h>
/* We are not supporting authentication conversations */
2007-11-28 21:15:03 +05:30
static struct pam_conv conv = { NULL, NULL};
#endif
#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
/* Some libc implementations don't define this */
#ifndef LIST_FOREACH_SAFE
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#endif
typedef struct scheduleitem
{
2007-04-11 18:14:47 +05:30
enum
{
SC_TIMEOUT,
SC_SIGNAL,
SC_GOTO,
SC_FOREVER
2007-04-11 18:14:47 +05:30
} type;
int value;
struct scheduleitem *gotoitem;
2008-07-08 16:18:21 +05:30
TAILQ_ENTRY(scheduleitem) entries;
} SCHEDULEITEM;
2008-07-08 16:18:21 +05:30
TAILQ_HEAD(, scheduleitem) schedule;
static char **nav = NULL;
2008-02-12 01:44:09 +05:30
extern const char *applet;
static char *changeuser = NULL;
extern char **environ;
static void free_schedulelist(void)
{
2008-07-08 16:18:21 +05:30
SCHEDULEITEM *s1 = TAILQ_FIRST(&schedule);
SCHEDULEITEM *s2;
while (s1) {
2008-07-08 16:18:21 +05:30
s2 = TAILQ_NEXT(s1, entries);
free(s1);
s1 = s2;
2007-04-11 18:14:47 +05:30
}
2008-07-08 16:18:21 +05:30
TAILQ_INIT(&schedule);
}
static void cleanup(void)
{
free(changeuser);
free(nav);
free_schedulelist();
}
static int parse_signal(const char *sig)
{
2007-04-11 18:14:47 +05:30
typedef struct signalpair
{
const char *name;
int signal;
} SIGNALPAIR;
2007-04-11 18:14:47 +05:30
static const SIGNALPAIR signallist[] = {
2007-04-11 18:14:47 +05:30
{ "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;
2008-01-22 16:37:39 +05:30
const char *s;
2007-04-11 18:14:47 +05:30
if (!sig || *sig == '\0')
return -1;
2007-04-11 18:14:47 +05:30
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", applet, sig);
2007-04-11 18:14:47 +05:30
}
if (strncmp(sig, "SIG", 3) == 0)
2008-01-22 16:37:39 +05:30
s = sig + 3;
2007-04-11 18:14:47 +05:30
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;
2007-04-11 18:14:47 +05:30
eerrorx("%s: `%s' is not a valid signal", applet, sig);
2008-01-22 16:37:39 +05:30
/* NOTREACHED */
}
static SCHEDULEITEM *parse_schedule_item(const char *string)
{
2007-04-11 18:14:47 +05:30
const char *after_hyph;
int sig;
SCHEDULEITEM *item = xmalloc(sizeof(*item));
item->value = 0;
item->gotoitem = NULL;
if (strcmp(string,"forever") == 0)
item->type = SC_FOREVER;
2008-04-17 15:49:58 +05:30
else if (isdigit((unsigned char)string[0])) {
item->type = SC_TIMEOUT;
2007-04-11 18:14:47 +05:30
errno = 0;
if (sscanf(string, "%d", &item->value) != 1)
eerrorx("%s: invalid timeout value in schedule `%s'", applet,
string);
2007-04-11 18:14:47 +05:30
} else if ((after_hyph = string + (string[0] == '-')) &&
((sig = parse_signal(after_hyph)) != -1))
2007-04-11 18:14:47 +05:30
{
item->type = SC_SIGNAL;
item->value = (int)sig;
2007-04-11 18:14:47 +05:30
}
else
eerrorx("%s: invalid schedule item `%s'", applet, string);
return item;
}
static void parse_schedule(const char *string, int timeout)
{
2007-04-11 18:14:47 +05:30
char buffer[20];
const char *slash;
int count = 0;
SCHEDULEITEM *repeatat = NULL;
2008-01-22 16:37:39 +05:30
size_t len;
SCHEDULEITEM *item;
2007-04-11 18:14:47 +05:30
if (string)
for (slash = string; *slash; slash++)
if (*slash == '/')
count++;
free_schedulelist();
2007-04-11 18:14:47 +05:30
if (count == 0) {
item = xmalloc(sizeof(*item));
item->type = SC_SIGNAL;
item->value = timeout;
item->gotoitem = NULL;
2008-07-08 16:18:21 +05:30
TAILQ_INSERT_TAIL(&schedule, item, entries);
item = xmalloc(sizeof(*item));
item->type = SC_TIMEOUT;
item->gotoitem = NULL;
2008-07-08 16:18:21 +05:30
TAILQ_INSERT_TAIL(&schedule, item, entries);
2007-04-11 18:14:47 +05:30
if (string) {
if (sscanf(string, "%d", &item->value) != 1)
eerrorx("%s: invalid timeout value in schedule", applet);
} else
item->value = 5;
2007-04-11 18:14:47 +05:30
return;
}
while (string != NULL) {
if ((slash = strchr(string, '/')))
2007-04-11 18:14:47 +05:30
len = slash - string;
else
len = strlen(string);
2007-04-11 18:14:47 +05:30
if (len >= (ptrdiff_t) sizeof(buffer))
eerrorx("%s: invalid schedule item, far too long", applet);
2007-04-11 18:14:47 +05:30
memcpy(buffer, string, len);
2007-04-11 18:14:47 +05:30
buffer[len] = 0;
string = slash ? slash + 1 : NULL;
item = parse_schedule_item(buffer);
2008-07-08 16:18:21 +05:30
TAILQ_INSERT_TAIL(&schedule, item, entries);
if (item->type == SC_FOREVER) {
2007-04-11 18:14:47 +05:30
if (repeatat)
eerrorx("%s: invalid schedule, `forever' "
"appears more than once", applet);
2007-04-11 18:14:47 +05:30
repeatat = item;
2007-04-11 18:14:47 +05:30
continue;
}
}
if (repeatat) {
item = xmalloc(sizeof(*item));
item->type = SC_GOTO;
item->value = 0;
item->gotoitem = repeatat;
2008-07-08 16:18:21 +05:30
TAILQ_INSERT_TAIL(&schedule, item, entries);
2007-04-11 18:14:47 +05:30
}
return;
}
static pid_t get_pid(const char *pidfile, bool quiet)
{
2007-04-11 18:14:47 +05:30
FILE *fp;
pid_t pid;
if (! pidfile)
return -1;
2007-04-11 18:14:47 +05:30
if ((fp = fopen(pidfile, "r")) == NULL) {
if (!quiet)
eerror("%s: fopen `%s': %s", applet, pidfile, strerror(errno));
return -1;
2007-04-11 18:14:47 +05:30
}
if (fscanf(fp, "%d", &pid) != 1) {
if (!quiet)
eerror("%s: no pid found in `%s'", applet, pidfile);
fclose(fp);
return -1;
2007-04-11 18:14:47 +05:30
}
fclose(fp);
2007-04-11 18:14:47 +05:30
return pid;
}
/* return number of processed killed, -1 on error */
static int do_stop(const char *exec, const char *const *argv,
pid_t pid, uid_t uid,int sig,
bool quiet, bool verbose, bool test)
{
RC_PIDLIST *pids;
RC_PID *pi;
RC_PID *np;
2007-04-11 18:14:47 +05:30
bool killed;
int nkilled = 0;
if (pid)
pids = rc_find_pids(NULL, NULL, 0, pid);
else
pids = rc_find_pids(exec, argv, uid, pid);
2007-04-11 18:14:47 +05:30
if (!pids)
return 0;
2007-04-11 18:14:47 +05:30
LIST_FOREACH_SAFE(pi, pids, entries, np) {
2007-04-11 18:14:47 +05:30
if (test) {
if (!quiet)
einfo("Would send signal %d to PID %d",
sig, pi->pid);
2007-04-11 18:14:47 +05:30
nkilled++;
} else {
if (verbose)
ebegin("Sending signal %d to PID %d",
sig, pi->pid);
errno = 0;
killed = (kill(pi->pid, sig) == 0 ||
errno == ESRCH ? true : false);
if (verbose)
eend(killed ? 0 : 1,
"%s: failed to send signal %d to PID %d: %s",
applet, sig, pi->pid, strerror(errno));
if (!killed) {
nkilled = -1;
} else {
if (nkilled != -1)
nkilled++;
}
2007-04-11 18:14:47 +05:30
}
free(pi);
2007-04-11 18:14:47 +05:30
}
free(pids);
return nkilled;
}
static int run_stop_schedule(const char *exec, const char *const *argv,
const char *pidfile, uid_t uid,
bool quiet, bool verbose, bool test)
{
2008-07-08 16:18:21 +05:30
SCHEDULEITEM *item = TAILQ_FIRST(&schedule);
2007-04-11 18:14:47 +05:30
int nkilled = 0;
int tkilled = 0;
int nrunning = 0;
long nloops;
struct timespec ts;
pid_t pid = 0;
2007-04-11 18:14:47 +05:30
if (verbose) {
if (exec)
einfo ("Will stop %s\n", exec);
2007-04-11 18:14:47 +05:30
if (pidfile)
einfo("Will stop PID in pidfile `%s'", pidfile);
2007-04-11 18:14:47 +05:30
if (uid)
einfo("Will stop processes owned by UID %d", uid);
2008-02-20 03:15:27 +05:30
if (argv && *argv)
einfo("Will stop processes of `%s'", *argv);
2007-04-11 18:14:47 +05:30
}
if (pidfile) {
pid = get_pid(pidfile, quiet);
if (pid == -1)
return 0;
}
2007-04-11 18:14:47 +05:30
while (item) {
switch (item->type) {
case SC_GOTO:
item = item->gotoitem;
continue;
2007-04-11 18:14:47 +05:30
case SC_SIGNAL:
nrunning = 0;
nkilled = do_stop(exec, argv, pid, uid, item->value,
quiet, verbose, test);
if (nkilled == 0) {
if (tkilled == 0) {
if (! quiet)
eerror("%s: no matching "
"processes found", applet);
2007-04-11 18:14:47 +05:30
}
return tkilled;
}
else if (nkilled == -1)
return 0;
2007-04-11 18:14:47 +05:30
tkilled += nkilled;
break;
case SC_TIMEOUT:
if (item->value < 1) {
item = NULL;
2007-04-11 18:14:47 +05:30
break;
}
2007-04-11 18:14:47 +05:30
nloops = (ONE_SECOND / POLL_INTERVAL) * item->value;
ts.tv_sec = 0;
ts.tv_nsec = POLL_INTERVAL;
while (nloops) {
if ((nrunning = do_stop(exec, argv, pid,
uid, 0, true, false, true)) == 0)
return 0;
if (nanosleep(&ts, NULL) == -1) {
if (errno == EINTR)
eerror("%s: caught an interrupt", applet);
else {
eerror("%s: nanosleep: %s",
applet, strerror(errno));
return 0;
2007-04-11 18:14:47 +05:30
}
}
nloops --;
}
break;
2007-04-11 18:14:47 +05:30
default:
eerror("%s: invalid schedule item `%d'", applet, item->type);
return 0;
2007-04-11 18:14:47 +05:30
}
if (item)
2008-07-08 16:18:21 +05:30
item = TAILQ_NEXT(item, entries);
2007-04-11 18:14:47 +05:30
}
if (test || (tkilled > 0 && nrunning == 0))
return nkilled;
2007-04-11 18:14:47 +05:30
if (! quiet) {
if (nrunning == 1)
eerror("%s: %d process refused to stop", applet, nrunning);
2007-04-11 18:14:47 +05:30
else
eerror("%s: %d process(es) refused to stop", applet, nrunning);
2007-04-11 18:14:47 +05:30
}
return -nrunning;
}
static void handle_signal(int sig)
{
2007-04-11 18:14:47 +05:30
int status;
int serrno = errno;
char signame[10] = { '\0' };
switch (sig) {
case SIGINT:
if (!signame[0])
snprintf(signame, sizeof(signame), "SIGINT");
/* FALLTHROUGH */
case SIGTERM:
if (!signame[0])
snprintf(signame, sizeof(signame), "SIGTERM");
/* FALLTHROUGH */
case SIGQUIT:
if (!signame[0])
snprintf(signame, sizeof(signame), "SIGQUIT");
eerrorx("%s: caught %s, aborting", applet, signame);
/* NOTREACHED */
case SIGCHLD:
for (;;) {
if (waitpid(-1, &status, WNOHANG) < 0) {
if (errno != ECHILD)
eerror("%s: waitpid: %s", applet, strerror(errno));
break;
2007-04-11 18:14:47 +05:30
}
}
break;
2007-04-11 18:14:47 +05:30
default:
eerror("%s: caught unknown signal %d", applet, sig);
2007-04-11 18:14:47 +05:30
}
/* Restore errno */
errno = serrno;
}
2007-08-17 16:31:18 +05:30
#include "_usage.h"
2008-08-20 15:32:11 +05:30
#define getoptstring "KN:R:Sbc:d:e:g:k:mn:op:s:tu:r:x:1:2:" getoptstring_COMMON
static const struct option longopts[] = {
2007-08-17 16:31:18 +05:30
{ "stop", 0, NULL, 'K'},
{ "nicelevel", 1, NULL, 'N'},
{ "retry", 1, NULL, 'R'},
{ "start", 0, NULL, 'S'},
{ "startas", 1, NULL, 'a'},
{ "background", 0, NULL, 'b'},
{ "chuid", 1, NULL, 'c'},
{ "chdir", 1, NULL, 'd'},
{ "env", 1, NULL, 'e'},
2008-08-20 15:32:11 +05:30
{ "umask", 1, NULL, 'k'},
2007-08-17 16:31:18 +05:30
{ "group", 1, NULL, 'g'},
{ "make-pidfile", 0, NULL, 'm'},
{ "name", 1, NULL, 'n'},
{ "oknodo", 0, NULL, 'o'},
{ "pidfile", 1, NULL, 'p'},
{ "signal", 1, NULL, 's'},
{ "test", 0, NULL, 't'},
{ "user", 1, NULL, 'u'},
{ "chroot", 1, NULL, 'r'},
{ "exec", 1, NULL, 'x'},
{ "stdout", 1, NULL, '1'},
{ "stderr", 1, NULL, '2'},
longopts_COMMON
};
static const char * const longopts_help[] = {
"Stop daemon",
"Set a nicelevel when starting",
"Retry schedule to use when stopping",
"Start daemon",
"deprecated, use --exec or --name",
"Force daemon to background",
"deprecated, use --user",
"Change the PWD",
"Set an environment string",
2008-08-20 15:32:11 +05:30
"Set the umask for the daemon",
"Change the process group",
"Create a pidfile",
"Match process name",
"deprecated",
"Match pid found in this file",
"Send a different signal",
"Test actions, don't do them",
"Change the process user",
"Chroot to this directory",
"Binary to start/stop",
"Redirect stdout to file",
"Redirect stderr to file",
longopts_help_COMMON
};
2007-08-17 16:31:18 +05:30
#include "_usage.c"
int start_stop_daemon(int argc, char **argv)
{
2007-04-11 18:14:47 +05:30
int devnull_fd = -1;
#ifdef TIOCNOTTY
2007-04-11 18:14:47 +05:30
int tty_fd = -1;
#endif
2007-08-17 16:31:18 +05:30
#ifdef HAVE_PAM
2007-04-11 18:14:47 +05:30
pam_handle_t *pamh = NULL;
int pamr;
#endif
int opt;
bool start = true;
2007-04-11 18:14:47 +05:30
bool oknodo = false;
bool test = false;
bool quiet;
2007-04-11 18:14:47 +05:30
bool verbose = false;
char *exec = NULL;
char *startas = NULL;
char *name = NULL;
2007-04-11 18:14:47 +05:30
char *pidfile = NULL;
int sig = SIGTERM;
int nicelevel = 0;
bool background = false;
bool makepidfile = false;
uid_t uid = 0;
gid_t gid = 0;
2007-04-11 18:14:47 +05:30
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, spid;
2007-04-11 18:14:47 +05:30
int i;
char *svcname = getenv("RC_SVCNAME");
RC_STRINGLIST *env_list;
RC_STRING *env;
char *tmp, *newpath, *np;
bool sethome = false;
bool setuser = false;
char *p;
char *token;
char exec_file[PATH_MAX];
struct passwd *pw;
struct group *gr;
char line[130];
FILE *fp;
size_t len;
2008-08-20 15:49:03 +05:30
bool setumask = false;
2008-08-20 15:32:11 +05:30
mode_t numask;
2007-04-11 18:14:47 +05:30
2008-07-08 16:18:21 +05:30
TAILQ_INIT(&schedule);
atexit(cleanup);
2007-04-11 18:14:47 +05:30
signal_setup(SIGINT, handle_signal);
signal_setup(SIGQUIT, handle_signal);
signal_setup(SIGTERM, handle_signal);
2007-04-11 18:14:47 +05:30
if ((tmp = getenv("SSD_NICELEVEL")))
if (sscanf(tmp, "%d", &nicelevel) != 1)
eerror("%s: invalid nice level `%s' (SSD_NICELEVEL)",
applet, tmp);
while ((opt = getopt_long(argc, argv, getoptstring, longopts,
(int *) 0)) != -1)
switch (opt) {
case 'K': /* --stop */
start = false;
break;
2007-04-11 18:14:47 +05:30
case 'N': /* --nice */
if (sscanf(optarg, "%d", &nicelevel) != 1)
eerrorx("%s: invalid nice level `%s'", applet, optarg);
break;
2007-04-11 18:14:47 +05:30
case 'R': /* --retry <schedule>|<timeout> */
parse_schedule(optarg, sig);
break;
2007-04-11 18:14:47 +05:30
case 'S': /* --start */
start = true;
break;
2007-04-11 18:14:47 +05:30
case 'b': /* --background */
background = true;
break;
case 'u': /* --user <username>|<uid> */
case 'c': /* --chuid <username>|<uid> */
{
p = optarg;
tmp = strsep(&p, ":");
changeuser = xstrdup(tmp);
if (sscanf(tmp, "%d", &tid) != 1)
pw = getpwnam(tmp);
else
pw = getpwuid((uid_t) tid);
if (! pw)
eerrorx("%s: user `%s' not found", applet, tmp);
uid = pw->pw_uid;
if (! gid)
gid = pw->pw_gid;
if (p) {
tmp = strsep (&p, ":");
if (sscanf(tmp, "%d", &tid) != 1)
gr = getgrnam(tmp);
else
gr = getgrgid((gid_t) tid);
2007-04-11 18:14:47 +05:30
if (! gr)
eerrorx("%s: group `%s' not found",
applet, tmp);
gid = gr->gr_gid;
}
}
break;
case 'd': /* --chdir /new/dir */
ch_dir = optarg;
break;
case 'e': /* --env */
if (putenv(optarg) == 0) {
if (strncmp("HOME=", optarg, 5) == 0)
sethome = true;
else if (strncmp("USER=", optarg, 5) == 0)
setuser = true;
}
break;
case 'g': /* --group <group>|<gid> */
{
if (sscanf(optarg, "%d", &tid) != 1)
gr = getgrnam(optarg);
else
gr = getgrgid((gid_t) tid);
2007-04-11 18:14:47 +05:30
if (! gr)
eerrorx("%s: group `%s' not found", applet, optarg);
gid = gr->gr_gid;
}
break;
2007-04-11 18:14:47 +05:30
2008-08-20 15:32:11 +05:30
case 'k':
if (parse_mode(&numask, optarg))
eerrorx("%s: invalid mode `%s'",
applet, optarg);
2008-08-20 15:49:03 +05:30
setumask = true;
2008-08-20 15:32:11 +05:30
break;
case 'm': /* --make-pidfile */
makepidfile = true;
break;
2007-04-11 18:14:47 +05:30
case 'n': /* --name <process-name> */
name = optarg;
break;
2007-04-11 18:14:47 +05:30
case 'o': /* --oknodo */
oknodo = true;
break;
2007-04-11 18:14:47 +05:30
case 'p': /* --pidfile <pid-file> */
pidfile = optarg;
break;
2007-04-11 18:14:47 +05:30
case 's': /* --signal <signal> */
sig = parse_signal(optarg);
break;
2007-04-11 18:14:47 +05:30
case 't': /* --test */
test = true;
break;
2007-04-11 18:14:47 +05:30
case 'r': /* --chroot /new/root */
ch_root = optarg;
break;
2007-04-11 18:14:47 +05:30
case 'a': /* --startas <name> */
startas = optarg;
break;
case 'x': /* --exec <executable> */
exec = optarg;
break;
2007-04-11 18:14:47 +05:30
case '1': /* --stdout /path/to/stdout.lgfile */
redirect_stdout = optarg;
break;
case '2': /* --stderr /path/to/stderr.logfile */
redirect_stderr = optarg;
break;
2007-04-11 18:14:47 +05:30
case_RC_COMMON_GETOPT
2007-04-11 18:14:47 +05:30
}
endpwent();
argc -= optind;
argv += optind;
quiet = rc_yesno(getenv("EINFO_QUIET"));
verbose = rc_yesno(getenv("EINFO_VERBOSE"));
2007-04-11 18:14:47 +05:30
/* Allow start-stop-daemon --signal HUP --exec /usr/sbin/dnsmasq
* instead of forcing --stop --oknodo as well */
if (!start &&
sig != SIGINT &&
sig != SIGTERM &&
sig != SIGQUIT &&
sig != SIGKILL)
oknodo = true;
if (!exec)
exec = startas;
else if (!name)
name = startas;
if (!exec) {
exec = *argv;
if (name)
*argv = name;
} else if (name)
*--argv = name;
else
*--argv = exec;
2007-04-11 18:14:47 +05:30
if (start && !exec)
eerrorx("%s: nothing to start", applet);
2007-04-11 18:14:47 +05:30
if (!start && !*argv && !pidfile && !name && !uid)
eerrorx("%s: --stop needs --exec, --pidfile, --name or --user", applet);
2007-04-11 18:14:47 +05:30
if (makepidfile && !pidfile)
eerrorx("%s: --make-pidfile is only relevant with --pidfile", applet);
2007-04-11 18:14:47 +05:30
if (background && !start)
eerrorx("%s: --background is only relevant with --start", applet);
2007-04-11 18:14:47 +05:30
if ((redirect_stdout || redirect_stderr) && !background)
eerrorx("%s: --stdout and --stderr are only relevant with --background",
2008-01-11 21:21:40 +05:30
applet);
2007-04-11 18:14:47 +05:30
if (!start) {
if (!TAILQ_FIRST(&schedule)) {
2007-04-11 18:14:47 +05:30
if (test || oknodo)
parse_schedule("0", sig);
2007-04-11 18:14:47 +05:30
else
parse_schedule(NULL, sig);
2007-04-11 18:14:47 +05:30
}
i = run_stop_schedule(exec, (const char *const *)argv,
pidfile, uid, quiet, verbose, test);
2007-04-11 18:14:47 +05:30
if (i < 0)
/* We failed to stop something */
exit(EXIT_FAILURE);
2007-04-11 18:14:47 +05:30
if (test || oknodo)
return i > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
2007-04-11 18:14:47 +05:30
/* Even if we have not actually killed anything, we should
* remove information about it as it may have unexpectedly
* crashed out. We should also return success as the end
* result would be the same. */
if (pidfile && exists(pidfile))
unlink(pidfile);
2007-04-11 18:14:47 +05:30
if (svcname)
rc_service_daemon_set(svcname, exec,
(const char *const *)argv,
pidfile, false);
exit(EXIT_SUCCESS);
2007-04-11 18:14:47 +05:30
}
/* Validate that the binary exists if we are starting */
if (*exec == '/' || *exec == '.') {
/* Full or relative path */
if (ch_root)
snprintf(exec_file, sizeof(exec_file), "%s/%s", ch_root, exec);
else
snprintf(exec_file, sizeof(exec_file), "%s", exec);
} else {
/* Something in $PATH */
p = tmp = xstrdup(getenv("PATH"));
*exec_file = '\0';
while ((token = strsep(&p, ":"))) {
if (ch_root)
snprintf(exec_file, sizeof(exec_file), "%s/%s/%s", ch_root, token, exec);
else
snprintf(exec_file, sizeof(exec_file), "%s/%s", token, exec);
if (exists(exec_file))
break;
*exec_file = '\0';
}
free(tmp);
}
if (!exists(exec_file)) {
eerror("%s: %s does not exist", applet,
*exec_file ? exec_file : exec);
exit(EXIT_FAILURE);
}
/* If we don't have a pidfile we should check if it's interpreted
* or not. If it we, we need to pass the interpreter through
* to our daemon calls to find it correctly. */
if (!pidfile) {
fp = fopen(exec_file, "r");
if (fp) {
fgets(line, sizeof(line), fp);
fclose(fp);
if (line[0] == '#' && line[1] == '!') {
/* Remove the trailing newline */
len = strlen(line) - 1;
if (line[len] == '\n')
line[len] = '\0';
strncpy(exec_file, line + 2, sizeof(exec_file));
opt = 0;
for (nav = argv; *nav; nav++)
opt++;
2008-09-18 18:39:51 +05:30
nav = xmalloc(sizeof(char *) * (opt + 2));
nav[0] = exec_file;
for (i = 0; i < opt; i++)
2008-09-18 18:39:51 +05:30
nav[i + 1] = argv[i];
nav[i + 1] = '\0';
argv = nav;
exec = exec_file;
}
}
}
if (pidfile)
2008-05-27 22:18:47 +05:30
pid = get_pid(pidfile, true);
else
pid = 0;
if (do_stop(exec, (const char * const *)argv, pid, uid,
0, true, false, true) > 0)
eerrorx("%s: %s is already running", applet, exec);
2007-04-11 18:14:47 +05:30
if (test) {
if (quiet)
exit (EXIT_SUCCESS);
einfon("Would start");
while (argc-- >= 0)
printf(" %s", *argv++);
printf("\n");
eindent();
if (uid != 0)
einfo("as user id %d", uid);
if (gid != 0)
einfo("as group id %d", gid);
2007-04-11 18:14:47 +05:30
if (ch_root)
einfo("in root `%s'", ch_root);
2007-04-11 18:14:47 +05:30
if (ch_dir)
einfo("in dir `%s'", ch_dir);
2007-04-11 18:14:47 +05:30
if (nicelevel != 0)
einfo("with a priority of %d", nicelevel);
if (name)
einfo ("with a process name of %s", name);
eoutdent();
exit(EXIT_SUCCESS);
2007-04-11 18:14:47 +05:30
}
if (verbose) {
ebegin("Detaching to start `%s'", exec);
eindent();
2007-04-11 18:14:47 +05:30
}
if (background)
signal_setup(SIGCHLD, handle_signal);
2007-04-11 18:14:47 +05:30
if ((pid = fork()) == -1)
eerrorx("%s: fork: %s", applet, strerror(errno));
2007-04-11 18:14:47 +05:30
/* Child process - lets go! */
if (pid == 0) {
pid_t mypid = getpid();
2008-08-20 15:49:03 +05:30
if (setumask)
umask(numask);
#ifdef TIOCNOTTY
2007-04-11 18:14:47 +05:30
tty_fd = open("/dev/tty", O_RDWR);
#endif
2007-04-11 18:14:47 +05:30
devnull_fd = open("/dev/null", O_RDWR);
2007-04-11 18:14:47 +05:30
if (nicelevel) {
if (setpriority(PRIO_PROCESS, mypid, nicelevel) == -1)
eerrorx("%s: setpritory %d: %s", applet, nicelevel,
2008-01-11 21:21:40 +05:30
strerror(errno));
2007-04-11 18:14:47 +05:30
}
if (ch_root && chroot(ch_root) < 0)
eerrorx("%s: chroot `%s': %s", applet, ch_root, strerror(errno));
if (ch_dir && chdir(ch_dir) < 0)
eerrorx("%s: chdir `%s': %s", applet, ch_dir, strerror(errno));
2007-04-11 18:14:47 +05:30
if (makepidfile && pidfile) {
fp = fopen(pidfile, "w");
2007-04-11 18:14:47 +05:30
if (! fp)
eerrorx("%s: fopen `%s': %s", applet, pidfile,
strerror(errno));
fprintf(fp, "%d\n", mypid);
fclose(fp);
2007-04-11 18:14:47 +05:30
}
#ifdef HAVE_PAM
2007-04-11 18:14:47 +05:30
if (changeuser != NULL)
pamr = pam_start("start-stop-daemon", changeuser, &conv, &pamh);
2007-04-11 18:14:47 +05:30
else
pamr = pam_start("start-stop-daemon", "nobody", &conv, &pamh);
2007-04-11 18:14:47 +05:30
if (pamr == PAM_SUCCESS)
pamr = pam_authenticate(pamh, PAM_SILENT);
2007-04-11 18:14:47 +05:30
if (pamr == PAM_SUCCESS)
pamr = pam_acct_mgmt(pamh, PAM_SILENT);
2007-04-11 18:14:47 +05:30
if (pamr == PAM_SUCCESS)
pamr = pam_open_session(pamh, PAM_SILENT);
2007-04-11 18:14:47 +05:30
if (pamr != PAM_SUCCESS)
eerrorx("%s: pam error: %s", applet, pam_strerror(pamh, pamr));
#endif
if (gid && setgid(gid))
eerrorx("%s: unable to set groupid to %d", applet, gid);
if (changeuser && initgroups(changeuser, gid))
eerrorx("%s: initgroups (%s, %d)", applet, changeuser, gid);
if (uid && setuid(uid))
2007-08-17 16:31:18 +05:30
eerrorx ("%s: unable to set userid to %d", applet, uid);
2007-04-11 18:14:47 +05:30
else {
pw = getpwuid(uid);
if (pw) {
if (!sethome) {
unsetenv("HOME");
if (pw->pw_dir)
setenv("HOME", pw->pw_dir, 1);
}
if (!setuser) {
unsetenv("USER");
if (pw->pw_name)
setenv("USER", pw->pw_name, 1);
}
2007-04-11 18:14:47 +05:30
}
}
/* Close any fd's to the passwd database */
endpwent();
#ifdef TIOCNOTTY
2007-04-11 18:14:47 +05:30
ioctl(tty_fd, TIOCNOTTY, 0);
close(tty_fd);
#endif
2007-04-11 18:14:47 +05:30
/* Clean the environment of any RC_ variables */
env_list = rc_stringlist_new();
i = 0;
while(environ[i])
rc_stringlist_add(env_list, environ[i++]);
TAILQ_FOREACH(env, env_list, entries) {
if ((strncmp(env->value, "RC_", 3) == 0 &&
strncmp(env->value, "RC_SERVICE=", 10) != 0 &&
strncmp(env->value, "RC_SVCNAME=", 10) != 0) ||
2008-03-20 01:29:50 +05:30
strncmp(env->value, "SSD_NICELEVEL=", 14) == 0)
{
p = strchr(env->value, '=');
*p = '\0';
unsetenv(env->value);
continue;
}
}
rc_stringlist_free(env_list);
/* For the path, remove the rcscript bin dir from it */
if ((tmp = xstrdup(getenv("PATH")))) {
len = strlen(tmp);
newpath = np = xmalloc(len);
p = tmp;
while ((token = strsep(&p, ":"))) {
if (strcmp(token, RC_LIBDIR "/bin") == 0 ||
strcmp(token, RC_LIBDIR "/sbin") == 0)
continue;
len = strlen(token);
if (np != newpath)
*np++ = ':';
memcpy(np, token, len);
np += len;
*np = '\0';
}
free(tmp);
unsetenv("PATH");
setenv("PATH", newpath, 1);
}
umask(022);
2007-04-11 18:14:47 +05:30
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",
applet, redirect_stdout, strerror(errno));
2007-04-11 18:14:47 +05:30
}
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",
applet, redirect_stderr, strerror(errno));
2007-04-11 18:14:47 +05:30
}
/* We don't redirect stdin as some daemons may need it */
if (background || quiet || redirect_stdout)
dup2(stdout_fd, STDOUT_FILENO);
if (background || quiet || redirect_stderr)
dup2(stderr_fd, STDERR_FILENO);
2007-04-11 18:14:47 +05:30
for (i = getdtablesize() - 1; i >= 3; --i)
2007-04-11 18:14:47 +05:30
close(i);
setsid();
execvp(exec, argv);
#ifdef HAVE_PAM
2007-04-11 18:14:47 +05:30
if (pamr == PAM_SUCCESS)
pam_close_session(pamh, PAM_SILENT);
#endif
eerrorx("%s: failed to exec `%s': %s", applet, exec, strerror(errno));
2007-04-11 18:14:47 +05:30
}
/* Parent process */
if (!background) {
2007-04-11 18:14:47 +05:30
/* As we're not backgrounding the process, wait for our pid to return */
i = 0;
spid = pid;
2007-04-11 18:14:47 +05:30
do {
pid = waitpid(spid, &i, 0);
2007-04-11 18:14:47 +05:30
if (pid < 1) {
eerror("waitpid %d: %s", spid, strerror(errno));
return -1;
2007-04-11 18:14:47 +05:30
}
} while (!WIFEXITED(i) && !WIFSIGNALED(i));
if (!WIFEXITED(i) || WEXITSTATUS(i) != 0) {
if (!quiet)
eerrorx("%s: failed to start `%s'", applet, exec);
exit(EXIT_FAILURE);
2007-04-11 18:14:47 +05:30
}
pid = spid;
2007-04-11 18:14:47 +05:30
}
/* 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 timespec ts;
int nloops = START_WAIT / POLL_INTERVAL;
int nloopsp = WAIT_PIDFILE / POLL_INTERVAL;
bool alive = false;
2008-01-11 21:21:40 +05:30
ts.tv_sec = 0;
ts.tv_nsec = POLL_INTERVAL;
while (nloops) {
if (nanosleep(&ts, NULL) == -1) {
if (errno == EINTR)
eerror("%s: caught an interrupt", applet);
else {
eerror("%s: nanosleep: %s", applet, strerror(errno));
return 0;
}
2007-04-11 18:14:47 +05:30
}
/* We wait for a specific amount of time for a pidfile to be
* created. Once everything is in place we then wait some more
* to ensure that the daemon really is running and won't abort due
* to a config error. */
if (!background && pidfile && nloopsp)
nloopsp--;
else
nloops--;
2007-04-11 18:14:47 +05:30
/* 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) {
/* The pidfile may not have been written yet - give it some time */
if ((pid = get_pid(pidfile, true)) == -1) {
2007-12-03 19:08:23 +05:30
if (! nloopsp)
eerrorx("%s: did not create a valid pid in `%s'",
applet, pidfile);
2007-04-11 18:14:47 +05:30
alive = true;
pid = 0;
2007-12-03 19:08:23 +05:30
} else
nloopsp = 0;
} else
pid = 0;
if (do_stop(exec, (const char *const *)argv,
pid, uid, 0, true, false, true) > 0)
alive = true;
2007-04-11 18:14:47 +05:30
}
if (!alive)
eerrorx("%s: %s died", applet, exec);
2007-04-11 18:14:47 +05:30
}
}
if (svcname)
rc_service_daemon_set(svcname, exec, (const char *const *)argv,
pidfile, true);
2007-04-11 18:14:47 +05:30
exit(EXIT_SUCCESS);
2008-01-22 16:37:39 +05:30
/* NOTREACHED */
}