/* * 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 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 #endif #ifdef HAVE_SYS_SYSCALL_H #include #endif #ifdef HAVE_SYS_SYSCTL_H #include #endif #ifdef HAVE_SYS_PROCFS_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 #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(OS_Hurd) #include #include #endif #if defined(OS_Darwin) #include #endif #ifdef HAVE_KVM_H #include #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 #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 . */ #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, }; enum match_code { MATCH_NONE = 0, MATCH_PID = 1 << 0, MATCH_PPID = 1 << 1, MATCH_PIDFILE = 1 << 2, MATCH_EXEC = 1 << 3, MATCH_NAME = 1 << 4, MATCH_USER = 1 << 5, }; /* 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 = 20L * NANOSEC_IN_MILLISEC; static enum action_code action; static enum match_code match_mode; static bool testmode = false; static int quietmode = 0; static int exitnodo = 1; static bool background = false; static bool close_io = true; static const char *output_io; static bool notify_await = false; static int notify_timeout = 60; static char *notify_sockdir; static char *notify_socket; 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) debug(const char *format, ...) { va_list arglist; if (quietmode >= 0) return; va_start(arglist, format); vprintf(format, arglist); va_end(arglist); } static void DPKG_ATTR_PRINTF(1) info(const char *format, ...) { va_list arglist; if (quietmode > 0) return; va_start(arglist, format); vprintf(format, arglist); va_end(arglist); } 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_VPRINTF(2) fatalv(int errno_fatal, const char *format, va_list args) { va_list args_copy; fprintf(stderr, "%s: ", progname); va_copy(args_copy, args); vfprintf(stderr, format, args_copy); va_end(args_copy); 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 DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1) fatal(const char *format, ...) { va_list args; va_start(args, format); fatalv(0, format, args); } static void DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1) fatale(const char *format, ...) { va_list args; va_start(args, format); fatalv(errno, format, args); } #define BUG(...) bug(__FILE__, __LINE__, __func__, __VA_ARGS__) static void DPKG_ATTR_NORET DPKG_ATTR_PRINTF(4) bug(const char *file, int line, const char *func, const char *format, ...) { va_list arglist; fprintf(stderr, "%s:%s:%d:%s: internal error: ", progname, file, line, func); va_start(arglist, format); vfprintf(stderr, format, arglist); va_end(arglist); if (action == ACTION_STATUS) exit(STATUS_UNKNOWN); else exit(3); } static void * xmalloc(int size) { void *ptr; ptr = malloc(size); if (ptr) return ptr; fatale("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; fatale("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) fatale("clock_gettime failed"); #else struct timeval tv; if (gettimeofday(&tv, NULL) != 0) fatale("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 int parse_unsigned(const char *string, int base, int *value_r) { long value; char *endptr; errno = 0; if (!string[0]) return -1; 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 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) fatale("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 cleanup_socket_dir(void) { (void)unlink(notify_socket); (void)rmdir(notify_sockdir); } static char * setup_socket_name(const char *suffix) { const char *basedir; if (getuid() == 0 && access("/run", F_OK) == 0) { basedir = "/run"; } else { basedir = getenv("TMPDIR"); if (basedir == NULL) basedir = P_tmpdir; } if (asprintf(¬ify_sockdir, "%s/%s.XXXXXX", basedir, suffix) < 0) fatale("cannot allocate socket directory name"); if (mkdtemp(notify_sockdir) == NULL) fatale("cannot create socket directory %s", notify_sockdir); atexit(cleanup_socket_dir); if (chown(notify_sockdir, runas_uid, runas_gid)) fatale("cannot change socket directory ownership"); if (asprintf(¬ify_socket, "%s/notify", notify_sockdir) < 0) fatale("cannot allocate socket name"); setenv("NOTIFY_SOCKET", notify_socket, 1); return notify_socket; } static void set_socket_passcred(int fd) { #ifdef SO_PASSCRED static const int enable = 1; (void)setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)); #endif } static int create_notify_socket(void) { const char *sockname; struct sockaddr_un su; int fd, rc, flags; /* Create notification socket. */ fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0); if (fd < 0) fatale("cannot create notification socket"); /* We could set SOCK_CLOEXEC instead, but then we would need to * check whether the socket call failed, try and then do this anyway, * when we have no threading problems to worry about. */ flags = fcntl(fd, F_GETFD); if (flags < 0) fatale("cannot read fd flags for notification socket"); if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) fatale("cannot set close-on-exec flag for notification socket"); sockname = setup_socket_name(".s-s-d-notify"); /* Bind to a socket in a temporary directory, selected based on * the platform. */ memset(&su, 0, sizeof(su)); su.sun_family = AF_UNIX; strncpy(su.sun_path, sockname, sizeof(su.sun_path) - 1); rc = bind(fd, &su, sizeof(su)); if (rc < 0) fatale("cannot bind to notification socket"); rc = chmod(su.sun_path, 0660); if (rc < 0) fatale("cannot change notification socket permissions"); rc = chown(su.sun_path, runas_uid, runas_gid); if (rc < 0) fatale("cannot change notification socket ownership"); /* XXX: Verify we are talking to an expected child? Although it is not * clear whether this is feasible given the knowledge we have got. */ set_socket_passcred(fd); return fd; } static void wait_for_notify(int fd) { struct timespec startat, now, elapsed, timeout, timeout_orig; fd_set fdrs; int rc; timeout.tv_sec = notify_timeout; timeout.tv_nsec = 0; timeout_orig = timeout; timespec_gettime(&startat); while (timeout.tv_sec >= 0 && timeout.tv_nsec >= 0) { FD_ZERO(&fdrs); FD_SET(fd, &fdrs); /* Wait for input. */ debug("Waiting for notifications... (timeout %lusec %lunsec)\n", timeout.tv_sec, timeout.tv_nsec); rc = pselect(fd + 1, &fdrs, NULL, NULL, &timeout, NULL); /* Catch non-restartable errors, that is, not signals nor * kernel out of resources. */ if (rc < 0 && (errno != EINTR && errno != EAGAIN)) fatale("cannot monitor notification socket for activity"); /* Timed-out. */ if (rc == 0) fatal("timed out waiting for a notification"); /* Update the timeout, as should not rely on pselect() having * done that for us, which is an unportable assumption. */ timespec_gettime(&now); timespec_sub(&now, &startat, &elapsed); timespec_sub(&timeout_orig, &elapsed, &timeout); /* Restartable error, a signal or kernel out of resources. */ if (rc < 0) continue; /* Parse it and check for a supported notification message, * once we get a READY=1, we exit. */ for (;;) { ssize_t nrecv; char buf[4096]; char *line, *line_next; nrecv = recv(fd, buf, sizeof(buf), 0); if (nrecv < 0 && (errno != EINTR && errno != EAGAIN)) fatale("cannot receive notification packet"); if (nrecv < 0) break; buf[nrecv] = '\0'; for (line = buf; *line; line = line_next) { line_next = strchrnul(line, '\n'); if (*line_next == '\n') *line_next++ = '\0'; debug("Child sent some notification...\n"); if (strncmp(line, "EXTEND_TIMEOUT_USEC=", 20) == 0) { int extend_usec = 0; if (parse_unsigned(line + 20, 10, &extend_usec) != 0) fatale("cannot parse extended timeout notification %s", line); /* Reset the current timeout. */ timeout.tv_sec = extend_usec / 1000L; timeout.tv_nsec = (extend_usec % 1000L) * NANOSEC_IN_MILLISEC; timeout_orig = timeout; timespec_gettime(&startat); } else if (strncmp(line, "ERRNO=", 6) == 0) { int suberrno = 0; if (parse_unsigned(line + 6, 10, &suberrno) != 0) fatale("cannot parse errno notification %s", line); errno = suberrno; fatale("program failed to initialize"); } else if (strcmp(line, "READY=1") == 0) { debug("-> Notification => ready for service.\n"); return; } else { debug("-> Notification line '%s' received\n", line); } } } } } 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) fatale("unable to open pidfile '%s' for writing", filename); fprintf(fp, "%d\n", pid); if (fclose(fp)) fatale("unable to close pidfile '%s'", filename); } static void remove_pidfile(const char *filename) { if (unlink(filename) < 0 && errno != ENOENT) fatale("cannot remove pidfile '%s'", filename); } static void daemonize(void) { int notify_fd = -1; pid_t pid; sigset_t mask; sigset_t oldmask; debug("Detaching to start %s...\n", 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) fatale("cannot block SIGCHLD"); if (notify_await) notify_fd = create_notify_socket(); pid = fork(); if (pid < 0) fatale("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); if (notify_await) { /* Wait for a readiness notification from the second * child, so that we can safely exit when the service * is up. */ wait_for_notify(notify_fd); close(notify_fd); cleanup_socket_dir(); } _exit(0); } /* Close the notification socket, even though it is close-on-exec. */ if (notify_await) close(notify_fd); /* Create a new session. */ if (setsid() < 0) fatale("cannot set session ID"); pid = fork(); if (pid < 0) fatale("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 pidfile. */ 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) fatale("cannot restore signal mask"); debug("Detaching complete...\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 [