| 1 |
/*
|
| 2 |
start-stop-daemon
|
| 3 |
Starts, stops, tests and signals daemons
|
| 4 |
Copyright 2007 Gentoo Foundation
|
| 5 |
Released under the GPLv2
|
| 6 |
|
| 7 |
This is essentially a ground up re-write of Debians
|
| 8 |
start-stop-daemon for cleaner code and to integrate into our RC
|
| 9 |
system so we can monitor daemons a little.
|
| 10 |
*/
|
| 11 |
|
| 12 |
#define APPLET "start-stop-daemon"
|
| 13 |
|
| 14 |
/* nano seconds */
|
| 15 |
#define POLL_INTERVAL 20000000
|
| 16 |
#define START_WAIT 100000000
|
| 17 |
#define ONE_SECOND 1000000000
|
| 18 |
|
| 19 |
#include <sys/types.h>
|
| 20 |
#include <sys/ioctl.h>
|
| 21 |
#include <sys/resource.h>
|
| 22 |
#include <sys/stat.h>
|
| 23 |
#include <sys/termios.h>
|
| 24 |
#include <sys/time.h>
|
| 25 |
#include <sys/wait.h>
|
| 26 |
#include <ctype.h>
|
| 27 |
#include <errno.h>
|
| 28 |
#include <fcntl.h>
|
| 29 |
#include <getopt.h>
|
| 30 |
#include <grp.h>
|
| 31 |
#include <pwd.h>
|
| 32 |
#include <signal.h>
|
| 33 |
#include <stddef.h>
|
| 34 |
#include <stdio.h>
|
| 35 |
#include <stdlib.h>
|
| 36 |
#include <string.h>
|
| 37 |
#include <time.h>
|
| 38 |
#include <unistd.h>
|
| 39 |
|
| 40 |
#ifdef HAVE_PAM
|
| 41 |
#include <security/pam_appl.h>
|
| 42 |
|
| 43 |
/* We are not supporting authentication conversations */
|
| 44 |
static struct pam_conv conv = { NULL, NULL} ;
|
| 45 |
#endif
|
| 46 |
|
| 47 |
#include "builtins.h"
|
| 48 |
#include "einfo.h"
|
| 49 |
#include "rc.h"
|
| 50 |
#include "rc-misc.h"
|
| 51 |
#include "strlist.h"
|
| 52 |
|
| 53 |
typedef struct schedulelist
|
| 54 |
{
|
| 55 |
enum
|
| 56 |
{
|
| 57 |
schedule_timeout,
|
| 58 |
schedule_signal,
|
| 59 |
schedule_goto,
|
| 60 |
schedule_forever
|
| 61 |
} type;
|
| 62 |
int value;
|
| 63 |
struct schedulelist *gotolist;
|
| 64 |
struct schedulelist *next;
|
| 65 |
} schedulelist_t;
|
| 66 |
static schedulelist_t *schedule;
|
| 67 |
|
| 68 |
static char *applet;
|
| 69 |
static char *changeuser;
|
| 70 |
static char **newenv;
|
| 71 |
|
| 72 |
extern char **environ;
|
| 73 |
|
| 74 |
static void free_schedulelist (schedulelist_t **list)
|
| 75 |
{
|
| 76 |
schedulelist_t *here;
|
| 77 |
schedulelist_t *next;
|
| 78 |
|
| 79 |
for (here = *list; here; here = next) {
|
| 80 |
next = here->next;
|
| 81 |
free (here);
|
| 82 |
}
|
| 83 |
|
| 84 |
*list = NULL;
|
| 85 |
}
|
| 86 |
|
| 87 |
static void cleanup (void)
|
| 88 |
{
|
| 89 |
if (changeuser)
|
| 90 |
free (changeuser);
|
| 91 |
|
| 92 |
if (schedule)
|
| 93 |
free_schedulelist (&schedule);
|
| 94 |
|
| 95 |
if (newenv)
|
| 96 |
rc_strlist_free (newenv);
|
| 97 |
}
|
| 98 |
|
| 99 |
static int parse_signal (const char *sig)
|
| 100 |
{
|
| 101 |
typedef struct signalpair
|
| 102 |
{
|
| 103 |
const char *name;
|
| 104 |
int signal;
|
| 105 |
} signalpair_t;
|
| 106 |
|
| 107 |
static const signalpair_t signallist[] = {
|
| 108 |
{ "ABRT", SIGABRT },
|
| 109 |
{ "ALRM", SIGALRM },
|
| 110 |
{ "FPE", SIGFPE },
|
| 111 |
{ "HUP", SIGHUP },
|
| 112 |
{ "ILL", SIGILL },
|
| 113 |
{ "INT", SIGINT },
|
| 114 |
{ "KILL", SIGKILL },
|
| 115 |
{ "PIPE", SIGPIPE },
|
| 116 |
{ "QUIT", SIGQUIT },
|
| 117 |
{ "SEGV", SIGSEGV },
|
| 118 |
{ "TERM", SIGTERM },
|
| 119 |
{ "USR1", SIGUSR1 },
|
| 120 |
{ "USR2", SIGUSR2 },
|
| 121 |
{ "CHLD", SIGCHLD },
|
| 122 |
{ "CONT", SIGCONT },
|
| 123 |
{ "STOP", SIGSTOP },
|
| 124 |
{ "TSTP", SIGTSTP },
|
| 125 |
{ "TTIN", SIGTTIN },
|
| 126 |
{ "TTOU", SIGTTOU }
|
| 127 |
};
|
| 128 |
|
| 129 |
unsigned int i = 0;
|
| 130 |
char *s;
|
| 131 |
|
| 132 |
if (! sig || strlen (sig) == 0)
|
| 133 |
return (-1);
|
| 134 |
|
| 135 |
if (sscanf (sig, "%u", &i) == 1) {
|
| 136 |
if (i > 0 && i < sizeof (signallist) / sizeof (signallist[0]))
|
| 137 |
return (i);
|
| 138 |
eerrorx ("%s: `%s' is not a valid signal", applet, sig);
|
| 139 |
}
|
| 140 |
|
| 141 |
if (strncmp (sig, "SIG", 3) == 0)
|
| 142 |
s = (char *) sig + 3;
|
| 143 |
else
|
| 144 |
s = NULL;
|
| 145 |
|
| 146 |
for (i = 0; i < sizeof (signallist) / sizeof (signallist[0]); i++)
|
| 147 |
if (strcmp (sig, signallist[i].name) == 0 ||
|
| 148 |
(s && strcmp (s, signallist[i].name) == 0))
|
| 149 |
return (signallist[i].signal);
|
| 150 |
|
| 151 |
eerrorx ("%s: `%s' is not a valid signal", applet, sig);
|
| 152 |
}
|
| 153 |
|
| 154 |
static void parse_schedule_item (schedulelist_t *item, const char *string)
|
| 155 |
{
|
| 156 |
const char *after_hyph;
|
| 157 |
int sig;
|
| 158 |
|
| 159 |
if (strcmp (string,"forever") == 0)
|
| 160 |
item->type = schedule_forever;
|
| 161 |
else if (isdigit (string[0])) {
|
| 162 |
item->type = schedule_timeout;
|
| 163 |
errno = 0;
|
| 164 |
if (sscanf (string, "%d", &item->value) != 1)
|
| 165 |
eerrorx ("%s: invalid timeout value in schedule `%s'", applet,
|
| 166 |
string);
|
| 167 |
} else if ((after_hyph = string + (string[0] == '-')) &&
|
| 168 |
((sig = parse_signal (after_hyph)) != -1))
|
| 169 |
{
|
| 170 |
item->type = schedule_signal;
|
| 171 |
item->value = (int) sig;
|
| 172 |
}
|
| 173 |
else
|
| 174 |
eerrorx ("%s: invalid schedule item `%s'", applet, string);
|
| 175 |
}
|
| 176 |
|
| 177 |
static void parse_schedule (const char *string, int default_signal)
|
| 178 |
{
|
| 179 |
char buffer[20];
|
| 180 |
const char *slash;
|
| 181 |
int count = 0;
|
| 182 |
schedulelist_t *repeatat = NULL;
|
| 183 |
ptrdiff_t len;
|
| 184 |
schedulelist_t *next;
|
| 185 |
|
| 186 |
if (string)
|
| 187 |
for (slash = string; *slash; slash++)
|
| 188 |
if (*slash == '/')
|
| 189 |
count++;
|
| 190 |
|
| 191 |
if (schedule)
|
| 192 |
free_schedulelist (&schedule);
|
| 193 |
|
| 194 |
schedule = rc_xmalloc (sizeof (schedulelist_t));
|
| 195 |
schedule->gotolist = NULL;
|
| 196 |
|
| 197 |
if (count == 0) {
|
| 198 |
schedule->type = schedule_signal;
|
| 199 |
schedule->value = default_signal;
|
| 200 |
schedule->next = rc_xmalloc (sizeof (schedulelist_t));
|
| 201 |
next = schedule->next;
|
| 202 |
next->type = schedule_timeout;
|
| 203 |
next->gotolist = NULL;
|
| 204 |
if (string) {
|
| 205 |
if (sscanf (string, "%d", &next->value) != 1)
|
| 206 |
eerrorx ("%s: invalid timeout value in schedule", applet);
|
| 207 |
}
|
| 208 |
else
|
| 209 |
next->value = 5;
|
| 210 |
next->next = NULL;
|
| 211 |
|
| 212 |
return;
|
| 213 |
}
|
| 214 |
|
| 215 |
next = schedule;
|
| 216 |
while (string != NULL) {
|
| 217 |
if ((slash = strchr (string, '/')))
|
| 218 |
len = slash - string;
|
| 219 |
else
|
| 220 |
len = strlen (string);
|
| 221 |
|
| 222 |
if (len >= (ptrdiff_t) sizeof (buffer))
|
| 223 |
eerrorx ("%s: invalid schedule item, far too long", applet);
|
| 224 |
|
| 225 |
memcpy (buffer, string, len);
|
| 226 |
buffer[len] = 0;
|
| 227 |
string = slash ? slash + 1 : NULL;
|
| 228 |
|
| 229 |
parse_schedule_item (next, buffer);
|
| 230 |
if (next->type == schedule_forever) {
|
| 231 |
if (repeatat)
|
| 232 |
eerrorx ("%s: invalid schedule, `forever' appears more than once",
|
| 233 |
applet);
|
| 234 |
|
| 235 |
repeatat = next;
|
| 236 |
continue;
|
| 237 |
}
|
| 238 |
|
| 239 |
if (string) {
|
| 240 |
next->next = rc_xmalloc (sizeof (schedulelist_t));
|
| 241 |
next = next->next;
|
| 242 |
next->gotolist = NULL;
|
| 243 |
}
|
| 244 |
}
|
| 245 |
|
| 246 |
if (repeatat) {
|
| 247 |
next->next = rc_xmalloc (sizeof (schedulelist_t));
|
| 248 |
next = next->next;
|
| 249 |
next->type = schedule_goto;
|
| 250 |
next->value = 0;
|
| 251 |
next->gotolist = repeatat;
|
| 252 |
}
|
| 253 |
|
| 254 |
next->next = NULL;
|
| 255 |
return;
|
| 256 |
}
|
| 257 |
|
| 258 |
static pid_t get_pid (const char *pidfile, bool quiet)
|
| 259 |
{
|
| 260 |
FILE *fp;
|
| 261 |
pid_t pid;
|
| 262 |
|
| 263 |
if (! pidfile)
|
| 264 |
return (-1);
|
| 265 |
|
| 266 |
if ((fp = fopen (pidfile, "r")) == NULL) {
|
| 267 |
if (! quiet)
|
| 268 |
eerror ("%s: fopen `%s': %s", applet, pidfile, strerror (errno));
|
| 269 |
return (-1);
|
| 270 |
}
|
| 271 |
|
| 272 |
if (fscanf (fp, "%d", &pid) != 1) {
|
| 273 |
if (! quiet)
|
| 274 |
eerror ("%s: no pid found in `%s'", applet, pidfile);
|
| 275 |
fclose (fp);
|
| 276 |
return (-1);
|
| 277 |
}
|
| 278 |
fclose (fp);
|
| 279 |
|
| 280 |
return (pid);
|
| 281 |
}
|
| 282 |
|
| 283 |
/* return number of processed killed, -1 on error */
|
| 284 |
static int do_stop (const char *exec, const char *cmd,
|
| 285 |
const char *pidfile, uid_t uid,int sig,
|
| 286 |
bool quiet, bool verbose, bool test)
|
| 287 |
{
|
| 288 |
pid_t *pids;
|
| 289 |
bool killed;
|
| 290 |
int nkilled = 0;
|
| 291 |
pid_t pid = 0;
|
| 292 |
int i;
|
| 293 |
|
| 294 |
if (pidfile)
|
| 295 |
if ((pid = get_pid (pidfile, quiet)) == -1)
|
| 296 |
return (quiet ? 0 : -1);
|
| 297 |
|
| 298 |
if ((pids = rc_find_pids (exec, cmd, uid, pid)) == NULL)
|
| 299 |
return (0);
|
| 300 |
|
| 301 |
for (i = 0; pids[i]; i++) {
|
| 302 |
if (test) {
|
| 303 |
if (! quiet)
|
| 304 |
einfo ("Would send signal %d to PID %d", sig, pids[i]);
|
| 305 |
nkilled++;
|
| 306 |
continue;
|
| 307 |
}
|
| 308 |
|
| 309 |
if (verbose)
|
| 310 |
ebegin ("Sending signal %d to PID %d", sig, pids[i]);
|
| 311 |
errno = 0;
|
| 312 |
killed = (kill (pids[i], sig) == 0 || errno == ESRCH ? true : false);
|
| 313 |
if (! killed) {
|
| 314 |
if (! quiet)
|
| 315 |
eerror ("%s: failed to send signal %d to PID %d: %s",
|
| 316 |
applet, sig, pids[i], strerror (errno));
|
| 317 |
if (verbose)
|
| 318 |
eend (1, NULL);
|
| 319 |
nkilled = -1;
|
| 320 |
} else {
|
| 321 |
if (verbose)
|
| 322 |
eend (0, NULL);
|
| 323 |
if (nkilled != -1)
|
| 324 |
nkilled++;
|
| 325 |
}
|
| 326 |
}
|
| 327 |
|
| 328 |
free (pids);
|
| 329 |
return (nkilled);
|
| 330 |
}
|
| 331 |
|
| 332 |
static int run_stop_schedule (const char *exec, const char *cmd,
|
| 333 |
const char *pidfile, uid_t uid,
|
| 334 |
bool quiet, bool verbose, bool test)
|
| 335 |
{
|
| 336 |
schedulelist_t *item = schedule;
|
| 337 |
int nkilled = 0;
|
| 338 |
int tkilled = 0;
|
| 339 |
int nrunning = 0;
|
| 340 |
long nloops;
|
| 341 |
struct timespec ts;
|
| 342 |
|
| 343 |
if (verbose) {
|
| 344 |
if (pidfile)
|
| 345 |
einfo ("Will stop PID in pidfile `%s'", pidfile);
|
| 346 |
if (uid)
|
| 347 |
einfo ("Will stop processes owned by UID %d", uid);
|
| 348 |
if (exec)
|
| 349 |
einfo ("Will stop processes of `%s'", exec);
|
| 350 |
if (cmd)
|
| 351 |
einfo ("Will stop processes called `%s'", cmd);
|
| 352 |
}
|
| 353 |
|
| 354 |
while (item) {
|
| 355 |
switch (item->type) {
|
| 356 |
case schedule_goto:
|
| 357 |
item = item->gotolist;
|
| 358 |
continue;
|
| 359 |
|
| 360 |
case schedule_signal:
|
| 361 |
nrunning = 0;
|
| 362 |
nkilled = do_stop (exec, cmd, pidfile, uid, item->value,
|
| 363 |
quiet, verbose, test);
|
| 364 |
if (nkilled == 0) {
|
| 365 |
if (tkilled == 0) {
|
| 366 |
if (! quiet)
|
| 367 |
eerror ("%s: no matching processes found", applet);
|
| 368 |
}
|
| 369 |
return (tkilled);
|
| 370 |
}
|
| 371 |
else if (nkilled == -1)
|
| 372 |
return (0);
|
| 373 |
|
| 374 |
tkilled += nkilled;
|
| 375 |
break;
|
| 376 |
case schedule_timeout:
|
| 377 |
if (item->value < 1) {
|
| 378 |
item = NULL;
|
| 379 |
break;
|
| 380 |
}
|
| 381 |
|
| 382 |
nloops = (ONE_SECOND / POLL_INTERVAL) * item->value;
|
| 383 |
ts.tv_sec = 0;
|
| 384 |
ts.tv_nsec = POLL_INTERVAL;
|
| 385 |
|
| 386 |
while (nloops) {
|
| 387 |
if ((nrunning = do_stop (exec, cmd, pidfile,
|
| 388 |
uid, 0, true, false, true)) == 0)
|
| 389 |
return (true);
|
| 390 |
|
| 391 |
if (nanosleep (&ts, NULL) == -1) {
|
| 392 |
if (errno == EINTR)
|
| 393 |
eerror ("%s: caught an interupt", applet);
|
| 394 |
else {
|
| 395 |
eerror ("%s: nanosleep: %s", applet, strerror (errno));
|
| 396 |
return (0);
|
| 397 |
}
|
| 398 |
}
|
| 399 |
nloops --;
|
| 400 |
}
|
| 401 |
break;
|
| 402 |
|
| 403 |
default:
|
| 404 |
eerror ("%s: invalid schedule item `%d'", applet, item->type);
|
| 405 |
return (0);
|
| 406 |
}
|
| 407 |
|
| 408 |
if (item)
|
| 409 |
item = item->next;
|
| 410 |
}
|
| 411 |
|
| 412 |
if (test || (tkilled > 0 && nrunning == 0))
|
| 413 |
return (nkilled);
|
| 414 |
|
| 415 |
if (! quiet) {
|
| 416 |
if (nrunning == 1)
|
| 417 |
eerror ("%s: %d process refused to stop", applet, nrunning);
|
| 418 |
else
|
| 419 |
eerror ("%s: %d process(es) refused to stop", applet, nrunning);
|
| 420 |
}
|
| 421 |
|
| 422 |
return (-nrunning);
|
| 423 |
}
|
| 424 |
|
| 425 |
static void handle_signal (int sig)
|
| 426 |
{
|
| 427 |
int pid;
|
| 428 |
int status;
|
| 429 |
int serrno = errno;
|
| 430 |
char signame[10] = { '\0' };
|
| 431 |
|
| 432 |
switch (sig) {
|
| 433 |
case SIGINT:
|
| 434 |
if (! signame[0])
|
| 435 |
snprintf (signame, sizeof (signame), "SIGINT");
|
| 436 |
case SIGTERM:
|
| 437 |
if (! signame[0])
|
| 438 |
snprintf (signame, sizeof (signame), "SIGTERM");
|
| 439 |
case SIGQUIT:
|
| 440 |
if (! signame[0])
|
| 441 |
snprintf (signame, sizeof (signame), "SIGQUIT");
|
| 442 |
eerrorx ("%s: caught %s, aborting", applet, signame);
|
| 443 |
|
| 444 |
case SIGCHLD:
|
| 445 |
while (1) {
|
| 446 |
if ((pid = waitpid (-1, &status, WNOHANG)) < 0) {
|
| 447 |
if (errno != ECHILD)
|
| 448 |
eerror ("%s: waitpid: %s", applet, strerror (errno));
|
| 449 |
break;
|
| 450 |
}
|
| 451 |
}
|
| 452 |
break;
|
| 453 |
|
| 454 |
default:
|
| 455 |
eerror ("%s: caught unknown signal %d", applet, sig);
|
| 456 |
}
|
| 457 |
|
| 458 |
/* Restore errno */
|
| 459 |
errno = serrno;
|
| 460 |
}
|
| 461 |
|
| 462 |
|
| 463 |
#include "_usage.h"
|
| 464 |
#define getoptstring "KN:R:Sbc:d:g:mn:op:qs:tu:r:vx:1:2:" getoptstring_COMMON
|
| 465 |
static struct option longopts[] = {
|
| 466 |
{ "stop", 0, NULL, 'K'},
|
| 467 |
{ "nicelevel", 1, NULL, 'N'},
|
| 468 |
{ "retry", 1, NULL, 'R'},
|
| 469 |
{ "start", 0, NULL, 'S'},
|
| 470 |
{ "startas", 1, NULL, 'a'},
|
| 471 |
{ "background", 0, NULL, 'b'},
|
| 472 |
{ "chuid", 1, NULL, 'c'},
|
| 473 |
{ "chdir", 1, NULL, 'd'},
|
| 474 |
{ "group", 1, NULL, 'g'},
|
| 475 |
{ "make-pidfile", 0, NULL, 'm'},
|
| 476 |
{ "name", 1, NULL, 'n'},
|
| 477 |
{ "oknodo", 0, NULL, 'o'},
|
| 478 |
{ "pidfile", 1, NULL, 'p'},
|
| 479 |
{ "quiet", 0, NULL, 'q'},
|
| 480 |
{ "signal", 1, NULL, 's'},
|
| 481 |
{ "test", 0, NULL, 't'},
|
| 482 |
{ "user", 1, NULL, 'u'},
|
| 483 |
{ "chroot", 1, NULL, 'r'},
|
| 484 |
{ "verbose", 0, NULL, 'v'},
|
| 485 |
{ "exec", 1, NULL, 'x'},
|
| 486 |
{ "stdout", 1, NULL, '1'},
|
| 487 |
{ "stderr", 1, NULL, '2'},
|
| 488 |
longopts_COMMON
|
| 489 |
{ NULL, 0, NULL, 0}
|
| 490 |
};
|
| 491 |
#include "_usage.c"
|
| 492 |
|
| 493 |
int start_stop_daemon (int argc, char **argv)
|
| 494 |
{
|
| 495 |
int devnull_fd = -1;
|
| 496 |
#ifdef TIOCNOTTY
|
| 497 |
int tty_fd = -1;
|
| 498 |
#endif
|
| 499 |
|
| 500 |
#ifdef HAVE_PAM
|
| 501 |
pam_handle_t *pamh = NULL;
|
| 502 |
int pamr;
|
| 503 |
#endif
|
| 504 |
|
| 505 |
int opt;
|
| 506 |
bool start = false;
|
| 507 |
bool stop = false;
|
| 508 |
bool oknodo = false;
|
| 509 |
bool test = false;
|
| 510 |
bool quiet = false;
|
| 511 |
bool verbose = false;
|
| 512 |
char *exec = NULL;
|
| 513 |
char *cmd = NULL;
|
| 514 |
char *pidfile = NULL;
|
| 515 |
int sig = SIGTERM;
|
| 516 |
int nicelevel = 0;
|
| 517 |
bool background = false;
|
| 518 |
bool makepidfile = false;
|
| 519 |
uid_t uid = 0;
|
| 520 |
gid_t gid = 0;
|
| 521 |
char *ch_root = NULL;
|
| 522 |
char *ch_dir = NULL;
|
| 523 |
int tid = 0;
|
| 524 |
char *redirect_stderr = NULL;
|
| 525 |
char *redirect_stdout = NULL;
|
| 526 |
int stdout_fd;
|
| 527 |
int stderr_fd;
|
| 528 |
pid_t pid;
|
| 529 |
int i;
|
| 530 |
char *svcname = getenv ("SVCNAME");
|
| 531 |
char *env;
|
| 532 |
|
| 533 |
applet = argv[0];
|
| 534 |
atexit (cleanup);
|
| 535 |
|
| 536 |
signal (SIGINT, handle_signal);
|
| 537 |
signal (SIGQUIT, handle_signal);
|
| 538 |
signal (SIGTERM, handle_signal);
|
| 539 |
|
| 540 |
if ((env = getenv ("SSD_NICELEVEL")))
|
| 541 |
if (sscanf (env, "%d", &nicelevel) != 1)
|
| 542 |
eerror ("%s: invalid nice level `%s' (SSD_NICELEVEL)", applet, env);
|
| 543 |
|
| 544 |
while ((opt = getopt_long (argc, argv, getoptstring, longopts,
|
| 545 |
(int *) 0)) != -1)
|
| 546 |
switch (opt) {
|
| 547 |
case 'K': /* --stop */
|
| 548 |
stop = true;
|
| 549 |
break;
|
| 550 |
|
| 551 |
case 'N': /* --nice */
|
| 552 |
if (sscanf (optarg, "%d", &nicelevel) != 1)
|
| 553 |
eerrorx ("%s: invalid nice level `%s'", applet, optarg);
|
| 554 |
break;
|
| 555 |
|
| 556 |
case 'R': /* --retry <schedule>|<timeout> */
|
| 557 |
parse_schedule (optarg, sig);
|
| 558 |
break;
|
| 559 |
|
| 560 |
case 'S': /* --start */
|
| 561 |
start = true;
|
| 562 |
break;
|
| 563 |
|
| 564 |
case 'b': /* --background */
|
| 565 |
background = true;
|
| 566 |
break;
|
| 567 |
|
| 568 |
case 'c': /* --chuid <username>|<uid> */
|
| 569 |
{
|
| 570 |
char *p = optarg;
|
| 571 |
char *cu = strsep (&p, ":");
|
| 572 |
struct passwd *pw = NULL;
|
| 573 |
|
| 574 |
changeuser = rc_xstrdup (cu);
|
| 575 |
if (sscanf (cu, "%d", &tid) != 1)
|
| 576 |
pw = getpwnam (cu);
|
| 577 |
else
|
| 578 |
pw = getpwuid (tid);
|
| 579 |
|
| 580 |
if (! pw)
|
| 581 |
eerrorx ("%s: user `%s' not found", applet, cu);
|
| 582 |
uid = pw->pw_uid;
|
| 583 |
if (! gid)
|
| 584 |
gid = pw->pw_gid;
|
| 585 |
|
| 586 |
if (p) {
|
| 587 |
struct group *gr = NULL;
|
| 588 |
char *cg = strsep (&p, ":");
|
| 589 |
|
| 590 |
if (sscanf (cg, "%d", &tid) != 1)
|
| 591 |
gr = getgrnam (cg);
|
| 592 |
else
|
| 593 |
gr = getgrgid (tid);
|
| 594 |
|
| 595 |
if (! gr)
|
| 596 |
eerrorx ("%s: group `%s' not found", applet, cg);
|
| 597 |
gid = gr->gr_gid;
|
| 598 |
}
|
| 599 |
}
|
| 600 |
break;
|
| 601 |
|
| 602 |
case 'd': /* --chdir /new/dir */
|
| 603 |
ch_dir = optarg;
|
| 604 |
break;
|
| 605 |
|
| 606 |
case 'g': /* --group <group>|<gid> */
|
| 607 |
{
|
| 608 |
struct group *gr = getgrnam (optarg);
|
| 609 |
|
| 610 |
if (sscanf (optarg, "%d", &tid) != 1)
|
| 611 |
gr = getgrnam (optarg);
|
| 612 |
else
|
| 613 |
gr = getgrgid (tid);
|
| 614 |
|
| 615 |
if (! gr)
|
| 616 |
eerrorx ("%s: group `%s' not found", applet, optarg);
|
| 617 |
gid = gr->gr_gid;
|
| 618 |
}
|
| 619 |
break;
|
| 620 |
|
| 621 |
case 'm': /* --make-pidfile */
|
| 622 |
makepidfile = true;
|
| 623 |
break;
|
| 624 |
|
| 625 |
case 'n': /* --name <process-name> */
|
| 626 |
cmd = optarg;
|
| 627 |
break;
|
| 628 |
|
| 629 |
case 'o': /* --oknodo */
|
| 630 |
oknodo = true;
|
| 631 |
break;
|
| 632 |
|
| 633 |
case 'p': /* --pidfile <pid-file> */
|
| 634 |
pidfile = optarg;
|
| 635 |
break;
|
| 636 |
|
| 637 |
case 'q': /* --quiet */
|
| 638 |
quiet = true;
|
| 639 |
break;
|
| 640 |
|
| 641 |
case 's': /* --signal <signal> */
|
| 642 |
sig = parse_signal (optarg);
|
| 643 |
break;
|
| 644 |
|
| 645 |
case 't': /* --test */
|
| 646 |
test = true;
|
| 647 |
break;
|
| 648 |
|
| 649 |
case 'u': /* --user <username>|<uid> */
|
| 650 |
if (sscanf (optarg, "%d", &tid) != 1) {
|
| 651 |
struct passwd *pw = getpwnam (optarg);
|
| 652 |
if (! pw)
|
| 653 |
eerrorx ("%s: user `%s' not found", applet, optarg);
|
| 654 |
uid = pw->pw_uid;
|
| 655 |
} else
|
| 656 |
uid = tid;
|
| 657 |
break;
|
| 658 |
|
| 659 |
case 'r': /* --chroot /new/root */
|
| 660 |
ch_root = optarg;
|
| 661 |
break;
|
| 662 |
|
| 663 |
case 'v': /* --verbose */
|
| 664 |
verbose = true;
|
| 665 |
break;
|
| 666 |
|
| 667 |
case 'a':
|
| 668 |
case 'x': /* --exec <executable> */
|
| 669 |
exec = optarg;
|
| 670 |
break;
|
| 671 |
|
| 672 |
case '1': /* --stdout /path/to/stdout.lgfile */
|
| 673 |
redirect_stdout = optarg;
|
| 674 |
break;
|
| 675 |
|
| 676 |
case '2': /* --stderr /path/to/stderr.logfile */
|
| 677 |
redirect_stderr = optarg;
|
| 678 |
break;
|
| 679 |
|
| 680 |
case_RC_COMMON_GETOPT
|
| 681 |
}
|
| 682 |
|
| 683 |
/* Respect RC as well as how we are called */
|
| 684 |
if (rc_is_env ("RC_QUIET", "yes") && ! verbose)
|
| 685 |
quiet = true;
|
| 686 |
|
| 687 |
/* Allow start-stop-daemon --signal HUP --exec /usr/sbin/dnsmasq
|
| 688 |
* instead of forcing --stop --oknodo as well */
|
| 689 |
if (! start && ! stop)
|
| 690 |
if (sig != SIGINT &&
|
| 691 |
sig != SIGTERM &&
|
| 692 |
sig != SIGQUIT &&
|
| 693 |
sig != SIGKILL)
|
| 694 |
{
|
| 695 |
oknodo = true;
|
| 696 |
stop = true;
|
| 697 |
}
|
| 698 |
|
| 699 |
if (start == stop)
|
| 700 |
eerrorx ("%s: need one of --start or --stop", applet);
|
| 701 |
|
| 702 |
if (start && ! exec)
|
| 703 |
eerrorx ("%s: --start needs --exec", applet);
|
| 704 |
|
| 705 |
if (stop && ! exec && ! pidfile && ! cmd && ! uid)
|
| 706 |
eerrorx ("%s: --stop needs --exec, --pidfile, --name or --user", applet);
|
| 707 |
|
| 708 |
if (makepidfile && ! pidfile)
|
| 709 |
eerrorx ("%s: --make-pidfile is only relevant with --pidfile", applet);
|
| 710 |
|
| 711 |
if (background && ! start)
|
| 712 |
eerrorx ("%s: --background is only relevant with --start", applet);
|
| 713 |
|
| 714 |
if ((redirect_stdout || redirect_stderr) && ! background)
|
| 715 |
eerrorx ("%s: --stdout and --stderr are only relevant with --background",
|
| 716 |
applet);
|
| 717 |
|
| 718 |
argc -= optind;
|
| 719 |
argv += optind;
|
| 720 |
|
| 721 |
/* Validate that the binary exists if we are starting */
|
| 722 |
if (exec && start) {
|
| 723 |
char *tmp;
|
| 724 |
if (ch_root)
|
| 725 |
tmp = rc_strcatpaths (ch_root, exec, (char *) NULL);
|
| 726 |
else
|
| 727 |
tmp = exec;
|
| 728 |
if (! rc_is_file (tmp)) {
|
| 729 |
eerror ("%s: %s does not exist", applet, tmp);
|
| 730 |
if (ch_root)
|
| 731 |
free (tmp);
|
| 732 |
exit (EXIT_FAILURE);
|
| 733 |
}
|
| 734 |
if (ch_root)
|
| 735 |
free (tmp);
|
| 736 |
}
|
| 737 |
|
| 738 |
if (stop) {
|
| 739 |
int result;
|
| 740 |
|
| 741 |
if (! schedule) {
|
| 742 |
if (test || oknodo)
|
| 743 |
parse_schedule ("0", sig);
|
| 744 |
else
|
| 745 |
parse_schedule (NULL, sig);
|
| 746 |
}
|
| 747 |
|
| 748 |
result = run_stop_schedule (exec, cmd, pidfile, uid, quiet, verbose, test);
|
| 749 |
if (test || oknodo)
|
| 750 |
return (result > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
| 751 |
if (result < 1)
|
| 752 |
exit (result == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
| 753 |
|
| 754 |
if (pidfile && rc_is_file (pidfile))
|
| 755 |
unlink (pidfile);
|
| 756 |
|
| 757 |
if (svcname)
|
| 758 |
rc_set_service_daemon (svcname, exec, cmd, pidfile, false);
|
| 759 |
|
| 760 |
exit (EXIT_SUCCESS);
|
| 761 |
}
|
| 762 |
|
| 763 |
if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0)
|
| 764 |
eerrorx ("%s: %s is already running", applet, exec);
|
| 765 |
|
| 766 |
if (test) {
|
| 767 |
if (quiet)
|
| 768 |
exit (EXIT_SUCCESS);
|
| 769 |
|
| 770 |
einfon ("Would start %s", exec);
|
| 771 |
while (argc-- > 0)
|
| 772 |
printf("%s ", *argv++);
|
| 773 |
printf ("\n");
|
| 774 |
eindent ();
|
| 775 |
if (uid != 0)
|
| 776 |
einfo ("as user id %d", uid);
|
| 777 |
if (gid != 0)
|
| 778 |
einfo ("as group id %d", gid);
|
| 779 |
if (ch_root)
|
| 780 |
einfo ("in root `%s'", ch_root);
|
| 781 |
if (ch_dir)
|
| 782 |
einfo ("in dir `%s'", ch_dir);
|
| 783 |
if (nicelevel != 0)
|
| 784 |
einfo ("with a priority of %d", nicelevel);
|
| 785 |
eoutdent ();
|
| 786 |
exit (EXIT_SUCCESS);
|
| 787 |
}
|
| 788 |
|
| 789 |
/* Ensure this is unset, so if the daemon does /etc/init.d/foo
|
| 790 |
Then we filter the environment accordingly */
|
| 791 |
unsetenv ("RC_SOFTLEVEL");
|
| 792 |
|
| 793 |
if (verbose) {
|
| 794 |
ebegin ("Detaching to start `%s'", exec);
|
| 795 |
eindent ();
|
| 796 |
}
|
| 797 |
|
| 798 |
if (background)
|
| 799 |
signal (SIGCHLD, handle_signal);
|
| 800 |
|
| 801 |
*--argv = exec;
|
| 802 |
if ((pid = fork ()) == -1)
|
| 803 |
eerrorx ("%s: fork: %s", applet, strerror (errno));
|
| 804 |
|
| 805 |
/* Child process - lets go! */
|
| 806 |
if (pid == 0) {
|
| 807 |
pid_t mypid = getpid ();
|
| 808 |
|
| 809 |
#ifdef TIOCNOTTY
|
| 810 |
tty_fd = open("/dev/tty", O_RDWR);
|
| 811 |
#endif
|
| 812 |
|
| 813 |
devnull_fd = open("/dev/null", O_RDWR);
|
| 814 |
|
| 815 |
if (nicelevel) {
|
| 816 |
if (setpriority (PRIO_PROCESS, mypid, nicelevel) == -1)
|
| 817 |
eerrorx ("%s: setpritory %d: %s", applet, nicelevel,
|
| 818 |
strerror(errno));
|
| 819 |
}
|
| 820 |
|
| 821 |
if (ch_root && chroot (ch_root) < 0)
|
| 822 |
eerrorx ("%s: chroot `%s': %s", applet, ch_root, strerror (errno));
|
| 823 |
|
| 824 |
if (ch_dir && chdir (ch_dir) < 0)
|
| 825 |
eerrorx ("%s: chdir `%s': %s", applet, ch_dir, strerror (errno));
|
| 826 |
|
| 827 |
if (makepidfile && pidfile) {
|
| 828 |
FILE *fp = fopen (pidfile, "w");
|
| 829 |
if (! fp)
|
| 830 |
eerrorx ("%s: fopen `%s': %s", applet, pidfile, strerror
|
| 831 |
(errno));
|
| 832 |
fprintf (fp, "%d\n", mypid);
|
| 833 |
fclose (fp);
|
| 834 |
}
|
| 835 |
|
| 836 |
#ifdef HAVE_PAM
|
| 837 |
if (changeuser != NULL)
|
| 838 |
pamr = pam_start ("start-stop-daemon", changeuser, &conv, &pamh);
|
| 839 |
else
|
| 840 |
pamr = pam_start ("start-stop-daemon", "nobody", &conv, &pamh);
|
| 841 |
|
| 842 |
if (pamr == PAM_SUCCESS)
|
| 843 |
pamr = pam_authenticate (pamh, PAM_SILENT);
|
| 844 |
if (pamr == PAM_SUCCESS)
|
| 845 |
pamr = pam_acct_mgmt (pamh, PAM_SILENT);
|
| 846 |
if (pamr == PAM_SUCCESS)
|
| 847 |
pamr = pam_open_session (pamh, PAM_SILENT);
|
| 848 |
if (pamr != PAM_SUCCESS)
|
| 849 |
eerrorx ("%s: pam error: %s", applet, pam_strerror(pamh, pamr));
|
| 850 |
#endif
|
| 851 |
|
| 852 |
if (gid && setgid (gid))
|
| 853 |
eerrorx ("%s: unable to set groupid to %d", applet, gid);
|
| 854 |
if (changeuser && initgroups (changeuser, gid))
|
| 855 |
eerrorx ("%s: initgroups (%s, %d)", applet, changeuser, gid);
|
| 856 |
if (uid && setuid (uid))
|
| 857 |
eerrorx ("%s: unable to set userid to %d", applet, uid);
|
| 858 |
else {
|
| 859 |
struct passwd *passwd = getpwuid (uid);
|
| 860 |
if (passwd) {
|
| 861 |
unsetenv ("HOME");
|
| 862 |
if (passwd->pw_dir)
|
| 863 |
setenv ("HOME", passwd->pw_dir, 1);
|
| 864 |
unsetenv ("USER");
|
| 865 |
if (passwd->pw_name)
|
| 866 |
setenv ("USER", passwd->pw_name, 1);
|
| 867 |
}
|
| 868 |
}
|
| 869 |
|
| 870 |
/* Close any fd's to the passwd database */
|
| 871 |
endpwent ();
|
| 872 |
|
| 873 |
#ifdef TIOCNOTTY
|
| 874 |
ioctl(tty_fd, TIOCNOTTY, 0);
|
| 875 |
close(tty_fd);
|
| 876 |
#endif
|
| 877 |
|
| 878 |
/* Clean the environment of any RC_ variables */
|
| 879 |
STRLIST_FOREACH (environ, env, i)
|
| 880 |
if (env && strncmp (env, "RC_", 3) != 0) {
|
| 881 |
/* For the path r, remove the rcscript bin dir from it */
|
| 882 |
if (strncmp (env, "PATH=" RC_LIBDIR "/bin:",
|
| 883 |
strlen ("PATH=" RC_LIBDIR "/bin:")) == 0)
|
| 884 |
{
|
| 885 |
char *path = env;
|
| 886 |
char *newpath;
|
| 887 |
int len;
|
| 888 |
path += strlen ("PATH=" RC_LIBDIR "/bin:");
|
| 889 |
len = sizeof (char *) * strlen (path) + 6;
|
| 890 |
newpath = rc_xmalloc (len);
|
| 891 |
snprintf (newpath, len, "PATH=%s", path);
|
| 892 |
newenv = rc_strlist_add (newenv, newpath);
|
| 893 |
free (newpath);
|
| 894 |
} else
|
| 895 |
newenv = rc_strlist_add (newenv, env);
|
| 896 |
}
|
| 897 |
|
| 898 |
umask (022);
|
| 899 |
|
| 900 |
stdout_fd = devnull_fd;
|
| 901 |
stderr_fd = devnull_fd;
|
| 902 |
if (redirect_stdout) {
|
| 903 |
if ((stdout_fd = open (redirect_stdout, O_WRONLY | O_CREAT | O_APPEND,
|
| 904 |
S_IRUSR | S_IWUSR)) == -1)
|
| 905 |
eerrorx ("%s: unable to open the logfile for stdout `%s': %s",
|
| 906 |
applet, redirect_stdout, strerror (errno));
|
| 907 |
}
|
| 908 |
if (redirect_stderr) {
|
| 909 |
if ((stderr_fd = open (redirect_stderr, O_WRONLY | O_CREAT | O_APPEND,
|
| 910 |
S_IRUSR | S_IWUSR)) == -1)
|
| 911 |
eerrorx ("%s: unable to open the logfile for stderr `%s': %s",
|
| 912 |
applet, redirect_stderr, strerror (errno));
|
| 913 |
}
|
| 914 |
|
| 915 |
if (background) {
|
| 916 |
/* Hmmm, some daemons may need stdin? */
|
| 917 |
dup2 (devnull_fd, STDIN_FILENO);
|
| 918 |
dup2 (stdout_fd, STDOUT_FILENO);
|
| 919 |
dup2 (stderr_fd, STDERR_FILENO);
|
| 920 |
}
|
| 921 |
|
| 922 |
for (i = getdtablesize () - 1; i >= 3; --i)
|
| 923 |
close(i);
|
| 924 |
|
| 925 |
setsid ();
|
| 926 |
|
| 927 |
execve (exec, argv, newenv);
|
| 928 |
#ifdef HAVE_PAM
|
| 929 |
if (pamr == PAM_SUCCESS)
|
| 930 |
pam_close_session (pamh, PAM_SILENT);
|
| 931 |
#endif
|
| 932 |
eerrorx ("%s: failed to exec `%s': %s", applet, exec, strerror (errno));
|
| 933 |
}
|
| 934 |
|
| 935 |
/* Parent process */
|
| 936 |
if (! background) {
|
| 937 |
/* As we're not backgrounding the process, wait for our pid to return */
|
| 938 |
int status = 0;
|
| 939 |
int savepid = pid;
|
| 940 |
|
| 941 |
errno = 0;
|
| 942 |
do {
|
| 943 |
pid = waitpid (savepid, &status, 0);
|
| 944 |
if (pid < 1) {
|
| 945 |
eerror ("waitpid %d: %s", savepid, strerror (errno));
|
| 946 |
return (-1);
|
| 947 |
}
|
| 948 |
} while (! WIFEXITED (status) && ! WIFSIGNALED (status));
|
| 949 |
|
| 950 |
if (! WIFEXITED (status) || WEXITSTATUS (status) != 0) {
|
| 951 |
if (! quiet)
|
| 952 |
eerrorx ("%s: failed to start `%s'", applet, exec);
|
| 953 |
exit (EXIT_FAILURE);
|
| 954 |
}
|
| 955 |
|
| 956 |
pid = savepid;
|
| 957 |
}
|
| 958 |
|
| 959 |
/* Wait a little bit and check that process is still running
|
| 960 |
We do this as some badly written daemons fork and then barf */
|
| 961 |
if (START_WAIT > 0) {
|
| 962 |
struct timespec ts;
|
| 963 |
int nloops = START_WAIT / POLL_INTERVAL;
|
| 964 |
bool alive = false;
|
| 965 |
bool retestpid = false;
|
| 966 |
|
| 967 |
ts.tv_sec = 0;
|
| 968 |
ts.tv_nsec = POLL_INTERVAL;
|
| 969 |
|
| 970 |
while (nloops) {
|
| 971 |
if (nanosleep (&ts, NULL) == -1) {
|
| 972 |
if (errno == EINTR)
|
| 973 |
eerror ("%s: caught an interupt", applet);
|
| 974 |
else {
|
| 975 |
eerror ("%s: nanosleep: %s", applet, strerror (errno));
|
| 976 |
return (0);
|
| 977 |
}
|
| 978 |
}
|
| 979 |
nloops --;
|
| 980 |
|
| 981 |
/* This is knarly.
|
| 982 |
If we backgrounded then we know the exact pid.
|
| 983 |
Otherwise if we have a pidfile then it *may* know the exact pid.
|
| 984 |
Failing that, we'll have to query processes.
|
| 985 |
We sleep first as some programs like ntp like to fork, and write
|
| 986 |
their pidfile a LONG time later. */
|
| 987 |
if (background) {
|
| 988 |
if (kill (pid, 0) == 0)
|
| 989 |
alive = true;
|
| 990 |
} else {
|
| 991 |
if (pidfile) {
|
| 992 |
/* The pidfile may not have been written yet - give it some time */
|
| 993 |
if (get_pid (pidfile, true) == -1) {
|
| 994 |
alive = true;
|
| 995 |
retestpid = true;
|
| 996 |
} else {
|
| 997 |
retestpid = false;
|
| 998 |
if (do_stop (NULL, NULL, pidfile, uid, 0,
|
| 999 |
true, false, true) > 0)
|
| 1000 |
alive = true;
|
| 1001 |
}
|
| 1002 |
} else {
|
| 1003 |
if (do_stop (exec, cmd, NULL, uid, 0, true, false, true)
|
| 1004 |
> 0)
|
| 1005 |
alive = true;
|
| 1006 |
}
|
| 1007 |
}
|
| 1008 |
|
| 1009 |
if (! alive)
|
| 1010 |
eerrorx ("%s: %s died", applet, exec);
|
| 1011 |
}
|
| 1012 |
|
| 1013 |
if (retestpid) {
|
| 1014 |
if (do_stop (NULL, NULL, pidfile, uid, 0, true,
|
| 1015 |
false, true) < 1)
|
| 1016 |
eerrorx ("%s: %s died", applet, exec);
|
| 1017 |
}
|
| 1018 |
}
|
| 1019 |
|
| 1020 |
if (svcname)
|
| 1021 |
rc_set_service_daemon (svcname, exec, cmd, pidfile, true);
|
| 1022 |
|
| 1023 |
exit (EXIT_SUCCESS);
|
| 1024 |
}
|