diff options
-rw-r--r-- | makefile | 12 | ||||
-rw-r--r-- | start-stop-daemon.8 | 382 | ||||
-rw-r--r-- | start-stop-daemon.c | 2534 |
3 files changed, 2928 insertions, 0 deletions
diff --git a/makefile b/makefile new file mode 100644 index 0000000..976a01a --- /dev/null +++ b/makefile @@ -0,0 +1,12 @@ +CC = cc +CFLAGS += -g -Wall +PROGRAM = start-stop-daemon +SOURCES = start-stop-daemon.c + +$(PROGRAM): $(SOURCES) + $(CC) $(CFLAGS) -o $(@) $(SOURCES) + + +all: $(PROGRAM) +clean: ; rm -f $(PROGRAM) +force: clean all diff --git a/start-stop-daemon.8 b/start-stop-daemon.8 new file mode 100644 index 0000000..de2d35c --- /dev/null +++ b/start-stop-daemon.8 @@ -0,0 +1,382 @@ +.\" dpkg manual page - start-stop-daemon(8) +.\" +.\" Copyright © 1999 Klee Dienes <klee@mit.edu> +.\" Copyright © 1999 Ben Collins <bcollins@debian.org> +.\" Copyright © 2000-2001 Wichert Akkerman <wakkerma@debian.org> +.\" Copyright © 2002-2003 Adam Heath <doogie@debian.org> +.\" Copyright © 2004 Scott James Remnant <keybuk@debian.org> +.\" Copyright © 2008-2015 Guillem Jover <guillem@debian.org> +.\" +.\" This is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program. If not, see <https://www.gnu.org/licenses/>. +. +.TH start\-stop\-daemon 8 "2017-07-04" "Debian Project" "dpkg suite" +.nh +.SH NAME +start\-stop\-daemon \- start and stop system daemon programs +. +.SH SYNOPSIS +.B start\-stop\-daemon +.RI [ option "...] " command +. +.SH DESCRIPTION +.B start\-stop\-daemon +is used to control the creation and termination of system-level processes. +Using one of the matching options, \fBstart\-stop\-daemon\fP +can be configured to find existing instances of a running process. +.PP +Note: unless +.B \-\-pid +or +.B \-\-pidfile +are specified, +.B start\-stop\-daemon +behaves similar to +.BR killall (1). +.B start\-stop\-daemon +will scan the process table looking for any processes which +match the process name, parent pid, uid, and/or gid (if specified). Any +matching process will prevent +.BR \-\-start +from starting the daemon. All matching processes will be sent the TERM +signal (or the one specified via \fB\-\-signal\fP or \fB\-\-retry\fP) if +.BR \-\-stop +is specified. For daemons which have long-lived children +which need to live through a +.BR \-\-stop , +you must specify a pidfile. +. +.SH COMMANDS +.TP +.BR \-S ", " \-\-start " [" \-\- "] \fIarguments\fP" +Check for the existence of a specified process. +If such a process exists, +.B start\-stop\-daemon +does nothing, and exits with error status 1 (0 if +.BR \-\-oknodo +is specified). +If such a process does not exist, it starts an +instance, using either the executable specified by +.B \-\-exec +or, if specified, by +.BR \-\-startas . +Any arguments given after +.BR \-\- +on the command line are passed unmodified to the program being +started. +.TP +.BR \-K ", " \-\-stop +Checks for the existence of a specified process. +If such a process exists, +.B start\-stop\-daemon +sends it the signal specified by +.BR \-\-signal , +and exits with error status 0. +If such a process does not exist, +.B start\-stop\-daemon +exits with error status 1 +(0 if +.BR \-\-oknodo +is specified). If +.B \-\-retry +is specified, then +.B start\-stop\-daemon +will check that the process(es) have terminated. +.TP +.BR \-T ", " \-\-status +Check for the existence of a specified process, and returns an exit status +code, according to the LSB Init Script Actions (since version 1.16.1). +.TP +.BR \-H ", " \-\-help +Show usage information and exit. +.TP +.BR \-V ", " \-\-version +Show the program version and exit. +. +.SH OPTIONS +.SS Matching options +.TP +.BR \-\-pid " \fIpid\fP" +Check for a process with the specified \fIpid\fP (since version 1.17.6). +The \fIpid\fP must be a number greater than 0. +.TP +.BR \-\-ppid " \fIppid\fP" +Check for a process with the specified parent pid \fIppid\fP +(since version 1.17.7). +The \fIppid\fP must be a number greater than 0. +.TP +.BR \-p ", " \-\-pidfile " \fIpid-file\fP" +Check whether a process has created the file \fIpid-file\fP. Note: using this +matching option alone might cause unintended processes to be acted on, if the +old process terminated without being able to remove the \fIpid-file\fP. +.TP +.BR \-x ", " \-\-exec " \fIexecutable\fP" +Check for processes that are instances of this \fIexecutable\fP. The +\fIexecutable\fP argument should be an absolute pathname. Note: this might +not work as intended with interpreted scripts, as the executable will point +to the interpreter. Take into account processes running from inside a chroot +will also be matched, so other match restrictions might be needed. +.TP +.BR \-n ", " \-\-name " \fIprocess-name\fP" +Check for processes with the name \fIprocess-name\fP. The \fIprocess-name\fP +is usually the process filename, but it could have been changed by the +process itself. Note: on most systems this information is retrieved from +the process comm name from the kernel, which tends to have a relatively +short length limit (assuming more than 15 characters is non-portable). +.TP +.BR \-u ", " \-\-user " \fIusername\fP|\fIuid\fP +Check for processes owned by the user specified by \fIusername\fP or +\fIuid\fP. Note: using this matching option alone will cause all processes +matching the user to be acted on. +. +.SS Generic options +.TP +.BR \-g ", " \-\-group " \fIgroup\fP|\fIgid\fP" +Change to \fIgroup\fP or \fIgid\fP when starting the process. +.TP +.BR \-s ", " \-\-signal " \fIsignal\fP" +With +.BR \-\-stop , +specifies the signal to send to processes being stopped (default TERM). +.TP +.BR \-R ", " \-\-retry " \fItimeout\fP|\fIschedule\fP" +With +.BR \-\-stop , +specifies that +.B start\-stop\-daemon +is to check whether the process(es) +do finish. It will check repeatedly whether any matching processes +are running, until none are. If the processes do not exit it will +then take further action as determined by the schedule. + +If +.I timeout +is specified instead of +.IR schedule , +then the schedule +.IB signal / timeout /KILL/ timeout +is used, where +.I signal +is the signal specified with +.BR \-\-signal . + +.I schedule +is a list of at least two items separated by slashes +.RB ( / ); +each item may be +.BI \- signal-number +or [\fB\-\fP]\fIsignal-name\fP, +which means to send that signal, +or +.IR timeout , +which means to wait that many seconds for processes to +exit, +or +.BR forever , +which means to repeat the rest of the schedule forever if +necessary. + +If the end of the schedule is reached and +.BR forever +is not specified, then +.B start\-stop\-daemon +exits with error status 2. +If a schedule is specified, then any signal specified +with +.B \-\-signal +is ignored. +.TP +.BR \-a ", " \-\-startas " \fIpathname\fP" +With +.BR \-\-start , +start the process specified by +.IR pathname . +If not specified, defaults to the argument given to +.BR \-\-exec . +.TP +.BR \-t ", " \-\-test +Print actions that would be taken and set appropriate return value, +but take no action. +.TP +.BR \-o ", " \-\-oknodo +Return exit status 0 instead of 1 if no actions are (would be) taken. +.TP +.BR \-q ", " \-\-quiet +Do not print informational messages; only display error messages. +.TP +.BR \-c ", " \-\-chuid " \fIusername\fR|\fIuid\fP[\fB:\fP\fIgroup\fR|\fIgid\fP]" +Change to this username/uid before starting the process. You can also +specify a group by appending a +.BR : , +then the group or gid in the same way +as you would for the \fBchown\fP(1) command (\fIuser\fP\fB:\fP\fIgroup\fP). +If a user is specified without a group, the primary GID for that user is used. +When using this option +you must realize that the primary and supplemental groups are set as well, +even if the +.B \-\-group +option is not specified. The +.B \-\-group +option is only for +groups that the user isn't normally a member of (like adding per process +group membership for generic users like +.BR nobody ). +.TP +.BR \-r ", " \-\-chroot " \fIroot\fP" +Chdir and chroot to +.I root +before starting the process. Please note that the pidfile is also written +after the chroot. +.TP +.BR \-d ", " \-\-chdir " \fIpath\fP" +Chdir to +.I path +before starting the process. This is done after the chroot if the +\fB\-r\fP|\fB\-\-chroot\fP option is set. When not specified, +.B start\-stop\-daemon +will chdir to the root directory before starting the process. +.TP +.BR \-b ", " \-\-background +Typically used with programs that don't detach on their own. This option +will force +.B start\-stop\-daemon +to fork before starting the process, and force it into the background. +.B Warning: start\-stop\-daemon +cannot check the exit status if the process fails to execute for +.B any +reason. This is a last resort, and is only meant for programs that either +make no sense forking on their own, or where it's not feasible to add the +code for them to do this themselves. +.TP +.BR \-C ", " \-\-no\-close +Do not close any file descriptor when forcing the daemon into the background +(since version 1.16.5). +Used for debugging purposes to see the process output, or to redirect file +descriptors to log the process output. +Only relevant when using \fB\-\-background\fP. +.TP +.BR \-N ", " \-\-nicelevel " \fIint\fP" +This alters the priority of the process before starting it. +.TP +.BR \-P ", " \-\-procsched " \fIpolicy\fP\fB:\fP\fIpriority\fP" +This alters the process scheduler policy and priority of the process before +starting it (since version 1.15.0). +The priority can be optionally specified by appending a \fB:\fP +followed by the value. The default \fIpriority\fP is 0. The currently +supported policy values are \fBother\fP, \fBfifo\fP and \fBrr\fP. +.TP +.BR \-I ", " \-\-iosched " \fIclass\fP\fB:\fP\fIpriority\fP" +This alters the IO scheduler class and priority of the process before starting +it (since version 1.15.0). +The priority can be optionally specified by appending a \fB:\fP followed +by the value. The default \fIpriority\fP is 4, unless \fIclass\fP is \fBidle\fP, +then \fIpriority\fP will always be 7. The currently supported values for +\fIclass\fP are \fBidle\fP, \fBbest-effort\fP and \fBreal-time\fP. +.TP +.BR \-k ", " \-\-umask " \fImask\fP" +This sets the umask of the process before starting it (since version 1.13.22). +.TP +.BR \-m ", " \-\-make\-pidfile +Used when starting a program that does not create its own pid file. This +option will make +.B start\-stop\-daemon +create the file referenced with +.B \-\-pidfile +and place the pid into it just before executing the process. Note, the +file will only be removed when stopping the program if +\fB\-\-remove\-pidfile\fP is used. +.B Note: +This feature may not work in all cases. Most notably when the program +being executed forks from its main process. Because of this, it is usually +only useful when combined with the +.B \-\-background +option. +.TP +.B \-\-remove\-pidfile +Used when stopping a program that does not remove its own pid file +(since version 1.17.19). +This option will make +.B start\-stop\-daemon +remove the file referenced with +.B \-\-pidfile +after terminating the process. +.TP +.BR \-v ", " \-\-verbose +Print verbose informational messages. +. +.SH EXIT STATUS +.TP +.B 0 +The requested action was performed. If +.B \-\-oknodo +was specified, it's also possible that nothing had to be done. +This can happen when +.B \-\-start +was specified and a matching process was already running, or when +.B \-\-stop +was specified and there were no matching processes. +.TP +.B 1 +If +.B \-\-oknodo +was not specified and nothing was done. +.TP +.B 2 +If +.B \-\-stop +and +.B \-\-retry +were specified, but the end of the schedule was reached and the processes were +still running. +.TP +.B 3 +Any other error. +.PP +When using the \fB\-\-status\fP command, the following status codes are +returned: +.TP +.B 0 +Program is running. +.TP +.B 1 +Program is not running and the pid file exists. +.TP +.B 3 +Program is not running. +.TP +.B 4 +Unable to determine program status. +. +.SH EXAMPLE +Start the \fBfood\fP daemon, unless one is already running (a process named +food, running as user food, with pid in food.pid): +.IP +.nf +start\-stop\-daemon \-\-start \-\-oknodo \-\-user food \-\-name food \\ + \-\-pidfile /run/food.pid \-\-startas /usr/sbin/food \\ + \-\-chuid food \-\- \-\-daemon +.fi +.PP +Send \fBSIGTERM\fP to \fBfood\fP and wait up to 5 seconds for it to stop: +.IP +.nf +start\-stop\-daemon \-\-stop \-\-oknodo \-\-user food \-\-name food \\ + \-\-pidfile /run/food.pid \-\-retry 5 +.fi +.PP +Demonstration of a custom schedule for stopping \fBfood\fP: +.IP +.nf +start\-stop\-daemon \-\-stop \-\-oknodo \-\-user food \-\-name food \\ + \-\-pidfile /run/food.pid \-\-retry=TERM/30/KILL/5 +.fi diff --git a/start-stop-daemon.c b/start-stop-daemon.c new file mode 100644 index 0000000..8135750 --- /dev/null +++ b/start-stop-daemon.c @@ -0,0 +1,2534 @@ +/* + * A rewrite of the original Debian's start-stop-daemon Perl script + * in C (faster - it is executed many times during system startup). + * + * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, + * public domain. Based conceptually on start-stop-daemon.pl, by Ian + * Jackson <ijackson@gnu.ai.mit.edu>. May be used and distributed + * freely for any purpose. Changes by Christian Schwarz + * <schwarz@monet.m.isar.de>, to make output conform to the Debian + * Console Message Standard, also placed in public domain. Minor + * changes by Klee Dienes <klee@debian.org>, also placed in the Public + * Domain. + * + * Changes by Ben Collins <bcollins@debian.org>, added --chuid, --background + * and --make-pidfile options, placed in public domain as well. + * + * Port to OpenBSD by Sontri Tomo Huynh <huynh.29@osu.edu> + * and Andreas Schuldei <andreas@schuldei.org> + * + * Changes by Ian Jackson: added --retry (and associated rearrangements). + */ + +#include <config.h> +#include <compat.h> + +#include <dpkg/macros.h> + +#if defined(__linux__) +# define OS_Linux +#elif defined(__GNU__) +# define OS_Hurd +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +# define OS_FreeBSD +#elif defined(__NetBSD__) +# define OS_NetBSD +#elif defined(__OpenBSD__) +# define OS_OpenBSD +#elif defined(__DragonFly__) +# define OS_DragonFlyBSD +#elif defined(__APPLE__) && defined(__MACH__) +# define OS_Darwin +#elif defined(__sun) +# define OS_Solaris +#elif defined(_AIX) +# define OS_AIX +#elif defined(__hpux) +# define OS_HPUX +#else +# error Unknown architecture - cannot build start-stop-daemon +#endif + +/* NetBSD needs this to expose struct proc. */ +#define _KMEMUSER 1 + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif +#ifdef HAVE_SYS_PROCFS_H +#include <sys/procfs.h> +#endif +#ifdef HAVE_SYS_PROC_H +#include <sys/proc.h> +#endif +#ifdef HAVE_SYS_USER_H +#include <sys/user.h> +#endif +#ifdef HAVE_SYS_PSTAT_H +#include <sys/pstat.h> +#endif +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/select.h> +#include <sys/ioctl.h> + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <time.h> +#include <fcntl.h> +#include <dirent.h> +#include <ctype.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <signal.h> +#include <termios.h> +#include <unistd.h> +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif +#include <stdbool.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#ifdef HAVE_ERROR_H +#include <error.h> +#endif +#ifdef HAVE_ERR_H +#include <err.h> +#endif + +#if defined(OS_Hurd) +#include <hurd.h> +#include <ps.h> +#endif + +#if defined(OS_Darwin) +#include <libproc.h> +#endif + +#ifdef HAVE_KVM_H +#include <kvm.h> +#if defined(OS_FreeBSD) +#define KVM_MEMFILE "/dev/null" +#else +#define KVM_MEMFILE NULL +#endif +#endif + +#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0 +#include <sched.h> +#else +#define SCHED_OTHER -1 +#define SCHED_FIFO -1 +#define SCHED_RR -1 +#endif + +#if defined(OS_Linux) +/* This comes from TASK_COMM_LEN defined in Linux' include/linux/sched.h. */ +#define PROCESS_NAME_SIZE 15 +#elif defined(OS_Solaris) +#define PROCESS_NAME_SIZE 15 +#elif defined(OS_Darwin) +#define PROCESS_NAME_SIZE 16 +#elif defined(OS_AIX) +/* This comes from PRFNSZ defined in AIX's <sys/procfs.h>. */ +#define PROCESS_NAME_SIZE 16 +#elif defined(OS_NetBSD) +#define PROCESS_NAME_SIZE 16 +#elif defined(OS_OpenBSD) +#define PROCESS_NAME_SIZE 16 +#elif defined(OS_FreeBSD) +#define PROCESS_NAME_SIZE 19 +#elif defined(OS_DragonFlyBSD) +/* On DragonFlyBSD MAXCOMLEN expands to 16. */ +#define PROCESS_NAME_SIZE MAXCOMLEN +#endif + +#if defined(SYS_ioprio_set) && defined(linux) +#define HAVE_IOPRIO_SET +#endif + +#define IOPRIO_CLASS_SHIFT 13 +#define IOPRIO_PRIO_VALUE(class, prio) (((class) << IOPRIO_CLASS_SHIFT) | (prio)) +#define IO_SCHED_PRIO_MIN 0 +#define IO_SCHED_PRIO_MAX 7 + +enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, +}; + +enum { + IOPRIO_CLASS_NONE, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + +enum action_code { + ACTION_NONE, + ACTION_START, + ACTION_STOP, + ACTION_STATUS, +}; + +/* Time conversion constants. */ +enum { + NANOSEC_IN_SEC = 1000000000L, + NANOSEC_IN_MILLISEC = 1000000L, + NANOSEC_IN_MICROSEC = 1000L, +}; + +/* The minimum polling interval, 20ms. */ +static const long MIN_POLL_INTERVAL = 20 * NANOSEC_IN_MILLISEC; + +static enum action_code action; +static bool testmode = false; +static int quietmode = 0; +static int exitnodo = 1; +static bool background = false; +static bool close_io = true; +static bool mpidfile = false; +static bool rpidfile = false; +static int signal_nr = SIGTERM; +static int user_id = -1; +static int runas_uid = -1; +static int runas_gid = -1; +static const char *userspec = NULL; +static char *changeuser = NULL; +static const char *changegroup = NULL; +static char *changeroot = NULL; +static const char *changedir = "/"; +static const char *cmdname = NULL; +static char *execname = NULL; +static char *startas = NULL; +static pid_t match_pid = -1; +static pid_t match_ppid = -1; +static const char *pidfile = NULL; +static char *what_stop = NULL; +static const char *progname = ""; +static int nicelevel = 0; +static int umask_value = -1; + +static struct stat exec_stat; +#if defined(OS_Hurd) +static struct proc_stat_list *procset = NULL; +#endif + +/* LSB Init Script process status exit codes. */ +enum status_code { + STATUS_OK = 0, + STATUS_DEAD_PIDFILE = 1, + STATUS_DEAD_LOCKFILE = 2, + STATUS_DEAD = 3, + STATUS_UNKNOWN = 4, +}; + +struct pid_list { + struct pid_list *next; + pid_t pid; +}; + +static struct pid_list *found = NULL; +static struct pid_list *killed = NULL; + +/* Resource scheduling policy. */ +struct res_schedule { + const char *policy_name; + int policy; + int priority; +}; + +struct schedule_item { + enum { + sched_timeout, + sched_signal, + sched_goto, + /* Only seen within parse_schedule and callees. */ + sched_forever, + } type; + /* Seconds, signal no., or index into array. */ + int value; +}; + +static struct res_schedule *proc_sched = NULL; +static struct res_schedule *io_sched = NULL; + +static int schedule_length; +static struct schedule_item *schedule = NULL; + + +static void DPKG_ATTR_PRINTF(1) +warning(const char *format, ...) +{ + va_list arglist; + + fprintf(stderr, "%s: warning: ", progname); + va_start(arglist, format); + vfprintf(stderr, format, arglist); + va_end(arglist); +} + +static void DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1) +fatal(const char *format, ...) +{ + va_list arglist; + int errno_fatal = errno; + + fprintf(stderr, "%s: ", progname); + va_start(arglist, format); + vfprintf(stderr, format, arglist); + va_end(arglist); + if (errno_fatal) + fprintf(stderr, " (%s)\n", strerror(errno_fatal)); + else + fprintf(stderr, "\n"); + + if (action == ACTION_STATUS) + exit(STATUS_UNKNOWN); + else + exit(2); +} + +static void * +xmalloc(int size) +{ + void *ptr; + + ptr = malloc(size); + if (ptr) + return ptr; + fatal("malloc(%d) failed", size); +} + +static char * +xstrndup(const char *str, size_t n) +{ + char *new_str; + + new_str = strndup(str, n); + if (new_str) + return new_str; + fatal("strndup(%s, %zu) failed", str, n); +} + +static void +timespec_gettime(struct timespec *ts) +{ +#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && \ + defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0 + if (clock_gettime(CLOCK_MONOTONIC, ts) < 0) + fatal("clock_gettime failed"); +#else + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) + fatal("gettimeofday failed"); + + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * NANOSEC_IN_MICROSEC; +#endif +} + +#define timespec_cmp(a, b, OP) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_nsec OP (b)->tv_nsec) : \ + ((a)->tv_sec OP (b)->tv_sec)) + +static void +timespec_sub(struct timespec *a, struct timespec *b, struct timespec *res) +{ + res->tv_sec = a->tv_sec - b->tv_sec; + res->tv_nsec = a->tv_nsec - b->tv_nsec; + if (res->tv_nsec < 0) { + res->tv_sec--; + res->tv_nsec += NANOSEC_IN_SEC; + } +} + +static void +timespec_mul(struct timespec *a, int b) +{ + long nsec = a->tv_nsec * b; + + a->tv_sec *= b; + a->tv_sec += nsec / NANOSEC_IN_SEC; + a->tv_nsec = nsec % NANOSEC_IN_SEC; +} + +static char * +newpath(const char *dirname, const char *filename) +{ + char *path; + size_t path_len; + + path_len = strlen(dirname) + 1 + strlen(filename) + 1; + path = xmalloc(path_len); + snprintf(path, path_len, "%s/%s", dirname, filename); + + return path; +} + +static long +get_open_fd_max(void) +{ +#ifdef HAVE_GETDTABLESIZE + return getdtablesize(); +#else + return sysconf(_SC_OPEN_MAX); +#endif +} + +#ifndef HAVE_SETSID +static void +detach_controlling_tty(void) +{ +#ifdef HAVE_TIOCNOTTY + int tty_fd; + + tty_fd = open("/dev/tty", O_RDWR); + + /* The current process does not have a controlling tty. */ + if (tty_fd < 0) + return; + + if (ioctl(tty_fd, TIOCNOTTY, 0) != 0) + fatal("unable to detach controlling tty"); + + close(tty_fd); +#endif +} + +static pid_t +setsid(void) +{ + if (setpgid(0, 0) < 0) + return -1: + + detach_controlling_tty(); + + return 0; +} +#endif + +static void +wait_for_child(pid_t pid) +{ + pid_t child; + int status; + + do { + child = waitpid(pid, &status, 0); + } while (child == -1 && errno == EINTR); + + if (child != pid) + fatal("error waiting for child"); + + if (WIFEXITED(status)) { + int ret = WEXITSTATUS(status); + + if (ret != 0) + fatal("child returned error exit status %d", ret); + } else if (WIFSIGNALED(status)) { + int signo = WTERMSIG(status); + + fatal("child was killed by signal %d", signo); + } else { + fatal("unexpected status %d waiting for child", status); + } +} + +static void +write_pidfile(const char *filename, pid_t pid) +{ + FILE *fp; + int fd; + + fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0666); + if (fd < 0) + fp = NULL; + else + fp = fdopen(fd, "w"); + + if (fp == NULL) + fatal("unable to open pidfile '%s' for writing", filename); + + fprintf(fp, "%d\n", pid); + + if (fclose(fp)) + fatal("unable to close pidfile '%s'", filename); +} + +static void +remove_pidfile(const char *filename) +{ + if (unlink(filename) < 0 && errno != ENOENT) + fatal("cannot remove pidfile '%s'", filename); +} + +static void +daemonize(void) +{ + pid_t pid; + sigset_t mask; + sigset_t oldmask; + + if (quietmode < 0) + printf("Detaching to start %s...", startas); + + /* Block SIGCHLD to allow waiting for the child process while it is + * performing actions, such as creating a pidfile. */ + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) + fatal("cannot block SIGCHLD"); + + pid = fork(); + if (pid < 0) + fatal("unable to do first fork"); + else if (pid) { /* First Parent. */ + /* Wait for the second parent to exit, so that if we need to + * perform any actions there, like creating a pidfile, we do + * not suffer from race conditions on return. */ + wait_for_child(pid); + + _exit(0); + } + + /* Create a new session. */ + if (setsid() < 0) + fatal("cannot set session ID"); + + pid = fork(); + if (pid < 0) + fatal("unable to do second fork"); + else if (pid) { /* Second parent. */ + /* Set a default umask for dumb programs, which might get + * overridden by the --umask option later on, so that we get + * a defined umask when creating the pidfille. */ + umask(022); + + if (mpidfile && pidfile != NULL) + /* User wants _us_ to make the pidfile. */ + write_pidfile(pidfile, pid); + + _exit(0); + } + + if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) + fatal("cannot restore signal mask"); + + if (quietmode < 0) + printf("done.\n"); +} + +static void +pid_list_push(struct pid_list **list, pid_t pid) +{ + struct pid_list *p; + + p = xmalloc(sizeof(*p)); + p->next = *list; + p->pid = pid; + *list = p; +} + +static void +pid_list_free(struct pid_list **list) +{ + struct pid_list *here, *next; + + for (here = *list; here != NULL; here = next) { + next = here->next; + free(here); + } + + *list = NULL; +} + +static void +usage(void) +{ + printf( +"Usage: start-stop-daemon [<option>...] <command>\n" +"\n"); + + printf( +"Commands:\n" +" -S, --start -- <argument>... start a program and pass <arguments> to it\n" +" -K, --stop stop a program\n" +" -T, --status get the program status\n" +" -H, --help print help information\n" +" -V, --version print version\n" +"\n"); + + printf( +"Matching options (at least one is required):\n" +" --pid <pid> pid to check\n" +" --ppid <ppid> parent pid to check\n" +" -p, --pidfile <pid-file> pid file to check\n" +" -x, --exec <executable> program to start/check if it is running\n" +" -n, --name <process-name> process name to check\n" +" -u, --user <username|uid> process owner to check\n" +"\n"); + + printf( +"Options:\n" +" -g, --group <group|gid> run process as this group\n" +" -c, --chuid <name|uid[:group|gid]>\n" +" change to this user/group before starting\n" +" process\n" +" -s, --signal <signal> signal to send (default TERM)\n" +" -a, --startas <pathname> program to start (default is <executable>)\n" +" -r, --chroot <directory> chroot to <directory> before starting\n" +" -d, --chdir <directory> change to <directory> (default is /)\n" +" -N, --nicelevel <incr> add incr to the process' nice level\n" +" -P, --procsched <policy[:prio]>\n" +" use <policy> with <prio> for the kernel\n" +" process scheduler (default prio is 0)\n" +" -I, --iosched <class[:prio]> use <class> with <prio> to set the IO\n" +" scheduler (default prio is 4)\n" +" -k, --umask <mask> change the umask to <mask> before starting\n" +" -b, --background force the process to detach\n" +" -C, --no-close do not close any file descriptor\n" +" -m, --make-pidfile create the pidfile before starting\n" +" --remove-pidfile delete the pidfile after stopping\n" +" -R, --retry <schedule> check whether processes die, and retry\n" +" -t, --test test mode, don't do anything\n" +" -o, --oknodo exit status 0 (not 1) if nothing done\n" +" -q, --quiet be more quiet\n" +" -v, --verbose be more verbose\n" +"\n"); + + printf( +"Retry <schedule> is <item>|/<item>/... where <item> is one of\n" +" -<signal-num>|[-]<signal-name> send that signal\n" +" <timeout> wait that many seconds\n" +" forever repeat remainder forever\n" +"or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n" +"\n"); + + printf( +"The process scheduler <policy> can be one of:\n" +" other, fifo or rr\n" +"\n"); + + printf( +"The IO scheduler <class> can be one of:\n" +" real-time, best-effort or idle\n" +"\n"); + + printf( +"Exit status:\n" +" 0 = done\n" +" 1 = nothing done (=> 0 if --oknodo)\n" +" 2 = with --retry, processes would not die\n" +" 3 = trouble\n" +"Exit status with --status:\n" +" 0 = program is running\n" +" 1 = program is not running and the pid file exists\n" +" 3 = program is not running\n" +" 4 = unable to determine status\n"); +} + +static void +do_version(void) +{ + printf("start-stop-daemon %s for Debian\n\n", VERSION); + + printf("Written by Marek Michalkiewicz, public domain.\n"); +} + +static void DPKG_ATTR_NORET +badusage(const char *msg) +{ + if (msg) + fprintf(stderr, "%s: %s\n", progname, msg); + fprintf(stderr, "Try '%s --help' for more information.\n", progname); + + if (action == ACTION_STATUS) + exit(STATUS_UNKNOWN); + else + exit(3); +} + +struct sigpair { + const char *name; + int signal; +}; + +static const struct sigpair siglist[] = { + { "ABRT", SIGABRT }, + { "ALRM", SIGALRM }, + { "FPE", SIGFPE }, + { "HUP", SIGHUP }, + { "ILL", SIGILL }, + { "INT", SIGINT }, + { "KILL", SIGKILL }, + { "PIPE", SIGPIPE }, + { "QUIT", SIGQUIT }, + { "SEGV", SIGSEGV }, + { "TERM", SIGTERM }, + { "USR1", SIGUSR1 }, + { "USR2", SIGUSR2 }, + { "CHLD", SIGCHLD }, + { "CONT", SIGCONT }, + { "STOP", SIGSTOP }, + { "TSTP", SIGTSTP }, + { "TTIN", SIGTTIN }, + { "TTOU", SIGTTOU } +}; + +static int +parse_unsigned(const char *string, int base, int *value_r) +{ + long value; + char *endptr; + + if (!string[0]) + return -1; + + errno = 0; + value = strtol(string, &endptr, base); + if (string == endptr || *endptr != '\0' || errno != 0) + return -1; + if (value < 0 || value > INT_MAX) + return -1; + + *value_r = value; + return 0; +} + +static int +parse_pid(const char *pid_str, int *pid_num) +{ + if (parse_unsigned(pid_str, 10, pid_num) != 0) + return -1; + if (*pid_num == 0) + return -1; + + return 0; +} + +static int +parse_signal(const char *sig_str, int *sig_num) +{ + unsigned int i; + + if (parse_unsigned(sig_str, 10, sig_num) == 0) + return 0; + + for (i = 0; i < array_count(siglist); i++) { + if (strcmp(sig_str, siglist[i].name) == 0) { + *sig_num = siglist[i].signal; + return 0; + } + } + return -1; +} + +static int +parse_umask(const char *string, int *value_r) +{ + return parse_unsigned(string, 0, value_r); +} + +static void +validate_proc_schedule(void) +{ +#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0 + int prio_min, prio_max; + + prio_min = sched_get_priority_min(proc_sched->policy); + prio_max = sched_get_priority_max(proc_sched->policy); + + if (proc_sched->priority < prio_min) + badusage("process scheduler priority less than min"); + if (proc_sched->priority > prio_max) + badusage("process scheduler priority greater than max"); +#endif +} + +static void +parse_proc_schedule(const char *string) +{ + char *policy_str; + size_t policy_len; + int prio = 0; + + policy_len = strcspn(string, ":"); + policy_str = xstrndup(string, policy_len); + + if (string[policy_len] == ':' && + parse_unsigned(string + policy_len + 1, 10, &prio) != 0) + fatal("invalid process scheduler priority"); + + proc_sched = xmalloc(sizeof(*proc_sched)); + proc_sched->policy_name = policy_str; + + if (strcmp(policy_str, "other") == 0) { + proc_sched->policy = SCHED_OTHER; + proc_sched->priority = 0; + } else if (strcmp(policy_str, "fifo") == 0) { + proc_sched->policy = SCHED_FIFO; + proc_sched->priority = prio; + } else if (strcmp(policy_str, "rr") == 0) { + proc_sched->policy = SCHED_RR; + proc_sched->priority = prio; + } else + badusage("invalid process scheduler policy"); + + validate_proc_schedule(); +} + +static void +parse_io_schedule(const char *string) +{ + char *class_str; + size_t class_len; + int prio = 4; + + class_len = strcspn(string, ":"); + class_str = xstrndup(string, class_len); + + if (string[class_len] == ':' && + parse_unsigned(string + class_len + 1, 10, &prio) != 0) + fatal("invalid IO scheduler priority"); + + io_sched = xmalloc(sizeof(*io_sched)); + io_sched->policy_name = class_str; + + if (strcmp(class_str, "real-time") == 0) { + io_sched->policy = IOPRIO_CLASS_RT; + io_sched->priority = prio; + } else if (strcmp(class_str, "best-effort") == 0) { + io_sched->policy = IOPRIO_CLASS_BE; + io_sched->priority = prio; + } else if (strcmp(class_str, "idle") == 0) { + io_sched->policy = IOPRIO_CLASS_IDLE; + io_sched->priority = 7; + } else + badusage("invalid IO scheduler policy"); + + if (io_sched->priority < IO_SCHED_PRIO_MIN) + badusage("IO scheduler priority less than min"); + if (io_sched->priority > IO_SCHED_PRIO_MAX) + badusage("IO scheduler priority greater than max"); +} + +static void +set_proc_schedule(struct res_schedule *sched) +{ +#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0 + struct sched_param param; + + param.sched_priority = sched->priority; + + if (sched_setscheduler(getpid(), sched->policy, ¶m) == -1) + fatal("unable to set process scheduler"); +#endif +} + +#ifdef HAVE_IOPRIO_SET +static inline int +ioprio_set(int which, int who, int ioprio) +{ + return syscall(SYS_ioprio_set, which, who, ioprio); +} +#endif + +static void +set_io_schedule(struct res_schedule *sched) +{ +#ifdef HAVE_IOPRIO_SET + int io_sched_mask; + + io_sched_mask = IOPRIO_PRIO_VALUE(sched->policy, sched->priority); + if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), io_sched_mask) == -1) + warning("unable to alter IO priority to mask %i (%s)\n", + io_sched_mask, strerror(errno)); +#endif +} + +static void +parse_schedule_item(const char *string, struct schedule_item *item) +{ + const char *after_hyph; + + if (strcmp(string, "forever") == 0) { + item->type = sched_forever; + } else if (isdigit(string[0])) { + item->type = sched_timeout; + if (parse_unsigned(string, 10, &item->value) != 0) + badusage("invalid timeout value in schedule"); + } else if ((after_hyph = string + (string[0] == '-')) && + parse_signal(after_hyph, &item->value) == 0) { + item->type = sched_signal; + } else { + badusage("invalid schedule item (must be [-]<signal-name>, " + "-<signal-number>, <timeout> or 'forever'"); + } +} + +static void +parse_schedule(const char *schedule_str) +{ + char item_buf[20]; + const char *slash; + int count, repeatat; + size_t str_len; + + count = 0; + for (slash = schedule_str; *slash; slash++) + if (*slash == '/') + count++; + + schedule_length = (count == 0) ? 4 : count + 1; + schedule = xmalloc(sizeof(*schedule) * schedule_length); + + if (count == 0) { + schedule[0].type = sched_signal; + schedule[0].value = signal_nr; + parse_schedule_item(schedule_str, &schedule[1]); + if (schedule[1].type != sched_timeout) { + badusage("--retry takes timeout, or schedule list" + " of at least two items"); + } + schedule[2].type = sched_signal; + schedule[2].value = SIGKILL; + schedule[3] = schedule[1]; + } else { + count = 0; + repeatat = -1; + while (schedule_str != NULL) { + slash = strchr(schedule_str, '/'); + str_len = slash ? (size_t)(slash - schedule_str) : strlen(schedule_str); + if (str_len >= sizeof(item_buf)) + badusage("invalid schedule item: far too long" + " (you must delimit items with slashes)"); + memcpy(item_buf, schedule_str, str_len); + item_buf[str_len] = '\0'; + schedule_str = slash ? slash + 1 : NULL; + + parse_schedule_item(item_buf, &schedule[count]); + if (schedule[count].type == sched_forever) { + if (repeatat >= 0) + badusage("invalid schedule: 'forever'" + " appears more than once"); + repeatat = count; + continue; + } + count++; + } + if (repeatat == count) + badusage("invalid schedule: 'forever' appears last, " + "nothing to repeat"); + if (repeatat >= 0) { + schedule[count].type = sched_goto; + schedule[count].value = repeatat; + count++; + } + assert(count == schedule_length); + } +} + +static void +set_action(enum action_code new_action) +{ + if (action == new_action) + return; + + if (action != ACTION_NONE) + badusage("only one command can be specified"); + + action = new_action; +} + +#define OPT_PID 500 +#define OPT_PPID 501 +#define OPT_RM_PIDFILE 502 + +static void +parse_options(int argc, char * const *argv) +{ + static struct option longopts[] = { + { "help", 0, NULL, 'H'}, + { "stop", 0, NULL, 'K'}, + { "start", 0, NULL, 'S'}, + { "status", 0, NULL, 'T'}, + { "version", 0, NULL, 'V'}, + { "startas", 1, NULL, 'a'}, + { "name", 1, NULL, 'n'}, + { "oknodo", 0, NULL, 'o'}, + { "pid", 1, NULL, OPT_PID}, + { "ppid", 1, NULL, OPT_PPID}, + { "pidfile", 1, NULL, 'p'}, + { "quiet", 0, NULL, 'q'}, + { "signal", 1, NULL, 's'}, + { "test", 0, NULL, 't'}, + { "user", 1, NULL, 'u'}, + { "group", 1, NULL, 'g'}, + { "chroot", 1, NULL, 'r'}, + { "verbose", 0, NULL, 'v'}, + { "exec", 1, NULL, 'x'}, + { "chuid", 1, NULL, 'c'}, + { "nicelevel", 1, NULL, 'N'}, + { "procsched", 1, NULL, 'P'}, + { "iosched", 1, NULL, 'I'}, + { "umask", 1, NULL, 'k'}, + { "background", 0, NULL, 'b'}, + { "no-close", 0, NULL, 'C'}, + { "make-pidfile", 0, NULL, 'm'}, + { "remove-pidfile", 0, NULL, OPT_RM_PIDFILE}, + { "retry", 1, NULL, 'R'}, + { "chdir", 1, NULL, 'd'}, + { NULL, 0, NULL, 0 } + }; + const char *pid_str = NULL; + const char *ppid_str = NULL; + const char *umask_str = NULL; + const char *signal_str = NULL; + const char *schedule_str = NULL; + const char *proc_schedule_str = NULL; + const char *io_schedule_str = NULL; + size_t changeuser_len; + int c; + + for (;;) { + c = getopt_long(argc, argv, + "HKSVTa:n:op:qr:s:tu:vx:c:N:P:I:k:bCmR:g:d:", + longopts, NULL); + if (c == -1) + break; + switch (c) { + case 'H': /* --help */ + usage(); + exit(0); + case 'K': /* --stop */ + set_action(ACTION_STOP); + break; + case 'S': /* --start */ + set_action(ACTION_START); + break; + case 'T': /* --status */ + set_action(ACTION_STATUS); + break; + case 'V': /* --version */ + do_version(); + exit(0); + case 'a': /* --startas <pathname> */ + startas = optarg; + break; + case 'n': /* --name <process-name> */ + cmdname = optarg; + break; + case 'o': /* --oknodo */ + exitnodo = 0; + break; + case OPT_PID: /* --pid <pid> */ + pid_str = optarg; + break; + case OPT_PPID: /* --ppid <ppid> */ + ppid_str = optarg; + break; + case 'p': /* --pidfile <pid-file> */ + pidfile = optarg; + break; + case 'q': /* --quiet */ + quietmode = true; + break; + case 's': /* --signal <signal> */ + signal_str = optarg; + break; + case 't': /* --test */ + testmode = true; + break; + case 'u': /* --user <username>|<uid> */ + userspec = optarg; + break; + case 'v': /* --verbose */ + quietmode = -1; + break; + case 'x': /* --exec <executable> */ + execname = optarg; + break; + case 'c': /* --chuid <username>|<uid> */ + /* We copy the string just in case we need the + * argument later. */ + changeuser_len = strcspn(optarg, ":"); + changeuser = xstrndup(optarg, changeuser_len); + if (optarg[changeuser_len] == ':') { + if (optarg[changeuser_len + 1] == '\0') + fatal("missing group name"); + changegroup = optarg + changeuser_len + 1; + } + break; + case 'g': /* --group <group>|<gid> */ + changegroup = optarg; + break; + case 'r': /* --chroot /new/root */ + changeroot = optarg; + break; + case 'N': /* --nice */ + nicelevel = atoi(optarg); + break; + case 'P': /* --procsched */ + proc_schedule_str = optarg; + break; + case 'I': /* --iosched */ + io_schedule_str = optarg; + break; + case 'k': /* --umask <mask> */ + umask_str = optarg; + break; + case 'b': /* --background */ + background = true; + break; + case 'C': /* --no-close */ + close_io = false; + break; + case 'm': /* --make-pidfile */ + mpidfile = true; + break; + case OPT_RM_PIDFILE: /* --remove-pidfile */ + rpidfile = true; + break; + case 'R': /* --retry <schedule>|<timeout> */ + schedule_str = optarg; + break; + case 'd': /* --chdir /new/dir */ + changedir = optarg; + break; + default: + /* Message printed by getopt. */ + badusage(NULL); + } + } + + if (pid_str != NULL) { + if (parse_pid(pid_str, &match_pid) != 0) + badusage("pid value must be a number greater than 0"); + } + + if (ppid_str != NULL) { + if (parse_pid(ppid_str, &match_ppid) != 0) + badusage("ppid value must be a number greater than 0"); + } + + if (signal_str != NULL) { + if (parse_signal(signal_str, &signal_nr) != 0) + badusage("signal value must be numeric or name" + " of signal (KILL, INT, ...)"); + } + + if (schedule_str != NULL) { + parse_schedule(schedule_str); + } + + if (proc_schedule_str != NULL) + parse_proc_schedule(proc_schedule_str); + + if (io_schedule_str != NULL) + parse_io_schedule(io_schedule_str); + + if (umask_str != NULL) { + if (parse_umask(umask_str, &umask_value) != 0) + badusage("umask value must be a positive number"); + } + + if (action == ACTION_NONE) + badusage("need one of --start or --stop or --status"); + + if (!execname && !pid_str && !ppid_str && !pidfile && !userspec && + !cmdname) + badusage("need at least one of --exec, --pid, --ppid, --pidfile, --user or --name"); + +#ifdef PROCESS_NAME_SIZE + if (cmdname && strlen(cmdname) > PROCESS_NAME_SIZE) + warning("this system is not able to track process names\n" + "longer than %d characters, please use --exec " + "instead of --name.\n", PROCESS_NAME_SIZE); +#endif + + if (!startas) + startas = execname; + + if (action == ACTION_START && !startas) + badusage("--start needs --exec or --startas"); + + if (mpidfile && pidfile == NULL) + badusage("--make-pidfile requires --pidfile"); + if (rpidfile && pidfile == NULL) + badusage("--remove-pidfile requires --pidfile"); + + if (pid_str && pidfile) + badusage("need either --pid of --pidfile, not both"); + + if (background && action != ACTION_START) + badusage("--background is only relevant with --start"); + + if (!close_io && !background) + badusage("--no-close is only relevant with --background"); +} + +static void +setup_options(void) +{ + if (execname) { + char *fullexecname; + + /* If it's a relative path, normalize it. */ + if (execname[0] != '/') + execname = newpath(changedir, execname); + + if (changeroot) + fullexecname = newpath(changeroot, execname); + else + fullexecname = execname; + + if (stat(fullexecname, &exec_stat)) + fatal("unable to stat %s", fullexecname); + + if (fullexecname != execname) + free(fullexecname); + } + + if (userspec && parse_unsigned(userspec, 10, &user_id) < 0) { + struct passwd *pw; + + pw = getpwnam(userspec); + if (!pw) + fatal("user '%s' not found", userspec); + + user_id = pw->pw_uid; + } + + if (changegroup && parse_unsigned(changegroup, 10, &runas_gid) < 0) { + struct group *gr; + + gr = getgrnam(changegroup); + if (!gr) + fatal("group '%s' not found", changegroup); + changegroup = gr->gr_name; + runas_gid = gr->gr_gid; + } + if (changeuser) { + struct passwd *pw; + struct stat st; + + if (parse_unsigned(changeuser, 10, &runas_uid) == 0) + pw = getpwuid(runas_uid); + else + pw = getpwnam(changeuser); + if (!pw) + fatal("user '%s' not found", changeuser); + changeuser = pw->pw_name; + runas_uid = pw->pw_uid; + if (changegroup == NULL) { + /* Pass the default group of this user. */ + changegroup = ""; /* Just empty. */ + runas_gid = pw->pw_gid; + } + if (stat(pw->pw_dir, &st) == 0) + setenv("HOME", pw->pw_dir, 1); + } +} + +#if defined(OS_Linux) +static const char * +proc_status_field(pid_t pid, const char *field) +{ + static char *line = NULL; + static size_t line_size = 0; + + FILE *fp; + char filename[32]; + char *value = NULL; + ssize_t line_len; + size_t field_len = strlen(field); + + sprintf(filename, "/proc/%d/status", pid); + fp = fopen(filename, "r"); + if (!fp) + return NULL; + while ((line_len = getline(&line, &line_size, fp)) >= 0) { + if (strncasecmp(line, field, field_len) == 0) { + line[line_len - 1] = '\0'; + + value = line + field_len; + while (isspace(*value)) + value++; + + break; + } + } + fclose(fp); + + return value; +} +#elif defined(OS_AIX) +static bool +proc_get_psinfo(pid_t pid, struct psinfo *psinfo) +{ + char filename[64]; + FILE *fp; + + sprintf(filename, "/proc/%d/psinfo", pid); + fp = fopen(filename, "r"); + if (!fp) + return false; + if (fread(psinfo, sizeof(*psinfo), 1, fp) == 0) + return false; + if (ferror(fp)) + return false; + + return true; +} +#elif defined(OS_Hurd) +static void +init_procset(void) +{ + struct ps_context *context; + error_t err; + + err = ps_context_create(getproc(), &context); + if (err) + error(1, err, "ps_context_create"); + + err = proc_stat_list_create(context, &procset); + if (err) + error(1, err, "proc_stat_list_create"); + + err = proc_stat_list_add_all(procset, 0, 0); + if (err) + error(1, err, "proc_stat_list_add_all"); +} + +static struct proc_stat * +get_proc_stat(pid_t pid, ps_flags_t flags) +{ + struct proc_stat *ps; + ps_flags_t wanted_flags = PSTAT_PID | flags; + + if (!procset) + init_procset(); + + ps = proc_stat_list_pid_proc_stat(procset, pid); + if (!ps) + return NULL; + if (proc_stat_set_flags(ps, wanted_flags)) + return NULL; + if ((proc_stat_flags(ps) & wanted_flags) != wanted_flags) + return NULL; + + return ps; +} +#elif defined(HAVE_KVM_H) +static kvm_t * +ssd_kvm_open(void) +{ + kvm_t *kd; + char errbuf[_POSIX2_LINE_MAX]; + + kd = kvm_openfiles(NULL, KVM_MEMFILE, NULL, O_RDONLY, errbuf); + if (kd == NULL) + errx(1, "%s", errbuf); + + return kd; +} + +static struct kinfo_proc * +ssd_kvm_get_procs(kvm_t *kd, int op, int arg, int *count) +{ + struct kinfo_proc *kp; + int lcount; + + if (count == NULL) + count = &lcount; + *count = 0; + +#if defined(OS_OpenBSD) + kp = kvm_getprocs(kd, op, arg, sizeof(*kp), count); +#else + kp = kvm_getprocs(kd, op, arg, count); +#endif + if (kp == NULL && errno != ESRCH) + errx(1, "%s", kvm_geterr(kd)); + + return kp; +} +#endif + +#if defined(OS_Linux) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + char lname[32]; + char lcontents[_POSIX_PATH_MAX + 1]; + char *filename; + const char deleted[] = " (deleted)"; + int nread; + struct stat sb; + + sprintf(lname, "/proc/%d/exe", pid); + nread = readlink(lname, lcontents, sizeof(lcontents) - 1); + if (nread == -1) + return false; + + filename = lcontents; + filename[nread] = '\0'; + + /* OpenVZ kernels contain a bogus patch that instead of appending, + * prepends the deleted marker. Workaround those. Otherwise handle + * the normal appended marker. */ + if (strncmp(filename, deleted, strlen(deleted)) == 0) + filename += strlen(deleted); + else if (strcmp(filename + nread - strlen(deleted), deleted) == 0) + filename[nread - strlen(deleted)] = '\0'; + + if (stat(filename, &sb) != 0) + return false; + + return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); +} +#elif defined(OS_AIX) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + struct stat sb; + char filename[64]; + + sprintf(filename, "/proc/%d/object/a.out", pid); + + if (stat(filename, &sb) != 0) + return false; + + return sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino; +} +#elif defined(OS_Hurd) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + struct proc_stat *ps; + struct stat sb; + const char *filename; + + ps = get_proc_stat(pid, PSTAT_ARGS); + if (ps == NULL) + return false; + + /* On old Hurd systems we have to use the argv[0] value, because + * there is nothing better. */ + filename = proc_stat_args(ps); +#ifdef PSTAT_EXE + /* On new Hurd systems we can use the correct value, as long + * as it's not NULL nor empty, as it was the case on the first + * implementation. */ + if (proc_stat_set_flags(ps, PSTAT_EXE) == 0 && + proc_stat_flags(ps) & PSTAT_EXE && + proc_stat_exe(ps) != NULL && + proc_stat_exe(ps)[0] != '\0') + filename = proc_stat_exe(ps); +#endif + + if (stat(filename, &sb) != 0) + return false; + + return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); +} +#elif defined(OS_Darwin) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + struct stat sb; + char pathname[_POSIX_PATH_MAX]; + + if (proc_pidpath(pid, pathname, sizeof(pathname)) < 0) + return false; + + if (stat(pathname, &sb) != 0) + return false; + + return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); +} +#elif defined(OS_HPUX) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + struct pst_status pst; + + if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) + return false; + return ((dev_t)pst.pst_text.psf_fsid.psfs_id == esb->st_dev && + (ino_t)pst.pst_text.psf_fileid == esb->st_ino); +} +#elif defined(OS_FreeBSD) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + struct stat sb; + int error, mib[4]; + size_t len; + char pathname[PATH_MAX]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = pid; + len = sizeof(pathname); + + error = sysctl(mib, 4, pathname, &len, NULL, 0); + if (error != 0 && errno != ESRCH) + return false; + if (len == 0) + pathname[0] = '\0'; + + if (stat(pathname, &sb) != 0) + return false; + + return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); +} +#elif defined(HAVE_KVM_H) +static bool +pid_is_exec(pid_t pid, const struct stat *esb) +{ + kvm_t *kd; + int argv_len = 0; + struct kinfo_proc *kp; + struct stat sb; + char buf[_POSIX2_LINE_MAX]; + char **pid_argv_p; + char *start_argv_0_p, *end_argv_0_p; + bool res = false; + + kd = ssd_kvm_open(); + kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); + if (kp == NULL) + goto cleanup; + + pid_argv_p = kvm_getargv(kd, kp, argv_len); + if (pid_argv_p == NULL) + errx(1, "%s", kvm_geterr(kd)); + + /* Find and compare string. */ + start_argv_0_p = *pid_argv_p; + + /* Find end of argv[0] then copy and cut of str there. */ + end_argv_0_p = strchr(*pid_argv_p, ' '); + if (end_argv_0_p == NULL) + /* There seems to be no space, so we have the command + * already in its desired form. */ + start_argv_0_p = *pid_argv_p; + else { + /* Tests indicate that this never happens, since + * kvm_getargv itself cuts of tailing stuff. This is + * not what the manpage says, however. */ + strncpy(buf, *pid_argv_p, (end_argv_0_p - start_argv_0_p)); + buf[(end_argv_0_p - start_argv_0_p) + 1] = '\0'; + start_argv_0_p = buf; + } + + if (stat(start_argv_0_p, &sb) != 0) + goto cleanup; + + res = (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); + +cleanup: + kvm_close(kd); + + return res; +} +#endif + +#if defined(OS_Linux) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + const char *ppid_str; + pid_t proc_ppid; + int rc; + + ppid_str = proc_status_field(pid, "PPid:"); + if (ppid_str == NULL) + return false; + + rc = parse_pid(ppid_str, &proc_ppid); + if (rc < 0) + return false; + + return proc_ppid == ppid; +} +#elif defined(OS_Hurd) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + struct proc_stat *ps; + struct procinfo *pi; + + ps = get_proc_stat(pid, PSTAT_PROC_INFO); + if (ps == NULL) + return false; + + pi = proc_stat_proc_info(ps); + + return pi->ppid == ppid; +} +#elif defined(OS_Darwin) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + struct proc_bsdinfo info; + + if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, sizeof(info)) < 0) + return false; + + return (pid_t)info.pbi_ppid == ppid; +} +#elif defined(OS_AIX) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + struct psinfo psi; + + if (!proc_get_psinfo(pid, &psi)) + return false; + + return (pid_t)psi.pr_ppid == ppid; +} +#elif defined(OS_HPUX) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + struct pst_status pst; + + if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) + return false; + + return pst.pst_ppid == ppid; +} +#elif defined(OS_FreeBSD) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + struct kinfo_proc kp; + int rc, mib[4]; + size_t len; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + len = sizeof(kp); + + rc = sysctl(mib, 4, &kp, &len, NULL, 0); + if (rc != 0 && errno != ESRCH) + return false; + if (len == 0 || len != sizeof(kp)) + return false; + + return kp.ki_ppid == ppid; +} +#elif defined(HAVE_KVM_H) +static bool +pid_is_child(pid_t pid, pid_t ppid) +{ + kvm_t *kd; + struct kinfo_proc *kp; + pid_t proc_ppid; + bool res = false; + + kd = ssd_kvm_open(); + kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); + if (kp == NULL) + goto cleanup; + +#if defined(OS_FreeBSD) + proc_ppid = kp->ki_ppid; +#elif defined(OS_OpenBSD) + proc_ppid = kp->p_ppid; +#elif defined(OS_DragonFlyBSD) + proc_ppid = kp->kp_ppid; +#else + proc_ppid = kp->kp_proc.p_ppid; +#endif + + res = (proc_ppid == ppid); + +cleanup: + kvm_close(kd); + + return res; +} +#endif + +#if defined(OS_Linux) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + struct stat sb; + char buf[32]; + + sprintf(buf, "/proc/%d", pid); + if (stat(buf, &sb) != 0) + return false; + return (sb.st_uid == uid); +} +#elif defined(OS_Hurd) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + struct proc_stat *ps; + + ps = get_proc_stat(pid, PSTAT_OWNER_UID); + return ps && (uid_t)proc_stat_owner_uid(ps) == uid; +} +#elif defined(OS_Darwin) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + struct proc_bsdinfo info; + + if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, sizeof(info)) < 0) + return false; + + return info.pbi_ruid == uid; +} +#elif defined(OS_AIX) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + struct psinfo psi; + + if (!proc_get_psinfo(pid, &psi)) + return false; + + return psi.pr_uid == uid; +} +#elif defined(OS_HPUX) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + struct pst_status pst; + + if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) + return false; + return ((uid_t)pst.pst_uid == uid); +} +#elif defined(OS_FreeBSD) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + struct kinfo_proc kp; + int rc, mib[4]; + size_t len; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + len = sizeof(kp); + + rc = sysctl(mib, 4, &kp, &len, NULL, 0); + if (rc != 0 && errno != ESRCH) + return false; + if (len == 0 || len != sizeof(kp)) + return false; + + return kp.ki_ruid == uid; +} +#elif defined(HAVE_KVM_H) +static bool +pid_is_user(pid_t pid, uid_t uid) +{ + kvm_t *kd; + uid_t proc_uid; + struct kinfo_proc *kp; + bool res = false; + + kd = ssd_kvm_open(); + kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); + if (kp == NULL) + goto cleanup; + +#if defined(OS_FreeBSD) + proc_uid = kp->ki_ruid; +#elif defined(OS_OpenBSD) + proc_uid = kp->p_ruid; +#elif defined(OS_DragonFlyBSD) + proc_uid = kp->kp_ruid; +#elif defined(OS_NetBSD) + proc_uid = kp->kp_eproc.e_pcred.p_ruid; +#else + if (kp->kp_proc.p_cred) + kvm_read(kd, (u_long)&(kp->kp_proc.p_cred->p_ruid), + &proc_uid, sizeof(uid_t)); + else + goto cleanup; +#endif + + res = (proc_uid == (uid_t)uid); + +cleanup: + kvm_close(kd); + + return res; +} +#endif + +#if defined(OS_Linux) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + const char *comm; + + comm = proc_status_field(pid, "Name:"); + if (comm == NULL) + return false; + + return strcmp(comm, name) == 0; +} +#elif defined(OS_Hurd) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + struct proc_stat *ps; + size_t argv0_len; + const char *argv0; + const char *binary_name; + + ps = get_proc_stat(pid, PSTAT_ARGS); + if (ps == NULL) + return false; + + argv0 = proc_stat_args(ps); + argv0_len = strlen(argv0) + 1; + + binary_name = basename(argv0); + if (strcmp(binary_name, name) == 0) + return true; + + /* XXX: This is all kinds of ugly, but on the Hurd there's no way to + * know the command name of a process, so we have to try to match + * also on argv[1] for the case of an interpreted script. */ + if (proc_stat_args_len(ps) > argv0_len) { + const char *script_name = basename(argv0 + argv0_len); + + return strcmp(script_name, name) == 0; + } + + return false; +} +#elif defined(OS_AIX) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + struct psinfo psi; + + if (!proc_get_psinfo(pid, &psi)) + return false; + + return strcmp(psi.pr_fname, name) == 0; +} +#elif defined(OS_HPUX) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + struct pst_status pst; + + if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0) + return false; + return (strcmp(pst.pst_ucomm, name) == 0); +} +#elif defined(OS_Darwin) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + char pathname[_POSIX_PATH_MAX]; + + if (proc_pidpath(pid, pathname, sizeof(pathname)) < 0) + return false; + + return strcmp(pathname, name) == 0; +} +#elif defined(OS_FreeBSD) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + struct kinfo_proc kp; + int rc, mib[4]; + size_t len; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + len = sizeof(kp); + + rc = sysctl(mib, 4, &kp, &len, NULL, 0); + if (rc != 0 && errno != ESRCH) + return false; + if (len == 0 || len != sizeof(kp)) + return false; + + return strcmp(kp.ki_comm, name) == 0; +} +#elif defined(HAVE_KVM_H) +static bool +pid_is_cmd(pid_t pid, const char *name) +{ + kvm_t *kd; + struct kinfo_proc *kp; + char *process_name; + bool res = false; + + kd = ssd_kvm_open(); + kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL); + if (kp == NULL) + goto cleanup; + +#if defined(OS_FreeBSD) + process_name = kp->ki_comm; +#elif defined(OS_OpenBSD) + process_name = kp->p_comm; +#elif defined(OS_DragonFlyBSD) + process_name = kp->kp_comm; +#else + process_name = kp->kp_proc.p_comm; +#endif + + res = (strcmp(name, process_name) == 0); + +cleanup: + kvm_close(kd); + + return res; +} +#endif + +#if defined(OS_Hurd) +static bool +pid_is_running(pid_t pid) +{ + return get_proc_stat(pid, 0) != NULL; +} +#else /* !OS_Hurd */ +static bool +pid_is_running(pid_t pid) +{ + if (kill(pid, 0) == 0 || errno == EPERM) + return true; + else if (errno == ESRCH) + return false; + else + fatal("error checking pid %u status", pid); +} +#endif + +static enum status_code +pid_check(pid_t pid) +{ + if (execname && !pid_is_exec(pid, &exec_stat)) + return STATUS_DEAD; + if (match_ppid > 0 && !pid_is_child(pid, match_ppid)) + return STATUS_DEAD; + if (userspec && !pid_is_user(pid, user_id)) + return STATUS_DEAD; + if (cmdname && !pid_is_cmd(pid, cmdname)) + return STATUS_DEAD; + if (action != ACTION_STOP && !pid_is_running(pid)) + return STATUS_DEAD; + + pid_list_push(&found, pid); + + return STATUS_OK; +} + +static enum status_code +do_pidfile(const char *name) +{ + FILE *f; + static pid_t pid = 0; + + if (pid) + return pid_check(pid); + + f = fopen(name, "r"); + if (f) { + enum status_code pid_status; + + if (fscanf(f, "%d", &pid) == 1) + pid_status = pid_check(pid); + else + pid_status = STATUS_UNKNOWN; + fclose(f); + + if (pid_status == STATUS_DEAD) + return STATUS_DEAD_PIDFILE; + else + return pid_status; + } else if (errno == ENOENT) + return STATUS_DEAD; + else + fatal("unable to open pidfile %s", name); +} + +#if defined(OS_Linux) || defined(OS_Solaris) || defined(OS_AIX) +static enum status_code +do_procinit(void) +{ + DIR *procdir; + struct dirent *entry; + int foundany; + pid_t pid; + enum status_code prog_status = STATUS_DEAD; + + procdir = opendir("/proc"); + if (!procdir) + fatal("unable to opendir /proc"); + + foundany = 0; + while ((entry = readdir(procdir)) != NULL) { + enum status_code pid_status; + + if (sscanf(entry->d_name, "%d", &pid) != 1) + continue; + foundany++; + + pid_status = pid_check(pid); + if (pid_status < prog_status) + prog_status = pid_status; + } + closedir(procdir); + if (!foundany) + fatal("nothing in /proc - not mounted?"); + + return prog_status; +} +#elif defined(OS_Hurd) +static int +check_proc_stat(struct proc_stat *ps) +{ + pid_check(proc_stat_pid(ps)); + return 0; +} + +static enum status_code +do_procinit(void) +{ + if (!procset) + init_procset(); + + proc_stat_list_for_each(procset, check_proc_stat); + + if (found) + return STATUS_OK; + else + return STATUS_DEAD; +} +#elif defined(OS_Darwin) +static enum status_code +do_procinit(void) +{ + pid_t *pid_buf; + int i, npids, pid_bufsize; + enum status_code prog_status = STATUS_DEAD; + + npids = proc_listallpids(NULL, 0); + if (npids == 0) + return STATUS_UNKNOWN; + + /* Try to avoid sudden changes in number of PIDs. */ + npids += 4096; + pid_bufsize = sizeof(pid_t) * npids; + pid_buf = xmalloc(pid_bufsize); + + npids = proc_listallpids(pid_buf, pid_bufsize); + if (npids == 0) + return STATUS_UNKNOWN; + + for (i = 0; i < npids; i++) { + enum status_code pid_status; + + pid_status = pid_check(pid_buf[i]); + if (pid_status < prog_status) + prog_status = pid_status; + } + + free(pid_buf); + + return prog_status; +} +#elif defined(OS_HPUX) +static enum status_code +do_procinit(void) +{ + struct pst_status pst[10]; + int i, count; + int idx = 0; + enum status_code prog_status = STATUS_DEAD; + + while ((count = pstat_getproc(pst, sizeof(pst[0]), 10, idx)) > 0) { + enum status_code pid_status; + + for (i = 0; i < count; i++) { + pid_status = pid_check(pst[i].pst_pid); + if (pid_status < prog_status) + prog_status = pid_status; + } + idx = pst[count - 1].pst_idx + 1; + } + + return prog_status; +} +#elif defined(OS_FreeBSD) +static enum status_code +do_procinit(void) +{ + struct kinfo_proc *kp; + int rc, mib[3]; + size_t len = 0; + int nentries, i; + enum status_code prog_status = STATUS_DEAD; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PROC; + + rc = sysctl(mib, 3, NULL, &len, NULL, 0); + if (rc != 0 && errno != ESRCH) + return STATUS_UNKNOWN; + if (len == 0) + return STATUS_UNKNOWN; + + kp = xmalloc(len); + rc = sysctl(mib, 3, kp, &len, NULL, 0); + if (rc != 0 && errno != ESRCH) + return STATUS_UNKNOWN; + if (len == 0) + return STATUS_UNKNOWN; + nentries = len / sizeof(*kp); + + for (i = 0; i < nentries; i++) { + enum status_code pid_status; + + pid_status = pid_check(kp[i].ki_pid); + if (pid_status < prog_status) + prog_status = pid_status; + } + + free(kp); + + return prog_status; +} +#elif defined(HAVE_KVM_H) +static enum status_code +do_procinit(void) +{ + kvm_t *kd; + int nentries, i; + struct kinfo_proc *kp; + enum status_code prog_status = STATUS_DEAD; + + kd = ssd_kvm_open(); + kp = ssd_kvm_get_procs(kd, KERN_PROC_ALL, 0, &nentries); + + for (i = 0; i < nentries; i++) { + enum status_code pid_status; + pid_t pid; + +#if defined(OS_FreeBSD) + pid = kp[i].ki_pid; +#elif defined(OS_OpenBSD) + pid = kp[i].p_pid; +#elif defined(OS_DragonFlyBSD) + pid = kp[i].kp_pid; +#else + pid = kp[i].kp_proc.p_pid; +#endif + + pid_status = pid_check(pid); + if (pid_status < prog_status) + prog_status = pid_status; + } + + kvm_close(kd); + + return prog_status; +} +#endif + +static enum status_code +do_findprocs(void) +{ + pid_list_free(&found); + + if (match_pid > 0) + return pid_check(match_pid); + else if (pidfile) + return do_pidfile(pidfile); + else + return do_procinit(); +} + +static int +do_start(int argc, char **argv) +{ + int devnull_fd = -1; + gid_t rgid; + uid_t ruid; + + do_findprocs(); + + if (found) { + if (quietmode <= 0) + printf("%s already running.\n", execname ? execname : "process"); + return exitnodo; + } + if (testmode && quietmode <= 0) { + printf("Would start %s ", startas); + while (argc-- > 0) + printf("%s ", *argv++); + if (changeuser != NULL) { + printf(" (as user %s[%d]", changeuser, runas_uid); + if (changegroup != NULL) + printf(", and group %s[%d])", changegroup, runas_gid); + else + printf(")"); + } + if (changeroot != NULL) + printf(" in directory %s", changeroot); + if (nicelevel) + printf(", and add %i to the priority", nicelevel); + if (proc_sched) + printf(", with scheduling policy %s with priority %i", + proc_sched->policy_name, proc_sched->priority); + if (io_sched) + printf(", with IO scheduling class %s with priority %i", + io_sched->policy_name, io_sched->priority); + printf(".\n"); + } + if (testmode) + return 0; + if (quietmode < 0) + printf("Starting %s...\n", startas); + *--argv = startas; + if (background) + /* Ok, we need to detach this process. */ + daemonize(); + else if (mpidfile && pidfile != NULL) + /* User wants _us_ to make the pidfile, but detach themself! */ + write_pidfile(pidfile, getpid()); + if (background && close_io) { + devnull_fd = open("/dev/null", O_RDWR); + if (devnull_fd < 0) + fatal("unable to open '%s'", "/dev/null"); + } + if (nicelevel) { + errno = 0; + if ((nice(nicelevel) == -1) && (errno != 0)) + fatal("unable to alter nice level by %i", nicelevel); + } + if (proc_sched) + set_proc_schedule(proc_sched); + if (io_sched) + set_io_schedule(io_sched); + if (umask_value >= 0) + umask(umask_value); + if (changeroot != NULL) { + if (chdir(changeroot) < 0) + fatal("unable to chdir() to %s", changeroot); + if (chroot(changeroot) < 0) + fatal("unable to chroot() to %s", changeroot); + } + if (chdir(changedir) < 0) + fatal("unable to chdir() to %s", changedir); + + rgid = getgid(); + ruid = getuid(); + if (changegroup != NULL) { + if (rgid != (gid_t)runas_gid) + if (setgid(runas_gid)) + fatal("unable to set gid to %d", runas_gid); + } + if (changeuser != NULL) { + /* We assume that if our real user and group are the same as + * the ones we should switch to, the supplementary groups + * will be already in place. */ + if (rgid != (gid_t)runas_gid || ruid != (uid_t)runas_uid) + if (initgroups(changeuser, runas_gid)) + fatal("unable to set initgroups() with gid %d", + runas_gid); + + if (ruid != (uid_t)runas_uid) + if (setuid(runas_uid)) + fatal("unable to set uid to %s", changeuser); + } + + if (background && close_io) { + int i; + + dup2(devnull_fd, 0); /* stdin */ + dup2(devnull_fd, 1); /* stdout */ + dup2(devnull_fd, 2); /* stderr */ + + /* Now close all extra fds. */ + for (i = get_open_fd_max() - 1; i >= 3; --i) + close(i); + } + execv(startas, argv); + fatal("unable to start %s", startas); +} + +static void +do_stop(int sig_num, int *n_killed, int *n_notkilled) +{ + struct pid_list *p; + + do_findprocs(); + + *n_killed = 0; + *n_notkilled = 0; + + if (!found) + return; + + pid_list_free(&killed); + + for (p = found; p; p = p->next) { + if (testmode) { + if (quietmode <= 0) + printf("Would send signal %d to %d.\n", + sig_num, p->pid); + (*n_killed)++; + } else if (kill(p->pid, sig_num) == 0) { + pid_list_push(&killed, p->pid); + (*n_killed)++; + } else { + if (sig_num) + warning("failed to kill %d: %s\n", + p->pid, strerror(errno)); + (*n_notkilled)++; + } + } +} + +static void +do_stop_summary(int retry_nr) +{ + struct pid_list *p; + + if (quietmode >= 0 || !killed) + return; + + printf("Stopped %s (pid", what_stop); + for (p = killed; p; p = p->next) + printf(" %d", p->pid); + putchar(')'); + if (retry_nr > 0) + printf(", retry #%d", retry_nr); + printf(".\n"); +} + +static void DPKG_ATTR_PRINTF(1) +set_what_stop(const char *format, ...) +{ + va_list arglist; + int rc; + + va_start(arglist, format); + rc = vasprintf(&what_stop, format, arglist); + va_end(arglist); + + if (rc < 0) + fatal("cannot allocate formatted string"); +} + +/* + * We want to keep polling for the processes, to see if they've exited, or + * until the timeout expires. + * + * This is a somewhat complicated algorithm to try to ensure that we notice + * reasonably quickly when all the processes have exited, but don't spend + * too much CPU time polling. In particular, on a fast machine with + * quick-exiting daemons we don't want to delay system shutdown too much, + * whereas on a slow one, or where processes are taking some time to exit, + * we want to increase the polling interval. + * + * The algorithm is as follows: we measure the elapsed time it takes to do + * one poll(), and wait a multiple of this time for the next poll. However, + * if that would put us past the end of the timeout period we wait only as + * long as the timeout period, but in any case we always wait at least + * MIN_POLL_INTERVAL (20ms). The multiple (‘ratio’) starts out as 2, and + * increases by 1 for each poll to a maximum of 10; so we use up to between + * 30% and 10% of the machine's resources (assuming a few reasonable things + * about system performance). + */ +static bool +do_stop_timeout(int timeout, int *n_killed, int *n_notkilled) +{ + struct timespec stopat, before, after, interval, maxinterval; + int rc, ratio; + + timespec_gettime(&stopat); + stopat.tv_sec += timeout; + ratio = 1; + for (;;) { + timespec_gettime(&before); + if (timespec_cmp(&before, &stopat, >)) + return false; + + do_stop(0, n_killed, n_notkilled); + if (!*n_killed) + return true; + + timespec_gettime(&after); + + if (!timespec_cmp(&after, &stopat, <)) + return false; + + if (ratio < 10) + ratio++; + + timespec_sub(&stopat, &after, &maxinterval); + timespec_sub(&after, &before, &interval); + timespec_mul(&interval, ratio); + + if (interval.tv_sec < 0 || interval.tv_nsec < 0) + interval.tv_sec = interval.tv_nsec = 0; + + if (timespec_cmp(&interval, &maxinterval, >)) + interval = maxinterval; + + if (interval.tv_sec == 0 && + interval.tv_nsec <= MIN_POLL_INTERVAL) + interval.tv_nsec = MIN_POLL_INTERVAL; + + rc = pselect(0, NULL, NULL, NULL, &interval, NULL); + if (rc < 0 && errno != EINTR) + fatal("select() failed for pause"); + } +} + +static int +finish_stop_schedule(bool anykilled) +{ + if (rpidfile && pidfile && !testmode) + remove_pidfile(pidfile); + + if (anykilled) + return 0; + + if (quietmode <= 0) + printf("No %s found running; none killed.\n", what_stop); + + return exitnodo; +} + +static int +run_stop_schedule(void) +{ + int position, n_killed, n_notkilled, value, retry_nr; + bool anykilled; + + if (testmode) { + if (schedule != NULL) { + if (quietmode <= 0) + printf("Ignoring --retry in test mode\n"); + schedule = NULL; + } + } + + if (cmdname) + set_what_stop("%s", cmdname); + else if (execname) + set_what_stop("%s", execname); + else if (pidfile) + set_what_stop("process in pidfile '%s'", pidfile); + else if (match_pid > 0) + set_what_stop("process with pid %d", match_pid); + else if (match_ppid > 0) + set_what_stop("process(es) with parent pid %d", match_ppid); + else if (userspec) + set_what_stop("process(es) owned by '%s'", userspec); + else + fatal("internal error, no match option, please report"); + + anykilled = false; + retry_nr = 0; + + if (schedule == NULL) { + do_stop(signal_nr, &n_killed, &n_notkilled); + do_stop_summary(0); + if (n_notkilled > 0 && quietmode <= 0) + printf("%d pids were not killed\n", n_notkilled); + if (n_killed) + anykilled = true; + return finish_stop_schedule(anykilled); + } + + for (position = 0; position < schedule_length; position++) { + reposition: + value = schedule[position].value; + n_notkilled = 0; + + switch (schedule[position].type) { + case sched_goto: + position = value; + goto reposition; + case sched_signal: + do_stop(value, &n_killed, &n_notkilled); + do_stop_summary(retry_nr++); + if (!n_killed) + return finish_stop_schedule(anykilled); + else + anykilled = true; + continue; + case sched_timeout: + if (do_stop_timeout(value, &n_killed, &n_notkilled)) + return finish_stop_schedule(anykilled); + else + continue; + default: + assert(!"schedule[].type value must be valid"); + } + } + + if (quietmode <= 0) + printf("Program %s, %d process(es), refused to die.\n", + what_stop, n_killed); + + return 2; +} + +int +main(int argc, char **argv) +{ + progname = argv[0]; + + parse_options(argc, argv); + setup_options(); + + argc -= optind; + argv += optind; + + if (action == ACTION_START) + return do_start(argc, argv); + else if (action == ACTION_STOP) + return run_stop_schedule(); + else if (action == ACTION_STATUS) + return do_findprocs(); + + return 0; +} |