]>
Commit | Line | Data |
---|---|---|
47a3a827 | 1 | // SPDX-License-Identifier: NONE |
183843cd DS |
2 | /* |
3 | * A rewrite of the original Debian's start-stop-daemon Perl script | |
4 | * in C (faster - it is executed many times during system startup). | |
5 | * | |
6 | * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, | |
7 | * public domain. Based conceptually on start-stop-daemon.pl, by Ian | |
8 | * Jackson <ijackson@gnu.ai.mit.edu>. May be used and distributed | |
9 | * freely for any purpose. Changes by Christian Schwarz | |
10 | * <schwarz@monet.m.isar.de>, to make output conform to the Debian | |
11 | * Console Message Standard, also placed in public domain. Minor | |
12 | * changes by Klee Dienes <klee@debian.org>, also placed in the Public | |
13 | * Domain. | |
14 | * | |
15 | * Changes by Ben Collins <bcollins@debian.org>, added --chuid, --background | |
16 | * and --make-pidfile options, placed in public domain aswell. | |
17 | * | |
18 | * Port to OpenBSD by Sontri Tomo Huynh <huynh.29@osu.edu> | |
19 | * and Andreas Schuldei <andreas@schuldei.org> | |
20 | * | |
21 | * Changes by Ian Jackson: added --retry (and associated rearrangements). | |
22 | * | |
23 | * Modified for Gentoo rc-scripts by Donny Davies <woodchip@gentoo.org>: | |
24 | * I removed the BSD/Hurd/OtherOS stuff, added #include <stddef.h> | |
25 | * and stuck in a #define VERSION "1.9.18". Now it compiles without | |
26 | * the whole automake/config.h dance. | |
27 | */ | |
28 | ||
b45ac5f5 DL |
29 | #ifdef HAVE_CONFIG_H |
30 | #include "config.h" | |
31 | #endif | |
32 | ||
183843cd DS |
33 | #ifdef HAVE_LXC |
34 | #define _GNU_SOURCE | |
35 | #include <sched.h> | |
36 | #endif /* HAVE_LXC */ | |
37 | ||
38 | #include <stddef.h> | |
b45ac5f5 | 39 | #undef VERSION |
183843cd DS |
40 | #define VERSION "1.9.18" |
41 | ||
42 | #define MIN_POLL_INTERVAL 20000 /*us*/ | |
43 | ||
44 | #include <errno.h> | |
45 | #include <stdio.h> | |
46 | #include <stdlib.h> | |
47 | #include <string.h> | |
48 | #include <stdarg.h> | |
49 | #include <signal.h> | |
50 | #include <sys/stat.h> | |
51 | #include <dirent.h> | |
52 | #include <sys/time.h> | |
53 | #include <sys/queue.h> | |
54 | #include <unistd.h> | |
55 | #include <getopt.h> | |
56 | #include <pwd.h> | |
57 | #include <grp.h> | |
58 | #include <sys/ioctl.h> | |
59 | #include <sys/types.h> | |
f89d8115 | 60 | #include <termios.h> |
183843cd DS |
61 | #include <fcntl.h> |
62 | #include <limits.h> | |
63 | #include <assert.h> | |
64 | #include <ctype.h> | |
8f500a1c | 65 | #ifdef linux |
183843cd | 66 | #include <linux/sched.h> |
8f500a1c | 67 | #endif |
183843cd DS |
68 | |
69 | static int testmode = 0; | |
70 | static int quietmode = 0; | |
71 | static int exitnodo = 1; | |
72 | static int start = 0; | |
73 | static int stop = 0; | |
74 | static int background = 0; | |
75 | static int mpidfile = 0; | |
76 | static int signal_nr = 15; | |
77 | static const char *signal_str = NULL; | |
78 | static int user_id = -1; | |
79 | static int runas_uid = -1; | |
80 | static int runas_gid = -1; | |
81 | static const char *userspec = NULL; | |
82 | static char *changeuser = NULL; | |
83 | static const char *changegroup = NULL; | |
84 | static char *changeroot = NULL; | |
85 | static const char *cmdname = NULL; | |
86 | static char *execname = NULL; | |
87 | static char *startas = NULL; | |
88 | static const char *pidfile = NULL; | |
89 | static char what_stop[1024]; | |
90 | static const char *schedule_str = NULL; | |
91 | static const char *progname = ""; | |
92 | static int nicelevel = 0; | |
93 | ||
94 | static struct stat exec_stat; | |
95 | ||
96 | struct pid_list { | |
97 | struct pid_list *next; | |
98 | pid_t pid; | |
99 | }; | |
100 | ||
101 | static struct pid_list *found = NULL; | |
102 | static struct pid_list *killed = NULL; | |
103 | ||
104 | struct schedule_item { | |
105 | enum { sched_timeout, sched_signal, sched_goto, sched_forever } type; | |
106 | int value; /* seconds, signal no., or index into array */ | |
107 | /* sched_forever is only seen within parse_schedule and callees */ | |
108 | }; | |
109 | ||
110 | static int schedule_length; | |
111 | static struct schedule_item *schedule = NULL; | |
112 | ||
113 | LIST_HEAD(namespace_head, namespace); | |
114 | ||
d62a17ae | 115 | struct namespace |
116 | { | |
183843cd | 117 | LIST_ENTRY(namespace) list; |
c9deb8fd | 118 | const char *path; |
183843cd DS |
119 | int nstype; |
120 | }; | |
121 | ||
122 | static struct namespace_head namespace_head; | |
123 | ||
124 | static void *xmalloc(int size); | |
125 | static void push(struct pid_list **list, pid_t pid); | |
126 | static void do_help(void); | |
d62a17ae | 127 | static void parse_options(int argc, char *const *argv); |
183843cd DS |
128 | static int pid_is_user(pid_t pid, uid_t uid); |
129 | static int pid_is_cmd(pid_t pid, const char *name); | |
130 | static void check(pid_t pid); | |
131 | static void do_pidfile(const char *name); | |
d62a17ae | 132 | static void do_stop(int signal_nr, int quietmode, int *n_killed, |
133 | int *n_notkilled, int retry_nr); | |
183843cd DS |
134 | static int pid_is_exec(pid_t pid, const struct stat *esb); |
135 | ||
136 | #ifdef __GNUC__ | |
137 | static void fatal(const char *format, ...) | |
138 | __attribute__((noreturn, format(printf, 1, 2))); | |
d62a17ae | 139 | static void badusage(const char *msg) __attribute__((noreturn)); |
183843cd DS |
140 | #else |
141 | static void fatal(const char *format, ...); | |
142 | static void badusage(const char *msg); | |
143 | #endif | |
144 | ||
145 | /* This next part serves only to construct the TVCALC macro, which | |
146 | * is used for doing arithmetic on struct timeval's. It works like this: | |
147 | * TVCALC(result, expression); | |
148 | * where result is a struct timeval (and must be an lvalue) and | |
149 | * expression is the single expression for both components. In this | |
150 | * expression you can use the special values TVELEM, which when fed a | |
151 | * const struct timeval* gives you the relevant component, and | |
152 | * TVADJUST. TVADJUST is necessary when subtracting timevals, to make | |
153 | * it easier to renormalise. Whenver you subtract timeval elements, | |
154 | * you must make sure that TVADJUST is added to the result of the | |
155 | * subtraction (before any resulting multiplication or what have you). | |
156 | * TVELEM must be linear in TVADJUST. | |
157 | */ | |
d62a17ae | 158 | typedef long tvselector(const struct timeval *); |
159 | static long tvselector_sec(const struct timeval *tv) | |
160 | { | |
161 | return tv->tv_sec; | |
183843cd | 162 | } |
d62a17ae | 163 | static long tvselector_usec(const struct timeval *tv) |
164 | { | |
165 | return tv->tv_usec; | |
166 | } | |
167 | #define TVCALC_ELEM(result, expr, sec, adj) \ | |
168 | { \ | |
169 | const long TVADJUST = adj; \ | |
170 | long (*const TVELEM)(const struct timeval *) = \ | |
171 | tvselector_##sec; \ | |
172 | (result).tv_##sec = (expr); \ | |
173 | } | |
174 | #define TVCALC(result, expr) \ | |
175 | do { \ | |
176 | TVCALC_ELEM(result, expr, sec, (-1)); \ | |
177 | TVCALC_ELEM(result, expr, usec, (+1000000)); \ | |
178 | (result).tv_sec += (result).tv_usec / 1000000; \ | |
179 | (result).tv_usec %= 1000000; \ | |
180 | } while (0) | |
183843cd DS |
181 | |
182 | ||
d62a17ae | 183 | static void fatal(const char *format, ...) |
183843cd DS |
184 | { |
185 | va_list arglist; | |
186 | ||
187 | fprintf(stderr, "%s: ", progname); | |
188 | va_start(arglist, format); | |
189 | vfprintf(stderr, format, arglist); | |
190 | va_end(arglist); | |
191 | putc('\n', stderr); | |
192 | exit(2); | |
193 | } | |
194 | ||
195 | ||
d62a17ae | 196 | static void *xmalloc(int size) |
183843cd DS |
197 | { |
198 | void *ptr; | |
199 | ||
200 | ptr = malloc(size); | |
201 | if (ptr) | |
202 | return ptr; | |
203 | fatal("malloc(%d) failed", size); | |
204 | } | |
205 | ||
d62a17ae | 206 | static void xgettimeofday(struct timeval *tv) |
183843cd | 207 | { |
d62a17ae | 208 | if (gettimeofday(tv, 0) != 0) |
183843cd DS |
209 | fatal("gettimeofday failed: %s", strerror(errno)); |
210 | } | |
211 | ||
d62a17ae | 212 | static void push(struct pid_list **list, pid_t pid) |
183843cd DS |
213 | { |
214 | struct pid_list *p; | |
215 | ||
216 | p = xmalloc(sizeof(*p)); | |
217 | p->next = *list; | |
218 | p->pid = pid; | |
219 | *list = p; | |
220 | } | |
221 | ||
d62a17ae | 222 | static void clear(struct pid_list **list) |
183843cd DS |
223 | { |
224 | struct pid_list *here, *next; | |
225 | ||
226 | for (here = *list; here != NULL; here = next) { | |
227 | next = here->next; | |
228 | free(here); | |
229 | } | |
230 | ||
231 | *list = NULL; | |
232 | } | |
233 | ||
7936827a | 234 | #ifdef linux |
d62a17ae | 235 | static const char *next_dirname(const char *s) |
183843cd | 236 | { |
c9deb8fd | 237 | const char *cur; |
183843cd | 238 | |
c4efd0f4 | 239 | cur = s; |
183843cd DS |
240 | |
241 | if (*cur != '\0') { | |
242 | for (; *cur != '/'; ++cur) | |
243 | if (*cur == '\0') | |
244 | return cur; | |
245 | ||
246 | for (; *cur == '/'; ++cur) | |
247 | ; | |
248 | } | |
249 | ||
250 | return cur; | |
251 | } | |
252 | ||
d62a17ae | 253 | static void add_namespace(const char *path) |
183843cd DS |
254 | { |
255 | int nstype; | |
256 | const char *nsdirname, *nsname, *cur; | |
257 | struct namespace *namespace; | |
258 | ||
c4efd0f4 | 259 | cur = path; |
183843cd DS |
260 | nsdirname = nsname = ""; |
261 | ||
262 | while ((cur = next_dirname(cur))[0] != '\0') { | |
263 | nsdirname = nsname; | |
264 | nsname = cur; | |
265 | } | |
266 | ||
e1be9119 | 267 | if (!strncmp(nsdirname, "ipcns/", strlen("ipcns/"))) |
183843cd | 268 | nstype = CLONE_NEWIPC; |
e1be9119 | 269 | else if (!strncmp(nsdirname, "netns/", strlen("netns/"))) |
183843cd | 270 | nstype = CLONE_NEWNET; |
e1be9119 | 271 | else if (!strncmp(nsdirname, "utcns/", strlen("utcns/"))) |
183843cd DS |
272 | nstype = CLONE_NEWUTS; |
273 | else | |
274 | badusage("invalid namepspace path"); | |
275 | ||
276 | namespace = xmalloc(sizeof(*namespace)); | |
c4efd0f4 | 277 | namespace->path = path; |
183843cd DS |
278 | namespace->nstype = nstype; |
279 | LIST_INSERT_HEAD(&namespace_head, namespace, list); | |
280 | } | |
8f500a1c | 281 | #endif |
183843cd DS |
282 | |
283 | #ifdef HAVE_LXC | |
4d762f26 | 284 | static void set_namespaces(void) |
183843cd DS |
285 | { |
286 | struct namespace *namespace; | |
287 | int fd; | |
288 | ||
a2addae8 | 289 | LIST_FOREACH (namespace, &namespace_head, list) { |
183843cd | 290 | if ((fd = open(namespace->path, O_RDONLY)) == -1) |
d62a17ae | 291 | fatal("open namespace %s: %s", namespace->path, |
292 | strerror(errno)); | |
183843cd DS |
293 | if (setns(fd, namespace->nstype) == -1) |
294 | fatal("setns %s: %s", namespace->path, strerror(errno)); | |
295 | } | |
296 | } | |
297 | #else | |
4d762f26 | 298 | static void set_namespaces(void) |
183843cd DS |
299 | { |
300 | if (!LIST_EMPTY(&namespace_head)) | |
301 | fatal("LCX namespaces not supported"); | |
302 | } | |
303 | #endif | |
304 | ||
d62a17ae | 305 | static void do_help(void) |
183843cd | 306 | { |
d62a17ae | 307 | printf("start-stop-daemon " VERSION |
308 | " for Debian - small and fast C version written by\n" | |
309 | "Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, public domain.\n" | |
310 | "\n" | |
311 | "Usage:\n" | |
312 | " start-stop-daemon -S|--start options ... -- arguments ...\n" | |
313 | " start-stop-daemon -K|--stop options ...\n" | |
314 | " start-stop-daemon -H|--help\n" | |
315 | " start-stop-daemon -V|--version\n" | |
316 | "\n" | |
317 | "Options (at least one of --exec|--pidfile|--user is required):\n" | |
318 | " -x|--exec <executable> program to start/check if it is running\n" | |
319 | " -p|--pidfile <pid-file> pid file to check\n" | |
320 | " -c|--chuid <name|uid[:group|gid]>\n" | |
321 | " change to this user/group before starting process\n" | |
322 | " -u|--user <username>|<uid> stop processes owned by this user\n" | |
323 | " -n|--name <process-name> stop processes with this name\n" | |
324 | " -s|--signal <signal> signal to send (default TERM)\n" | |
325 | " -a|--startas <pathname> program to start (default is <executable>)\n" | |
326 | " -N|--nicelevel <incr> add incr to the process's nice level\n" | |
327 | " -b|--background force the process to detach\n" | |
328 | " -m|--make-pidfile create the pidfile before starting\n" | |
329 | " -R|--retry <schedule> check whether processes die, and retry\n" | |
330 | " -t|--test test mode, don't do anything\n" | |
331 | " -o|--oknodo exit status 0 (not 1) if nothing done\n" | |
332 | " -q|--quiet be more quiet\n" | |
333 | " -v|--verbose be more verbose\n" | |
334 | "Retry <schedule> is <item>|/<item>/... where <item> is one of\n" | |
335 | " -<signal-num>|[-]<signal-name> send that signal\n" | |
336 | " <timeout> wait that many seconds\n" | |
337 | " forever repeat remainder forever\n" | |
338 | "or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n" | |
339 | "\n" | |
340 | "Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n" | |
341 | " 3 = trouble 2 = with --retry, processes wouldn't die\n"); | |
183843cd DS |
342 | } |
343 | ||
344 | ||
d62a17ae | 345 | static void badusage(const char *msg) |
183843cd DS |
346 | { |
347 | if (msg) | |
348 | fprintf(stderr, "%s: %s\n", progname, msg); | |
349 | fprintf(stderr, "Try `%s --help' for more information.\n", progname); | |
350 | exit(3); | |
351 | } | |
352 | ||
353 | struct sigpair { | |
354 | const char *name; | |
355 | int signal; | |
356 | }; | |
357 | ||
358 | const struct sigpair siglist[] = { | |
d62a17ae | 359 | {"ABRT", SIGABRT}, {"ALRM", SIGALRM}, {"FPE", SIGFPE}, |
360 | {"HUP", SIGHUP}, {"ILL", SIGILL}, {"INT", SIGINT}, | |
361 | {"KILL", SIGKILL}, {"PIPE", SIGPIPE}, {"QUIT", SIGQUIT}, | |
362 | {"SEGV", SIGSEGV}, {"TERM", SIGTERM}, {"USR1", SIGUSR1}, | |
363 | {"USR2", SIGUSR2}, {"CHLD", SIGCHLD}, {"CONT", SIGCONT}, | |
364 | {"STOP", SIGSTOP}, {"TSTP", SIGTSTP}, {"TTIN", SIGTTIN}, | |
365 | {"TTOU", SIGTTOU}}; | |
366 | ||
367 | static int parse_integer(const char *string, int *value_r) | |
368 | { | |
183843cd DS |
369 | unsigned long ul; |
370 | char *ep; | |
371 | ||
372 | if (!string[0]) | |
373 | return -1; | |
374 | ||
d62a17ae | 375 | ul = strtoul(string, &ep, 10); |
183843cd DS |
376 | if (ul > INT_MAX || *ep != '\0') |
377 | return -1; | |
378 | ||
d62a17ae | 379 | *value_r = ul; |
183843cd DS |
380 | return 0; |
381 | } | |
382 | ||
d62a17ae | 383 | static int parse_signal(const char *signal_str, int *signal_nr) |
183843cd DS |
384 | { |
385 | unsigned int i; | |
386 | ||
387 | if (parse_integer(signal_str, signal_nr) == 0) | |
388 | return 0; | |
389 | ||
d62a17ae | 390 | for (i = 0; i < sizeof(siglist) / sizeof(siglist[0]); i++) { |
391 | if (strcmp(signal_str, siglist[i].name) == 0) { | |
183843cd DS |
392 | *signal_nr = siglist[i].signal; |
393 | return 0; | |
394 | } | |
395 | } | |
396 | return -1; | |
397 | } | |
398 | ||
d62a17ae | 399 | static void parse_schedule_item(const char *string, struct schedule_item *item) |
400 | { | |
183843cd DS |
401 | const char *after_hyph; |
402 | ||
d62a17ae | 403 | if (!strcmp(string, "forever")) { |
183843cd | 404 | item->type = sched_forever; |
fefa5e0f | 405 | } else if (isdigit((unsigned char)string[0])) { |
183843cd DS |
406 | item->type = sched_timeout; |
407 | if (parse_integer(string, &item->value) != 0) | |
408 | badusage("invalid timeout value in schedule"); | |
d62a17ae | 409 | } else if ((after_hyph = string + (string[0] == '-')) |
410 | && parse_signal(after_hyph, &item->value) == 0) { | |
183843cd DS |
411 | item->type = sched_signal; |
412 | } else { | |
d62a17ae | 413 | badusage( |
3efd0893 | 414 | "invalid schedule item (must be [-]<signal-name>, -<signal-number>, <timeout> or `forever'"); |
183843cd DS |
415 | } |
416 | } | |
417 | ||
d62a17ae | 418 | static void parse_schedule(const char *schedule_str) |
419 | { | |
183843cd DS |
420 | char item_buf[20]; |
421 | const char *slash; | |
422 | int count, repeatat; | |
423 | ptrdiff_t str_len; | |
424 | ||
425 | count = 0; | |
426 | for (slash = schedule_str; *slash; slash++) | |
427 | if (*slash == '/') | |
428 | count++; | |
429 | ||
d62a17ae | 430 | schedule_length = (count == 0) ? 4 : count + 1; |
183843cd DS |
431 | schedule = xmalloc(sizeof(*schedule) * schedule_length); |
432 | ||
433 | if (count == 0) { | |
434 | schedule[0].type = sched_signal; | |
435 | schedule[0].value = signal_nr; | |
436 | parse_schedule_item(schedule_str, &schedule[1]); | |
437 | if (schedule[1].type != sched_timeout) { | |
d62a17ae | 438 | badusage( |
3efd0893 | 439 | "--retry takes timeout, or schedule list of at least two items"); |
183843cd DS |
440 | } |
441 | schedule[2].type = sched_signal; | |
442 | schedule[2].value = SIGKILL; | |
d62a17ae | 443 | schedule[3] = schedule[1]; |
183843cd DS |
444 | } else { |
445 | count = 0; | |
446 | repeatat = -1; | |
447 | while (schedule_str != NULL) { | |
d62a17ae | 448 | slash = strchr(schedule_str, '/'); |
449 | str_len = slash ? slash - schedule_str | |
450 | : (ptrdiff_t)strlen(schedule_str); | |
183843cd | 451 | if (str_len >= (ptrdiff_t)sizeof(item_buf)) |
d62a17ae | 452 | badusage( |
3efd0893 | 453 | "invalid schedule item: far too long (you must delimit items with slashes)"); |
183843cd DS |
454 | memcpy(item_buf, schedule_str, str_len); |
455 | item_buf[str_len] = 0; | |
d62a17ae | 456 | schedule_str = slash ? slash + 1 : NULL; |
183843cd DS |
457 | |
458 | parse_schedule_item(item_buf, &schedule[count]); | |
459 | if (schedule[count].type == sched_forever) { | |
460 | if (repeatat >= 0) | |
d62a17ae | 461 | badusage( |
3efd0893 | 462 | "invalid schedule: `forever' appears more than once"); |
183843cd DS |
463 | repeatat = count; |
464 | continue; | |
465 | } | |
466 | count++; | |
467 | } | |
468 | if (repeatat >= 0) { | |
469 | schedule[count].type = sched_goto; | |
470 | schedule[count].value = repeatat; | |
471 | count++; | |
472 | } | |
473 | assert(count == schedule_length); | |
474 | } | |
475 | } | |
476 | ||
d62a17ae | 477 | static void parse_options(int argc, char *const *argv) |
183843cd DS |
478 | { |
479 | static struct option longopts[] = { | |
d62a17ae | 480 | {"help", 0, NULL, 'H'}, {"stop", 0, NULL, 'K'}, |
481 | {"start", 0, NULL, 'S'}, {"version", 0, NULL, 'V'}, | |
482 | {"startas", 1, NULL, 'a'}, {"name", 1, NULL, 'n'}, | |
483 | {"oknodo", 0, NULL, 'o'}, {"pidfile", 1, NULL, 'p'}, | |
484 | {"quiet", 0, NULL, 'q'}, {"signal", 1, NULL, 's'}, | |
485 | {"test", 0, NULL, 't'}, {"user", 1, NULL, 'u'}, | |
486 | {"chroot", 1, NULL, 'r'}, {"namespace", 1, NULL, 'd'}, | |
487 | {"verbose", 0, NULL, 'v'}, {"exec", 1, NULL, 'x'}, | |
488 | {"chuid", 1, NULL, 'c'}, {"nicelevel", 1, NULL, 'N'}, | |
489 | {"background", 0, NULL, 'b'}, {"make-pidfile", 0, NULL, 'm'}, | |
490 | {"retry", 1, NULL, 'R'}, {NULL, 0, NULL, 0}}; | |
183843cd DS |
491 | int c; |
492 | ||
493 | for (;;) { | |
d62a17ae | 494 | c = getopt_long(argc, argv, |
495 | "HKSVa:n:op:qr:d:s:tu:vx:c:N:bmR:", longopts, | |
496 | (int *)0); | |
183843cd DS |
497 | if (c == -1) |
498 | break; | |
499 | switch (c) { | |
d62a17ae | 500 | case 'H': /* --help */ |
183843cd DS |
501 | do_help(); |
502 | exit(0); | |
d62a17ae | 503 | case 'K': /* --stop */ |
183843cd DS |
504 | stop = 1; |
505 | break; | |
d62a17ae | 506 | case 'S': /* --start */ |
183843cd DS |
507 | start = 1; |
508 | break; | |
d62a17ae | 509 | case 'V': /* --version */ |
183843cd DS |
510 | printf("start-stop-daemon " VERSION "\n"); |
511 | exit(0); | |
d62a17ae | 512 | case 'a': /* --startas <pathname> */ |
183843cd DS |
513 | startas = optarg; |
514 | break; | |
d62a17ae | 515 | case 'n': /* --name <process-name> */ |
183843cd DS |
516 | cmdname = optarg; |
517 | break; | |
d62a17ae | 518 | case 'o': /* --oknodo */ |
183843cd DS |
519 | exitnodo = 0; |
520 | break; | |
d62a17ae | 521 | case 'p': /* --pidfile <pid-file> */ |
183843cd DS |
522 | pidfile = optarg; |
523 | break; | |
d62a17ae | 524 | case 'q': /* --quiet */ |
183843cd DS |
525 | quietmode = 1; |
526 | break; | |
d62a17ae | 527 | case 's': /* --signal <signal> */ |
183843cd DS |
528 | signal_str = optarg; |
529 | break; | |
d62a17ae | 530 | case 't': /* --test */ |
183843cd DS |
531 | testmode = 1; |
532 | break; | |
d62a17ae | 533 | case 'u': /* --user <username>|<uid> */ |
183843cd DS |
534 | userspec = optarg; |
535 | break; | |
d62a17ae | 536 | case 'v': /* --verbose */ |
183843cd DS |
537 | quietmode = -1; |
538 | break; | |
d62a17ae | 539 | case 'x': /* --exec <executable> */ |
183843cd DS |
540 | execname = optarg; |
541 | break; | |
d62a17ae | 542 | case 'c': /* --chuid <username>|<uid> */ |
44f12f20 | 543 | changeuser = strtok(optarg, ":"); |
183843cd DS |
544 | changegroup = strtok(NULL, ":"); |
545 | break; | |
d62a17ae | 546 | case 'r': /* --chroot /new/root */ |
183843cd DS |
547 | changeroot = optarg; |
548 | break; | |
549 | case 'd': /* --namespace /.../<ipcns>|<netns>|<utsns>/name */ | |
8f500a1c | 550 | #ifdef linux |
183843cd | 551 | add_namespace(optarg); |
8f500a1c | 552 | #endif |
183843cd | 553 | break; |
d62a17ae | 554 | case 'N': /* --nice */ |
183843cd DS |
555 | nicelevel = atoi(optarg); |
556 | break; | |
d62a17ae | 557 | case 'b': /* --background */ |
183843cd DS |
558 | background = 1; |
559 | break; | |
d62a17ae | 560 | case 'm': /* --make-pidfile */ |
183843cd DS |
561 | mpidfile = 1; |
562 | break; | |
d62a17ae | 563 | case 'R': /* --retry <schedule>|<timeout> */ |
183843cd DS |
564 | schedule_str = optarg; |
565 | break; | |
566 | default: | |
d62a17ae | 567 | badusage(NULL); /* message printed by getopt */ |
183843cd DS |
568 | } |
569 | } | |
570 | ||
571 | if (signal_str != NULL) { | |
d62a17ae | 572 | if (parse_signal(signal_str, &signal_nr) != 0) |
573 | badusage( | |
3efd0893 | 574 | "signal value must be numeric or name of signal (KILL, INTR, ...)"); |
183843cd DS |
575 | } |
576 | ||
577 | if (schedule_str != NULL) { | |
578 | parse_schedule(schedule_str); | |
579 | } | |
580 | ||
581 | if (start == stop) | |
582 | badusage("need one of --start or --stop"); | |
583 | ||
584 | if (!execname && !pidfile && !userspec && !cmdname) | |
d62a17ae | 585 | badusage( |
586 | "need at least one of --exec, --pidfile, --user or --name"); | |
183843cd DS |
587 | |
588 | if (!startas) | |
589 | startas = execname; | |
590 | ||
591 | if (start && !startas) | |
592 | badusage("--start needs --exec or --startas"); | |
593 | ||
594 | if (mpidfile && pidfile == NULL) | |
595 | badusage("--make-pidfile is only relevant with --pidfile"); | |
596 | ||
597 | if (background && !start) | |
598 | badusage("--background is only relevant with --start"); | |
183843cd DS |
599 | } |
600 | ||
d62a17ae | 601 | static int pid_is_exec(pid_t pid, const struct stat *esb) |
183843cd DS |
602 | { |
603 | struct stat sb; | |
2b7165e7 | 604 | char buf[PATH_MAX]; |
183843cd | 605 | |
772270f3 | 606 | snprintf(buf, sizeof(buf), "/proc/%ld/exe", (long)pid); |
183843cd DS |
607 | if (stat(buf, &sb) != 0) |
608 | return 0; | |
609 | return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); | |
610 | } | |
611 | ||
612 | ||
d62a17ae | 613 | static int pid_is_user(pid_t pid, uid_t uid) |
183843cd DS |
614 | { |
615 | struct stat sb; | |
2b7165e7 | 616 | char buf[PATH_MAX]; |
183843cd | 617 | |
772270f3 | 618 | snprintf(buf, sizeof(buf), "/proc/%ld", (long)pid); |
183843cd DS |
619 | if (stat(buf, &sb) != 0) |
620 | return 0; | |
621 | return (sb.st_uid == uid); | |
622 | } | |
623 | ||
624 | ||
d62a17ae | 625 | static int pid_is_cmd(pid_t pid, const char *name) |
183843cd | 626 | { |
2b7165e7 | 627 | char buf[PATH_MAX]; |
183843cd DS |
628 | FILE *f; |
629 | int c; | |
630 | ||
772270f3 | 631 | snprintf(buf, sizeof(buf), "/proc/%ld/stat", (long)pid); |
183843cd DS |
632 | f = fopen(buf, "r"); |
633 | if (!f) | |
634 | return 0; | |
635 | while ((c = getc(f)) != EOF && c != '(') | |
636 | ; | |
637 | if (c != '(') { | |
638 | fclose(f); | |
639 | return 0; | |
640 | } | |
641 | /* this hopefully handles command names containing ')' */ | |
642 | while ((c = getc(f)) != EOF && c == *name) | |
643 | name++; | |
644 | fclose(f); | |
645 | return (c == ')' && *name == '\0'); | |
646 | } | |
647 | ||
648 | ||
d62a17ae | 649 | static void check(pid_t pid) |
183843cd DS |
650 | { |
651 | if (execname && !pid_is_exec(pid, &exec_stat)) | |
652 | return; | |
653 | if (userspec && !pid_is_user(pid, user_id)) | |
654 | return; | |
655 | if (cmdname && !pid_is_cmd(pid, cmdname)) | |
656 | return; | |
657 | push(&found, pid); | |
658 | } | |
659 | ||
d62a17ae | 660 | static void do_pidfile(const char *name) |
183843cd DS |
661 | { |
662 | FILE *f; | |
ae9eebca | 663 | long pid; |
183843cd DS |
664 | |
665 | f = fopen(name, "r"); | |
666 | if (f) { | |
ae9eebca DL |
667 | if (fscanf(f, "%ld", &pid) == 1) |
668 | check((pid_t)pid); | |
183843cd DS |
669 | fclose(f); |
670 | } else if (errno != ENOENT) | |
671 | fatal("open pidfile %s: %s", name, strerror(errno)); | |
183843cd DS |
672 | } |
673 | ||
674 | /* WTA: this needs to be an autoconf check for /proc/pid existance. | |
675 | */ | |
d62a17ae | 676 | static void do_procinit(void) |
183843cd DS |
677 | { |
678 | DIR *procdir; | |
679 | struct dirent *entry; | |
680 | int foundany; | |
ae9eebca | 681 | long pid; |
183843cd DS |
682 | |
683 | procdir = opendir("/proc"); | |
684 | if (!procdir) | |
685 | fatal("opendir /proc: %s", strerror(errno)); | |
686 | ||
687 | foundany = 0; | |
688 | while ((entry = readdir(procdir)) != NULL) { | |
ae9eebca | 689 | if (sscanf(entry->d_name, "%ld", &pid) != 1) |
183843cd DS |
690 | continue; |
691 | foundany++; | |
ae9eebca | 692 | check((pid_t)pid); |
183843cd DS |
693 | } |
694 | closedir(procdir); | |
695 | if (!foundany) | |
696 | fatal("nothing in /proc - not mounted?"); | |
697 | } | |
698 | ||
d62a17ae | 699 | static void do_findprocs(void) |
183843cd DS |
700 | { |
701 | clear(&found); | |
d62a17ae | 702 | |
183843cd DS |
703 | if (pidfile) |
704 | do_pidfile(pidfile); | |
705 | else | |
706 | do_procinit(); | |
707 | } | |
708 | ||
709 | /* return 1 on failure */ | |
d62a17ae | 710 | static void do_stop(int signal_nr, int quietmode, int *n_killed, |
711 | int *n_notkilled, int retry_nr) | |
183843cd DS |
712 | { |
713 | struct pid_list *p; | |
714 | ||
d62a17ae | 715 | do_findprocs(); |
716 | ||
717 | *n_killed = 0; | |
718 | *n_notkilled = 0; | |
719 | ||
720 | if (!found) | |
721 | return; | |
722 | ||
723 | clear(&killed); | |
183843cd DS |
724 | |
725 | for (p = found; p; p = p->next) { | |
726 | if (testmode) | |
ae9eebca DL |
727 | printf("Would send signal %d to %ld.\n", signal_nr, |
728 | (long)p->pid); | |
d62a17ae | 729 | else if (kill(p->pid, signal_nr) == 0) { |
183843cd | 730 | push(&killed, p->pid); |
d62a17ae | 731 | (*n_killed)++; |
183843cd | 732 | } else { |
ae9eebca DL |
733 | printf("%s: warning: failed to kill %ld: %s\n", |
734 | progname, (long)p->pid, strerror(errno)); | |
d62a17ae | 735 | (*n_notkilled)++; |
183843cd DS |
736 | } |
737 | } | |
738 | if (quietmode < 0 && killed) { | |
d62a17ae | 739 | printf("Stopped %s (pid", what_stop); |
183843cd | 740 | for (p = killed; p; p = p->next) |
ae9eebca | 741 | printf(" %ld", (long)p->pid); |
d62a17ae | 742 | putchar(')'); |
743 | if (retry_nr > 0) | |
744 | printf(", retry #%d", retry_nr); | |
745 | printf(".\n"); | |
183843cd DS |
746 | } |
747 | } | |
748 | ||
749 | ||
d62a17ae | 750 | static void set_what_stop(const char *str) |
183843cd DS |
751 | { |
752 | strncpy(what_stop, str, sizeof(what_stop)); | |
d62a17ae | 753 | what_stop[sizeof(what_stop) - 1] = '\0'; |
183843cd DS |
754 | } |
755 | ||
d62a17ae | 756 | static int run_stop_schedule(void) |
183843cd | 757 | { |
d62a17ae | 758 | int r, position, n_killed, n_notkilled, value, ratio, anykilled, |
759 | retry_nr; | |
183843cd DS |
760 | struct timeval stopat, before, after, interval, maxinterval; |
761 | ||
762 | if (testmode) { | |
763 | if (schedule != NULL) { | |
764 | printf("Ignoring --retry in test mode\n"); | |
765 | schedule = NULL; | |
766 | } | |
767 | } | |
768 | ||
769 | if (cmdname) | |
770 | set_what_stop(cmdname); | |
771 | else if (execname) | |
772 | set_what_stop(execname); | |
773 | else if (pidfile) | |
774 | sprintf(what_stop, "process in pidfile `%.200s'", pidfile); | |
775 | else if (userspec) | |
776 | sprintf(what_stop, "process(es) owned by `%.200s'", userspec); | |
777 | else | |
778 | fatal("internal error, please report"); | |
779 | ||
780 | anykilled = 0; | |
781 | retry_nr = 0; | |
c604467a | 782 | n_killed = 0; |
183843cd DS |
783 | |
784 | if (schedule == NULL) { | |
785 | do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0); | |
786 | if (n_notkilled > 0 && quietmode <= 0) | |
787 | printf("%d pids were not killed\n", n_notkilled); | |
788 | if (n_killed) | |
789 | anykilled = 1; | |
790 | goto x_finished; | |
791 | } | |
792 | ||
d62a17ae | 793 | for (position = 0; position < schedule_length;) { |
794 | value = schedule[position].value; | |
183843cd DS |
795 | n_notkilled = 0; |
796 | ||
797 | switch (schedule[position].type) { | |
798 | ||
799 | case sched_goto: | |
800 | position = value; | |
801 | continue; | |
802 | ||
803 | case sched_signal: | |
d62a17ae | 804 | do_stop(value, quietmode, &n_killed, &n_notkilled, |
805 | retry_nr++); | |
183843cd DS |
806 | if (!n_killed) |
807 | goto x_finished; | |
808 | else | |
809 | anykilled = 1; | |
810 | goto next_item; | |
811 | ||
812 | case sched_timeout: | |
d62a17ae | 813 | /* We want to keep polling for the processes, to see if |
814 | * they've exited, | |
815 | * or until the timeout expires. | |
816 | * | |
817 | * This is a somewhat complicated algorithm to try to | |
818 | * ensure that we | |
819 | * notice reasonably quickly when all the processes have | |
820 | * exited, but | |
821 | * don't spend too much CPU time polling. In | |
822 | * particular, on a fast | |
823 | * machine with quick-exiting daemons we don't want to | |
824 | * delay system | |
825 | * shutdown too much, whereas on a slow one, or where | |
826 | * processes are | |
827 | * taking some time to exit, we want to increase the | |
828 | * polling | |
829 | * interval. | |
830 | * | |
831 | * The algorithm is as follows: we measure the elapsed | |
832 | * time it takes | |
833 | * to do one poll(), and wait a multiple of this time | |
834 | * for the next | |
835 | * poll. However, if that would put us past the end of | |
836 | * the timeout | |
837 | * period we wait only as long as the timeout period, | |
838 | * but in any case | |
839 | * we always wait at least MIN_POLL_INTERVAL (20ms). | |
840 | * The multiple | |
841 | * (`ratio') starts out as 2, and increases by 1 for | |
842 | * each poll to a | |
843 | * maximum of 10; so we use up to between 30% and 10% of | |
844 | * the | |
845 | * machine's resources (assuming a few reasonable things | |
846 | * about system | |
847 | * performance). | |
848 | */ | |
183843cd DS |
849 | xgettimeofday(&stopat); |
850 | stopat.tv_sec += value; | |
851 | ratio = 1; | |
852 | for (;;) { | |
853 | xgettimeofday(&before); | |
d62a17ae | 854 | if (timercmp(&before, &stopat, >)) |
183843cd DS |
855 | goto next_item; |
856 | ||
857 | do_stop(0, 1, &n_killed, &n_notkilled, 0); | |
858 | if (!n_killed) | |
859 | goto x_finished; | |
860 | ||
861 | xgettimeofday(&after); | |
862 | ||
d62a17ae | 863 | if (!timercmp(&after, &stopat, <)) |
183843cd DS |
864 | goto next_item; |
865 | ||
866 | if (ratio < 10) | |
867 | ratio++; | |
868 | ||
9d303b37 DL |
869 | TVCALC(interval, |
870 | ratio * (TVELEM(&after) - TVELEM(&before) | |
871 | + TVADJUST)); | |
872 | TVCALC(maxinterval, | |
873 | TVELEM(&stopat) - TVELEM(&after) | |
874 | + TVADJUST); | |
183843cd | 875 | |
d62a17ae | 876 | if (timercmp(&interval, &maxinterval, >)) |
183843cd DS |
877 | interval = maxinterval; |
878 | ||
d62a17ae | 879 | if (interval.tv_sec == 0 |
880 | && interval.tv_usec <= MIN_POLL_INTERVAL) | |
881 | interval.tv_usec = MIN_POLL_INTERVAL; | |
183843cd | 882 | |
d62a17ae | 883 | r = select(0, 0, 0, 0, &interval); |
183843cd DS |
884 | if (r < 0 && errno != EINTR) |
885 | fatal("select() failed for pause: %s", | |
886 | strerror(errno)); | |
887 | } | |
888 | ||
0678d01a | 889 | case sched_forever: |
183843cd | 890 | assert(!"schedule[].type value must be valid"); |
183843cd DS |
891 | } |
892 | ||
893 | next_item: | |
894 | position++; | |
895 | } | |
896 | ||
897 | if (quietmode <= 0) | |
898 | printf("Program %s, %d process(es), refused to die.\n", | |
899 | what_stop, n_killed); | |
900 | ||
901 | return 2; | |
902 | ||
903 | x_finished: | |
904 | if (!anykilled) { | |
905 | if (quietmode <= 0) | |
d62a17ae | 906 | printf("No %s found running; none killed.\n", |
907 | what_stop); | |
183843cd DS |
908 | return exitnodo; |
909 | } else { | |
910 | return 0; | |
911 | } | |
912 | } | |
913 | ||
914 | /* | |
915 | int main(int argc, char **argv) NONRETURNING; | |
916 | */ | |
917 | ||
d62a17ae | 918 | int main(int argc, char **argv) |
183843cd DS |
919 | { |
920 | progname = argv[0]; | |
921 | ||
922 | LIST_INIT(&namespace_head); | |
923 | ||
924 | parse_options(argc, argv); | |
925 | argc -= optind; | |
926 | argv += optind; | |
927 | ||
928 | if (execname && stat(execname, &exec_stat)) | |
929 | fatal("stat %s: %s", execname, strerror(errno)); | |
930 | ||
931 | if (userspec && sscanf(userspec, "%d", &user_id) != 1) { | |
932 | struct passwd *pw; | |
933 | ||
934 | pw = getpwnam(userspec); | |
935 | if (!pw) | |
936 | fatal("user `%s' not found\n", userspec); | |
937 | ||
938 | user_id = pw->pw_uid; | |
939 | } | |
940 | ||
941 | if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) { | |
942 | struct group *gr = getgrnam(changegroup); | |
943 | if (!gr) | |
944 | fatal("group `%s' not found\n", changegroup); | |
945 | runas_gid = gr->gr_gid; | |
946 | } | |
947 | if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) { | |
948 | struct passwd *pw = getpwnam(changeuser); | |
949 | if (!pw) | |
950 | fatal("user `%s' not found\n", changeuser); | |
951 | runas_uid = pw->pw_uid; | |
d62a17ae | 952 | if (changegroup |
953 | == NULL) { /* pass the default group of this user */ | |
183843cd DS |
954 | changegroup = ""; /* just empty */ |
955 | runas_gid = pw->pw_gid; | |
956 | } | |
957 | } | |
958 | ||
959 | if (stop) { | |
960 | int i = run_stop_schedule(); | |
961 | exit(i); | |
962 | } | |
963 | ||
964 | do_findprocs(); | |
965 | ||
966 | if (found) { | |
967 | if (quietmode <= 0) | |
968 | printf("%s already running.\n", execname); | |
969 | exit(exitnodo); | |
970 | } | |
971 | if (testmode) { | |
972 | printf("Would start %s ", startas); | |
973 | while (argc-- > 0) | |
974 | printf("%s ", *argv++); | |
975 | if (changeuser != NULL) { | |
976 | printf(" (as user %s[%d]", changeuser, runas_uid); | |
977 | if (changegroup != NULL) | |
d62a17ae | 978 | printf(", and group %s[%d])", changegroup, |
979 | runas_gid); | |
183843cd DS |
980 | else |
981 | printf(")"); | |
982 | } | |
983 | if (changeroot != NULL) | |
984 | printf(" in directory %s", changeroot); | |
985 | if (nicelevel) | |
986 | printf(", and add %i to the priority", nicelevel); | |
987 | printf(".\n"); | |
988 | exit(0); | |
989 | } | |
990 | if (quietmode < 0) | |
991 | printf("Starting %s...\n", startas); | |
992 | *--argv = startas; | |
993 | if (changeroot != NULL) { | |
994 | if (chdir(changeroot) < 0) | |
995 | fatal("Unable to chdir() to %s", changeroot); | |
996 | if (chroot(changeroot) < 0) | |
997 | fatal("Unable to chroot() to %s", changeroot); | |
998 | } | |
999 | if (changeuser != NULL) { | |
d62a17ae | 1000 | if (setgid(runas_gid)) |
1001 | fatal("Unable to set gid to %d", runas_gid); | |
183843cd | 1002 | if (initgroups(changeuser, runas_gid)) |
d62a17ae | 1003 | fatal("Unable to set initgroups() with gid %d", |
1004 | runas_gid); | |
183843cd DS |
1005 | if (setuid(runas_uid)) |
1006 | fatal("Unable to set uid to %s", changeuser); | |
1007 | } | |
1008 | ||
1009 | if (background) { /* ok, we need to detach this process */ | |
1010 | int i, fd; | |
1011 | if (quietmode < 0) | |
0437e105 | 1012 | printf("Detaching to start %s...", startas); |
183843cd | 1013 | i = fork(); |
d62a17ae | 1014 | if (i < 0) { |
183843cd DS |
1015 | fatal("Unable to fork.\n"); |
1016 | } | |
1017 | if (i) { /* parent */ | |
1018 | if (quietmode < 0) | |
1019 | printf("done.\n"); | |
1020 | exit(0); | |
1021 | } | |
d62a17ae | 1022 | /* child continues here */ |
1023 | /* now close all extra fds */ | |
1024 | for (i = getdtablesize() - 1; i >= 0; --i) | |
1025 | close(i); | |
1026 | /* change tty */ | |
183843cd | 1027 | fd = open("/dev/tty", O_RDWR); |
9b606d6c | 1028 | if (fd >= 0) { |
b0bde9f3 A |
1029 | if (ioctl(fd, TIOCNOTTY, 0) < 0) |
1030 | printf("ioctl TIOCNOTTY failed: %s\n", | |
1031 | strerror(errno)); | |
9b606d6c | 1032 | close(fd); |
1033 | } | |
183843cd | 1034 | chdir("/"); |
d62a17ae | 1035 | umask(022); /* set a default for dumb programs */ |
1036 | setpgid(0, 0); /* set the process group */ | |
1037 | fd = open("/dev/null", O_RDWR); /* stdin */ | |
657f9948 | 1038 | if (fd >= 0) { |
1039 | dup(fd); /* stdout */ | |
1040 | dup(fd); /* stderr */ | |
1041 | } | |
183843cd DS |
1042 | } |
1043 | if (nicelevel) { | |
1044 | errno = 0; | |
1045 | if (nice(nicelevel) < 0 && errno) | |
1046 | fatal("Unable to alter nice level by %i: %s", nicelevel, | |
d62a17ae | 1047 | strerror(errno)); |
183843cd | 1048 | } |
d62a17ae | 1049 | if (mpidfile |
1050 | && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */ | |
183843cd DS |
1051 | FILE *pidf = fopen(pidfile, "w"); |
1052 | pid_t pidt = getpid(); | |
1053 | if (pidf == NULL) | |
d62a17ae | 1054 | fatal("Unable to open pidfile `%s' for writing: %s", |
1055 | pidfile, strerror(errno)); | |
ae9eebca | 1056 | fprintf(pidf, "%ld\n", (long)pidt); |
183843cd DS |
1057 | fclose(pidf); |
1058 | } | |
1059 | set_namespaces(); | |
1060 | execv(startas, argv); | |
1061 | fatal("Unable to start %s: %s", startas, strerror(errno)); | |
1062 | } |