/* * 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 , * public domain. Based conceptually on start-stop-daemon.pl, by Ian * Jackson . May be used and distributed * freely for any purpose. Changes by Christian Schwarz * , to make output conform to the Debian * Console Message Standard, also placed in public domain. Minor * changes by Klee Dienes , also placed in the Public * Domain. * * Changes by Ben Collins , added --chuid, --background * and --make-pidfile options, placed in public domain as well. * * Port to OpenBSD by Sontri Tomo Huynh * and Andreas Schuldei * * Changes by Ian Jackson: added --retry (and associated rearrangements). */ #include #include #include #if defined(linux) # define OSLinux #elif defined(__GNU__) # define OSHurd #elif defined(__sun) # define OSsunos #elif defined(OPENBSD) || defined(__OpenBSD__) # define OSOpenBSD #elif defined(hpux) # define OShpux #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) # define OSFreeBSD #elif defined(__NetBSD__) # define OSNetBSD #elif defined(__DragonFly__) # define OSDragonFlyBSD #else # error Unknown architecture - cannot build start-stop-daemon #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_SYSCALL_H #include #endif #ifdef HAVE_SYS_SYSCTL_H #include #endif #ifdef HAVE_SYS_PROC_H #include #endif #ifdef HAVE_SYS_USER_H #include #endif #ifdef HAVE_SYS_PSTAT_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDDEF_H #include #endif #include #include #include #include #include #ifdef HAVE_ERROR_H #include #endif #ifdef HAVE_ERR_H #include #endif #if defined(OSHurd) #include #include #endif #ifdef HAVE_KVM_H #include #if defined(OSFreeBSD) #define KVM_MEMFILE "/dev/null" #else #define KVM_MEMFILE NULL #endif #endif #ifdef _POSIX_PRIORITY_SCHEDULING #include #else #define SCHED_OTHER -1 #define SCHED_FIFO -1 #define SCHED_RR -1 #endif #if defined(OSLinux) /* This comes from TASK_COMM_LEN defined in Linux' include/linux/sched.h. */ #define PROCESS_NAME_SIZE 15 #elif defined(OSsunos) #define PROCESS_NAME_SIZE 15 #elif defined(OSDarwin) #define PROCESS_NAME_SIZE 16 #elif defined(OSNetBSD) #define PROCESS_NAME_SIZE 16 #elif defined(OSOpenBSD) #define PROCESS_NAME_SIZE 16 #elif defined(OSFreeBSD) #define PROCESS_NAME_SIZE 19 #elif defined(OSDragonFlyBSD) /* 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[1024]; static const char *progname = ""; static int nicelevel = 0; static int umask_value = -1; static struct stat exec_stat; #if defined(OSHurd) 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 err = WEXITSTATUS(status); if (err != 0) fatal("child returned error exit status %d", err); } 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 [