From cbffab246997fb5a06211dfb706b54e5ae5bb59f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 16:58:51 +0200 Subject: Adding upstream version 1.21.22. Signed-off-by: Daniel Baumann --- utils/start-stop-daemon.c | 2896 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2896 insertions(+) create mode 100644 utils/start-stop-daemon.c (limited to 'utils/start-stop-daemon.c') diff --git a/utils/start-stop-daemon.c b/utils/start-stop-daemon.c new file mode 100644 index 0000000..ca67976 --- /dev/null +++ b/utils/start-stop-daemon.c @@ -0,0 +1,2896 @@ +/* + * 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 + +/* At least macOS and AIX do not define this. */ +#ifndef SOCK_NONBLOCK +#define SOCK_NONBLOCK 0 +#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 LIBCOMPAT_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 LIBCOMPAT_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 LIBCOMPAT_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 LIBCOMPAT_ATTR_NORET LIBCOMPAT_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 LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1) +fatal(const char *format, ...) +{ + va_list args; + + va_start(args, format); + fatalv(0, format, args); +} + +static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_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 LIBCOMPAT_ATTR_NORET LIBCOMPAT_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(RUNSTATEDIR, F_OK) == 0) { + basedir = RUNSTATEDIR; + } 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, (struct sockaddr *)&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 [