Commit | Line | Data |
---|---|---|
487cc3be VM |
1 | /* |
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 as well. | |
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 | */ | |
22 | ||
23 | #include <config.h> | |
24 | #include <compat.h> | |
25 | ||
26 | #include <dpkg/macros.h> | |
27 | ||
28 | #if defined(linux) | |
29 | # define OSLinux | |
30 | #elif defined(__GNU__) | |
31 | # define OSHurd | |
32 | #elif defined(__sun) | |
33 | # define OSsunos | |
34 | #elif defined(OPENBSD) || defined(__OpenBSD__) | |
35 | # define OSOpenBSD | |
36 | #elif defined(hpux) | |
37 | # define OShpux | |
38 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) | |
39 | # define OSFreeBSD | |
40 | #elif defined(__NetBSD__) | |
41 | # define OSNetBSD | |
42 | #elif defined(__DragonFly__) | |
43 | # define OSDragonFlyBSD | |
44 | #else | |
45 | # error Unknown architecture - cannot build start-stop-daemon | |
46 | #endif | |
47 | ||
48 | #ifdef HAVE_SYS_PARAM_H | |
49 | #include <sys/param.h> | |
50 | #endif | |
51 | #ifdef HAVE_SYS_SYSCALL_H | |
52 | #include <sys/syscall.h> | |
53 | #endif | |
54 | #ifdef HAVE_SYS_SYSCTL_H | |
55 | #include <sys/sysctl.h> | |
56 | #endif | |
57 | #ifdef HAVE_SYS_PROC_H | |
58 | #include <sys/proc.h> | |
59 | #endif | |
60 | #ifdef HAVE_SYS_USER_H | |
61 | #include <sys/user.h> | |
62 | #endif | |
63 | #ifdef HAVE_SYS_PSTAT_H | |
64 | #include <sys/pstat.h> | |
65 | #endif | |
66 | #include <sys/types.h> | |
67 | #include <sys/time.h> | |
68 | #include <sys/stat.h> | |
69 | #include <sys/wait.h> | |
70 | #include <sys/select.h> | |
71 | #include <sys/ioctl.h> | |
72 | ||
73 | #include <assert.h> | |
74 | #include <errno.h> | |
75 | #include <limits.h> | |
76 | #include <time.h> | |
77 | #include <fcntl.h> | |
78 | #include <dirent.h> | |
79 | #include <ctype.h> | |
80 | #include <string.h> | |
81 | #include <pwd.h> | |
82 | #include <grp.h> | |
83 | #include <signal.h> | |
84 | #include <termios.h> | |
85 | #include <unistd.h> | |
86 | #ifdef HAVE_STDDEF_H | |
87 | #include <stddef.h> | |
88 | #endif | |
89 | #include <stdbool.h> | |
90 | #include <stdarg.h> | |
91 | #include <stdlib.h> | |
92 | #include <stdio.h> | |
93 | #include <getopt.h> | |
94 | #ifdef HAVE_ERROR_H | |
95 | #include <error.h> | |
96 | #endif | |
97 | #ifdef HAVE_ERR_H | |
98 | #include <err.h> | |
99 | #endif | |
100 | ||
101 | #if defined(OSHurd) | |
102 | #include <hurd.h> | |
103 | #include <ps.h> | |
104 | #endif | |
105 | ||
106 | #ifdef HAVE_KVM_H | |
107 | #include <kvm.h> | |
108 | #if defined(OSFreeBSD) | |
109 | #define KVM_MEMFILE "/dev/null" | |
110 | #else | |
111 | #define KVM_MEMFILE NULL | |
112 | #endif | |
113 | #endif | |
114 | ||
115 | #ifdef _POSIX_PRIORITY_SCHEDULING | |
116 | #include <sched.h> | |
117 | #else | |
118 | #define SCHED_OTHER -1 | |
119 | #define SCHED_FIFO -1 | |
120 | #define SCHED_RR -1 | |
121 | #endif | |
122 | ||
123 | #if defined(OSLinux) | |
124 | /* This comes from TASK_COMM_LEN defined in Linux' include/linux/sched.h. */ | |
125 | #define PROCESS_NAME_SIZE 15 | |
126 | #elif defined(OSsunos) | |
127 | #define PROCESS_NAME_SIZE 15 | |
128 | #elif defined(OSDarwin) | |
129 | #define PROCESS_NAME_SIZE 16 | |
130 | #elif defined(OSNetBSD) | |
131 | #define PROCESS_NAME_SIZE 16 | |
132 | #elif defined(OSOpenBSD) | |
133 | #define PROCESS_NAME_SIZE 16 | |
134 | #elif defined(OSFreeBSD) | |
135 | #define PROCESS_NAME_SIZE 19 | |
136 | #elif defined(OSDragonFlyBSD) | |
137 | /* On DragonFlyBSD MAXCOMLEN expands to 16. */ | |
138 | #define PROCESS_NAME_SIZE MAXCOMLEN | |
139 | #endif | |
140 | ||
141 | #if defined(SYS_ioprio_set) && defined(linux) | |
142 | #define HAVE_IOPRIO_SET | |
143 | #endif | |
144 | ||
145 | #define IOPRIO_CLASS_SHIFT 13 | |
146 | #define IOPRIO_PRIO_VALUE(class, prio) (((class) << IOPRIO_CLASS_SHIFT) | (prio)) | |
147 | #define IO_SCHED_PRIO_MIN 0 | |
148 | #define IO_SCHED_PRIO_MAX 7 | |
149 | ||
150 | enum { | |
151 | IOPRIO_WHO_PROCESS = 1, | |
152 | IOPRIO_WHO_PGRP, | |
153 | IOPRIO_WHO_USER, | |
154 | }; | |
155 | ||
156 | enum { | |
157 | IOPRIO_CLASS_NONE, | |
158 | IOPRIO_CLASS_RT, | |
159 | IOPRIO_CLASS_BE, | |
160 | IOPRIO_CLASS_IDLE, | |
161 | }; | |
162 | ||
163 | enum action_code { | |
164 | ACTION_NONE, | |
165 | ACTION_START, | |
166 | ACTION_STOP, | |
167 | ACTION_STATUS, | |
168 | }; | |
169 | ||
170 | /* Time conversion constants. */ | |
171 | enum { | |
172 | NANOSEC_IN_SEC = 1000000000L, | |
173 | NANOSEC_IN_MILLISEC = 1000000L, | |
174 | NANOSEC_IN_MICROSEC = 1000L, | |
175 | }; | |
176 | ||
177 | /* The minimum polling interval, 20ms. */ | |
178 | static const long MIN_POLL_INTERVAL = 20 * NANOSEC_IN_MILLISEC; | |
179 | ||
180 | static enum action_code action; | |
181 | static bool testmode = false; | |
182 | static int quietmode = 0; | |
183 | static int exitnodo = 1; | |
184 | static bool background = false; | |
185 | static bool close_io = true; | |
186 | static bool mpidfile = false; | |
187 | static bool rpidfile = false; | |
188 | static int signal_nr = SIGTERM; | |
189 | static int user_id = -1; | |
190 | static int runas_uid = -1; | |
191 | static int runas_gid = -1; | |
192 | static const char *userspec = NULL; | |
193 | static char *changeuser = NULL; | |
194 | static const char *changegroup = NULL; | |
195 | static char *changeroot = NULL; | |
196 | static const char *changedir = "/"; | |
197 | static const char *cmdname = NULL; | |
198 | static char *execname = NULL; | |
199 | static char *startas = NULL; | |
200 | static pid_t match_pid = -1; | |
201 | static pid_t match_ppid = -1; | |
202 | static const char *pidfile = NULL; | |
203 | static char what_stop[1024]; | |
204 | static const char *progname = ""; | |
205 | static int nicelevel = 0; | |
206 | static int umask_value = -1; | |
207 | ||
208 | static struct stat exec_stat; | |
209 | #if defined(OSHurd) | |
210 | static struct proc_stat_list *procset = NULL; | |
211 | #endif | |
212 | ||
213 | /* LSB Init Script process status exit codes. */ | |
214 | enum status_code { | |
215 | STATUS_OK = 0, | |
216 | STATUS_DEAD_PIDFILE = 1, | |
217 | STATUS_DEAD_LOCKFILE = 2, | |
218 | STATUS_DEAD = 3, | |
219 | STATUS_UNKNOWN = 4, | |
220 | }; | |
221 | ||
222 | struct pid_list { | |
223 | struct pid_list *next; | |
224 | pid_t pid; | |
225 | }; | |
226 | ||
227 | static struct pid_list *found = NULL; | |
228 | static struct pid_list *killed = NULL; | |
229 | ||
230 | /* Resource scheduling policy. */ | |
231 | struct res_schedule { | |
232 | const char *policy_name; | |
233 | int policy; | |
234 | int priority; | |
235 | }; | |
236 | ||
237 | struct schedule_item { | |
238 | enum { | |
239 | sched_timeout, | |
240 | sched_signal, | |
241 | sched_goto, | |
242 | /* Only seen within parse_schedule and callees. */ | |
243 | sched_forever, | |
244 | } type; | |
245 | /* Seconds, signal no., or index into array. */ | |
246 | int value; | |
247 | }; | |
248 | ||
249 | static struct res_schedule *proc_sched = NULL; | |
250 | static struct res_schedule *io_sched = NULL; | |
251 | ||
252 | static int schedule_length; | |
253 | static struct schedule_item *schedule = NULL; | |
254 | ||
255 | ||
256 | static void DPKG_ATTR_PRINTF(1) | |
257 | warning(const char *format, ...) | |
258 | { | |
259 | va_list arglist; | |
260 | ||
261 | fprintf(stderr, "%s: warning: ", progname); | |
262 | va_start(arglist, format); | |
263 | vfprintf(stderr, format, arglist); | |
264 | va_end(arglist); | |
265 | } | |
266 | ||
267 | static void DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1) | |
268 | fatal(const char *format, ...) | |
269 | { | |
270 | va_list arglist; | |
271 | int errno_fatal = errno; | |
272 | ||
273 | fprintf(stderr, "%s: ", progname); | |
274 | va_start(arglist, format); | |
275 | vfprintf(stderr, format, arglist); | |
276 | va_end(arglist); | |
277 | if (errno_fatal) | |
278 | fprintf(stderr, " (%s)\n", strerror(errno_fatal)); | |
279 | else | |
280 | fprintf(stderr, "\n"); | |
281 | ||
282 | if (action == ACTION_STATUS) | |
283 | exit(STATUS_UNKNOWN); | |
284 | else | |
285 | exit(2); | |
286 | } | |
287 | ||
288 | static void * | |
289 | xmalloc(int size) | |
290 | { | |
291 | void *ptr; | |
292 | ||
293 | ptr = malloc(size); | |
294 | if (ptr) | |
295 | return ptr; | |
296 | fatal("malloc(%d) failed", size); | |
297 | } | |
298 | ||
299 | static char * | |
300 | xstrndup(const char *str, size_t n) | |
301 | { | |
302 | char *new_str; | |
303 | ||
304 | new_str = strndup(str, n); | |
305 | if (new_str) | |
306 | return new_str; | |
307 | fatal("strndup(%s, %zu) failed", str, n); | |
308 | } | |
309 | ||
310 | static void | |
311 | timespec_gettime(struct timespec *ts) | |
312 | { | |
313 | #if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && \ | |
314 | defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0 | |
315 | if (clock_gettime(CLOCK_MONOTONIC, ts) < 0) | |
316 | fatal("clock_gettime failed"); | |
317 | #else | |
318 | struct timeval tv; | |
319 | ||
320 | if (gettimeofday(&tv, NULL) != 0) | |
321 | fatal("gettimeofday failed"); | |
322 | ||
323 | ts->tv_sec = tv.tv_sec; | |
324 | ts->tv_nsec = tv.tv_usec * NANOSEC_IN_MICROSEC; | |
325 | #endif | |
326 | } | |
327 | ||
328 | #define timespec_cmp(a, b, OP) \ | |
329 | (((a)->tv_sec == (b)->tv_sec) ? \ | |
330 | ((a)->tv_nsec OP (b)->tv_nsec) : \ | |
331 | ((a)->tv_sec OP (b)->tv_sec)) | |
332 | ||
333 | static void | |
334 | timespec_sub(struct timespec *a, struct timespec *b, struct timespec *res) | |
335 | { | |
336 | res->tv_sec = a->tv_sec - b->tv_sec; | |
337 | res->tv_nsec = a->tv_nsec - b->tv_nsec; | |
338 | if (res->tv_nsec < 0) { | |
339 | res->tv_sec--; | |
340 | res->tv_nsec += NANOSEC_IN_SEC; | |
341 | } | |
342 | } | |
343 | ||
344 | static void | |
345 | timespec_mul(struct timespec *a, int b) | |
346 | { | |
347 | long nsec = a->tv_nsec * b; | |
348 | ||
349 | a->tv_sec *= b; | |
350 | a->tv_sec += nsec / NANOSEC_IN_SEC; | |
351 | a->tv_nsec = nsec % NANOSEC_IN_SEC; | |
352 | } | |
353 | ||
354 | static char * | |
355 | newpath(const char *dirname, const char *filename) | |
356 | { | |
357 | char *path; | |
358 | size_t path_len; | |
359 | ||
360 | path_len = strlen(dirname) + 1 + strlen(filename) + 1; | |
361 | path = xmalloc(path_len); | |
362 | snprintf(path, path_len, "%s/%s", dirname, filename); | |
363 | ||
364 | return path; | |
365 | } | |
366 | ||
367 | static long | |
368 | get_open_fd_max(void) | |
369 | { | |
370 | #ifdef HAVE_GETDTABLESIZE | |
371 | return getdtablesize(); | |
372 | #else | |
373 | return sysconf(_SC_OPEN_MAX); | |
374 | #endif | |
375 | } | |
376 | ||
377 | #ifndef HAVE_SETSID | |
378 | static void | |
379 | detach_controlling_tty(void) | |
380 | { | |
381 | #ifdef HAVE_TIOCNOTTY | |
382 | int tty_fd; | |
383 | ||
384 | tty_fd = open("/dev/tty", O_RDWR); | |
385 | ||
386 | /* The current process does not have a controlling tty. */ | |
387 | if (tty_fd < 0) | |
388 | return; | |
389 | ||
390 | if (ioctl(tty_fd, TIOCNOTTY, 0) != 0) | |
391 | fatal("unable to detach controlling tty"); | |
392 | ||
393 | close(tty_fd); | |
394 | #endif | |
395 | } | |
396 | ||
397 | static pid_t | |
398 | setsid(void) | |
399 | { | |
400 | if (setpgid(0, 0) < 0) | |
401 | return -1: | |
402 | ||
403 | detach_controlling_tty(); | |
404 | ||
405 | return 0; | |
406 | } | |
407 | #endif | |
408 | ||
409 | static void | |
410 | wait_for_child(pid_t pid) | |
411 | { | |
412 | pid_t child; | |
413 | int status; | |
414 | ||
415 | do { | |
416 | child = waitpid(pid, &status, 0); | |
417 | } while (child == -1 && errno == EINTR); | |
418 | ||
419 | if (child != pid) | |
420 | fatal("error waiting for child"); | |
421 | ||
422 | if (WIFEXITED(status)) { | |
423 | int err = WEXITSTATUS(status); | |
424 | ||
425 | if (err != 0) | |
426 | fatal("child returned error exit status %d", err); | |
427 | } else if (WIFSIGNALED(status)) { | |
428 | int signo = WTERMSIG(status); | |
429 | ||
430 | fatal("child was killed by signal %d", signo); | |
431 | } else { | |
432 | fatal("unexpected status %d waiting for child", status); | |
433 | } | |
434 | } | |
435 | ||
436 | static void | |
437 | write_pidfile(const char *filename, pid_t pid) | |
438 | { | |
439 | FILE *fp; | |
440 | int fd; | |
441 | ||
442 | fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0666); | |
443 | if (fd < 0) | |
444 | fp = NULL; | |
445 | else | |
446 | fp = fdopen(fd, "w"); | |
447 | ||
448 | if (fp == NULL) | |
449 | fatal("unable to open pidfile '%s' for writing", filename); | |
450 | ||
451 | fprintf(fp, "%d\n", pid); | |
452 | ||
453 | if (fclose(fp)) | |
454 | fatal("unable to close pidfile '%s'", filename); | |
455 | } | |
456 | ||
457 | static void | |
458 | remove_pidfile(const char *filename) | |
459 | { | |
460 | if (unlink(filename) < 0 && errno != ENOENT) | |
461 | fatal("cannot remove pidfile '%s'", filename); | |
462 | } | |
463 | ||
464 | static void | |
465 | daemonize(void) | |
466 | { | |
467 | pid_t pid; | |
468 | sigset_t mask; | |
469 | sigset_t oldmask; | |
470 | ||
471 | if (quietmode < 0) | |
472 | printf("Detaching to start %s...", startas); | |
473 | ||
474 | /* Block SIGCHLD to allow waiting for the child process while it is | |
475 | * performing actions, such as creating a pidfile. */ | |
476 | sigemptyset(&mask); | |
477 | sigaddset(&mask, SIGCHLD); | |
478 | if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) | |
479 | fatal("cannot block SIGCHLD"); | |
480 | ||
481 | pid = fork(); | |
482 | if (pid < 0) | |
483 | fatal("unable to do first fork"); | |
484 | else if (pid) { /* First Parent. */ | |
485 | /* Wait for the second parent to exit, so that if we need to | |
486 | * perform any actions there, like creating a pidfile, we do | |
487 | * not suffer from race conditions on return. */ | |
488 | wait_for_child(pid); | |
489 | ||
490 | _exit(0); | |
491 | } | |
492 | ||
493 | /* Create a new session. */ | |
494 | if (setsid() < 0) | |
495 | fatal("cannot set session ID"); | |
496 | ||
497 | pid = fork(); | |
498 | if (pid < 0) | |
499 | fatal("unable to do second fork"); | |
500 | else if (pid) { /* Second parent. */ | |
501 | /* Set a default umask for dumb programs, which might get | |
502 | * overridden by the --umask option later on, so that we get | |
503 | * a defined umask when creating the pidfille. */ | |
504 | umask(022); | |
505 | ||
506 | if (mpidfile && pidfile != NULL) | |
507 | /* User wants _us_ to make the pidfile. */ | |
508 | write_pidfile(pidfile, pid); | |
509 | ||
510 | _exit(0); | |
511 | } | |
512 | ||
513 | if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) | |
514 | fatal("cannot restore signal mask"); | |
515 | ||
516 | if (quietmode < 0) | |
517 | printf("done.\n"); | |
518 | } | |
519 | ||
520 | static void | |
521 | pid_list_push(struct pid_list **list, pid_t pid) | |
522 | { | |
523 | struct pid_list *p; | |
524 | ||
525 | p = xmalloc(sizeof(*p)); | |
526 | p->next = *list; | |
527 | p->pid = pid; | |
528 | *list = p; | |
529 | } | |
530 | ||
531 | static void | |
532 | pid_list_free(struct pid_list **list) | |
533 | { | |
534 | struct pid_list *here, *next; | |
535 | ||
536 | for (here = *list; here != NULL; here = next) { | |
537 | next = here->next; | |
538 | free(here); | |
539 | } | |
540 | ||
541 | *list = NULL; | |
542 | } | |
543 | ||
544 | static void | |
545 | usage(void) | |
546 | { | |
547 | printf( | |
548 | "Usage: start-stop-daemon [<option> ...] <command>\n" | |
549 | "\n" | |
550 | "Commands:\n" | |
551 | " -S|--start -- <argument> ... start a program and pass <arguments> to it\n" | |
552 | " -K|--stop stop a program\n" | |
553 | " -T|--status get the program status\n" | |
554 | " -H|--help print help information\n" | |
555 | " -V|--version print version\n" | |
556 | "\n" | |
557 | "Matching options (at least one is required):\n" | |
558 | " --pid <pid> pid to check\n" | |
559 | " --ppid <ppid> parent pid to check\n" | |
560 | " -p|--pidfile <pid-file> pid file to check\n" | |
561 | " -x|--exec <executable> program to start/check if it is running\n" | |
562 | " -n|--name <process-name> process name to check\n" | |
563 | " -u|--user <username|uid> process owner to check\n" | |
564 | "\n" | |
565 | "Options:\n" | |
566 | " -g|--group <group|gid> run process as this group\n" | |
567 | " -c|--chuid <name|uid[:group|gid]>\n" | |
568 | " change to this user/group before starting\n" | |
569 | " process\n" | |
570 | " -s|--signal <signal> signal to send (default TERM)\n" | |
571 | " -a|--startas <pathname> program to start (default is <executable>)\n" | |
572 | " -r|--chroot <directory> chroot to <directory> before starting\n" | |
573 | " -d|--chdir <directory> change to <directory> (default is /)\n" | |
574 | " -N|--nicelevel <incr> add incr to the process' nice level\n" | |
575 | " -P|--procsched <policy[:prio]>\n" | |
576 | " use <policy> with <prio> for the kernel\n" | |
577 | " process scheduler (default prio is 0)\n" | |
578 | " -I|--iosched <class[:prio]> use <class> with <prio> to set the IO\n" | |
579 | " scheduler (default prio is 4)\n" | |
580 | " -k|--umask <mask> change the umask to <mask> before starting\n" | |
581 | " -b|--background force the process to detach\n" | |
582 | " -C|--no-close do not close any file descriptor\n" | |
583 | " -m|--make-pidfile create the pidfile before starting\n" | |
584 | " |--remove-pidfile delete the pidfile after stopping\n" | |
585 | " -R|--retry <schedule> check whether processes die, and retry\n" | |
586 | " -t|--test test mode, don't do anything\n" | |
587 | " -o|--oknodo exit status 0 (not 1) if nothing done\n" | |
588 | " -q|--quiet be more quiet\n" | |
589 | " -v|--verbose be more verbose\n" | |
590 | "\n" | |
591 | "Retry <schedule> is <item>|/<item>/... where <item> is one of\n" | |
592 | " -<signal-num>|[-]<signal-name> send that signal\n" | |
593 | " <timeout> wait that many seconds\n" | |
594 | " forever repeat remainder forever\n" | |
595 | "or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n" | |
596 | "\n" | |
597 | "The process scheduler <policy> can be one of:\n" | |
598 | " other, fifo or rr\n" | |
599 | "\n" | |
600 | "The IO scheduler <class> can be one of:\n" | |
601 | " real-time, best-effort or idle\n" | |
602 | "\n" | |
603 | "Exit status:\n" | |
604 | " 0 = done\n" | |
605 | " 1 = nothing done (=> 0 if --oknodo)\n" | |
606 | " 2 = with --retry, processes would not die\n" | |
607 | " 3 = trouble\n" | |
608 | "Exit status with --status:\n" | |
609 | " 0 = program is running\n" | |
610 | " 1 = program is not running and the pid file exists\n" | |
611 | " 3 = program is not running\n" | |
612 | " 4 = unable to determine status\n"); | |
613 | } | |
614 | ||
615 | static void | |
616 | do_version(void) | |
617 | { | |
618 | printf("start-stop-daemon %s for Debian\n\n", VERSION); | |
619 | ||
620 | printf("Written by Marek Michalkiewicz, public domain.\n"); | |
621 | } | |
622 | ||
623 | static void DPKG_ATTR_NORET | |
624 | badusage(const char *msg) | |
625 | { | |
626 | if (msg) | |
627 | fprintf(stderr, "%s: %s\n", progname, msg); | |
628 | fprintf(stderr, "Try '%s --help' for more information.\n", progname); | |
629 | ||
630 | if (action == ACTION_STATUS) | |
631 | exit(STATUS_UNKNOWN); | |
632 | else | |
633 | exit(3); | |
634 | } | |
635 | ||
636 | struct sigpair { | |
637 | const char *name; | |
638 | int signal; | |
639 | }; | |
640 | ||
641 | static const struct sigpair siglist[] = { | |
642 | { "ABRT", SIGABRT }, | |
643 | { "ALRM", SIGALRM }, | |
644 | { "FPE", SIGFPE }, | |
645 | { "HUP", SIGHUP }, | |
646 | { "ILL", SIGILL }, | |
647 | { "INT", SIGINT }, | |
648 | { "KILL", SIGKILL }, | |
649 | { "PIPE", SIGPIPE }, | |
650 | { "QUIT", SIGQUIT }, | |
651 | { "SEGV", SIGSEGV }, | |
652 | { "TERM", SIGTERM }, | |
653 | { "USR1", SIGUSR1 }, | |
654 | { "USR2", SIGUSR2 }, | |
655 | { "CHLD", SIGCHLD }, | |
656 | { "CONT", SIGCONT }, | |
657 | { "STOP", SIGSTOP }, | |
658 | { "TSTP", SIGTSTP }, | |
659 | { "TTIN", SIGTTIN }, | |
660 | { "TTOU", SIGTTOU } | |
661 | }; | |
662 | ||
663 | static int | |
664 | parse_unsigned(const char *string, int base, int *value_r) | |
665 | { | |
666 | long value; | |
667 | char *endptr; | |
668 | ||
669 | if (!string[0]) | |
670 | return -1; | |
671 | ||
672 | errno = 0; | |
673 | value = strtol(string, &endptr, base); | |
674 | if (string == endptr || *endptr != '\0' || errno != 0) | |
675 | return -1; | |
676 | if (value < 0 || value > INT_MAX) | |
677 | return -1; | |
678 | ||
679 | *value_r = value; | |
680 | return 0; | |
681 | } | |
682 | ||
683 | static int | |
684 | parse_pid(const char *pid_str, int *pid_num) | |
685 | { | |
686 | if (parse_unsigned(pid_str, 10, pid_num) != 0) | |
687 | return -1; | |
688 | if (*pid_num == 0) | |
689 | return -1; | |
690 | ||
691 | return 0; | |
692 | } | |
693 | ||
694 | static int | |
695 | parse_signal(const char *sig_str, int *sig_num) | |
696 | { | |
697 | unsigned int i; | |
698 | ||
699 | if (parse_unsigned(sig_str, 10, sig_num) == 0) | |
700 | return 0; | |
701 | ||
702 | for (i = 0; i < array_count(siglist); i++) { | |
703 | if (strcmp(sig_str, siglist[i].name) == 0) { | |
704 | *sig_num = siglist[i].signal; | |
705 | return 0; | |
706 | } | |
707 | } | |
708 | return -1; | |
709 | } | |
710 | ||
711 | static int | |
712 | parse_umask(const char *string, int *value_r) | |
713 | { | |
714 | return parse_unsigned(string, 0, value_r); | |
715 | } | |
716 | ||
717 | static void | |
718 | validate_proc_schedule(void) | |
719 | { | |
720 | #ifdef _POSIX_PRIORITY_SCHEDULING | |
721 | int prio_min, prio_max; | |
722 | ||
723 | prio_min = sched_get_priority_min(proc_sched->policy); | |
724 | prio_max = sched_get_priority_max(proc_sched->policy); | |
725 | ||
726 | if (proc_sched->priority < prio_min) | |
727 | badusage("process scheduler priority less than min"); | |
728 | if (proc_sched->priority > prio_max) | |
729 | badusage("process scheduler priority greater than max"); | |
730 | #endif | |
731 | } | |
732 | ||
733 | static void | |
734 | parse_proc_schedule(const char *string) | |
735 | { | |
736 | char *policy_str; | |
737 | size_t policy_len; | |
738 | int prio = 0; | |
739 | ||
740 | policy_len = strcspn(string, ":"); | |
741 | policy_str = xstrndup(string, policy_len); | |
742 | ||
743 | if (string[policy_len] == ':' && | |
744 | parse_unsigned(string + policy_len + 1, 10, &prio) != 0) | |
745 | fatal("invalid process scheduler priority"); | |
746 | ||
747 | proc_sched = xmalloc(sizeof(*proc_sched)); | |
748 | proc_sched->policy_name = policy_str; | |
749 | ||
750 | if (strcmp(policy_str, "other") == 0) { | |
751 | proc_sched->policy = SCHED_OTHER; | |
752 | proc_sched->priority = 0; | |
753 | } else if (strcmp(policy_str, "fifo") == 0) { | |
754 | proc_sched->policy = SCHED_FIFO; | |
755 | proc_sched->priority = prio; | |
756 | } else if (strcmp(policy_str, "rr") == 0) { | |
757 | proc_sched->policy = SCHED_RR; | |
758 | proc_sched->priority = prio; | |
759 | } else | |
760 | badusage("invalid process scheduler policy"); | |
761 | ||
762 | validate_proc_schedule(); | |
763 | } | |
764 | ||
765 | static void | |
766 | parse_io_schedule(const char *string) | |
767 | { | |
768 | char *class_str; | |
769 | size_t class_len; | |
770 | int prio = 4; | |
771 | ||
772 | class_len = strcspn(string, ":"); | |
773 | class_str = xstrndup(string, class_len); | |
774 | ||
775 | if (string[class_len] == ':' && | |
776 | parse_unsigned(string + class_len + 1, 10, &prio) != 0) | |
777 | fatal("invalid IO scheduler priority"); | |
778 | ||
779 | io_sched = xmalloc(sizeof(*io_sched)); | |
780 | io_sched->policy_name = class_str; | |
781 | ||
782 | if (strcmp(class_str, "real-time") == 0) { | |
783 | io_sched->policy = IOPRIO_CLASS_RT; | |
784 | io_sched->priority = prio; | |
785 | } else if (strcmp(class_str, "best-effort") == 0) { | |
786 | io_sched->policy = IOPRIO_CLASS_BE; | |
787 | io_sched->priority = prio; | |
788 | } else if (strcmp(class_str, "idle") == 0) { | |
789 | io_sched->policy = IOPRIO_CLASS_IDLE; | |
790 | io_sched->priority = 7; | |
791 | } else | |
792 | badusage("invalid IO scheduler policy"); | |
793 | ||
794 | if (io_sched->priority < IO_SCHED_PRIO_MIN) | |
795 | badusage("IO scheduler priority less than min"); | |
796 | if (io_sched->priority > IO_SCHED_PRIO_MAX) | |
797 | badusage("IO scheduler priority greater than max"); | |
798 | } | |
799 | ||
800 | static void | |
801 | set_proc_schedule(struct res_schedule *sched) | |
802 | { | |
803 | #ifdef _POSIX_PRIORITY_SCHEDULING | |
804 | struct sched_param param; | |
805 | ||
806 | param.sched_priority = sched->priority; | |
807 | ||
808 | if (sched_setscheduler(getpid(), sched->policy, ¶m) == -1) | |
809 | fatal("unable to set process scheduler"); | |
810 | #endif | |
811 | } | |
812 | ||
813 | #ifdef HAVE_IOPRIO_SET | |
814 | static inline int | |
815 | ioprio_set(int which, int who, int ioprio) | |
816 | { | |
817 | return syscall(SYS_ioprio_set, which, who, ioprio); | |
818 | } | |
819 | #endif | |
820 | ||
821 | static void | |
822 | set_io_schedule(struct res_schedule *sched) | |
823 | { | |
824 | #ifdef HAVE_IOPRIO_SET | |
825 | int io_sched_mask; | |
826 | ||
827 | io_sched_mask = IOPRIO_PRIO_VALUE(sched->policy, sched->priority); | |
828 | if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), io_sched_mask) == -1) | |
829 | warning("unable to alter IO priority to mask %i (%s)\n", | |
830 | io_sched_mask, strerror(errno)); | |
831 | #endif | |
832 | } | |
833 | ||
834 | static void | |
835 | parse_schedule_item(const char *string, struct schedule_item *item) | |
836 | { | |
837 | const char *after_hyph; | |
838 | ||
839 | if (strcmp(string, "forever") == 0) { | |
840 | item->type = sched_forever; | |
841 | } else if (isdigit(string[0])) { | |
842 | item->type = sched_timeout; | |
843 | if (parse_unsigned(string, 10, &item->value) != 0) | |
844 | badusage("invalid timeout value in schedule"); | |
845 | } else if ((after_hyph = string + (string[0] == '-')) && | |
846 | parse_signal(after_hyph, &item->value) == 0) { | |
847 | item->type = sched_signal; | |
848 | } else { | |
849 | badusage("invalid schedule item (must be [-]<signal-name>, " | |
850 | "-<signal-number>, <timeout> or 'forever'"); | |
851 | } | |
852 | } | |
853 | ||
854 | static void | |
855 | parse_schedule(const char *schedule_str) | |
856 | { | |
857 | char item_buf[20]; | |
858 | const char *slash; | |
859 | int count, repeatat; | |
860 | size_t str_len; | |
861 | ||
862 | count = 0; | |
863 | for (slash = schedule_str; *slash; slash++) | |
864 | if (*slash == '/') | |
865 | count++; | |
866 | ||
867 | schedule_length = (count == 0) ? 4 : count + 1; | |
868 | schedule = xmalloc(sizeof(*schedule) * schedule_length); | |
869 | ||
870 | if (count == 0) { | |
871 | schedule[0].type = sched_signal; | |
872 | schedule[0].value = signal_nr; | |
873 | parse_schedule_item(schedule_str, &schedule[1]); | |
874 | if (schedule[1].type != sched_timeout) { | |
875 | badusage("--retry takes timeout, or schedule list" | |
876 | " of at least two items"); | |
877 | } | |
878 | schedule[2].type = sched_signal; | |
879 | schedule[2].value = SIGKILL; | |
880 | schedule[3] = schedule[1]; | |
881 | } else { | |
882 | count = 0; | |
883 | repeatat = -1; | |
884 | while (schedule_str != NULL) { | |
885 | slash = strchr(schedule_str, '/'); | |
886 | str_len = slash ? (size_t)(slash - schedule_str) : strlen(schedule_str); | |
887 | if (str_len >= sizeof(item_buf)) | |
888 | badusage("invalid schedule item: far too long" | |
889 | " (you must delimit items with slashes)"); | |
890 | memcpy(item_buf, schedule_str, str_len); | |
891 | item_buf[str_len] = '\0'; | |
892 | schedule_str = slash ? slash + 1 : NULL; | |
893 | ||
894 | parse_schedule_item(item_buf, &schedule[count]); | |
895 | if (schedule[count].type == sched_forever) { | |
896 | if (repeatat >= 0) | |
897 | badusage("invalid schedule: 'forever'" | |
898 | " appears more than once"); | |
899 | repeatat = count; | |
900 | continue; | |
901 | } | |
902 | count++; | |
903 | } | |
904 | if (repeatat == count) | |
905 | badusage("invalid schedule: 'forever' appears last, " | |
906 | "nothing to repeat"); | |
907 | if (repeatat >= 0) { | |
908 | schedule[count].type = sched_goto; | |
909 | schedule[count].value = repeatat; | |
910 | count++; | |
911 | } | |
912 | assert(count == schedule_length); | |
913 | } | |
914 | } | |
915 | ||
916 | static void | |
917 | set_action(enum action_code new_action) | |
918 | { | |
919 | if (action == new_action) | |
920 | return; | |
921 | ||
922 | if (action != ACTION_NONE) | |
923 | badusage("only one command can be specified"); | |
924 | ||
925 | action = new_action; | |
926 | } | |
927 | ||
928 | #define OPT_PID 500 | |
929 | #define OPT_PPID 501 | |
930 | #define OPT_RM_PIDFILE 502 | |
931 | ||
932 | static void | |
933 | parse_options(int argc, char * const *argv) | |
934 | { | |
935 | static struct option longopts[] = { | |
936 | { "help", 0, NULL, 'H'}, | |
937 | { "stop", 0, NULL, 'K'}, | |
938 | { "start", 0, NULL, 'S'}, | |
939 | { "status", 0, NULL, 'T'}, | |
940 | { "version", 0, NULL, 'V'}, | |
941 | { "startas", 1, NULL, 'a'}, | |
942 | { "name", 1, NULL, 'n'}, | |
943 | { "oknodo", 0, NULL, 'o'}, | |
944 | { "pid", 1, NULL, OPT_PID}, | |
945 | { "ppid", 1, NULL, OPT_PPID}, | |
946 | { "pidfile", 1, NULL, 'p'}, | |
947 | { "quiet", 0, NULL, 'q'}, | |
948 | { "signal", 1, NULL, 's'}, | |
949 | { "test", 0, NULL, 't'}, | |
950 | { "user", 1, NULL, 'u'}, | |
951 | { "group", 1, NULL, 'g'}, | |
952 | { "chroot", 1, NULL, 'r'}, | |
953 | { "verbose", 0, NULL, 'v'}, | |
954 | { "exec", 1, NULL, 'x'}, | |
955 | { "chuid", 1, NULL, 'c'}, | |
956 | { "nicelevel", 1, NULL, 'N'}, | |
957 | { "procsched", 1, NULL, 'P'}, | |
958 | { "iosched", 1, NULL, 'I'}, | |
959 | { "umask", 1, NULL, 'k'}, | |
960 | { "background", 0, NULL, 'b'}, | |
961 | { "no-close", 0, NULL, 'C'}, | |
962 | { "make-pidfile", 0, NULL, 'm'}, | |
963 | { "remove-pidfile", 0, NULL, OPT_RM_PIDFILE}, | |
964 | { "retry", 1, NULL, 'R'}, | |
965 | { "chdir", 1, NULL, 'd'}, | |
966 | { NULL, 0, NULL, 0 } | |
967 | }; | |
968 | const char *pid_str = NULL; | |
969 | const char *ppid_str = NULL; | |
970 | const char *umask_str = NULL; | |
971 | const char *signal_str = NULL; | |
972 | const char *schedule_str = NULL; | |
973 | const char *proc_schedule_str = NULL; | |
974 | const char *io_schedule_str = NULL; | |
975 | size_t changeuser_len; | |
976 | int c; | |
977 | ||
978 | for (;;) { | |
979 | c = getopt_long(argc, argv, | |
980 | "HKSVTa:n:op:qr:s:tu:vx:c:N:P:I:k:bCmR:g:d:", | |
981 | longopts, NULL); | |
982 | if (c == -1) | |
983 | break; | |
984 | switch (c) { | |
985 | case 'H': /* --help */ | |
986 | usage(); | |
987 | exit(0); | |
988 | case 'K': /* --stop */ | |
989 | set_action(ACTION_STOP); | |
990 | break; | |
991 | case 'S': /* --start */ | |
992 | set_action(ACTION_START); | |
993 | break; | |
994 | case 'T': /* --status */ | |
995 | set_action(ACTION_STATUS); | |
996 | break; | |
997 | case 'V': /* --version */ | |
998 | do_version(); | |
999 | exit(0); | |
1000 | case 'a': /* --startas <pathname> */ | |
1001 | startas = optarg; | |
1002 | break; | |
1003 | case 'n': /* --name <process-name> */ | |
1004 | cmdname = optarg; | |
1005 | break; | |
1006 | case 'o': /* --oknodo */ | |
1007 | exitnodo = 0; | |
1008 | break; | |
1009 | case OPT_PID: /* --pid <pid> */ | |
1010 | pid_str = optarg; | |
1011 | break; | |
1012 | case OPT_PPID: /* --ppid <ppid> */ | |
1013 | ppid_str = optarg; | |
1014 | break; | |
1015 | case 'p': /* --pidfile <pid-file> */ | |
1016 | pidfile = optarg; | |
1017 | break; | |
1018 | case 'q': /* --quiet */ | |
1019 | quietmode = true; | |
1020 | break; | |
1021 | case 's': /* --signal <signal> */ | |
1022 | signal_str = optarg; | |
1023 | break; | |
1024 | case 't': /* --test */ | |
1025 | testmode = true; | |
1026 | break; | |
1027 | case 'u': /* --user <username>|<uid> */ | |
1028 | userspec = optarg; | |
1029 | break; | |
1030 | case 'v': /* --verbose */ | |
1031 | quietmode = -1; | |
1032 | break; | |
1033 | case 'x': /* --exec <executable> */ | |
1034 | execname = optarg; | |
1035 | break; | |
1036 | case 'c': /* --chuid <username>|<uid> */ | |
1037 | /* We copy the string just in case we need the | |
1038 | * argument later. */ | |
1039 | changeuser_len = strcspn(optarg, ":"); | |
1040 | changeuser = xstrndup(optarg, changeuser_len); | |
1041 | if (optarg[changeuser_len] == ':') { | |
1042 | if (optarg[changeuser_len + 1] == '\0') | |
1043 | fatal("missing group name"); | |
1044 | changegroup = optarg + changeuser_len + 1; | |
1045 | } | |
1046 | break; | |
1047 | case 'g': /* --group <group>|<gid> */ | |
1048 | changegroup = optarg; | |
1049 | break; | |
1050 | case 'r': /* --chroot /new/root */ | |
1051 | changeroot = optarg; | |
1052 | break; | |
1053 | case 'N': /* --nice */ | |
1054 | nicelevel = atoi(optarg); | |
1055 | break; | |
1056 | case 'P': /* --procsched */ | |
1057 | proc_schedule_str = optarg; | |
1058 | break; | |
1059 | case 'I': /* --iosched */ | |
1060 | io_schedule_str = optarg; | |
1061 | break; | |
1062 | case 'k': /* --umask <mask> */ | |
1063 | umask_str = optarg; | |
1064 | break; | |
1065 | case 'b': /* --background */ | |
1066 | background = true; | |
1067 | break; | |
1068 | case 'C': /* --no-close */ | |
1069 | close_io = false; | |
1070 | break; | |
1071 | case 'm': /* --make-pidfile */ | |
1072 | mpidfile = true; | |
1073 | break; | |
1074 | case OPT_RM_PIDFILE: /* --remove-pidfile */ | |
1075 | rpidfile = true; | |
1076 | break; | |
1077 | case 'R': /* --retry <schedule>|<timeout> */ | |
1078 | schedule_str = optarg; | |
1079 | break; | |
1080 | case 'd': /* --chdir /new/dir */ | |
1081 | changedir = optarg; | |
1082 | break; | |
1083 | default: | |
1084 | /* Message printed by getopt. */ | |
1085 | badusage(NULL); | |
1086 | } | |
1087 | } | |
1088 | ||
1089 | if (pid_str != NULL) { | |
1090 | if (parse_pid(pid_str, &match_pid) != 0) | |
1091 | badusage("pid value must be a number greater than 0"); | |
1092 | } | |
1093 | ||
1094 | if (ppid_str != NULL) { | |
1095 | if (parse_pid(ppid_str, &match_ppid) != 0) | |
1096 | badusage("ppid value must be a number greater than 0"); | |
1097 | } | |
1098 | ||
1099 | if (signal_str != NULL) { | |
1100 | if (parse_signal(signal_str, &signal_nr) != 0) | |
1101 | badusage("signal value must be numeric or name" | |
1102 | " of signal (KILL, INT, ...)"); | |
1103 | } | |
1104 | ||
1105 | if (schedule_str != NULL) { | |
1106 | parse_schedule(schedule_str); | |
1107 | } | |
1108 | ||
1109 | if (proc_schedule_str != NULL) | |
1110 | parse_proc_schedule(proc_schedule_str); | |
1111 | ||
1112 | if (io_schedule_str != NULL) | |
1113 | parse_io_schedule(io_schedule_str); | |
1114 | ||
1115 | if (umask_str != NULL) { | |
1116 | if (parse_umask(umask_str, &umask_value) != 0) | |
1117 | badusage("umask value must be a positive number"); | |
1118 | } | |
1119 | ||
1120 | if (action == ACTION_NONE) | |
1121 | badusage("need one of --start or --stop or --status"); | |
1122 | ||
1123 | if (!execname && !pid_str && !ppid_str && !pidfile && !userspec && | |
1124 | !cmdname) | |
1125 | badusage("need at least one of --exec, --pid, --ppid, --pidfile, --user or --name"); | |
1126 | ||
1127 | #ifdef PROCESS_NAME_SIZE | |
1128 | if (cmdname && strlen(cmdname) > PROCESS_NAME_SIZE) | |
1129 | warning("this system is not able to track process names\n" | |
1130 | "longer than %d characters, please use --exec " | |
1131 | "instead of --name.\n", PROCESS_NAME_SIZE); | |
1132 | #endif | |
1133 | ||
1134 | if (!startas) | |
1135 | startas = execname; | |
1136 | ||
1137 | if (action == ACTION_START && !startas) | |
1138 | badusage("--start needs --exec or --startas"); | |
1139 | ||
1140 | if (mpidfile && pidfile == NULL) | |
1141 | badusage("--make-pidfile requires --pidfile"); | |
1142 | if (rpidfile && pidfile == NULL) | |
1143 | badusage("--remove-pidfile requires --pidfile"); | |
1144 | ||
1145 | if (pid_str && pidfile) | |
1146 | badusage("need either --pid of --pidfile, not both"); | |
1147 | ||
1148 | if (background && action != ACTION_START) | |
1149 | badusage("--background is only relevant with --start"); | |
1150 | ||
1151 | if (!close_io && !background) | |
1152 | badusage("--no-close is only relevant with --background"); | |
1153 | } | |
1154 | ||
1155 | static void | |
1156 | setup_options(void) | |
1157 | { | |
1158 | if (execname) { | |
1159 | char *fullexecname; | |
1160 | ||
1161 | /* If it's a relative path, normalize it. */ | |
1162 | if (execname[0] != '/') | |
1163 | execname = newpath(changedir, execname); | |
1164 | ||
1165 | if (changeroot) | |
1166 | fullexecname = newpath(changeroot, execname); | |
1167 | else | |
1168 | fullexecname = execname; | |
1169 | ||
1170 | if (stat(fullexecname, &exec_stat)) | |
1171 | fatal("unable to stat %s", fullexecname); | |
1172 | ||
1173 | if (fullexecname != execname) | |
1174 | free(fullexecname); | |
1175 | } | |
1176 | ||
1177 | if (userspec && sscanf(userspec, "%d", &user_id) != 1) { | |
1178 | struct passwd *pw; | |
1179 | ||
1180 | pw = getpwnam(userspec); | |
1181 | if (!pw) | |
1182 | fatal("user '%s' not found", userspec); | |
1183 | ||
1184 | user_id = pw->pw_uid; | |
1185 | } | |
1186 | ||
1187 | if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) { | |
1188 | struct group *gr; | |
1189 | ||
1190 | gr = getgrnam(changegroup); | |
1191 | if (!gr) | |
1192 | fatal("group '%s' not found", changegroup); | |
1193 | changegroup = gr->gr_name; | |
1194 | runas_gid = gr->gr_gid; | |
1195 | } | |
1196 | if (changeuser) { | |
1197 | struct passwd *pw; | |
1198 | struct stat st; | |
1199 | ||
1200 | if (sscanf(changeuser, "%d", &runas_uid) == 1) | |
1201 | pw = getpwuid(runas_uid); | |
1202 | else | |
1203 | pw = getpwnam(changeuser); | |
1204 | if (!pw) | |
1205 | fatal("user '%s' not found", changeuser); | |
1206 | changeuser = pw->pw_name; | |
1207 | runas_uid = pw->pw_uid; | |
1208 | if (changegroup == NULL) { | |
1209 | /* Pass the default group of this user. */ | |
1210 | changegroup = ""; /* Just empty. */ | |
1211 | runas_gid = pw->pw_gid; | |
1212 | } | |
1213 | if (stat(pw->pw_dir, &st) == 0) | |
1214 | setenv("HOME", pw->pw_dir, 1); | |
1215 | } | |
1216 | } | |
1217 | ||
1218 | #if defined(OSLinux) | |
1219 | static const char * | |
1220 | proc_status_field(pid_t pid, const char *field) | |
1221 | { | |
1222 | static char *line = NULL; | |
1223 | static size_t line_size = 0; | |
1224 | ||
1225 | FILE *fp; | |
1226 | char filename[32]; | |
1227 | char *value = NULL; | |
1228 | ssize_t line_len; | |
1229 | size_t field_len = strlen(field); | |
1230 | ||
1231 | sprintf(filename, "/proc/%d/status", pid); | |
1232 | fp = fopen(filename, "r"); | |
1233 | if (!fp) | |
1234 | return NULL; | |
1235 | while ((line_len = getline(&line, &line_size, fp)) >= 0) { | |
1236 | if (strncasecmp(line, field, field_len) == 0) { | |
1237 | line[line_len - 1] = '\0'; | |
1238 | ||
1239 | value = line + field_len; | |
1240 | while (isspace(*value)) | |
1241 | value++; | |
1242 | ||
1243 | break; | |
1244 | } | |
1245 | } | |
1246 | fclose(fp); | |
1247 | ||
1248 | return value; | |
1249 | } | |
1250 | #endif | |
1251 | ||
1252 | #if defined(OSHurd) | |
1253 | static void | |
1254 | init_procset(void) | |
1255 | { | |
1256 | struct ps_context *context; | |
1257 | error_t err; | |
1258 | ||
1259 | err = ps_context_create(getproc(), &context); | |
1260 | if (err) | |
1261 | error(1, err, "ps_context_create"); | |
1262 | ||
1263 | err = proc_stat_list_create(context, &procset); | |
1264 | if (err) | |
1265 | error(1, err, "proc_stat_list_create"); | |
1266 | ||
1267 | err = proc_stat_list_add_all(procset, 0, 0); | |
1268 | if (err) | |
1269 | error(1, err, "proc_stat_list_add_all"); | |
1270 | } | |
1271 | ||
1272 | static struct proc_stat * | |
1273 | get_proc_stat(pid_t pid, ps_flags_t flags) | |
1274 | { | |
1275 | struct proc_stat *ps; | |
1276 | ps_flags_t wanted_flags = PSTAT_PID | flags; | |
1277 | ||
1278 | if (!procset) | |
1279 | init_procset(); | |
1280 | ||
1281 | ps = proc_stat_list_pid_proc_stat(procset, pid); | |
1282 | if (!ps) | |
1283 | return NULL; | |
1284 | if (proc_stat_set_flags(ps, wanted_flags)) | |
1285 | return NULL; | |
1286 | if ((proc_stat_flags(ps) & wanted_flags) != wanted_flags) | |
1287 | return NULL; | |
1288 | ||
1289 | return ps; | |
1290 | } | |
1291 | #elif defined(HAVE_KVM_H) | |
1292 | static kvm_t * | |
1293 | ssd_kvm_open(void) | |
1294 | { | |
1295 | kvm_t *kd; | |
1296 | char errbuf[_POSIX2_LINE_MAX]; | |
1297 | ||
1298 | kd = kvm_openfiles(NULL, KVM_MEMFILE, NULL, O_RDONLY, errbuf); | |
1299 | if (kd == NULL) | |
1300 | errx(1, "%s", errbuf); | |
1301 | ||
1302 | return kd; | |
1303 | } | |
1304 | ||
1305 | static struct kinfo_proc * | |
1306 | ssd_kvm_get_procs(kvm_t *kd, int op, int arg, int *count) | |
1307 | { | |
1308 | struct kinfo_proc *kp; | |
1309 | int lcount; | |
1310 | ||
1311 | if (count == NULL) | |
1312 | count = &lcount; | |
1313 | *count = 0; | |
1314 | ||
1315 | kp = kvm_getprocs(kd, op, arg, count); | |
1316 | if (kp == NULL && errno != ESRCH) | |
1317 | errx(1, "%s", kvm_geterr(kd)); | |
1318 | ||
1319 | return kp; | |
1320 | } | |
1321 | #endif | |
1322 | ||
1323 | #if defined(OSLinux) | |
1324 | static bool | |
1325 | pid_is_exec(pid_t pid, const struct stat *esb) | |
1326 | { | |
1327 | char lname[32]; | |
1328 | char lcontents[_POSIX_PATH_MAX + 1]; | |
1329 | char *filename; | |
1330 | const char deleted[] = " (deleted)"; | |
1331 | int nread; | |
1332 | struct stat sb; | |
1333 | ||
1334 | sprintf(lname, "/proc/%d/exe", pid); | |
1335 | nread = readlink(lname, lcontents, sizeof(lcontents) - 1); | |
1336 | if (nread == -1) | |
1337 | return false; | |
1338 | ||
1339 | filename = lcontents; | |
1340 | filename[nread] = '\0'; | |
1341 | ||
1342 | /* OpenVZ kernels contain a bogus patch that instead of appending, | |
1343 | * prepends the deleted marker. Workaround those. Otherwise handle | |
1344 | * the normal appended marker. */ | |
1345 | if (strncmp(filename, deleted, strlen(deleted)) == 0) | |
1346 | filename += strlen(deleted); | |
1347 | else if (strcmp(filename + nread - strlen(deleted), deleted) == 0) | |
1348 | filename[nread - strlen(deleted)] = '\0'; | |
1349 | ||
1350 | if (stat(filename, &sb) != 0) | |
1351 | return false; | |
1352 | ||
1353 | return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); | |
1354 | } | |
1355 | #elif defined(OSHurd) | |
1356 | static bool | |
1357 | pid_is_exec(pid_t pid, const struct stat *esb) | |
1358 | { | |
1359 | struct proc_stat *ps; | |
1360 | struct stat sb; | |
1361 | const char *filename; | |
1362 | ||
1363 | ps = get_proc_stat(pid, PSTAT_ARGS); | |
1364 | if (ps == NULL) | |
1365 | return false; | |
1366 | ||
1367 | filename = proc_stat_args(ps); | |
1368 | ||
1369 | if (stat(filename, &sb) != 0) | |
1370 | return false; | |
1371 | ||
1372 | return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); | |
1373 | } | |
1374 | #elif defined(OShpux) | |
1375 | static bool | |
1376 | pid_is_exec(pid_t pid, const struct stat *esb) | |
1377 | { | |
1378 | struct pst_status pst; | |
1379 | ||
1380 | if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) | |
1381 | return false; | |
1382 | return ((dev_t)pst.pst_text.psf_fsid.psfs_id == esb->st_dev && | |
1383 | (ino_t)pst.pst_text.psf_fileid == esb->st_ino); | |
1384 | } | |
1385 | #elif defined(OSFreeBSD) | |
1386 | static bool | |
1387 | pid_is_exec(pid_t pid, const struct stat *esb) | |
1388 | { | |
1389 | struct stat sb; | |
1390 | int error, name[4]; | |
1391 | size_t len; | |
1392 | char pathname[PATH_MAX]; | |
1393 | ||
1394 | name[0] = CTL_KERN; | |
1395 | name[1] = KERN_PROC; | |
1396 | name[2] = KERN_PROC_PATHNAME; | |
1397 | name[3] = pid; | |
1398 | len = sizeof(pathname); | |
1399 | ||
1400 | error = sysctl(name, 4, pathname, &len, NULL, 0); | |
1401 | if (error != 0 && errno != ESRCH) | |
1402 | return false; | |
1403 | if (len == 0) | |
1404 | pathname[0] = '\0'; | |
1405 | ||
1406 | if (stat(pathname, &sb) != 0) | |
1407 | return false; | |
1408 | ||
1409 | return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); | |
1410 | } | |
1411 | #elif defined(HAVE_KVM_H) | |
1412 | static bool | |
1413 | pid_is_exec(pid_t pid, const struct stat *esb) | |
1414 | { | |
1415 | kvm_t *kd; | |
1416 | int argv_len = 0; | |
1417 | struct kinfo_proc *kp; | |
1418 | struct stat sb; | |
1419 | char buf[_POSIX2_LINE_MAX]; | |
1420 | char **pid_argv_p; | |
1421 | char *start_argv_0_p, *end_argv_0_p; | |
1422 | bool res = false; | |
1423 | ||
1424 | kd = ssd_kvm_open(); | |
1425 | kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); | |
1426 | if (kp == NULL) | |
1427 | goto cleanup; | |
1428 | ||
1429 | pid_argv_p = kvm_getargv(kd, kp, argv_len); | |
1430 | if (pid_argv_p == NULL) | |
1431 | errx(1, "%s", kvm_geterr(kd)); | |
1432 | ||
1433 | /* Find and compare string. */ | |
1434 | start_argv_0_p = *pid_argv_p; | |
1435 | ||
1436 | /* Find end of argv[0] then copy and cut of str there. */ | |
1437 | end_argv_0_p = strchr(*pid_argv_p, ' '); | |
1438 | if (end_argv_0_p == NULL) | |
1439 | /* There seems to be no space, so we have the command | |
1440 | * already in its desired form. */ | |
1441 | start_argv_0_p = *pid_argv_p; | |
1442 | else { | |
1443 | /* Tests indicate that this never happens, since | |
1444 | * kvm_getargv itself cuts of tailing stuff. This is | |
1445 | * not what the manpage says, however. */ | |
1446 | strncpy(buf, *pid_argv_p, (end_argv_0_p - start_argv_0_p)); | |
1447 | buf[(end_argv_0_p - start_argv_0_p) + 1] = '\0'; | |
1448 | start_argv_0_p = buf; | |
1449 | } | |
1450 | ||
1451 | if (stat(start_argv_0_p, &sb) != 0) | |
1452 | goto cleanup; | |
1453 | ||
1454 | res = (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); | |
1455 | ||
1456 | cleanup: | |
1457 | kvm_close(kd); | |
1458 | ||
1459 | return res; | |
1460 | } | |
1461 | #endif | |
1462 | ||
1463 | #if defined(OSLinux) | |
1464 | static bool | |
1465 | pid_is_child(pid_t pid, pid_t ppid) | |
1466 | { | |
1467 | const char *ppid_str; | |
1468 | pid_t proc_ppid; | |
1469 | int rc; | |
1470 | ||
1471 | ppid_str = proc_status_field(pid, "PPid:"); | |
1472 | if (ppid_str == NULL) | |
1473 | return false; | |
1474 | ||
1475 | rc = parse_pid(ppid_str, &proc_ppid); | |
1476 | if (rc < 0) | |
1477 | return false; | |
1478 | ||
1479 | return proc_ppid == ppid; | |
1480 | } | |
1481 | #elif defined(OSHurd) | |
1482 | static bool | |
1483 | pid_is_child(pid_t pid, pid_t ppid) | |
1484 | { | |
1485 | struct proc_stat *ps; | |
1486 | struct procinfo *pi; | |
1487 | ||
1488 | ps = get_proc_stat(pid, PSTAT_PROC_INFO); | |
1489 | if (ps == NULL) | |
1490 | return false; | |
1491 | ||
1492 | pi = proc_stat_proc_info(ps); | |
1493 | ||
1494 | return pi->ppid == ppid; | |
1495 | } | |
1496 | #elif defined(OShpux) | |
1497 | static bool | |
1498 | pid_is_child(pid_t pid, pid_t ppid) | |
1499 | { | |
1500 | struct pst_status pst; | |
1501 | ||
1502 | if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) | |
1503 | return false; | |
1504 | ||
1505 | return pst.pst_ppid == ppid; | |
1506 | } | |
1507 | #elif defined(HAVE_KVM_H) | |
1508 | static bool | |
1509 | pid_is_child(pid_t pid, pid_t ppid) | |
1510 | { | |
1511 | kvm_t *kd; | |
1512 | struct kinfo_proc *kp; | |
1513 | pid_t proc_ppid; | |
1514 | bool res = false; | |
1515 | ||
1516 | kd = ssd_kvm_open(); | |
1517 | kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); | |
1518 | if (kp == NULL) | |
1519 | goto cleanup; | |
1520 | ||
1521 | #if defined(OSFreeBSD) | |
1522 | proc_ppid = kp->ki_ppid; | |
1523 | #elif defined(OSOpenBSD) | |
1524 | proc_ppid = kp->p_ppid; | |
1525 | #elif defined(OSDragonFlyBSD) | |
1526 | proc_ppid = kp->kp_ppid; | |
1527 | #else | |
1528 | proc_ppid = kp->kp_proc.p_ppid; | |
1529 | #endif | |
1530 | ||
1531 | res = (proc_ppid == ppid); | |
1532 | ||
1533 | cleanup: | |
1534 | kvm_close(kd); | |
1535 | ||
1536 | return res; | |
1537 | } | |
1538 | #endif | |
1539 | ||
1540 | #if defined(OSLinux) | |
1541 | static bool | |
1542 | pid_is_user(pid_t pid, uid_t uid) | |
1543 | { | |
1544 | struct stat sb; | |
1545 | char buf[32]; | |
1546 | ||
1547 | sprintf(buf, "/proc/%d", pid); | |
1548 | if (stat(buf, &sb) != 0) | |
1549 | return false; | |
1550 | return (sb.st_uid == uid); | |
1551 | } | |
1552 | #elif defined(OSHurd) | |
1553 | static bool | |
1554 | pid_is_user(pid_t pid, uid_t uid) | |
1555 | { | |
1556 | struct proc_stat *ps; | |
1557 | ||
1558 | ps = get_proc_stat(pid, PSTAT_OWNER_UID); | |
1559 | return ps && (uid_t)proc_stat_owner_uid(ps) == uid; | |
1560 | } | |
1561 | #elif defined(OShpux) | |
1562 | static bool | |
1563 | pid_is_user(pid_t pid, uid_t uid) | |
1564 | { | |
1565 | struct pst_status pst; | |
1566 | ||
1567 | if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) | |
1568 | return false; | |
1569 | return ((uid_t)pst.pst_uid == uid); | |
1570 | } | |
1571 | #elif defined(HAVE_KVM_H) | |
1572 | static bool | |
1573 | pid_is_user(pid_t pid, uid_t uid) | |
1574 | { | |
1575 | kvm_t *kd; | |
1576 | uid_t proc_uid; | |
1577 | struct kinfo_proc *kp; | |
1578 | bool res = false; | |
1579 | ||
1580 | kd = ssd_kvm_open(); | |
1581 | kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); | |
1582 | if (kp == NULL) | |
1583 | goto cleanup; | |
1584 | ||
1585 | #if defined(OSFreeBSD) | |
1586 | proc_uid = kp->ki_ruid; | |
1587 | #elif defined(OSOpenBSD) | |
1588 | proc_uid = kp->p_ruid; | |
1589 | #elif defined(OSDragonFlyBSD) | |
1590 | proc_uid = kp->kp_ruid; | |
1591 | #else | |
1592 | if (kp->kp_proc.p_cred) | |
1593 | kvm_read(kd, (u_long)&(kp->kp_proc.p_cred->p_ruid), | |
1594 | &proc_uid, sizeof(uid_t)); | |
1595 | else | |
1596 | goto cleanup; | |
1597 | #endif | |
1598 | ||
1599 | res = (proc_uid == (uid_t)uid); | |
1600 | ||
1601 | cleanup: | |
1602 | kvm_close(kd); | |
1603 | ||
1604 | return res; | |
1605 | } | |
1606 | #endif | |
1607 | ||
1608 | #if defined(OSLinux) | |
1609 | static bool | |
1610 | pid_is_cmd(pid_t pid, const char *name) | |
1611 | { | |
1612 | const char *comm; | |
1613 | ||
1614 | comm = proc_status_field(pid, "Name:"); | |
1615 | if (comm == NULL) | |
1616 | return false; | |
1617 | ||
1618 | return strcmp(comm, name) == 0; | |
1619 | } | |
1620 | #elif defined(OSHurd) | |
1621 | static bool | |
1622 | pid_is_cmd(pid_t pid, const char *name) | |
1623 | { | |
1624 | struct proc_stat *ps; | |
1625 | size_t argv0_len; | |
1626 | const char *argv0; | |
1627 | const char *binary_name; | |
1628 | ||
1629 | ps = get_proc_stat(pid, PSTAT_ARGS); | |
1630 | if (ps == NULL) | |
1631 | return false; | |
1632 | ||
1633 | argv0 = proc_stat_args(ps); | |
1634 | argv0_len = strlen(argv0) + 1; | |
1635 | ||
1636 | binary_name = basename(argv0); | |
1637 | if (strcmp(binary_name, name) == 0) | |
1638 | return true; | |
1639 | ||
1640 | /* XXX: This is all kinds of ugly, but on the Hurd there's no way to | |
1641 | * know the command name of a process, so we have to try to match | |
1642 | * also on argv[1] for the case of an interpreted script. */ | |
1643 | if (proc_stat_args_len(ps) > argv0_len) { | |
1644 | const char *script_name = basename(argv0 + argv0_len); | |
1645 | ||
1646 | return strcmp(script_name, name) == 0; | |
1647 | } | |
1648 | ||
1649 | return false; | |
1650 | } | |
1651 | #elif defined(OShpux) | |
1652 | static bool | |
1653 | pid_is_cmd(pid_t pid, const char *name) | |
1654 | { | |
1655 | struct pst_status pst; | |
1656 | ||
1657 | if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) | |
1658 | return false; | |
1659 | return (strcmp(pst.pst_ucomm, name) == 0); | |
1660 | } | |
1661 | #elif defined(HAVE_KVM_H) | |
1662 | static bool | |
1663 | pid_is_cmd(pid_t pid, const char *name) | |
1664 | { | |
1665 | kvm_t *kd; | |
1666 | struct kinfo_proc *kp; | |
1667 | char *process_name; | |
1668 | bool res = false; | |
1669 | ||
1670 | kd = ssd_kvm_open(); | |
1671 | kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); | |
1672 | if (kp == NULL) | |
1673 | goto cleanup; | |
1674 | ||
1675 | #if defined(OSFreeBSD) | |
1676 | process_name = kp->ki_comm; | |
1677 | #elif defined(OSOpenBSD) | |
1678 | process_name = kp->p_comm; | |
1679 | #elif defined(OSDragonFlyBSD) | |
1680 | process_name = kp->kp_comm; | |
1681 | #else | |
1682 | process_name = kp->kp_proc.p_comm; | |
1683 | #endif | |
1684 | ||
1685 | res = (strcmp(name, process_name) == 0); | |
1686 | ||
1687 | cleanup: | |
1688 | kvm_close(kd); | |
1689 | ||
1690 | return res; | |
1691 | } | |
1692 | #endif | |
1693 | ||
1694 | #if defined(OSHurd) | |
1695 | static bool | |
1696 | pid_is_running(pid_t pid) | |
1697 | { | |
1698 | return get_proc_stat(pid, 0) != NULL; | |
1699 | } | |
1700 | #else /* !OSHurd */ | |
1701 | static bool | |
1702 | pid_is_running(pid_t pid) | |
1703 | { | |
1704 | if (kill(pid, 0) == 0 || errno == EPERM) | |
1705 | return true; | |
1706 | else if (errno == ESRCH) | |
1707 | return false; | |
1708 | else | |
1709 | fatal("error checking pid %u status", pid); | |
1710 | } | |
1711 | #endif | |
1712 | ||
1713 | static enum status_code | |
1714 | pid_check(pid_t pid) | |
1715 | { | |
1716 | if (execname && !pid_is_exec(pid, &exec_stat)) | |
1717 | return STATUS_DEAD; | |
1718 | if (match_ppid > 0 && !pid_is_child(pid, match_ppid)) | |
1719 | return STATUS_DEAD; | |
1720 | if (userspec && !pid_is_user(pid, user_id)) | |
1721 | return STATUS_DEAD; | |
1722 | if (cmdname && !pid_is_cmd(pid, cmdname)) | |
1723 | return STATUS_DEAD; | |
1724 | if (action != ACTION_STOP && !pid_is_running(pid)) | |
1725 | return STATUS_DEAD; | |
1726 | ||
1727 | pid_list_push(&found, pid); | |
1728 | ||
1729 | return STATUS_OK; | |
1730 | } | |
1731 | ||
1732 | static enum status_code | |
1733 | do_pidfile(const char *name) | |
1734 | { | |
1735 | FILE *f; | |
1736 | static pid_t pid = 0; | |
1737 | ||
1738 | if (pid) | |
1739 | return pid_check(pid); | |
1740 | ||
1741 | f = fopen(name, "r"); | |
1742 | if (f) { | |
1743 | enum status_code pid_status; | |
1744 | ||
1745 | if (fscanf(f, "%d", &pid) == 1) | |
1746 | pid_status = pid_check(pid); | |
1747 | else | |
1748 | pid_status = STATUS_UNKNOWN; | |
1749 | fclose(f); | |
1750 | ||
1751 | if (pid_status == STATUS_DEAD) | |
1752 | return STATUS_DEAD_PIDFILE; | |
1753 | else | |
1754 | return pid_status; | |
1755 | } else if (errno == ENOENT) | |
1756 | return STATUS_DEAD; | |
1757 | else | |
1758 | fatal("unable to open pidfile %s", name); | |
1759 | } | |
1760 | ||
1761 | #if defined(OSLinux) || defined (OSsunos) | |
1762 | static enum status_code | |
1763 | do_procinit(void) | |
1764 | { | |
1765 | DIR *procdir; | |
1766 | struct dirent *entry; | |
1767 | int foundany; | |
1768 | pid_t pid; | |
1769 | enum status_code prog_status = STATUS_DEAD; | |
1770 | ||
1771 | procdir = opendir("/proc"); | |
1772 | if (!procdir) | |
1773 | fatal("unable to opendir /proc"); | |
1774 | ||
1775 | foundany = 0; | |
1776 | while ((entry = readdir(procdir)) != NULL) { | |
1777 | enum status_code pid_status; | |
1778 | ||
1779 | if (sscanf(entry->d_name, "%d", &pid) != 1) | |
1780 | continue; | |
1781 | foundany++; | |
1782 | ||
1783 | pid_status = pid_check(pid); | |
1784 | if (pid_status < prog_status) | |
1785 | prog_status = pid_status; | |
1786 | } | |
1787 | closedir(procdir); | |
1788 | if (!foundany) | |
1789 | fatal("nothing in /proc - not mounted?"); | |
1790 | ||
1791 | return prog_status; | |
1792 | } | |
1793 | #elif defined(OSHurd) | |
1794 | static int | |
1795 | check_proc_stat(struct proc_stat *ps) | |
1796 | { | |
1797 | pid_check(ps->pid); | |
1798 | return 0; | |
1799 | } | |
1800 | ||
1801 | static enum status_code | |
1802 | do_procinit(void) | |
1803 | { | |
1804 | if (!procset) | |
1805 | init_procset(); | |
1806 | ||
1807 | proc_stat_list_for_each(procset, check_proc_stat); | |
1808 | ||
1809 | if (found) | |
1810 | return STATUS_OK; | |
1811 | else | |
1812 | return STATUS_DEAD; | |
1813 | } | |
1814 | #elif defined(OShpux) | |
1815 | static enum status_code | |
1816 | do_procinit(void) | |
1817 | { | |
1818 | struct pst_status pst[10]; | |
1819 | int i, count; | |
1820 | int idx = 0; | |
1821 | enum status_code prog_status = STATUS_DEAD; | |
1822 | ||
1823 | while ((count = pstat_getproc(pst, sizeof(pst[0]), 10, idx)) > 0) { | |
1824 | enum status_code pid_status; | |
1825 | ||
1826 | for (i = 0; i < count; i++) { | |
1827 | pid_status = pid_check(pst[i].pst_pid); | |
1828 | if (pid_status < prog_status) | |
1829 | prog_status = pid_status; | |
1830 | } | |
1831 | idx = pst[count - 1].pst_idx + 1; | |
1832 | } | |
1833 | ||
1834 | return prog_status; | |
1835 | } | |
1836 | #elif defined(HAVE_KVM_H) | |
1837 | static enum status_code | |
1838 | do_procinit(void) | |
1839 | { | |
1840 | kvm_t *kd; | |
1841 | int nentries, i; | |
1842 | struct kinfo_proc *kp; | |
1843 | enum status_code prog_status = STATUS_DEAD; | |
1844 | ||
1845 | kd = ssd_kvm_open(); | |
1846 | kp = ssd_kvm_get_procs(kd, KERN_PROC_ALL, 0, &nentries); | |
1847 | ||
1848 | for (i = 0; i < nentries; i++) { | |
1849 | enum status_code pid_status; | |
1850 | pid_t pid; | |
1851 | ||
1852 | #if defined(OSFreeBSD) | |
1853 | pid = kp[i].ki_pid; | |
1854 | #elif defined(OSOpenBSD) | |
1855 | pid = kp[i].p_pid; | |
1856 | #elif defined(OSDragonFlyBSD) | |
1857 | pid = kp[i].kp_pid; | |
1858 | #else | |
1859 | pid = kp[i].kp_proc.p_pid; | |
1860 | #endif | |
1861 | ||
1862 | pid_status = pid_check(pid); | |
1863 | if (pid_status < prog_status) | |
1864 | prog_status = pid_status; | |
1865 | } | |
1866 | ||
1867 | kvm_close(kd); | |
1868 | ||
1869 | return prog_status; | |
1870 | } | |
1871 | #endif | |
1872 | ||
1873 | static enum status_code | |
1874 | do_findprocs(void) | |
1875 | { | |
1876 | pid_list_free(&found); | |
1877 | ||
1878 | if (match_pid > 0) | |
1879 | return pid_check(match_pid); | |
1880 | else if (pidfile) | |
1881 | return do_pidfile(pidfile); | |
1882 | else | |
1883 | return do_procinit(); | |
1884 | } | |
1885 | ||
1886 | static void | |
1887 | do_start(int argc, char **argv) | |
1888 | { | |
1889 | int devnull_fd = -1; | |
1890 | gid_t rgid; | |
1891 | uid_t ruid; | |
1892 | ||
1893 | do_findprocs(); | |
1894 | ||
1895 | if (found) { | |
1896 | if (quietmode <= 0) | |
1897 | printf("%s already running.\n", execname ? execname : "process"); | |
1898 | exit(exitnodo); | |
1899 | } | |
1900 | if (testmode && quietmode <= 0) { | |
1901 | printf("Would start %s ", startas); | |
1902 | while (argc-- > 0) | |
1903 | printf("%s ", *argv++); | |
1904 | if (changeuser != NULL) { | |
1905 | printf(" (as user %s[%d]", changeuser, runas_uid); | |
1906 | if (changegroup != NULL) | |
1907 | printf(", and group %s[%d])", changegroup, runas_gid); | |
1908 | else | |
1909 | printf(")"); | |
1910 | } | |
1911 | if (changeroot != NULL) | |
1912 | printf(" in directory %s", changeroot); | |
1913 | if (nicelevel) | |
1914 | printf(", and add %i to the priority", nicelevel); | |
1915 | if (proc_sched) | |
1916 | printf(", with scheduling policy %s with priority %i", | |
1917 | proc_sched->policy_name, proc_sched->priority); | |
1918 | if (io_sched) | |
1919 | printf(", with IO scheduling class %s with priority %i", | |
1920 | io_sched->policy_name, io_sched->priority); | |
1921 | printf(".\n"); | |
1922 | } | |
1923 | if (testmode) | |
1924 | exit(0); | |
1925 | if (quietmode < 0) | |
1926 | printf("Starting %s...\n", startas); | |
1927 | *--argv = startas; | |
1928 | if (background) | |
1929 | /* Ok, we need to detach this process. */ | |
1930 | daemonize(); | |
1931 | else if (mpidfile && pidfile != NULL) | |
1932 | /* User wants _us_ to make the pidfile, but detach themself! */ | |
1933 | write_pidfile(pidfile, getpid()); | |
1934 | if (background && close_io) { | |
1935 | devnull_fd = open("/dev/null", O_RDWR); | |
1936 | if (devnull_fd < 0) | |
1937 | fatal("unable to open '%s'", "/dev/null"); | |
1938 | } | |
1939 | if (nicelevel) { | |
1940 | errno = 0; | |
1941 | if ((nice(nicelevel) == -1) && (errno != 0)) | |
1942 | fatal("unable to alter nice level by %i", nicelevel); | |
1943 | } | |
1944 | if (proc_sched) | |
1945 | set_proc_schedule(proc_sched); | |
1946 | if (io_sched) | |
1947 | set_io_schedule(io_sched); | |
1948 | if (umask_value >= 0) | |
1949 | umask(umask_value); | |
1950 | if (changeroot != NULL) { | |
1951 | if (chdir(changeroot) < 0) | |
1952 | fatal("unable to chdir() to %s", changeroot); | |
1953 | if (chroot(changeroot) < 0) | |
1954 | fatal("unable to chroot() to %s", changeroot); | |
1955 | } | |
1956 | if (chdir(changedir) < 0) | |
1957 | fatal("unable to chdir() to %s", changedir); | |
1958 | ||
1959 | rgid = getgid(); | |
1960 | ruid = getuid(); | |
1961 | if (changegroup != NULL) { | |
1962 | if (rgid != (gid_t)runas_gid) | |
1963 | if (setgid(runas_gid)) | |
1964 | fatal("unable to set gid to %d", runas_gid); | |
1965 | } | |
1966 | if (changeuser != NULL) { | |
1967 | /* We assume that if our real user and group are the same as | |
1968 | * the ones we should switch to, the supplementary groups | |
1969 | * will be already in place. */ | |
1970 | if (rgid != (gid_t)runas_gid || ruid != (uid_t)runas_uid) | |
1971 | if (initgroups(changeuser, runas_gid)) | |
1972 | fatal("unable to set initgroups() with gid %d", | |
1973 | runas_gid); | |
1974 | ||
1975 | if (ruid != (uid_t)runas_uid) | |
1976 | if (setuid(runas_uid)) | |
1977 | fatal("unable to set uid to %s", changeuser); | |
1978 | } | |
1979 | ||
1980 | if (background && close_io) { | |
1981 | int i; | |
1982 | ||
1983 | dup2(devnull_fd, 0); /* stdin */ | |
1984 | dup2(devnull_fd, 1); /* stdout */ | |
1985 | dup2(devnull_fd, 2); /* stderr */ | |
1986 | ||
1987 | /* Now close all extra fds. */ | |
1988 | for (i = get_open_fd_max() - 1; i >= 3; --i) | |
1989 | close(i); | |
1990 | } | |
1991 | execv(startas, argv); | |
1992 | fatal("unable to start %s", startas); | |
1993 | } | |
1994 | ||
1995 | static void | |
1996 | do_stop(int sig_num, int *n_killed, int *n_notkilled) | |
1997 | { | |
1998 | struct pid_list *p; | |
1999 | ||
2000 | do_findprocs(); | |
2001 | ||
2002 | *n_killed = 0; | |
2003 | *n_notkilled = 0; | |
2004 | ||
2005 | if (!found) | |
2006 | return; | |
2007 | ||
2008 | pid_list_free(&killed); | |
2009 | ||
2010 | for (p = found; p; p = p->next) { | |
2011 | if (testmode) { | |
2012 | if (quietmode <= 0) | |
2013 | printf("Would send signal %d to %d.\n", | |
2014 | sig_num, p->pid); | |
2015 | (*n_killed)++; | |
2016 | } else if (kill(p->pid, sig_num) == 0) { | |
2017 | pid_list_push(&killed, p->pid); | |
2018 | (*n_killed)++; | |
2019 | } else { | |
2020 | if (sig_num) | |
2021 | warning("failed to kill %d: %s\n", | |
2022 | p->pid, strerror(errno)); | |
2023 | (*n_notkilled)++; | |
2024 | } | |
2025 | } | |
2026 | } | |
2027 | ||
2028 | static void | |
2029 | do_stop_summary(int retry_nr) | |
2030 | { | |
2031 | struct pid_list *p; | |
2032 | ||
2033 | if (quietmode >= 0 || !killed) | |
2034 | return; | |
2035 | ||
2036 | printf("Stopped %s (pid", what_stop); | |
2037 | for (p = killed; p; p = p->next) | |
2038 | printf(" %d", p->pid); | |
2039 | putchar(')'); | |
2040 | if (retry_nr > 0) | |
2041 | printf(", retry #%d", retry_nr); | |
2042 | printf(".\n"); | |
2043 | } | |
2044 | ||
2045 | static void | |
2046 | set_what_stop(const char *str) | |
2047 | { | |
2048 | strncpy(what_stop, str, sizeof(what_stop)); | |
2049 | what_stop[sizeof(what_stop) - 1] = '\0'; | |
2050 | } | |
2051 | ||
2052 | /* | |
2053 | * We want to keep polling for the processes, to see if they've exited, or | |
2054 | * until the timeout expires. | |
2055 | * | |
2056 | * This is a somewhat complicated algorithm to try to ensure that we notice | |
2057 | * reasonably quickly when all the processes have exited, but don't spend | |
2058 | * too much CPU time polling. In particular, on a fast machine with | |
2059 | * quick-exiting daemons we don't want to delay system shutdown too much, | |
2060 | * whereas on a slow one, or where processes are taking some time to exit, | |
2061 | * we want to increase the polling interval. | |
2062 | * | |
2063 | * The algorithm is as follows: we measure the elapsed time it takes to do | |
2064 | * one poll(), and wait a multiple of this time for the next poll. However, | |
2065 | * if that would put us past the end of the timeout period we wait only as | |
2066 | * long as the timeout period, but in any case we always wait at least | |
2067 | * MIN_POLL_INTERVAL (20ms). The multiple (‘ratio’) starts out as 2, and | |
2068 | * increases by 1 for each poll to a maximum of 10; so we use up to between | |
2069 | * 30% and 10% of the machine's resources (assuming a few reasonable things | |
2070 | * about system performance). | |
2071 | */ | |
2072 | static bool | |
2073 | do_stop_timeout(int timeout, int *n_killed, int *n_notkilled) | |
2074 | { | |
2075 | struct timespec stopat, before, after, interval, maxinterval; | |
2076 | int rc, ratio; | |
2077 | ||
2078 | timespec_gettime(&stopat); | |
2079 | stopat.tv_sec += timeout; | |
2080 | ratio = 1; | |
2081 | for (;;) { | |
2082 | timespec_gettime(&before); | |
2083 | if (timespec_cmp(&before, &stopat, >)) | |
2084 | return false; | |
2085 | ||
2086 | do_stop(0, n_killed, n_notkilled); | |
2087 | if (!*n_killed) | |
2088 | return true; | |
2089 | ||
2090 | timespec_gettime(&after); | |
2091 | ||
2092 | if (!timespec_cmp(&after, &stopat, <)) | |
2093 | return false; | |
2094 | ||
2095 | if (ratio < 10) | |
2096 | ratio++; | |
2097 | ||
2098 | timespec_sub(&stopat, &after, &maxinterval); | |
2099 | timespec_sub(&after, &before, &interval); | |
2100 | timespec_mul(&interval, ratio); | |
2101 | ||
2102 | if (interval.tv_sec < 0 || interval.tv_nsec < 0) | |
2103 | interval.tv_sec = interval.tv_nsec = 0; | |
2104 | ||
2105 | if (timespec_cmp(&interval, &maxinterval, >)) | |
2106 | interval = maxinterval; | |
2107 | ||
2108 | if (interval.tv_sec == 0 && | |
2109 | interval.tv_nsec <= MIN_POLL_INTERVAL) | |
2110 | interval.tv_nsec = MIN_POLL_INTERVAL; | |
2111 | ||
2112 | rc = pselect(0, NULL, NULL, NULL, &interval, NULL); | |
2113 | if (rc < 0 && errno != EINTR) | |
2114 | fatal("select() failed for pause"); | |
2115 | } | |
2116 | } | |
2117 | ||
2118 | static int | |
2119 | finish_stop_schedule(bool anykilled) | |
2120 | { | |
2121 | if (rpidfile && pidfile && !testmode) | |
2122 | remove_pidfile(pidfile); | |
2123 | ||
2124 | if (anykilled) | |
2125 | return 0; | |
2126 | ||
2127 | if (quietmode <= 0) | |
2128 | printf("No %s found running; none killed.\n", what_stop); | |
2129 | ||
2130 | return exitnodo; | |
2131 | } | |
2132 | ||
2133 | static int | |
2134 | run_stop_schedule(void) | |
2135 | { | |
2136 | int position, n_killed, n_notkilled, value, retry_nr; | |
2137 | bool anykilled; | |
2138 | ||
2139 | if (testmode) { | |
2140 | if (schedule != NULL) { | |
2141 | if (quietmode <= 0) | |
2142 | printf("Ignoring --retry in test mode\n"); | |
2143 | schedule = NULL; | |
2144 | } | |
2145 | } | |
2146 | ||
2147 | if (cmdname) | |
2148 | set_what_stop(cmdname); | |
2149 | else if (execname) | |
2150 | set_what_stop(execname); | |
2151 | else if (pidfile) | |
2152 | sprintf(what_stop, "process in pidfile '%.200s'", pidfile); | |
2153 | else if (match_pid > 0) | |
2154 | sprintf(what_stop, "process with pid %d", match_pid); | |
2155 | else if (match_ppid > 0) | |
2156 | sprintf(what_stop, "process(es) with parent pid %d", match_ppid); | |
2157 | else if (userspec) | |
2158 | sprintf(what_stop, "process(es) owned by '%.200s'", userspec); | |
2159 | else | |
2160 | fatal("internal error, no match option, please report"); | |
2161 | ||
2162 | anykilled = false; | |
2163 | retry_nr = 0; | |
2164 | ||
2165 | if (schedule == NULL) { | |
2166 | do_stop(signal_nr, &n_killed, &n_notkilled); | |
2167 | do_stop_summary(0); | |
2168 | if (n_notkilled > 0 && quietmode <= 0) | |
2169 | printf("%d pids were not killed\n", n_notkilled); | |
2170 | if (n_killed) | |
2171 | anykilled = true; | |
2172 | return finish_stop_schedule(anykilled); | |
2173 | } | |
2174 | ||
2175 | for (position = 0; position < schedule_length; position++) { | |
2176 | reposition: | |
2177 | value = schedule[position].value; | |
2178 | n_notkilled = 0; | |
2179 | ||
2180 | switch (schedule[position].type) { | |
2181 | case sched_goto: | |
2182 | position = value; | |
2183 | goto reposition; | |
2184 | case sched_signal: | |
2185 | do_stop(value, &n_killed, &n_notkilled); | |
2186 | do_stop_summary(retry_nr++); | |
2187 | if (!n_killed) | |
2188 | return finish_stop_schedule(anykilled); | |
2189 | else | |
2190 | anykilled = true; | |
2191 | continue; | |
2192 | case sched_timeout: | |
2193 | if (do_stop_timeout(value, &n_killed, &n_notkilled)) | |
2194 | return finish_stop_schedule(anykilled); | |
2195 | else | |
2196 | continue; | |
2197 | default: | |
2198 | assert(!"schedule[].type value must be valid"); | |
2199 | } | |
2200 | } | |
2201 | ||
2202 | if (quietmode <= 0) | |
2203 | printf("Program %s, %d process(es), refused to die.\n", | |
2204 | what_stop, n_killed); | |
2205 | ||
2206 | return 2; | |
2207 | } | |
2208 | ||
2209 | int | |
2210 | main(int argc, char **argv) | |
2211 | { | |
2212 | progname = argv[0]; | |
2213 | ||
2214 | parse_options(argc, argv); | |
2215 | setup_options(); | |
2216 | ||
2217 | argc -= optind; | |
2218 | argv += optind; | |
2219 | ||
2220 | if (action == ACTION_START) | |
2221 | do_start(argc, argv); | |
2222 | ||
2223 | if (action == ACTION_STOP) { | |
2224 | int i = run_stop_schedule(); | |
2225 | exit(i); | |
2226 | } | |
2227 | ||
2228 | if (action == ACTION_STATUS) { | |
2229 | enum status_code prog_status; | |
2230 | ||
2231 | prog_status = do_findprocs(); | |
2232 | exit(prog_status); | |
2233 | } | |
2234 | ||
2235 | return 0; | |
2236 | } |