Annotation of /tags/init-0_6_0/src/start-stop-daemon.c
Parent Directory | Revision Log
Revision 1317 -
(hide annotations)
(download)
Fri May 27 18:52:31 2011 UTC (13 years, 4 months ago) by niro
File MIME type: text/plain
File size: 35433 byte(s)
Fri May 27 18:52:31 2011 UTC (13 years, 4 months ago) by niro
File MIME type: text/plain
File size: 35433 byte(s)
tagged 'init-0_6_0'
1 | niro | 111 | /* |
2 | * A rewrite of the original Debian's start-stop-daemon Perl script | ||
3 | * in C (faster - it is executed many times during system startup). | ||
4 | * | ||
5 | * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, | ||
6 | * public domain. Based conceptually on start-stop-daemon.pl, by Ian | ||
7 | * Jackson <ijackson@gnu.ai.mit.edu>. May be used and distributed | ||
8 | * freely for any purpose. Changes by Christian Schwarz | ||
9 | * <schwarz@monet.m.isar.de>, to make output conform to the Debian | ||
10 | * Console Message Standard, also placed in public domain. Minor | ||
11 | * changes by Klee Dienes <klee@debian.org>, also placed in the Public | ||
12 | * Domain. | ||
13 | * | ||
14 | * Changes by Ben Collins <bcollins@debian.org>, added --chuid, --background | ||
15 | * and --make-pidfile options, placed in public domain aswell. | ||
16 | * | ||
17 | * Port to OpenBSD by Sontri Tomo Huynh <huynh.29@osu.edu> | ||
18 | * and Andreas Schuldei <andreas@schuldei.org> | ||
19 | * | ||
20 | * Changes by Ian Jackson: added --retry (and associated rearrangements). | ||
21 | * Modified for Gentoo rc-scripts by Donny Davies <woodchip@gentoo.org>: | ||
22 | * I removed the BSD/Hurd/OtherOS stuff, added #include <stddef.h> | ||
23 | * and stuck in a #define VERSION "1.9.18". Now it compiles without | ||
24 | * the whole automake/config.h dance. | ||
25 | * | ||
26 | * Updated by Aron Griffis <agriffis@gentoo.org>: | ||
27 | * Fetched updates from Debian's dpkg-1.10.20, including fix for | ||
28 | * Gentoo bug 22686 (start-stop-daemon in baselayout doesn't allow | ||
29 | * altered nicelevel). | ||
30 | niro | 1268 | * |
31 | * | ||
32 | * Changes by Quequero <quequero@bitchx.it>: | ||
33 | * Added -e|--env for setting an environment variable before starting the | ||
34 | * process. | ||
35 | * Moved --make-pidfile after chrooting process (pid file will be wrote in | ||
36 | * new root if -r option is used!). | ||
37 | * Daemon binary will be stat()ed correctly if it's going to be chrooted | ||
38 | * with -r|--chroot. | ||
39 | * | ||
40 | niro | 111 | */ |
41 | |||
42 | niro | 1268 | #define VERSION "1.13.11" |
43 | niro | 111 | |
44 | #define NONRETURNPRINTFFORMAT(x, y) \ | ||
45 | __attribute__((noreturn, format(printf, x, y))) | ||
46 | #define NONRETURNING \ | ||
47 | __attribute__((noreturn)) | ||
48 | |||
49 | #if defined(linux) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) | ||
50 | # define OSLinux | ||
51 | #elif defined(__GNU__) | ||
52 | # define OSHURD | ||
53 | #elif defined(__sparc__) | ||
54 | # define OSsunos | ||
55 | #elif defined(OPENBSD) || defined(__OpenBSD__) | ||
56 | # define OSOpenBSD | ||
57 | #elif defined(hpux) | ||
58 | # define OShpux | ||
59 | #elif defined(__FreeBSD__) | ||
60 | # define OSFreeBSD | ||
61 | #elif defined(__NetBSD__) | ||
62 | # define OSNetBSD | ||
63 | niro | 1268 | #elif defined(__APPLE__) |
64 | # define OSDarwin | ||
65 | niro | 111 | #else |
66 | # error Unknown architecture - cannot build start-stop-daemon | ||
67 | #endif | ||
68 | |||
69 | #define MIN_POLL_INTERVAL 20000 /*us*/ | ||
70 | |||
71 | #if defined(OSHURD) | ||
72 | # include <hurd.h> | ||
73 | # include <ps.h> | ||
74 | #endif | ||
75 | |||
76 | niro | 1268 | #if defined(OSOpenBSD) || defined(OSFreeBSD) || defined(OSNetBSD) || defined(OSDarwin) |
77 | niro | 111 | #include <sys/param.h> |
78 | #include <sys/user.h> | ||
79 | #include <sys/proc.h> | ||
80 | #include <sys/stat.h> | ||
81 | #include <sys/sysctl.h> | ||
82 | #include <sys/types.h> | ||
83 | niro | 1268 | |
84 | niro | 111 | #include <err.h> |
85 | #include <kvm.h> | ||
86 | #include <limits.h> | ||
87 | #endif | ||
88 | |||
89 | #if defined(OShpux) | ||
90 | #include <sys/param.h> | ||
91 | #include <sys/pstat.h> | ||
92 | #endif | ||
93 | |||
94 | #include <errno.h> | ||
95 | #include <stdio.h> | ||
96 | #include <stdlib.h> | ||
97 | #include <string.h> | ||
98 | #include <stdarg.h> | ||
99 | #include <signal.h> | ||
100 | #include <sys/stat.h> | ||
101 | #include <dirent.h> | ||
102 | #include <sys/time.h> | ||
103 | #include <unistd.h> | ||
104 | #include <getopt.h> | ||
105 | #include <pwd.h> | ||
106 | #include <grp.h> | ||
107 | #include <sys/ioctl.h> | ||
108 | #include <sys/types.h> | ||
109 | #include <sys/termios.h> | ||
110 | #include <fcntl.h> | ||
111 | #include <limits.h> | ||
112 | #include <assert.h> | ||
113 | #include <ctype.h> | ||
114 | |||
115 | niro | 1268 | #include <stddef.h> |
116 | |||
117 | niro | 111 | #include "headers.h" |
118 | |||
119 | #ifdef HURD_IHASH_H | ||
120 | # include <hurd/ihash.h> | ||
121 | #endif | ||
122 | |||
123 | static int testmode = 0; | ||
124 | static int quietmode = 0; | ||
125 | static int exitnodo = 1; | ||
126 | static int start = 0; | ||
127 | static int stop = 0; | ||
128 | static int background = 0; | ||
129 | static int mpidfile = 0; | ||
130 | static int signal_nr = 15; | ||
131 | static const char *signal_str = NULL; | ||
132 | static int user_id = -1; | ||
133 | static int runas_uid = -1; | ||
134 | static int runas_gid = -1; | ||
135 | niro | 1268 | static char *env = NULL; |
136 | niro | 111 | static const char *userspec = NULL; |
137 | static char *changeuser = NULL; | ||
138 | static const char *changegroup = NULL; | ||
139 | static char *changeroot = NULL; | ||
140 | niro | 1268 | static const char *changedir = "/"; |
141 | niro | 111 | static const char *cmdname = NULL; |
142 | static char *execname = NULL; | ||
143 | static char *startas = NULL; | ||
144 | static const char *pidfile = NULL; | ||
145 | static char what_stop[1024]; | ||
146 | static const char *schedule_str = NULL; | ||
147 | static const char *progname = ""; | ||
148 | static int nicelevel = 0; | ||
149 | |||
150 | static struct stat exec_stat; | ||
151 | #if defined(OSHURD) | ||
152 | niro | 1268 | static struct proc_stat_list *procset = NULL; |
153 | niro | 111 | #endif |
154 | |||
155 | |||
156 | struct pid_list { | ||
157 | struct pid_list *next; | ||
158 | pid_t pid; | ||
159 | }; | ||
160 | |||
161 | static struct pid_list *found = NULL; | ||
162 | static struct pid_list *killed = NULL; | ||
163 | |||
164 | struct schedule_item { | ||
165 | enum { sched_timeout, sched_signal, sched_goto, sched_forever } type; | ||
166 | int value; /* seconds, signal no., or index into array */ | ||
167 | /* sched_forever is only seen within parse_schedule and callees */ | ||
168 | }; | ||
169 | |||
170 | static int schedule_length; | ||
171 | static struct schedule_item *schedule = NULL; | ||
172 | |||
173 | static void *xmalloc(int size); | ||
174 | static void push(struct pid_list **list, pid_t pid); | ||
175 | static void do_help(void); | ||
176 | static void parse_options(int argc, char * const *argv); | ||
177 | static int pid_is_user(pid_t pid, uid_t uid); | ||
178 | static int pid_is_cmd(pid_t pid, const char *name); | ||
179 | static void check(pid_t pid); | ||
180 | static void do_pidfile(const char *name); | ||
181 | static void do_stop(int signal_nr, int quietmode, | ||
182 | int *n_killed, int *n_notkilled, int retry_nr); | ||
183 | #if defined(OSLinux) || defined(OShpux) | ||
184 | static int pid_is_exec(pid_t pid, const struct stat *esb); | ||
185 | #endif | ||
186 | |||
187 | #ifdef __GNUC__ | ||
188 | static void fatal(const char *format, ...) | ||
189 | NONRETURNPRINTFFORMAT(1, 2); | ||
190 | static void badusage(const char *msg) | ||
191 | NONRETURNING; | ||
192 | #else | ||
193 | static void fatal(const char *format, ...); | ||
194 | static void badusage(const char *msg); | ||
195 | #endif | ||
196 | |||
197 | /* This next part serves only to construct the TVCALC macro, which | ||
198 | * is used for doing arithmetic on struct timeval's. It works like this: | ||
199 | * TVCALC(result, expression); | ||
200 | * where result is a struct timeval (and must be an lvalue) and | ||
201 | * expression is the single expression for both components. In this | ||
202 | * expression you can use the special values TVELEM, which when fed a | ||
203 | * const struct timeval* gives you the relevant component, and | ||
204 | * TVADJUST. TVADJUST is necessary when subtracting timevals, to make | ||
205 | * it easier to renormalise. Whenver you subtract timeval elements, | ||
206 | * you must make sure that TVADJUST is added to the result of the | ||
207 | * subtraction (before any resulting multiplication or what have you). | ||
208 | * TVELEM must be linear in TVADJUST. | ||
209 | */ | ||
210 | typedef long tvselector(const struct timeval*); | ||
211 | static long tvselector_sec(const struct timeval *tv) { return tv->tv_sec; } | ||
212 | static long tvselector_usec(const struct timeval *tv) { return tv->tv_usec; } | ||
213 | niro | 1268 | #define TVCALC_ELEM(result, expr, sec, adj) \ |
214 | { \ | ||
215 | const long TVADJUST = adj; \ | ||
216 | long (*const TVELEM)(const struct timeval*) = tvselector_##sec; \ | ||
217 | (result).tv_##sec = (expr); \ | ||
218 | niro | 111 | } |
219 | niro | 1268 | #define TVCALC(result,expr) \ |
220 | do { \ | ||
221 | TVCALC_ELEM(result, expr, sec, (-1)); \ | ||
222 | TVCALC_ELEM(result, expr, usec, (+1000000)); \ | ||
223 | (result).tv_sec += (result).tv_usec / 1000000; \ | ||
224 | (result).tv_usec %= 1000000; \ | ||
225 | niro | 111 | } while(0) |
226 | |||
227 | |||
228 | static void | ||
229 | fatal(const char *format, ...) | ||
230 | { | ||
231 | va_list arglist; | ||
232 | |||
233 | fprintf(stderr, "%s: ", progname); | ||
234 | va_start(arglist, format); | ||
235 | vfprintf(stderr, format, arglist); | ||
236 | va_end(arglist); | ||
237 | niro | 1268 | fprintf(stderr, " (%s)\n", strerror (errno)); |
238 | niro | 111 | exit(2); |
239 | } | ||
240 | |||
241 | |||
242 | static void * | ||
243 | xmalloc(int size) | ||
244 | { | ||
245 | void *ptr; | ||
246 | |||
247 | ptr = malloc(size); | ||
248 | if (ptr) | ||
249 | return ptr; | ||
250 | fatal("malloc(%d) failed", size); | ||
251 | } | ||
252 | |||
253 | |||
254 | static void | ||
255 | xgettimeofday(struct timeval *tv) | ||
256 | { | ||
257 | if (gettimeofday(tv,0) != 0) | ||
258 | fatal("gettimeofday failed: %s", strerror(errno)); | ||
259 | } | ||
260 | |||
261 | |||
262 | static void | ||
263 | push(struct pid_list **list, pid_t pid) | ||
264 | { | ||
265 | struct pid_list *p; | ||
266 | |||
267 | p = xmalloc(sizeof(*p)); | ||
268 | p->next = *list; | ||
269 | p->pid = pid; | ||
270 | *list = p; | ||
271 | } | ||
272 | |||
273 | static void | ||
274 | clear(struct pid_list **list) | ||
275 | { | ||
276 | struct pid_list *here, *next; | ||
277 | |||
278 | for (here = *list; here != NULL; here = next) { | ||
279 | next = here->next; | ||
280 | free(here); | ||
281 | } | ||
282 | |||
283 | *list = NULL; | ||
284 | } | ||
285 | |||
286 | static void | ||
287 | do_help(void) | ||
288 | { | ||
289 | printf( | ||
290 | "start-stop-daemon " VERSION " for Debian - small and fast C version written by\n" | ||
291 | "Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, public domain.\n" | ||
292 | "\n" | ||
293 | "Usage:\n" | ||
294 | " start-stop-daemon -S|--start options ... -- arguments ...\n" | ||
295 | " start-stop-daemon -K|--stop options ...\n" | ||
296 | " start-stop-daemon -H|--help\n" | ||
297 | " start-stop-daemon -V|--version\n" | ||
298 | "\n" | ||
299 | "Options (at least one of --exec|--pidfile|--user is required):\n" | ||
300 | " -x|--exec <executable> program to start/check if it is running\n" | ||
301 | " -p|--pidfile <pid-file> pid file to check\n" | ||
302 | " -c|--chuid <name|uid[:group|gid]>\n" | ||
303 | " change to this user/group before starting process\n" | ||
304 | " -u|--user <username>|<uid> stop processes owned by this user\n" | ||
305 | " -g|--group <group|gid> run process as this group\n" | ||
306 | " -n|--name <process-name> stop processes with this name\n" | ||
307 | " -s|--signal <signal> signal to send (default TERM)\n" | ||
308 | " -a|--startas <pathname> program to start (default is <executable>)\n" | ||
309 | " -C|--chdir <directory> Change to <directory>(default is /)\n" | ||
310 | " -N|--nicelevel <incr> add incr to the process's nice level\n" | ||
311 | " -b|--background force the process to detach\n" | ||
312 | " -m|--make-pidfile create the pidfile before starting\n" | ||
313 | " -R|--retry <schedule> check whether processes die, and retry\n" | ||
314 | niro | 1268 | " -e|--env <env-name> set an environment variable (PWD=\"/\")\n" |
315 | " -r|--chroot <path> chroot process to given directory\n" | ||
316 | niro | 111 | " -t|--test test mode, don't do anything\n" |
317 | " -o|--oknodo exit status 0 (not 1) if nothing done\n" | ||
318 | " -q|--quiet be more quiet\n" | ||
319 | " -v|--verbose be more verbose\n" | ||
320 | "Retry <schedule> is <item>|/<item>/... where <item> is one of\n" | ||
321 | " -<signal-num>|[-]<signal-name> send that signal\n" | ||
322 | " <timeout> wait that many seconds\n" | ||
323 | " forever repeat remainder forever\n" | ||
324 | "or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n" | ||
325 | "\n" | ||
326 | "Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n" | ||
327 | " 3 = trouble 2 = with --retry, processes wouldn't die\n"); | ||
328 | } | ||
329 | |||
330 | |||
331 | static void | ||
332 | badusage(const char *msg) | ||
333 | { | ||
334 | if (msg) | ||
335 | fprintf(stderr, "%s: %s\n", progname, msg); | ||
336 | fprintf(stderr, "Try `%s --help' for more information.\n", progname); | ||
337 | exit(3); | ||
338 | } | ||
339 | |||
340 | struct sigpair { | ||
341 | const char *name; | ||
342 | int signal; | ||
343 | }; | ||
344 | |||
345 | const struct sigpair siglist[] = { | ||
346 | { "ABRT", SIGABRT }, | ||
347 | { "ALRM", SIGALRM }, | ||
348 | { "FPE", SIGFPE }, | ||
349 | { "HUP", SIGHUP }, | ||
350 | { "ILL", SIGILL }, | ||
351 | { "INT", SIGINT }, | ||
352 | { "KILL", SIGKILL }, | ||
353 | { "PIPE", SIGPIPE }, | ||
354 | { "QUIT", SIGQUIT }, | ||
355 | { "SEGV", SIGSEGV }, | ||
356 | { "TERM", SIGTERM }, | ||
357 | { "USR1", SIGUSR1 }, | ||
358 | { "USR2", SIGUSR2 }, | ||
359 | { "CHLD", SIGCHLD }, | ||
360 | { "CONT", SIGCONT }, | ||
361 | { "STOP", SIGSTOP }, | ||
362 | { "TSTP", SIGTSTP }, | ||
363 | { "TTIN", SIGTTIN }, | ||
364 | { "TTOU", SIGTTOU } | ||
365 | }; | ||
366 | |||
367 | static int parse_integer(const char *string, int *value_r) { | ||
368 | unsigned long ul; | ||
369 | char *ep; | ||
370 | |||
371 | if (!string[0]) | ||
372 | return -1; | ||
373 | |||
374 | ul= strtoul(string,&ep,10); | ||
375 | if (ul > INT_MAX || *ep != '\0') | ||
376 | return -1; | ||
377 | |||
378 | *value_r= ul; | ||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | static int parse_signal(const char *signal_str, int *signal_nr) | ||
383 | { | ||
384 | unsigned int i; | ||
385 | |||
386 | if (parse_integer(signal_str, signal_nr) == 0) | ||
387 | return 0; | ||
388 | |||
389 | for (i = 0; i < sizeof (siglist) / sizeof (siglist[0]); i++) { | ||
390 | if (strcmp (signal_str, siglist[i].name) == 0) { | ||
391 | *signal_nr = siglist[i].signal; | ||
392 | return 0; | ||
393 | } | ||
394 | } | ||
395 | return -1; | ||
396 | } | ||
397 | |||
398 | static void | ||
399 | parse_schedule_item(const char *string, struct schedule_item *item) { | ||
400 | const char *after_hyph; | ||
401 | |||
402 | if (!strcmp(string,"forever")) { | ||
403 | item->type = sched_forever; | ||
404 | } else if (isdigit(string[0])) { | ||
405 | item->type = sched_timeout; | ||
406 | if (parse_integer(string, &item->value) != 0) | ||
407 | badusage("invalid timeout value in schedule"); | ||
408 | } else if ((after_hyph = string + (string[0] == '-')) && | ||
409 | parse_signal(after_hyph, &item->value) == 0) { | ||
410 | item->type = sched_signal; | ||
411 | } else { | ||
412 | badusage("invalid schedule item (must be [-]<signal-name>, " | ||
413 | "-<signal-number>, <timeout> or `forever'"); | ||
414 | } | ||
415 | } | ||
416 | |||
417 | static void | ||
418 | parse_schedule(const char *schedule_str) { | ||
419 | char item_buf[20]; | ||
420 | const char *slash; | ||
421 | int count, repeatat; | ||
422 | ptrdiff_t str_len; | ||
423 | |||
424 | count = 0; | ||
425 | for (slash = schedule_str; *slash; slash++) | ||
426 | if (*slash == '/') | ||
427 | count++; | ||
428 | |||
429 | schedule_length = (count == 0) ? 4 : count+1; | ||
430 | schedule = xmalloc(sizeof(*schedule) * schedule_length); | ||
431 | |||
432 | if (count == 0) { | ||
433 | schedule[0].type = sched_signal; | ||
434 | schedule[0].value = signal_nr; | ||
435 | parse_schedule_item(schedule_str, &schedule[1]); | ||
436 | if (schedule[1].type != sched_timeout) { | ||
437 | badusage ("--retry takes timeout, or schedule list" | ||
438 | " of at least two items"); | ||
439 | } | ||
440 | schedule[2].type = sched_signal; | ||
441 | schedule[2].value = SIGKILL; | ||
442 | schedule[3]= schedule[1]; | ||
443 | } else { | ||
444 | count = 0; | ||
445 | repeatat = -1; | ||
446 | while (schedule_str != NULL) { | ||
447 | slash = strchr(schedule_str,'/'); | ||
448 | str_len = slash ? slash - schedule_str : strlen(schedule_str); | ||
449 | if (str_len >= (ptrdiff_t)sizeof(item_buf)) | ||
450 | badusage("invalid schedule item: far too long" | ||
451 | " (you must delimit items with slashes)"); | ||
452 | memcpy(item_buf, schedule_str, str_len); | ||
453 | item_buf[str_len] = 0; | ||
454 | schedule_str = slash ? slash+1 : NULL; | ||
455 | |||
456 | parse_schedule_item(item_buf, &schedule[count]); | ||
457 | if (schedule[count].type == sched_forever) { | ||
458 | if (repeatat >= 0) | ||
459 | badusage("invalid schedule: `forever'" | ||
460 | " appears more than once"); | ||
461 | repeatat = count; | ||
462 | continue; | ||
463 | } | ||
464 | count++; | ||
465 | } | ||
466 | if (repeatat >= 0) { | ||
467 | schedule[count].type = sched_goto; | ||
468 | schedule[count].value = repeatat; | ||
469 | count++; | ||
470 | } | ||
471 | assert(count == schedule_length); | ||
472 | } | ||
473 | } | ||
474 | |||
475 | static void | ||
476 | parse_options(int argc, char * const *argv) | ||
477 | { | ||
478 | static struct option longopts[] = { | ||
479 | niro | 1268 | { "help", 0, NULL, 'H'}, |
480 | { "stop", 0, NULL, 'K'}, | ||
481 | { "start", 0, NULL, 'S'}, | ||
482 | { "version", 0, NULL, 'V'}, | ||
483 | { "startas", 1, NULL, 'a'}, | ||
484 | { "env", 1, NULL, 'e'}, | ||
485 | { "name", 1, NULL, 'n'}, | ||
486 | { "oknodo", 0, NULL, 'o'}, | ||
487 | { "pidfile", 1, NULL, 'p'}, | ||
488 | { "quiet", 0, NULL, 'q'}, | ||
489 | { "signal", 1, NULL, 's'}, | ||
490 | { "test", 0, NULL, 't'}, | ||
491 | { "user", 1, NULL, 'u'}, | ||
492 | { "group", 1, NULL, 'g'}, | ||
493 | { "chroot", 1, NULL, 'r'}, | ||
494 | { "verbose", 0, NULL, 'v'}, | ||
495 | { "exec", 1, NULL, 'x'}, | ||
496 | { "chuid", 1, NULL, 'c'}, | ||
497 | { "nicelevel", 1, NULL, 'N'}, | ||
498 | niro | 111 | { "background", 0, NULL, 'b'}, |
499 | { "make-pidfile", 0, NULL, 'm'}, | ||
500 | niro | 1268 | { "retry", 1, NULL, 'R'}, |
501 | niro | 111 | { "chdir", 1, NULL, 'd'}, |
502 | niro | 1268 | { NULL, 0, NULL, 0} |
503 | niro | 111 | }; |
504 | int c; | ||
505 | |||
506 | for (;;) { | ||
507 | niro | 1268 | c = getopt_long(argc, argv, "HKSVa:n:op:qr:e:s:tu:vx:c:N:bmR:g:d:", |
508 | niro | 111 | longopts, (int *) 0); |
509 | if (c == -1) | ||
510 | break; | ||
511 | switch (c) { | ||
512 | case 'H': /* --help */ | ||
513 | do_help(); | ||
514 | exit(0); | ||
515 | case 'K': /* --stop */ | ||
516 | stop = 1; | ||
517 | break; | ||
518 | case 'S': /* --start */ | ||
519 | start = 1; | ||
520 | break; | ||
521 | case 'V': /* --version */ | ||
522 | printf("start-stop-daemon " VERSION "\n"); | ||
523 | exit(0); | ||
524 | case 'a': /* --startas <pathname> */ | ||
525 | startas = optarg; | ||
526 | break; | ||
527 | case 'n': /* --name <process-name> */ | ||
528 | cmdname = optarg; | ||
529 | break; | ||
530 | case 'o': /* --oknodo */ | ||
531 | exitnodo = 0; | ||
532 | break; | ||
533 | case 'p': /* --pidfile <pid-file> */ | ||
534 | pidfile = optarg; | ||
535 | break; | ||
536 | case 'q': /* --quiet */ | ||
537 | quietmode = 1; | ||
538 | break; | ||
539 | case 's': /* --signal <signal> */ | ||
540 | signal_str = optarg; | ||
541 | break; | ||
542 | case 't': /* --test */ | ||
543 | testmode = 1; | ||
544 | break; | ||
545 | case 'u': /* --user <username>|<uid> */ | ||
546 | userspec = optarg; | ||
547 | break; | ||
548 | case 'v': /* --verbose */ | ||
549 | quietmode = -1; | ||
550 | break; | ||
551 | case 'x': /* --exec <executable> */ | ||
552 | execname = optarg; | ||
553 | break; | ||
554 | case 'c': /* --chuid <username>|<uid> */ | ||
555 | /* we copy the string just in case we need the | ||
556 | * argument later. */ | ||
557 | changeuser = strdup(optarg); | ||
558 | changeuser = strtok(changeuser, ":"); | ||
559 | changegroup = strtok(NULL, ":"); | ||
560 | break; | ||
561 | case 'g': /* --group <group>|<gid> */ | ||
562 | changegroup = optarg; | ||
563 | break; | ||
564 | case 'r': /* --chroot /new/root */ | ||
565 | changeroot = optarg; | ||
566 | break; | ||
567 | niro | 1268 | case 'e': /* --env <env-name> */ |
568 | if (env) | ||
569 | badusage("Only one --env option is supported, use /usr/bin/env if you need more"); | ||
570 | env = optarg; | ||
571 | break; | ||
572 | niro | 111 | case 'N': /* --nice */ |
573 | nicelevel = atoi(optarg); | ||
574 | break; | ||
575 | case 'b': /* --background */ | ||
576 | background = 1; | ||
577 | break; | ||
578 | case 'm': /* --make-pidfile */ | ||
579 | mpidfile = 1; | ||
580 | break; | ||
581 | case 'R': /* --retry <schedule>|<timeout> */ | ||
582 | schedule_str = optarg; | ||
583 | break; | ||
584 | case 'd': /* --chdir /new/dir */ | ||
585 | changedir = optarg; | ||
586 | break; | ||
587 | default: | ||
588 | badusage(NULL); /* message printed by getopt */ | ||
589 | } | ||
590 | } | ||
591 | |||
592 | if (signal_str != NULL) { | ||
593 | if (parse_signal (signal_str, &signal_nr) != 0) | ||
594 | badusage("signal value must be numeric or name" | ||
595 | " of signal (KILL, INT, ...)"); | ||
596 | } | ||
597 | |||
598 | if (schedule_str != NULL) { | ||
599 | parse_schedule(schedule_str); | ||
600 | } | ||
601 | |||
602 | if (start == stop) | ||
603 | badusage("need one of --start or --stop"); | ||
604 | |||
605 | if (!execname && !pidfile && !userspec && !cmdname) | ||
606 | badusage("need at least one of --exec, --pidfile, --user or --name"); | ||
607 | |||
608 | if (!startas) | ||
609 | startas = execname; | ||
610 | |||
611 | if (start && !startas) | ||
612 | badusage("--start needs --exec or --startas"); | ||
613 | |||
614 | if (mpidfile && pidfile == NULL) | ||
615 | badusage("--make-pidfile is only relevant with --pidfile"); | ||
616 | |||
617 | if (background && !start) | ||
618 | badusage("--background is only relevant with --start"); | ||
619 | |||
620 | } | ||
621 | |||
622 | #if defined(OSLinux) | ||
623 | static int | ||
624 | pid_is_exec(pid_t pid, const struct stat *esb) | ||
625 | { | ||
626 | struct stat sb; | ||
627 | char buf[32]; | ||
628 | |||
629 | sprintf(buf, "/proc/%d/exe", pid); | ||
630 | if (stat(buf, &sb) != 0) | ||
631 | return 0; | ||
632 | return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); | ||
633 | } | ||
634 | |||
635 | |||
636 | static int | ||
637 | pid_is_user(pid_t pid, uid_t uid) | ||
638 | { | ||
639 | struct stat sb; | ||
640 | char buf[32]; | ||
641 | |||
642 | sprintf(buf, "/proc/%d", pid); | ||
643 | if (stat(buf, &sb) != 0) | ||
644 | return 0; | ||
645 | return (sb.st_uid == uid); | ||
646 | } | ||
647 | |||
648 | |||
649 | static int | ||
650 | pid_is_cmd(pid_t pid, const char *name) | ||
651 | { | ||
652 | char buf[32]; | ||
653 | FILE *f; | ||
654 | int c; | ||
655 | |||
656 | sprintf(buf, "/proc/%d/stat", pid); | ||
657 | f = fopen(buf, "r"); | ||
658 | if (!f) | ||
659 | return 0; | ||
660 | while ((c = getc(f)) != EOF && c != '(') | ||
661 | ; | ||
662 | if (c != '(') { | ||
663 | fclose(f); | ||
664 | return 0; | ||
665 | } | ||
666 | /* this hopefully handles command names containing ')' */ | ||
667 | while ((c = getc(f)) != EOF && c == *name) | ||
668 | name++; | ||
669 | fclose(f); | ||
670 | return (c == ')' && *name == '\0'); | ||
671 | } | ||
672 | #endif /* OSLinux */ | ||
673 | |||
674 | |||
675 | #if defined(OSHURD) | ||
676 | niro | 1268 | static void |
677 | init_procset(void) | ||
678 | { | ||
679 | struct ps_context *context; | ||
680 | error_t err; | ||
681 | |||
682 | err = ps_context_create(getproc(), &context); | ||
683 | if (err) | ||
684 | error(1, err, "ps_context_create"); | ||
685 | |||
686 | err = proc_stat_list_create(context, &procset); | ||
687 | if (err) | ||
688 | error(1, err, "proc_stat_list_create"); | ||
689 | |||
690 | err = proc_stat_list_add_all(procset, 0, 0); | ||
691 | if (err) | ||
692 | error(1, err, "proc_stat_list_add_all"); | ||
693 | } | ||
694 | |||
695 | static struct proc_stat * | ||
696 | get_proc_stat (pid_t pid, ps_flags_t flags) | ||
697 | { | ||
698 | struct proc_stat *ps; | ||
699 | ps_flags_t wanted_flags = PSTAT_PID | flags; | ||
700 | |||
701 | if (!procset) | ||
702 | init_procset(); | ||
703 | |||
704 | ps = proc_stat_list_pid_proc_stat(procset, pid); | ||
705 | if (!ps) | ||
706 | return NULL; | ||
707 | if (proc_stat_set_flags(ps, wanted_flags)) | ||
708 | return NULL; | ||
709 | if ((proc_stat_flags(ps) & wanted_flags) != wanted_flags) | ||
710 | return NULL; | ||
711 | |||
712 | return ps; | ||
713 | } | ||
714 | |||
715 | niro | 111 | static int |
716 | pid_is_user(pid_t pid, uid_t uid) | ||
717 | { | ||
718 | niro | 1268 | struct proc_stat *ps; |
719 | niro | 111 | |
720 | niro | 1268 | ps = get_proc_stat(pid, PSTAT_OWNER_UID); |
721 | return ps && proc_stat_owner_uid(ps) == uid; | ||
722 | niro | 111 | } |
723 | |||
724 | static int | ||
725 | pid_is_cmd(pid_t pid, const char *name) | ||
726 | { | ||
727 | niro | 1268 | struct proc_stat *ps; |
728 | |||
729 | ps = get_proc_stat(pid, PSTAT_ARGS); | ||
730 | return ps && !strcmp(proc_stat_args(ps), name); | ||
731 | niro | 111 | } |
732 | |||
733 | niro | 1268 | static int |
734 | pid_is_running(pid_t pid) | ||
735 | { | ||
736 | return get_proc_stat(pid, 0) != NULL; | ||
737 | } | ||
738 | niro | 111 | |
739 | niro | 1268 | #else /* !OSHURD */ |
740 | |||
741 | niro | 111 | static int |
742 | pid_is_running(pid_t pid) | ||
743 | { | ||
744 | struct stat sb; | ||
745 | char buf[32]; | ||
746 | |||
747 | sprintf(buf, "/proc/%d", pid); | ||
748 | if (stat(buf, &sb) != 0) { | ||
749 | if (errno!=ENOENT) | ||
750 | fatal("Error stating %s: %s", buf, strerror(errno)); | ||
751 | return 0; | ||
752 | } | ||
753 | |||
754 | return 1; | ||
755 | } | ||
756 | |||
757 | niro | 1268 | #endif /* OSHURD */ |
758 | |||
759 | niro | 111 | static void |
760 | check(pid_t pid) | ||
761 | { | ||
762 | #if defined(OSLinux) || defined(OShpux) | ||
763 | if (execname && !pid_is_exec(pid, &exec_stat)) | ||
764 | niro | 1268 | return; |
765 | #elif defined(OSHURD) || defined(OSFreeBSD) || defined(OSNetBSD) || defined(OSDarwin) | ||
766 | /* I will try this to see if it works */ | ||
767 | niro | 111 | if (execname && !pid_is_cmd(pid, execname)) |
768 | niro | 1268 | return; |
769 | niro | 111 | #endif |
770 | if (userspec && !pid_is_user(pid, user_id)) | ||
771 | return; | ||
772 | if (cmdname && !pid_is_cmd(pid, cmdname)) | ||
773 | return; | ||
774 | if (start && !pid_is_running(pid)) | ||
775 | return; | ||
776 | push(&found, pid); | ||
777 | } | ||
778 | |||
779 | static void | ||
780 | do_pidfile(const char *name) | ||
781 | { | ||
782 | FILE *f; | ||
783 | pid_t pid; | ||
784 | |||
785 | f = fopen(name, "r"); | ||
786 | if (f) { | ||
787 | if (fscanf(f, "%d", &pid) == 1) | ||
788 | check(pid); | ||
789 | fclose(f); | ||
790 | } else if (errno != ENOENT) | ||
791 | fatal("open pidfile %s: %s", name, strerror(errno)); | ||
792 | |||
793 | } | ||
794 | |||
795 | /* WTA: this needs to be an autoconf check for /proc/pid existance. | ||
796 | */ | ||
797 | |||
798 | #if defined(OSLinux) || defined (OSsunos) || defined(OSfreebsd) | ||
799 | static void | ||
800 | do_procinit(void) | ||
801 | { | ||
802 | DIR *procdir; | ||
803 | struct dirent *entry; | ||
804 | int foundany; | ||
805 | pid_t pid; | ||
806 | |||
807 | procdir = opendir("/proc"); | ||
808 | if (!procdir) | ||
809 | fatal("opendir /proc: %s", strerror(errno)); | ||
810 | |||
811 | foundany = 0; | ||
812 | while ((entry = readdir(procdir)) != NULL) { | ||
813 | if (sscanf(entry->d_name, "%d", &pid) != 1) | ||
814 | continue; | ||
815 | foundany++; | ||
816 | check(pid); | ||
817 | } | ||
818 | closedir(procdir); | ||
819 | if (!foundany) | ||
820 | fatal("nothing in /proc - not mounted?"); | ||
821 | } | ||
822 | #endif /* OSLinux */ | ||
823 | |||
824 | |||
825 | #if defined(OSHURD) | ||
826 | niro | 1268 | static int |
827 | check_proc_stat (struct proc_stat *ps) | ||
828 | niro | 111 | { |
829 | niro | 1268 | check(ps->pid); |
830 | niro | 111 | return 0; |
831 | } | ||
832 | |||
833 | static void | ||
834 | do_procinit(void) | ||
835 | { | ||
836 | niro | 1268 | if (!procset) |
837 | init_procset(); | ||
838 | niro | 111 | |
839 | niro | 1268 | proc_stat_list_for_each (procset, check_proc_stat); |
840 | niro | 111 | } |
841 | #endif /* OSHURD */ | ||
842 | |||
843 | |||
844 | #if defined(OSOpenBSD) || defined(OSFreeBSD) || defined(OSNetBSD) | ||
845 | niro | 1268 | |
846 | # if defined(OSNetBSD) | ||
847 | # define _KINFO_PROC2 kinfo_proc2 | ||
848 | # define _GET_KINFO_UID(kp) (kp->p_ruid) | ||
849 | # define _GET_KINFO_COMM(kp) (kp->p_comm) | ||
850 | # else | ||
851 | # define _KINFO_PROC2 kinfo_proc | ||
852 | # define _GET_KINFO_UID(kp) (kp->ki_ruid) | ||
853 | # define _GET_KINFO_COMM(kp) (kp->ki_comm) | ||
854 | # endif | ||
855 | |||
856 | niro | 111 | static int |
857 | pid_is_cmd(pid_t pid, const char *name) | ||
858 | { | ||
859 | niro | 1268 | kvm_t *kd; |
860 | int nentries, argv_len=0; | ||
861 | struct kinfo_proc *kp; | ||
862 | char errbuf[_POSIX2_LINE_MAX], buf[_POSIX2_LINE_MAX]; | ||
863 | niro | 111 | char **pid_argv_p; |
864 | char *start_argv_0_p, *end_argv_0_p; | ||
865 | niro | 1268 | |
866 | kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf); | ||
867 | if (kd == 0) | ||
868 | errx(1, "%s", errbuf); | ||
869 | if ((kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &nentries)) == 0) | ||
870 | errx(1, "%s", kvm_geterr(kd)); | ||
871 | niro | 111 | if ((pid_argv_p = kvm_getargv(kd, kp, argv_len)) == 0) |
872 | niro | 1268 | errx(1, "%s", kvm_geterr(kd)); |
873 | niro | 111 | |
874 | start_argv_0_p = *pid_argv_p; | ||
875 | /* find and compare string */ | ||
876 | niro | 1268 | |
877 | niro | 111 | /* find end of argv[0] then copy and cut of str there. */ |
878 | niro | 1268 | if ((end_argv_0_p = strchr(*pid_argv_p, ' ')) == 0 ) |
879 | /* There seems to be no space, so we have the command | ||
880 | * allready in its desired form. */ | ||
881 | start_argv_0_p = *pid_argv_p; | ||
882 | niro | 111 | else { |
883 | niro | 1268 | /* Tests indicate that this never happens, since |
884 | * kvm_getargv itselfe cuts of tailing stuff. This is | ||
885 | * not what the manpage says, however. */ | ||
886 | strncpy(buf, *pid_argv_p, (end_argv_0_p - start_argv_0_p)); | ||
887 | buf[(end_argv_0_p - start_argv_0_p) + 1] = '\0'; | ||
888 | start_argv_0_p = buf; | ||
889 | niro | 111 | } |
890 | niro | 1268 | |
891 | niro | 111 | if (strlen(name) != strlen(start_argv_0_p)) |
892 | niro | 1268 | return 0; |
893 | return (strcmp(name, start_argv_0_p) == 0) ? 1 : 0; | ||
894 | niro | 111 | } |
895 | niro | 1268 | |
896 | niro | 111 | static int |
897 | pid_is_user(pid_t pid, uid_t uid) | ||
898 | { | ||
899 | kvm_t *kd; | ||
900 | int nentries; /* Value not used */ | ||
901 | uid_t proc_uid; | ||
902 | niro | 1268 | struct _KINFO_PROC2 *kp; |
903 | niro | 111 | char errbuf[_POSIX2_LINE_MAX]; |
904 | |||
905 | kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf); | ||
906 | if (kd == 0) | ||
907 | errx(1, "%s", errbuf); | ||
908 | if ((kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &nentries)) == 0) | ||
909 | errx(1, "%s", kvm_geterr(kd)); | ||
910 | niro | 1268 | if (_GET_KINFO_UID(kp)) |
911 | kvm_read(kd, (u_long)&(_GET_KINFO_UID(kp)), | ||
912 | niro | 111 | &proc_uid, sizeof(uid_t)); |
913 | else | ||
914 | return 0; | ||
915 | return (proc_uid == (uid_t)uid); | ||
916 | } | ||
917 | |||
918 | static int | ||
919 | pid_is_exec(pid_t pid, const char *name) | ||
920 | { | ||
921 | kvm_t *kd; | ||
922 | int nentries; | ||
923 | niro | 1268 | struct _KINFO_PROC2 *kp; |
924 | niro | 111 | char errbuf[_POSIX2_LINE_MAX], *pidexec; |
925 | |||
926 | kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf); | ||
927 | if (kd == 0) | ||
928 | errx(1, "%s", errbuf); | ||
929 | if ((kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &nentries)) == 0) | ||
930 | errx(1, "%s", kvm_geterr(kd)); | ||
931 | niro | 1268 | pidexec = _GET_KINFO_COMM(kp); |
932 | niro | 111 | if (strlen(name) != strlen(pidexec)) |
933 | return 0; | ||
934 | return (strcmp(name, pidexec) == 0) ? 1 : 0; | ||
935 | } | ||
936 | |||
937 | |||
938 | static void | ||
939 | do_procinit(void) | ||
940 | { | ||
941 | /* Nothing to do */ | ||
942 | } | ||
943 | |||
944 | #endif /* OSOpenBSD */ | ||
945 | |||
946 | niro | 1268 | #if defined(OSDarwin) |
947 | int | ||
948 | pid_is_user(pid_t pid, uid_t uid) | ||
949 | { | ||
950 | int mib[4]; | ||
951 | size_t size; | ||
952 | struct kinfo_proc ki; | ||
953 | niro | 111 | |
954 | niro | 1268 | size = sizeof(ki); |
955 | mib[0] = CTL_KERN; | ||
956 | mib[1] = KERN_PROC; | ||
957 | mib[2] = KERN_PROC_PID; | ||
958 | mib[3] = pid; | ||
959 | if (sysctl(mib, 4, &ki, &size, NULL, 0) < 0) | ||
960 | errx(1, "%s", "Failure calling sysctl"); | ||
961 | return (uid == ki.kp_eproc.e_pcred.p_ruid); | ||
962 | } | ||
963 | |||
964 | static int | ||
965 | pid_is_cmd(pid_t pid, const char *name) | ||
966 | { | ||
967 | int mib[4]; | ||
968 | size_t size; | ||
969 | struct kinfo_proc ki; | ||
970 | |||
971 | size = sizeof(ki); | ||
972 | mib[0] = CTL_KERN; | ||
973 | mib[1] = KERN_PROC; | ||
974 | mib[2] = KERN_PROC_PID; | ||
975 | mib[3] = pid; | ||
976 | if (sysctl(mib, 4, &ki, &size, NULL, 0) < 0) | ||
977 | errx(1, "%s", "Failure calling sysctl"); | ||
978 | return (!strncmp(name, ki.kp_proc.p_comm, MAXCOMLEN)); | ||
979 | } | ||
980 | |||
981 | static void | ||
982 | do_procinit(void) | ||
983 | { | ||
984 | int mib[3]; | ||
985 | size_t size; | ||
986 | int nprocs, ret, i; | ||
987 | struct kinfo_proc *procs = NULL, *newprocs; | ||
988 | |||
989 | mib[0] = CTL_KERN; | ||
990 | mib[1] = KERN_PROC; | ||
991 | mib[2] = KERN_PROC_ALL; | ||
992 | ret = sysctl(mib, 3, NULL, &size, NULL, 0); | ||
993 | /* Allocate enough memory for entire process table */ | ||
994 | do { | ||
995 | size += size / 10; | ||
996 | newprocs = realloc(procs, size); | ||
997 | if (newprocs == NULL) { | ||
998 | if (procs) | ||
999 | free(procs); | ||
1000 | errx(1, "%s", "Could not reallocate memory"); | ||
1001 | } | ||
1002 | procs = newprocs; | ||
1003 | ret = sysctl(mib, 3, procs, &size, NULL, 0); | ||
1004 | } while (ret >= 0 && errno == ENOMEM); | ||
1005 | |||
1006 | if (ret < 0) | ||
1007 | errx(1, "%s", "Failure calling sysctl"); | ||
1008 | |||
1009 | /* Verify size of proc structure */ | ||
1010 | if (size % sizeof(struct kinfo_proc) != 0) | ||
1011 | errx(1, "%s", "proc size mismatch, userland out of sync with kernel"); | ||
1012 | nprocs = size / sizeof(struct kinfo_proc); | ||
1013 | for (i = 0; i < nprocs; i++) { | ||
1014 | check(procs[i].kp_proc.p_pid); | ||
1015 | } | ||
1016 | } | ||
1017 | #endif /* OSDarwin */ | ||
1018 | |||
1019 | niro | 111 | #if defined(OShpux) |
1020 | static int | ||
1021 | pid_is_user(pid_t pid, uid_t uid) | ||
1022 | { | ||
1023 | struct pst_status pst; | ||
1024 | |||
1025 | if (pstat_getproc(&pst, sizeof(pst), (size_t) 0, (int) pid) < 0) | ||
1026 | return 0; | ||
1027 | return ((uid_t) pst.pst_uid == uid); | ||
1028 | } | ||
1029 | |||
1030 | static int | ||
1031 | pid_is_cmd(pid_t pid, const char *name) | ||
1032 | { | ||
1033 | struct pst_status pst; | ||
1034 | |||
1035 | if (pstat_getproc(&pst, sizeof(pst), (size_t) 0, (int) pid) < 0) | ||
1036 | return 0; | ||
1037 | return (strcmp(pst.pst_ucomm, name) == 0); | ||
1038 | } | ||
1039 | |||
1040 | static int | ||
1041 | pid_is_exec(pid_t pid, const struct stat *esb) | ||
1042 | { | ||
1043 | struct pst_status pst; | ||
1044 | |||
1045 | if (pstat_getproc(&pst, sizeof(pst), (size_t) 0, (int) pid) < 0) | ||
1046 | return 0; | ||
1047 | return ((dev_t) pst.pst_text.psf_fsid.psfs_id == esb->st_dev | ||
1048 | && (ino_t) pst.pst_text.psf_fileid == esb->st_ino); | ||
1049 | } | ||
1050 | |||
1051 | static void | ||
1052 | do_procinit(void) | ||
1053 | { | ||
1054 | struct pst_status pst[10]; | ||
1055 | int i, count; | ||
1056 | int idx = 0; | ||
1057 | |||
1058 | while ((count = pstat_getproc(pst, sizeof(pst[0]), 10, idx)) > 0) { | ||
1059 | niro | 1268 | for (i = 0; i < count; i++) |
1060 | niro | 111 | check(pst[i].pst_pid); |
1061 | niro | 1268 | idx = pst[count - 1].pst_idx + 1; |
1062 | niro | 111 | } |
1063 | } | ||
1064 | #endif /* OShpux */ | ||
1065 | |||
1066 | |||
1067 | static void | ||
1068 | do_findprocs(void) | ||
1069 | { | ||
1070 | clear(&found); | ||
1071 | niro | 1268 | |
1072 | niro | 111 | if (pidfile) |
1073 | do_pidfile(pidfile); | ||
1074 | else | ||
1075 | do_procinit(); | ||
1076 | } | ||
1077 | |||
1078 | /* return 1 on failure */ | ||
1079 | static void | ||
1080 | do_stop(int signal_nr, int quietmode, int *n_killed, int *n_notkilled, int retry_nr) | ||
1081 | { | ||
1082 | struct pid_list *p; | ||
1083 | |||
1084 | niro | 1268 | do_findprocs(); |
1085 | niro | 111 | |
1086 | niro | 1268 | *n_killed = 0; |
1087 | *n_notkilled = 0; | ||
1088 | |||
1089 | if (!found) | ||
1090 | return; | ||
1091 | |||
1092 | clear(&killed); | ||
1093 | |||
1094 | niro | 111 | for (p = found; p; p = p->next) { |
1095 | if (testmode) { | ||
1096 | printf("Would send signal %d to %d.\n", | ||
1097 | signal_nr, p->pid); | ||
1098 | (*n_killed)++; | ||
1099 | } else if (kill(p->pid, signal_nr) == 0) { | ||
1100 | push(&killed, p->pid); | ||
1101 | niro | 1268 | (*n_killed)++; |
1102 | niro | 111 | } else { |
1103 | printf("%s: warning: failed to kill %d: %s\n", | ||
1104 | progname, p->pid, strerror(errno)); | ||
1105 | niro | 1268 | (*n_notkilled)++; |
1106 | niro | 111 | } |
1107 | } | ||
1108 | if (quietmode < 0 && killed) { | ||
1109 | niro | 1268 | printf("Stopped %s (pid", what_stop); |
1110 | niro | 111 | for (p = killed; p; p = p->next) |
1111 | printf(" %d", p->pid); | ||
1112 | niro | 1268 | putchar(')'); |
1113 | if (retry_nr > 0) | ||
1114 | printf(", retry #%d", retry_nr); | ||
1115 | printf(".\n"); | ||
1116 | niro | 111 | } |
1117 | } | ||
1118 | |||
1119 | |||
1120 | static void | ||
1121 | set_what_stop(const char *str) | ||
1122 | { | ||
1123 | strncpy(what_stop, str, sizeof(what_stop)); | ||
1124 | what_stop[sizeof(what_stop)-1] = '\0'; | ||
1125 | } | ||
1126 | |||
1127 | static int | ||
1128 | run_stop_schedule(void) | ||
1129 | { | ||
1130 | int r, position, n_killed, n_notkilled, value, ratio, anykilled, retry_nr; | ||
1131 | struct timeval stopat, before, after, interval, maxinterval; | ||
1132 | |||
1133 | if (testmode) { | ||
1134 | if (schedule != NULL) { | ||
1135 | printf("Ignoring --retry in test mode\n"); | ||
1136 | schedule = NULL; | ||
1137 | } | ||
1138 | } | ||
1139 | |||
1140 | if (cmdname) | ||
1141 | set_what_stop(cmdname); | ||
1142 | else if (execname) | ||
1143 | set_what_stop(execname); | ||
1144 | else if (pidfile) | ||
1145 | sprintf(what_stop, "process in pidfile `%.200s'", pidfile); | ||
1146 | else if (userspec) | ||
1147 | sprintf(what_stop, "process(es) owned by `%.200s'", userspec); | ||
1148 | else | ||
1149 | fatal("internal error, please report"); | ||
1150 | |||
1151 | anykilled = 0; | ||
1152 | retry_nr = 0; | ||
1153 | |||
1154 | if (schedule == NULL) { | ||
1155 | do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0); | ||
1156 | if (n_notkilled > 0 && quietmode <= 0) | ||
1157 | printf("%d pids were not killed\n", n_notkilled); | ||
1158 | if (n_killed) | ||
1159 | anykilled = 1; | ||
1160 | goto x_finished; | ||
1161 | } | ||
1162 | |||
1163 | for (position = 0; position < schedule_length; ) { | ||
1164 | value= schedule[position].value; | ||
1165 | n_notkilled = 0; | ||
1166 | |||
1167 | switch (schedule[position].type) { | ||
1168 | |||
1169 | case sched_goto: | ||
1170 | position = value; | ||
1171 | continue; | ||
1172 | |||
1173 | case sched_signal: | ||
1174 | do_stop(value, quietmode, &n_killed, &n_notkilled, retry_nr++); | ||
1175 | if (!n_killed) | ||
1176 | goto x_finished; | ||
1177 | else | ||
1178 | anykilled = 1; | ||
1179 | goto next_item; | ||
1180 | |||
1181 | case sched_timeout: | ||
1182 | /* We want to keep polling for the processes, to see if they've exited, | ||
1183 | * or until the timeout expires. | ||
1184 | * | ||
1185 | * This is a somewhat complicated algorithm to try to ensure that we | ||
1186 | * notice reasonably quickly when all the processes have exited, but | ||
1187 | * don't spend too much CPU time polling. In particular, on a fast | ||
1188 | * machine with quick-exiting daemons we don't want to delay system | ||
1189 | * shutdown too much, whereas on a slow one, or where processes are | ||
1190 | * taking some time to exit, we want to increase the polling | ||
1191 | * interval. | ||
1192 | * | ||
1193 | * The algorithm is as follows: we measure the elapsed time it takes | ||
1194 | * to do one poll(), and wait a multiple of this time for the next | ||
1195 | * poll. However, if that would put us past the end of the timeout | ||
1196 | * period we wait only as long as the timeout period, but in any case | ||
1197 | * we always wait at least MIN_POLL_INTERVAL (20ms). The multiple | ||
1198 | * (`ratio') starts out as 2, and increases by 1 for each poll to a | ||
1199 | * maximum of 10; so we use up to between 30% and 10% of the | ||
1200 | * machine's resources (assuming a few reasonable things about system | ||
1201 | * performance). | ||
1202 | */ | ||
1203 | xgettimeofday(&stopat); | ||
1204 | stopat.tv_sec += value; | ||
1205 | ratio = 1; | ||
1206 | for (;;) { | ||
1207 | xgettimeofday(&before); | ||
1208 | if (timercmp(&before,&stopat,>)) | ||
1209 | goto next_item; | ||
1210 | |||
1211 | do_stop(0, 1, &n_killed, &n_notkilled, 0); | ||
1212 | if (!n_killed) | ||
1213 | goto x_finished; | ||
1214 | |||
1215 | xgettimeofday(&after); | ||
1216 | |||
1217 | if (!timercmp(&after,&stopat,<)) | ||
1218 | goto next_item; | ||
1219 | |||
1220 | if (ratio < 10) | ||
1221 | ratio++; | ||
1222 | |||
1223 | TVCALC(interval, ratio * (TVELEM(&after) - TVELEM(&before) + TVADJUST)); | ||
1224 | TVCALC(maxinterval, TVELEM(&stopat) - TVELEM(&after) + TVADJUST); | ||
1225 | |||
1226 | if (timercmp(&interval,&maxinterval,>)) | ||
1227 | interval = maxinterval; | ||
1228 | |||
1229 | if (interval.tv_sec == 0 && | ||
1230 | interval.tv_usec <= MIN_POLL_INTERVAL) | ||
1231 | niro | 1268 | interval.tv_usec = MIN_POLL_INTERVAL; |
1232 | niro | 111 | |
1233 | r = select(0,0,0,0,&interval); | ||
1234 | if (r < 0 && errno != EINTR) | ||
1235 | fatal("select() failed for pause: %s", | ||
1236 | strerror(errno)); | ||
1237 | } | ||
1238 | |||
1239 | default: | ||
1240 | assert(!"schedule[].type value must be valid"); | ||
1241 | |||
1242 | } | ||
1243 | |||
1244 | next_item: | ||
1245 | position++; | ||
1246 | } | ||
1247 | |||
1248 | if (quietmode <= 0) | ||
1249 | printf("Program %s, %d process(es), refused to die.\n", | ||
1250 | what_stop, n_killed); | ||
1251 | |||
1252 | return 2; | ||
1253 | |||
1254 | x_finished: | ||
1255 | if (!anykilled) { | ||
1256 | if (quietmode <= 0) | ||
1257 | printf("No %s found running; none killed.\n", what_stop); | ||
1258 | return exitnodo; | ||
1259 | } else { | ||
1260 | return 0; | ||
1261 | } | ||
1262 | } | ||
1263 | |||
1264 | |||
1265 | int | ||
1266 | main(int argc, char **argv) | ||
1267 | { | ||
1268 | int devnull_fd = -1; | ||
1269 | #ifdef HAVE_TIOCNOTTY | ||
1270 | int tty_fd = -1; | ||
1271 | #endif | ||
1272 | progname = argv[0]; | ||
1273 | |||
1274 | parse_options(argc, argv); | ||
1275 | argc -= optind; | ||
1276 | argv += optind; | ||
1277 | |||
1278 | niro | 1268 | if (changeroot == NULL) { |
1279 | if (execname && stat(execname, &exec_stat)) | ||
1280 | fatal("stat %s: %s", execname, strerror(errno)); | ||
1281 | } else { | ||
1282 | if (execname) { | ||
1283 | char *tmp = NULL; | ||
1284 | niro | 111 | |
1285 | niro | 1268 | tmp = malloc(strlen(changeroot) + strlen(execname) + 1); |
1286 | strncpy(tmp, changeroot, strlen(changeroot)); | ||
1287 | strncat(tmp, execname, strlen(execname)); | ||
1288 | |||
1289 | if (stat(tmp, &exec_stat)) { | ||
1290 | fatal("stat %s: %s", tmp, strerror(errno)); | ||
1291 | free(tmp); | ||
1292 | } else { | ||
1293 | free(tmp); | ||
1294 | } | ||
1295 | } | ||
1296 | } | ||
1297 | |||
1298 | niro | 111 | if (userspec && sscanf(userspec, "%d", &user_id) != 1) { |
1299 | struct passwd *pw; | ||
1300 | |||
1301 | pw = getpwnam(userspec); | ||
1302 | if (!pw) | ||
1303 | fatal("user `%s' not found\n", userspec); | ||
1304 | |||
1305 | user_id = pw->pw_uid; | ||
1306 | } | ||
1307 | |||
1308 | if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) { | ||
1309 | struct group *gr = getgrnam(changegroup); | ||
1310 | if (!gr) | ||
1311 | fatal("group `%s' not found\n", changegroup); | ||
1312 | runas_gid = gr->gr_gid; | ||
1313 | } | ||
1314 | if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) { | ||
1315 | struct passwd *pw = getpwnam(changeuser); | ||
1316 | if (!pw) | ||
1317 | fatal("user `%s' not found\n", changeuser); | ||
1318 | runas_uid = pw->pw_uid; | ||
1319 | if (changegroup == NULL) { /* pass the default group of this user */ | ||
1320 | changegroup = ""; /* just empty */ | ||
1321 | runas_gid = pw->pw_gid; | ||
1322 | } | ||
1323 | } | ||
1324 | |||
1325 | if (stop) { | ||
1326 | int i = run_stop_schedule(); | ||
1327 | exit(i); | ||
1328 | } | ||
1329 | |||
1330 | do_findprocs(); | ||
1331 | |||
1332 | if (found) { | ||
1333 | if (quietmode <= 0) | ||
1334 | printf("%s already running.\n", execname ? execname : "process"); | ||
1335 | exit(exitnodo); | ||
1336 | } | ||
1337 | if (testmode) { | ||
1338 | printf("Would start %s ", startas); | ||
1339 | while (argc-- > 0) | ||
1340 | printf("%s ", *argv++); | ||
1341 | if (changeuser != NULL) { | ||
1342 | printf(" (as user %s[%d]", changeuser, runas_uid); | ||
1343 | if (changegroup != NULL) | ||
1344 | printf(", and group %s[%d])", changegroup, runas_gid); | ||
1345 | else | ||
1346 | printf(")"); | ||
1347 | } | ||
1348 | if (changeroot != NULL) | ||
1349 | printf(" in directory %s", changeroot); | ||
1350 | if (nicelevel) | ||
1351 | printf(", and add %i to the priority", nicelevel); | ||
1352 | printf(".\n"); | ||
1353 | exit(0); | ||
1354 | } | ||
1355 | if (quietmode < 0) | ||
1356 | printf("Starting %s...\n", startas); | ||
1357 | *--argv = startas; | ||
1358 | if (background) { /* ok, we need to detach this process */ | ||
1359 | int i; | ||
1360 | if (quietmode < 0) | ||
1361 | niro | 1268 | printf("Detaching to start %s...", startas); |
1362 | niro | 111 | i = fork(); |
1363 | if (i<0) { | ||
1364 | fatal("Unable to fork.\n"); | ||
1365 | } | ||
1366 | if (i) { /* parent */ | ||
1367 | if (quietmode < 0) | ||
1368 | printf("done.\n"); | ||
1369 | exit(0); | ||
1370 | } | ||
1371 | /* child continues here */ | ||
1372 | |||
1373 | #ifdef HAVE_TIOCNOTTY | ||
1374 | tty_fd=open("/dev/tty", O_RDWR); | ||
1375 | #endif | ||
1376 | devnull_fd=open("/dev/null", O_RDWR); | ||
1377 | } | ||
1378 | if (nicelevel) { | ||
1379 | errno=0; | ||
1380 | if ((nice(nicelevel)==-1) && (errno!=0)) | ||
1381 | fatal("Unable to alter nice level by %i: %s", nicelevel, | ||
1382 | strerror(errno)); | ||
1383 | } | ||
1384 | if (changeroot != NULL) { | ||
1385 | if (chdir(changeroot) < 0) | ||
1386 | fatal("Unable to chdir() to %s", changeroot); | ||
1387 | if (chroot(changeroot) < 0) | ||
1388 | fatal("Unable to chroot() to %s", changeroot); | ||
1389 | } | ||
1390 | niro | 1268 | if (chdir(changedir) < 0) |
1391 | niro | 111 | fatal("Unable to chdir() to %s", changedir); |
1392 | niro | 1268 | if (mpidfile && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */ |
1393 | FILE *pidf = fopen(pidfile, "w"); | ||
1394 | pid_t pidt = getpid(); | ||
1395 | if (pidf == NULL) | ||
1396 | fatal("Unable to open pidfile `%s' for writing: %s", pidfile, | ||
1397 | strerror(errno)); | ||
1398 | fprintf(pidf, "%d\n", pidt); | ||
1399 | fclose(pidf); | ||
1400 | } | ||
1401 | niro | 111 | if (changeuser != NULL) { |
1402 | niro | 1268 | if (setgid(runas_gid)) |
1403 | fatal("Unable to set gid to %d", runas_gid); | ||
1404 | niro | 111 | if (initgroups(changeuser, runas_gid)) |
1405 | fatal("Unable to set initgroups() with gid %d", runas_gid); | ||
1406 | if (setuid(runas_uid)) | ||
1407 | fatal("Unable to set uid to %s", changeuser); | ||
1408 | } | ||
1409 | niro | 1268 | if (env != NULL) { |
1410 | if(putenv(env)) | ||
1411 | fatal("Unable to set variable: %s", env); | ||
1412 | } | ||
1413 | niro | 111 | if (background) { /* continue background setup */ |
1414 | int i; | ||
1415 | #ifdef HAVE_TIOCNOTTY | ||
1416 | /* change tty */ | ||
1417 | ioctl(tty_fd, TIOCNOTTY, 0); | ||
1418 | close(tty_fd); | ||
1419 | #endif | ||
1420 | umask(022); /* set a default for dumb programs */ | ||
1421 | dup2(devnull_fd,0); /* stdin */ | ||
1422 | dup2(devnull_fd,1); /* stdout */ | ||
1423 | dup2(devnull_fd,2); /* stderr */ | ||
1424 | #if defined(OShpux) | ||
1425 | /* now close all extra fds */ | ||
1426 | for (i=sysconf(_SC_OPEN_MAX)-1; i>=3; --i) close(i); | ||
1427 | #else | ||
1428 | /* now close all extra fds */ | ||
1429 | for (i=getdtablesize()-1; i>=3; --i) close(i); | ||
1430 | #endif | ||
1431 | |||
1432 | /* create a new session */ | ||
1433 | #ifdef HAVE_SETSID | ||
1434 | setsid(); | ||
1435 | #else | ||
1436 | setpgid(0,0); | ||
1437 | #endif | ||
1438 | } | ||
1439 | execv(startas, argv); | ||
1440 | fatal("Unable to start %s: %s", startas, strerror(errno)); | ||
1441 | } |