diff --git a/src/librc/librc.c b/src/librc/librc.c index acb448a01..41948d357 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -1041,30 +1041,52 @@ rc_service_value_get(const char *service, const char *option) return buffer; } -bool -rc_service_value_set(const char *service, const char *option, const char *value) +static inline int +open_optiondir(const char *service) { - bool ret = true; - int optdirfd; + if (mkdirat(rc_dirfd(RC_DIR_OPTIONS), service, 0755) != 0 && errno != EEXIST) + return -1; + + return openat(rc_dirfd(RC_DIR_OPTIONS), service, O_RDONLY | O_DIRECTORY); +} + +RC_PRINTF(3, 4) bool +rc_service_value_fmt(const char *service, const char *option, const char *fmt, ...) +{ + int optdirfd = open_optiondir(service); + va_list ap; FILE *fp; - if (mkdirat(rc_dirfd(RC_DIR_OPTIONS), service, 0755) != 0 && errno != EEXIST) + if (optdirfd == -1) return false; - if ((optdirfd = openat(rc_dirfd(RC_DIR_OPTIONS), service, O_RDONLY | O_DIRECTORY)) == -1) + if (!(fp = do_fopenat(optdirfd, option, O_WRONLY | O_CREAT | O_TRUNC))) { + close(optdirfd); return false; + } - if (!value) { - unlinkat(optdirfd, option, 0); - } else if ((fp = do_fopenat(optdirfd, option, O_WRONLY | O_CREAT | O_TRUNC))) { - fprintf(fp, "%s", value); - fclose(fp); - } else { - ret = false; + va_start(ap, fmt); + vfprintf(fp, fmt, ap); + va_end(ap); + + fclose(fp); + return true; +} + +bool +rc_service_value_set(const char *service, const char *option, const char *value) +{ + int optdirfd; + + if (value) { + return rc_service_value_fmt(service, option, "%s", value); } + if ((optdirfd = open_optiondir(service)) == -1) + return false; + unlinkat(optdirfd, option, 0); close(optdirfd); - return ret; + return true; } bool diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index e34373de3..5bb4abb25 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -340,6 +340,14 @@ bool rc_service_started_daemon(const char *, const char *, * @return saved value */ char *rc_service_value_get(const char *, const char *); +/*! Format and save a persistent value for a service + * @param service to save for + * @param option to save + * @param format string + * @return true if saved, otherwise false */ +__attribute__((__format__(__printf__, 3, 4))) +bool rc_service_value_fmt(const char *, const char *, const char *, ...); + /*! Save a persistent value for a service * @param service to save for * @param option to save diff --git a/src/shared/helpers.h b/src/shared/helpers.h index 62daee064..391ddf8e6 100644 --- a/src/shared/helpers.h +++ b/src/shared/helpers.h @@ -78,6 +78,22 @@ RC_UNUSED static void *xrealloc(void *ptr, size_t size) /* NOTREACHED */ } +RC_UNUSED static char *xstrndup(const char *str, size_t n) +{ + char *value; + + if (!str) + return (NULL); + + value = strndup(str, n); + + if (value) + return (value); + + ERRX; + /* NOTREACHED */ +} + RC_UNUSED static char *xstrdup(const char *str) { char *value; diff --git a/src/shared/misc.c b/src/shared/misc.c index 51cdc6fee..28bf2051e 100644 --- a/src/shared/misc.c +++ b/src/shared/misc.c @@ -375,7 +375,7 @@ exec_service(const char *service, const char *arg) } int -parse_mode(mode_t *mode, char *text) +parse_mode(mode_t *mode, const char *text) { char *p; unsigned long l; diff --git a/src/shared/misc.h b/src/shared/misc.h index bee987a41..57483d35f 100644 --- a/src/shared/misc.h +++ b/src/shared/misc.h @@ -76,7 +76,7 @@ int is_writable(const char *); #define service_start(service) exec_service(service, "start") #define service_stop(service) exec_service(service, "stop") -int parse_mode(mode_t *, char *); +int parse_mode(mode_t *, const char *); /* Handy function so we can wrap einfo around our deptree */ RC_DEPTREE *_rc_deptree_load (int, int *); diff --git a/src/shared/rc_exec.c b/src/shared/rc_exec.c index 437d537a1..1ffb7dd3c 100644 --- a/src/shared/rc_exec.c +++ b/src/shared/rc_exec.c @@ -167,13 +167,13 @@ int rc_waitpid(pid_t pid) return status; } -int rc_pipe_command(const char *cmd, int devnullfd) +int rc_pipe_command(const char *cmd) { const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; struct exec_result res; struct exec_args args = exec_init(argv); args.redirect_stdin = EXEC_MKPIPE; - args.redirect_stdout = args.redirect_stderr = devnullfd; + args.redirect_stdout = args.redirect_stderr = EXEC_DEVNULL; res = do_exec(&args); return (res.pid > 0) ? res.proc_stdin : -1; } diff --git a/src/shared/rc_exec.h b/src/shared/rc_exec.h index ac3da3202..41edf2dbd 100644 --- a/src/shared/rc_exec.h +++ b/src/shared/rc_exec.h @@ -55,6 +55,6 @@ struct exec_result do_exec(struct exec_args *args); /* some exec related helplers */ int rc_waitpid(pid_t pid); -int rc_pipe_command(const char *cmd, int devnullfd); +int rc_pipe_command(const char *cmd); #endif diff --git a/src/shared/timeutils.c b/src/shared/timeutils.c index e0bcb56d1..686757301 100644 --- a/src/shared/timeutils.c +++ b/src/shared/timeutils.c @@ -69,6 +69,9 @@ int64_t parse_duration(const char *duration) char *end; long val; + if (!duration) + return 0; + errno = 0; val = strtol(duration, &end, 10); if (errno == ERANGE || *duration == '\0' || val < 0) diff --git a/src/start-stop-daemon/start-stop-daemon.c b/src/start-stop-daemon/start-stop-daemon.c index 36b8fd4f6..c3c9d8cd1 100644 --- a/src/start-stop-daemon/start-stop-daemon.c +++ b/src/start-stop-daemon/start-stop-daemon.c @@ -1085,7 +1085,7 @@ int main(int argc, char **argv) " for stdout `%s': %s", applet, redirect_stdout, strerror(errno)); }else if (stdout_process) { - stdout_fd = rc_pipe_command(stdout_process, devnull_fd); + stdout_fd = rc_pipe_command(stdout_process); if (stdout_fd == -1) eerrorx("%s: unable to open the logging process" " for stdout `%s': %s", @@ -1099,7 +1099,7 @@ int main(int argc, char **argv) " for stderr `%s': %s", applet, redirect_stderr, strerror(errno)); }else if (stderr_process) { - stderr_fd = rc_pipe_command(stderr_process, devnull_fd); + stderr_fd = rc_pipe_command(stderr_process); if (stderr_fd == -1) eerrorx("%s: unable to open the logging process" " for stderr `%s': %s", diff --git a/src/supervise-daemon/daemon.c b/src/supervise-daemon/daemon.c new file mode 100644 index 000000000..a2301a11f --- /dev/null +++ b/src/supervise-daemon/daemon.c @@ -0,0 +1,364 @@ +#ifdef __linux__ +#include +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "daemon.h" + +#include "rc_exec.h" +#include "helpers.h" +#include "misc.h" + +#if !defined(SYS_ioprio_set) && defined(__NR_ioprio_set) +# define SYS_ioprio_set __NR_ioprio_set +#endif +#if !defined(__DragonFly__) +static inline int ioprio_set(int which RC_UNUSED, int who RC_UNUSED, + int ioprio RC_UNUSED) +{ +#ifdef SYS_ioprio_set + return syscall(SYS_ioprio_set, which, who, ioprio); +#else + return 0; +#endif +} +#endif + +extern const char *applet; + +static bool +set_nicelevel(const char *option) +{ + int nicelevel; + FILE *fp; + + if (sscanf(option, "%d", &nicelevel) != 1 || setpriority(PRIO_PROCESS, getpid(), nicelevel)) { + syslog(LOG_ERR, "setpriority %d: %s", nicelevel, strerror(errno)); + return false; + } + + if ((fp = fopen("/proc/self/autogroup", "r+"))) { + fprintf(fp, "%d\n", nicelevel); + fclose(fp); + } else if (errno != ENOENT) { + syslog(LOG_ERR, "autogroup nice %d: %s", nicelevel, strerror(errno)); + return false; + } + + return true; +} + +static bool +set_ionice(const char *option) +{ + int ionicec, ioniced; + if (sscanf(option, "%d:%d", &ionicec, &ioniced) == 0) { + syslog(LOG_ERR, "invalid ionice '%s'", optarg); + return false; + } + if (ionicec == 0) + ioniced = 0; + else if (ionicec == 3) + ioniced = 7; + ionicec <<= 13; /* class shift */ + + if (ioprio_set(1, getpid(), ionicec | ioniced) == -1) { + syslog(LOG_ERR, "ioprio_set %d %d: %s", ionicec, ioniced, strerror(errno)); + return false; + } + return true; +} + +static bool +set_oom_score(const char *option) +{ + int score; + FILE *fp; + + if (sscanf(option, "%d", &score) != 1) { + syslog(LOG_ERR, "invalid oom-score-adj '%s'", option); + return false; + } + + if (!(fp = fopen("/proc/self/oom_score_adj", "w"))) { + syslog(LOG_ERR, "oom_score_adj %d: %s", score, strerror(errno)); + return false; + } + + fprintf(fp, "%d\n", score); + fclose(fp); + + return true; +} + +static bool +set_capabilities(const char *option) +{ +#ifdef __linux__ + cap_iab_t cap_iab; + + if (!(cap_iab = cap_iab_from_text(option)) || cap_iab_set_proc(cap_iab) != 0 || cap_free(cap_iab) != 0) { + syslog(LOG_ERR, "failed to set capabilities"); + return false; + } + + return true; +#endif + + syslog(LOG_ERR, "capabilities are not supported"); + errno = ENOSYS; + return false; +} + +static bool +set_secbits(const char *option) +{ +#ifdef __linux__ + unsigned secbits; + char *end; + + if (*option == '\0') { + syslog(LOG_ERR, "secbits are empty"); + return false; + } + + secbits = strtoul(option, &end, 0); + if (option == end) { + syslog(LOG_ERR, "could not parse secbits: %s", option); + return false; + } + + if (cap_set_secbits(secbits) < 0) { + syslog(LOG_ERR, "could not set securebits to 0x%x: %s", secbits, strerror(errno)); + return false; + } + + return true; +#endif + + syslog(LOG_ERR, "secbits are not supported"); + errno = ENOSYS; + return false; +} + +static bool +set_no_new_privs(const char *option) +{ + if (!rc_yesno(option)) + return true; +#ifdef PR_SET_NO_NEW_PRIVS + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { + syslog(LOG_ERR, "prctl: %s", strerror(errno)); + return false; + } + + return true; +#endif + + syslog(LOG_ERR, "no-new-privs are not supported"); + errno = ENOSYS; + return false; +} + +static bool +set_umask(const char *option) +{ + mode_t mask = 022; + if (parse_mode(&mask, option)) { + syslog(LOG_ERR, "%s: invalid mode '%s'", applet, optarg); + return false; + } + + umask(mask); + return true; +} + +static bool +set_scheduler(RC_UNUSED const char *option) +{ + /* TODO */ + return true; +} + +static char *expand_home(const char *path) +{ + char *expanded = NULL; + struct passwd *pw; + const char *home; + size_t size; + FILE *fp; + + if (path[0] == '\\' && path[1] == '~') + return xstrdup(path + 1); + else if (path[0] != '~') + return xstrdup(path); + + fp = xopen_memstream(&expanded, &size); + path++; + + if (path[0] == '/' || path[0] == '\0') { + if ((home = getenv("HOME"))) { + fputs(home, fp); + } else if ((pw = getpwuid(getuid()))) { + fputs(pw->pw_dir, fp); + } else { + syslog(LOG_ERR, "getpwuid: %s", strerror(errno)); + goto err; + } + } else { + char *username; + + if ((home = strchr(path, '/'))) { + username = xstrndup(path, home - path); + path = home + 1; + } else { + username = xstrdup(path); + path = NULL; + } + + if (!(pw = getpwnam(username))) { + syslog(LOG_ERR, "user %s not found", username); + goto err; + } + fputs(pw->pw_dir, fp); + free(username); + } + + if (path) + fputs(path, fp); + + xclose_memstream(fp); + return expanded; + +err: + xclose_memstream(fp); + free(expanded); + return NULL; +} + +static bool +set_dir(const char *option) +{ + char *expand_path = expand_home(option); + if (!expand_path) + return false; + if (chdir(expand_path) == -1) { + syslog(LOG_ERR, "chdir(%s): %s", expand_path, strerror(errno)); + return false; + } + free(expand_path); + return true; +} + +static bool +set_root(const char *option) +{ + char *expand_path = expand_home(option); + if (!expand_path) + return false; + if (chroot(expand_path) == -1) { + syslog(LOG_ERR, "chroot(%s): %s", expand_path, strerror(errno)); + return false; + } + free(expand_path); + return true; +} + +enum open_type { READONLY, CREATE, REDIRECT }; + +static bool +do_open(const char *option, enum open_type type, int target) +{ + int flags = type == CREATE ? O_WRONLY | O_CREAT | O_APPEND : O_RDONLY; + int fd; + + if (type == REDIRECT) { + if ((fd = rc_pipe_command(option)) == -1) { + syslog(LOG_ERR, "failed to pipe command to %s", option); + return false; + } + } else { + if ((fd = open(option, flags, S_IRUSR | S_IWUSR)) == -1) { + syslog(LOG_ERR, "failed to open %s", option); + return false; + } + } + + if (dup2(fd, target) != -1) { + syslog(LOG_ERR, "dup2: %s", strerror(errno)); + return false; + } + + return true; +} + +RC_NORETURN void +child_process(const char *svcname, struct notify *notify, char **argv) +{ + /* the order of the options matter, as they're applied + * in order so earlier options affect later ones! */ + static const struct { + const char * const name; + bool (*handler)(const char *option); + enum open_type type; + int target; + } options[] = { + { "umask", set_umask, 0, 0 }, + { "nicelevel", set_nicelevel, 0, 0 }, + { "ionice", set_ionice, 0, 0 }, + { "oom-score-adj", set_oom_score, 0, 0 }, + { "scheduler", set_scheduler, 0, 0 }, + { "scheduler-priority", set_scheduler, 0, 0 }, + { "capabilities", set_capabilities, 0, 0 }, + { "secbits", set_secbits, 0, 0 }, + { "no-new-privs", set_no_new_privs, 0, 0 }, + /* chdir must be relative to the chroot */ + { "chroot", set_root, 0, 0 }, + { "chdir", set_dir, 0, 0 }, + + { "stdin", NULL, READONLY, STDIN_FILENO }, + { "stdout", NULL, CREATE, STDOUT_FILENO }, + { "stderr", NULL, CREATE, STDERR_FILENO }, + { "stdout-logger", NULL, REDIRECT, STDOUT_FILENO }, + { "stderr-logger", NULL, REDIRECT, STDERR_FILENO }, + }; + char *exec; + if (!(exec = rc_service_value_get(svcname, "exec"))) + exec = argv[0]; + + setsid(); + + for (size_t i = 0; i < ARRAY_SIZE(options); i++) { + char *option; + if (!(option = rc_service_value_get(svcname, options[i].name))) + continue; + if (options[i].handler ? !options[i].handler(option) + : do_open(options[i].name, options[i].type, options[i].target)) + exit(EXIT_FAILURE); + free(option); + } + + cloexec_fds_from(3); + + if (notify->type == NOTIFY_FD && dup2(notify->pipe[1], notify->fd) == -1) { + syslog(LOG_ERR, "Failed to initialize ready fd."); + exit(EXIT_FAILURE); + } + + execvp(*argv, argv); + + syslog(LOG_ERR, "%s: failed to exec '%s': %s", applet, *argv, strerror(errno)); + exit(EXIT_FAILURE); +} diff --git a/src/supervise-daemon/daemon.h b/src/supervise-daemon/daemon.h new file mode 100644 index 000000000..029f49f04 --- /dev/null +++ b/src/supervise-daemon/daemon.h @@ -0,0 +1,10 @@ +#ifndef __DAEMON_H__ +#define __DAEMON_H__ + +#include "misc.h" +#include "helpers.h" + +RC_NORETURN void supervise(const char *svcname); +RC_NORETURN void child_process(const char *svcname, struct notify *notify, char **argv); + +#endif diff --git a/src/supervise-daemon/meson.build b/src/supervise-daemon/meson.build index 2b91aaa88..8f7082cec 100644 --- a/src/supervise-daemon/meson.build +++ b/src/supervise-daemon/meson.build @@ -1,11 +1,24 @@ executable('supervise-daemon', - ['supervise-daemon.c', rc_exec_c, misc_c, plugin_c, schedules_c, timeutils_c, usage_c, version_h], + [ + 'supervise-daemon.c', 'supervise.c', 'daemon.c', + rc_exec_c, misc_c, plugin_c, schedules_c, + timeutils_c, usage_c, version_h + ], c_args : [cc_branding_flags, cc_pam_flags, cc_selinux_flags], link_with: [libeinfo, librc], dependencies: [dl_dep, pam_dep, cap_dep, util_dep, selinux_dep], include_directories: [incdir, einfo_incdir, rc_incdir], install: true, - install_dir: sbindir) + install_dir: rc_sbindir) + +executable('start-stop-daemon', + [ 'start-stop-daemon.c', rc_exec_c, misc_c, plugin_c, schedules_c, timeutils_c, version_h ], + c_args : [cc_branding_flags, cc_pam_flags, cc_selinux_flags], + link_with: [libeinfo, librc], + dependencies: [dl_dep, pam_dep, cap_dep, util_dep, selinux_dep], + include_directories: [incdir, einfo_incdir, rc_incdir], + install: true, + install_dir: rc_sbindir) if get_option('pam') install_data('supervise-daemon.pam', diff --git a/src/supervise-daemon/start-stop-daemon.c b/src/supervise-daemon/start-stop-daemon.c new file mode 100644 index 000000000..002b9dd97 --- /dev/null +++ b/src/supervise-daemon/start-stop-daemon.c @@ -0,0 +1,160 @@ +#include +#include +#include + +#include +#include + +#include "helpers.h" + +enum { + /* This has to come first so following values stay in the 0x100+ range. */ + LONGOPT_BASE = 0x100, + + LONGOPT_CAPABILITIES, + LONGOPT_OOM_SCORE_ADJ, + LONGOPT_NO_NEW_PRIVS, + LONGOPT_SCHEDULER, + LONGOPT_SCHEDULER_PRIO, + LONGOPT_SECBITS, + LONGOPT_STDERR_LOGGER, + LONGOPT_STDOUT_LOGGER, + LONGOPT_NOTIFY, +}; + +const char *applet; +const char getoptstring[] = "u:d:r:k:N:I:R:p:0:1:2:a:A:D:m:P:"; +const struct option longopts[] = { + { "start", no_argument, NULL, 'S' }, + { "stop", no_argument, NULL, 'K' }, + { "signal", no_argument, NULL, 's' }, + + /* daemon management options */ + { "notify", required_argument, NULL, LONGOPT_NOTIFY }, + { "pidfile", required_argument, NULL, 'p' }, + { "retry", required_argument, NULL, 'R' }, + + { "respawn-delay", required_argument, NULL, 'D'}, + { "respawn-max", required_argument, NULL, 'm'}, + { "respawn-period", required_argument, NULL, 'P'}, + + { "healthcheck-timer", required_argument, NULL, 'a'}, + { "healthcheck-delay", required_argument, NULL, 'A'}, + + /* input/output options */ + { "stdin", required_argument, NULL, '0' }, + { "stdout", required_argument, NULL, '1' }, + { "stderr", required_argument, NULL, '2' }, + { "stdout-logger", required_argument, NULL, LONGOPT_STDOUT_LOGGER }, + { "stderr-logger", required_argument, NULL, LONGOPT_STDERR_LOGGER }, + + /* environment setup options */ + { "user", required_argument, NULL, 'u' }, + { "chdir", required_argument, NULL, 'd' }, + { "chroot", required_argument, NULL, 'r' }, + { "umask", required_argument, NULL, 'k' }, + { "nicelevel", required_argument, NULL, 'N' }, + { "ionice", required_argument, NULL, 'I' }, + { "capabilities", required_argument, NULL, LONGOPT_CAPABILITIES }, + { "secbits", required_argument, NULL, LONGOPT_SECBITS }, + { "oom-score-adj", required_argument, NULL, LONGOPT_OOM_SCORE_ADJ }, + { "no-new-privs", no_argument, NULL, LONGOPT_NO_NEW_PRIVS }, + { "scheduler", required_argument, NULL, LONGOPT_SCHEDULER }, + { "scheduler-priority", required_argument, NULL, LONGOPT_SCHEDULER_PRIO }, + + /* legacy options, do nothing but warn for compat */ + { "exec", required_argument, NULL, 'x' }, + { "group", required_argument, NULL, 'g' }, + { "interpreted", no_argument, NULL, 'i' }, + { "progress", no_argument, NULL, 'P' }, + { "make-pidfile", no_argument, NULL, 'm' }, + { "wait", no_argument, NULL, 'w' }, + { "background", no_argument, NULL, 'b' }, + { "test", no_argument, NULL, 't' }, +}; + +int main(int argc, char **argv) { + const char *svcname, *start_opts[ARRAY_SIZE(longopts)] = {0}; + enum { START, STOP, SIGNAL } action = START; + bool supervise_compat; + struct pollfd ctrlfd; + char *cmdline, *env; + FILE *argv_stream; + int idx, opt; + size_t len; + + applet = basename_c(argv[0]); + supervise_compat = strcmp(applet, "supervise-daemon") == 0; + + if (rc_yesno(getenv("RC_USER_SERVICES"))) + rc_set_user(); + + while ((opt = getopt_long(argc, argv, getoptstring, longopts, &idx)) != -1) + switch (opt) { + case 'S': action = START; break; + case 'K': action = STOP; break; + case 's': action = SIGNAL; break; + case 'i': case 'P': case 'm': case 'w': case 'b': case 't': case 'R': + ewarn("DEPRECATED: -%c|--%s no longer takes any effect in the new supervise-daemon and" + "start-stop-daemon, and will be an error in the future.\n", opt, longopts[idx].name); + break; + case 'x': + ewarn("DEPRECATED: -x|--exec is no longer supported, please supply the command name as the first argument."); + /* fall through */ + default: + start_opts[idx] = longopts[idx].has_arg ? optarg : "true"; + break; + } + + svcname = supervise_compat ? argv[optind++] : getenv("RC_SVCNAME"); + argc -= optind; + argv += optind; + + if (!argc) + return EXIT_FAILURE; + + switch (action) { + case START: + if ((ctrlfd.fd = openat(rc_dirfd(RC_DIR_DAEMONS), "supervise-daemon", O_WRONLY)) == -1) + return EXIT_FAILURE; + + rc_service_value_fmt(svcname, "argc", "%d", argc); + argv_stream = xopen_memstream(&cmdline, &len); + for (int i = 0; i < argc; i++) + fprintf(argv_stream, "%s\n", argv[i]); + xclose_memstream(argv_stream); + rc_service_value_set(svcname, "argv", cmdline); + + if ((env = getenv("SSD_NICELEVEL"))) + rc_service_value_set(svcname, "nicelevel", env); + if ((env = getenv("SSD_IONICELEVEL"))) + rc_service_value_set(svcname, "ionice", env); + if ((env = getenv("SSD_OOM_SCORE_ADJ"))) + rc_service_value_set(svcname, "oom-score-adj", env); + + for (size_t i = 0; i < ARRAY_SIZE(longopts); i++) + rc_service_value_set(svcname, longopts[i].name, start_opts[i]); + + if (write(ctrlfd.fd, svcname, strlen(svcname)) == -1) + return EXIT_FAILURE; + + do { + poll(&ctrlfd, 1, -1); + } while (!(ctrlfd.revents & POLLHUP)); + + return EXIT_SUCCESS; + case STOP: + if ((ctrlfd.fd = openat(rc_dirfd(RC_DIR_DAEMONS), svcname, O_WRONLY)) == -1) + return EXIT_FAILURE; + if (write(ctrlfd.fd, "stop", strlen("stop")) == -1) + return EXIT_FAILURE; + break; + case SIGNAL: + if ((ctrlfd.fd = openat(rc_dirfd(RC_DIR_DAEMONS), svcname, O_WRONLY)) == -1) + return EXIT_FAILURE; + if (argc <= 0) + return EXIT_FAILURE; + if (dprintf(ctrlfd.fd, "signal %s", argv[0]) == -1) + return EXIT_FAILURE; + } +} diff --git a/src/supervise-daemon/supervise-daemon.c b/src/supervise-daemon/supervise-daemon.c index 8116fc80f..8c76829c7 100644 --- a/src/supervise-daemon/supervise-daemon.c +++ b/src/supervise-daemon/supervise-daemon.c @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2016 The OpenRC Authors. + * Copyright (c) 2025 The OpenRC Authors. * See the Authors file at the top-level directory of this distribution and * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS * @@ -16,502 +16,82 @@ * except according to the terms contained in the LICENSE file. */ -/* nano seconds */ -#define POLL_INTERVAL 20000000 -#define WAIT_PIDFILE 500000000 -#define ONE_SECOND 1000000000 -#define ONE_MS 1000000 +#include +#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include #include -#include -#include -#include -#include -#include -#ifdef __linux__ -# include /* For io priority */ -# include /* For prctl */ -#endif #include -#include -#include -#include -#include -#include #include -#ifdef HAVE_PAM -#include - -/* We are not supporting authentication conversations */ -static struct pam_conv conv = { NULL, NULL}; -#endif +#include +#include -#ifdef __linux__ -#include -#endif - -#include "einfo.h" -#include "queue.h" -#include "rc.h" -#include "misc.h" -#include "rc_exec.h" -#include "plugin.h" -#include "schedules.h" -#include "timeutils.h" +#include "daemon.h" #include "_usage.h" -#include "helpers.h" - -/* Use long option value that is out of range for 8 bit getopt values. - * The exact enum value is internal and can freely change, so we keep the - * options sorted. - */ -enum { - /* This has to come first so following values stay in the 0x100+ range. */ - LONGOPT_BASE = 0x100, - - LONGOPT_CAPABILITIES, - LONGOPT_OOM_SCORE_ADJ, - LONGOPT_NO_NEW_PRIVS, - LONGOPT_SECBITS, - LONGOPT_STDERR_LOGGER, - LONGOPT_STDOUT_LOGGER, - LONGOPT_NOTIFY, -}; const char *applet = NULL; const char *extraopts = NULL; -const char getoptstring[] = "A:a:D:d:e:g:I:Kk:m:N:p:R:r:s:Su:0:1:2:3" \ - getoptstring_COMMON; -const struct option longopts[] = { - { "healthcheck-timer", 1, NULL, 'a'}, - { "healthcheck-delay", 1, NULL, 'A'}, - { "capabilities", 1, NULL, LONGOPT_CAPABILITIES}, - { "secbits", 1, NULL, LONGOPT_SECBITS}, - { "no-new-privs", 0, NULL, LONGOPT_NO_NEW_PRIVS}, - { "respawn-delay", 1, NULL, 'D'}, - { "chdir", 1, NULL, 'd'}, - { "env", 1, NULL, 'e'}, - { "group", 1, NULL, 'g'}, - { "ionice", 1, NULL, 'I'}, - { "stop", 0, NULL, 'K'}, - { "umask", 1, NULL, 'k'}, - { "respawn-max", 1, NULL, 'm'}, - { "nicelevel", 1, NULL, 'N'}, - { "oom-score-adj",1, NULL, LONGOPT_OOM_SCORE_ADJ}, - { "pidfile", 1, NULL, 'p'}, - { "respawn-period", 1, NULL, 'P'}, - { "retry", 1, NULL, 'R'}, - { "chroot", 1, NULL, 'r'}, - { "signal", 1, NULL, 's'}, - { "start", 0, NULL, 'S'}, - { "user", 1, NULL, 'u'}, - { "stdin", 1, NULL, '0'}, - { "stdout", 1, NULL, '1'}, - { "stderr", 1, NULL, '2'}, - { "stdout-logger",1, NULL, LONGOPT_STDOUT_LOGGER}, - { "stderr-logger",1, NULL, LONGOPT_STDERR_LOGGER}, - { "reexec", 0, NULL, '3'}, - { "notify", 1, NULL, LONGOPT_NOTIFY}, - longopts_COMMON -}; -const char * const longopts_help[] = { - "set an initial health check delay", - "set a health check timer", - "Set the inheritable, ambient and bounding capabilities", - "Set the security-bits for the program", - "Set the No New Privs flag for the program", - "Set a respawn delay", - "Change the PWD", - "Set an environment string", - "Change the process group", - "Set an ionice class:data when starting", - "Stop daemon", - "Set the umask for the daemon", - "set maximum number of respawn attempts", - "Set a nicelevel when starting", - "Set OOM score adjustment when starting", - "Match pid found in this file", - "Set respawn time period", - "Retry schedule to use when stopping", - "Chroot to this directory", - "Send a signal to the daemon", - "Start daemon", - "Change the process user", - "Redirect stdin to file", - "Redirect stdout to file", - "Redirect stderr to file", - "Redirect stdout to process", - "Redirect stderr to process", - "reexec (used internally)", - longopts_help_COMMON -}; +const char getoptstring[] = getoptstring_COMMON; +const struct option longopts[] = { longopts_COMMON }; +const char * const longopts_help[] = { longopts_help_COMMON }; const char *usagestring = NULL; -static int healthcheckdelay = 0; -static int healthchecktimer = 0; -static volatile sig_atomic_t do_healthcheck = 0; -static volatile sig_atomic_t exiting = 0; -static int nicelevel = INT_MIN; -static int ionicec = -1; -static int ioniced = 0; -static int oom_score_adj = INT_MIN; -static char *changeuser, *ch_root, *ch_dir; -static uid_t uid = 0; -static gid_t gid = 0; -static int devnull_fd = -1; -static int stdin_fd; -static int stdout_fd; -static int stderr_fd; -static struct notify notify; -static char *redirect_stdin = NULL; -static char *redirect_stderr = NULL; -static char *redirect_stdout = NULL; -static char *stderr_process = NULL; -static char *stdout_process = NULL; -#ifdef TIOCNOTTY -static int tty_fd = -1; -#endif -static pid_t child_pid; -static int respawn_count = 0; -static int64_t respawn_delay; -static int respawn_max = 10; -static int64_t respawn_period; -static char *fifopath = NULL; -static int fifo_fd = 0; -static char *pidfile = NULL; -static char *svcname = NULL; static bool verbose = false; -#ifdef __linux__ -static cap_iab_t cap_iab = NULL; -static unsigned secbits = 0; -#endif -#ifdef PR_SET_NO_NEW_PRIVS -static bool no_new_privs = false; -#endif extern char **environ; -#if !defined(SYS_ioprio_set) && defined(__NR_ioprio_set) -# define SYS_ioprio_set __NR_ioprio_set -#endif -#if !defined(__DragonFly__) -static inline int ioprio_set(int which RC_UNUSED, int who RC_UNUSED, - int ioprio RC_UNUSED) -{ -#ifdef SYS_ioprio_set - return syscall(SYS_ioprio_set, which, who, ioprio); -#else - return 0; -#endif -} -#endif - -static void cleanup(void) -{ - free(changeuser); -} - -RC_NORETURN static void re_exec_supervisor(void) -{ - syslog(LOG_WARNING, "Re-executing for %s", svcname); - execlp("supervise-daemon", "supervise-daemon", svcname, "--reexec", - (char *) NULL); - syslog(LOG_ERR, "Unable to execute supervise-daemon: %s", - strerror(errno)); - exit(EXIT_FAILURE); -} - -static void handle_signal(int sig) -{ - int serrno = errno; +struct supervisor { pid_t pid; + char *svcname; + struct supervisor *prev, *next; +} *root; - switch (sig) { - case SIGALRM: - do_healthcheck = 1; - break; - case SIGCHLD: - if (exiting) - while (waitpid((pid_t)(-1), NULL, WNOHANG) > 0) {} - else { - while ((pid = waitpid((pid_t)(-1), NULL, WNOHANG|WNOWAIT)) > 0) { - if (pid == child_pid) - break; - pid = waitpid(pid, NULL, WNOHANG); - } - } - break; - case SIGTERM: - exiting = 1; - break; - default: - syslog(LOG_WARNING, "caught signal %d", sig); - re_exec_supervisor(); - } - /* Restore errno */ - errno = serrno; -} +int signal_pipe[2]; -static char * expand_home(const char *home, const char *path) +static void +handle_sigchld(int sig, siginfo_t *info, RC_UNUSED void *ctx) { - char *opath, *ppath, *p, *nh; - struct passwd *pw; - - if (!path || *path != '~') - return xstrdup(path); + if (sig != SIGCHLD) + return; - opath = ppath = xstrdup(path); - if (ppath[1] != '/' && ppath[1] != '\0') { - p = strchr(ppath + 1, '/'); - if (p) - *p = '\0'; - pw = getpwnam(ppath + 1); - if (pw) { - home = pw->pw_dir; - ppath = p; - if (ppath) - *ppath = '/'; - } else - home = NULL; - } else - ppath++; - - if (!home) { - free(opath); - return xstrdup(path); - } - if (!ppath) { - free(opath); - return xstrdup(home); - } - - xasprintf(&nh, "%s%s", home, ppath); - free(opath); - return nh; + write(signal_pipe[1], info, sizeof(*info)); } -static char *make_cmdline(char **argv) +static void +add_to_tree(pid_t pid, const char *svcname) { - char **c; - char *cmdline; - size_t len; - FILE *mem = xopen_memstream(&cmdline, &len); - - for (c = argv; c && *c; c++) { - fputs(*c, mem); - if (c[1]) - fputc(' ', mem); - } - xclose_memstream(mem); - return cmdline; -} + struct supervisor *entry = xmalloc(sizeof(*entry)); + *entry = (struct supervisor) { + .pid = pid, + .svcname = xstrdup(svcname), + .next = root + }; -static pid_t exec_command(const char *cmd) -{ - char *file; - pid_t pid = -1; - sigset_t full; - sigset_t old; - struct sigaction sa; + if (root) + root->prev = entry; - file = rc_service_resolve(svcname); - if (!exists(file)) { - free(file); - return 0; - } - - /* We need to block signals until we have forked */ - memset(&sa, 0, sizeof (sa)); - sa.sa_handler = SIG_DFL; - sigemptyset(&sa.sa_mask); - sigfillset(&full); - sigprocmask(SIG_SETMASK, &full, &old); - - pid = fork(); - if (pid == 0) { - /* Restore default handlers */ - sigaction(SIGCHLD, &sa, NULL); - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGWINCH, &sa, NULL); - - /* Unmask signals */ - sigprocmask(SIG_SETMASK, &old, NULL); - - /* Safe to run now */ - execl(file, file, cmd, (char *) NULL); - syslog(LOG_ERR, "unable to exec `%s': %s\n", - file, strerror(errno)); - _exit(EXIT_FAILURE); - } - - if (pid == -1) - syslog(LOG_ERR, "fork: %s\n",strerror (errno)); - - sigprocmask(SIG_SETMASK, &old, NULL); - free(file); - return pid; + root = entry; } -RC_NORETURN static void child_process(char *exec, char **argv) +static void +setup_environment(void) { - RC_STRINGLIST *env_list; + struct sigaction sa = { .sa_sigaction = handle_sigchld }; + RC_STRINGLIST *env_list = rc_stringlist_new(); RC_STRING *env; - int i; - char *p; - char *token; - size_t len; - char *newpath; - char *np; - char *cmdline = NULL; - time_t start_time; - char start_count_string[20]; - char start_time_string[20]; - FILE *fp; - -#ifdef HAVE_PAM - pam_handle_t *pamh = NULL; - int pamr; - const char *const *pamenv = NULL; -#endif - - setsid(); - - if (svcname) { - start_time = time(NULL); - from_time_t(start_time_string, start_time); - rc_service_value_set(svcname, "start_time", start_time_string); - sprintf(start_count_string, "%i", respawn_count); - rc_service_value_set(svcname, "start_count", start_count_string); - sprintf(start_count_string, "%d", getpid()); - rc_service_value_set(svcname, "child_pid", start_count_string); - } - - if (nicelevel != INT_MIN) { - if (setpriority(PRIO_PROCESS, getpid(), nicelevel) == -1) - eerrorx("%s: setpriority %d: %s", applet, nicelevel, - strerror(errno)); - /* Open in "r+" mode to avoid creating if non-existent. */ - fp = fopen("/proc/self/autogroup", "r+"); - if (fp) { - fprintf(fp, "%d\n", nicelevel); - fclose(fp); - } else if (errno != ENOENT) - eerrorx("%s: autogroup nice %d: %s", applet, - nicelevel, strerror(errno)); - } - - if (ionicec != -1 && ioprio_set(1, getpid(), ionicec | ioniced) == -1) - eerrorx("%s: ioprio_set %d %d: %s", applet, ionicec, ioniced, - strerror(errno)); - - if (oom_score_adj != INT_MIN) { - fp = fopen("/proc/self/oom_score_adj", "w"); - if (!fp) - eerrorx("%s: oom_score_adj %d: %s", applet, - oom_score_adj, strerror(errno)); - fprintf(fp, "%d\n", oom_score_adj); - fclose(fp); - } - - if (ch_root && chroot(ch_root) < 0) - eerrorx("%s: chroot `%s': %s", applet, ch_root, strerror(errno)); - - if (ch_dir && chdir(ch_dir) < 0) - eerrorx("%s: chdir `%s': %s", applet, ch_dir, strerror(errno)); - -#ifdef HAVE_PAM - if (changeuser != NULL) { - pamr = pam_start("supervise-daemon", - changeuser, &conv, &pamh); - - if (pamr == PAM_SUCCESS) - pamr = pam_acct_mgmt(pamh, PAM_SILENT); - if (pamr == PAM_SUCCESS) - pamr = pam_open_session(pamh, PAM_SILENT); - if (pamr != PAM_SUCCESS) - eerrorx("%s: pam error: %s", applet, pam_strerror(pamh, pamr)); - } -#endif - - if (gid && setgid(gid)) - eerrorx("%s: unable to set groupid to %d", applet, gid); - if (changeuser && initgroups(changeuser, gid)) - eerrorx("%s: initgroups (%s, %d)", applet, changeuser, gid); -#ifdef __linux__ - if (uid && cap_setuid(uid)) -#else - if (uid && setuid(uid)) -#endif - eerrorx ("%s: unable to set userid to %d", applet, uid); - - /* Close any fd's to the passwd database */ - endpwent(); - -#ifdef __linux__ - if (cap_iab != NULL) { - i = cap_iab_set_proc(cap_iab); - - if (cap_free(cap_iab) != 0) - eerrorx("Could not releasable memory: %s", strerror(errno)); - - if (i != 0) - eerrorx("Could not set iab: %s", strerror(errno)); - } - - if (secbits != 0) { - if (cap_set_secbits(secbits) < 0) - eerrorx("Could not set securebits to 0x%x: %s", secbits, strerror(errno)); - } -#endif - -#ifdef PR_SET_NO_NEW_PRIVS - if (no_new_privs) { - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) - eerrorx("Could not set No New Privs flag: %s", strerror(errno)); - } -#endif + sigset_t signals; + const char *path; + int fd; - /* remove the controlling tty */ -#ifdef TIOCNOTTY - ioctl(tty_fd, TIOCNOTTY, 0); - close(tty_fd); -#endif + openlog(applet, LOG_PID, LOG_DAEMON); /* Clean the environment of any RC_ variables */ - env_list = rc_stringlist_new(); - i = 0; - while (environ[i]) + for (size_t i = 0; environ[i]; i++) rc_stringlist_add(env_list, environ[i++]); -#ifdef HAVE_PAM - if (changeuser != NULL) { - pamenv = (const char *const *)pam_getenvlist(pamh); - if (pamenv) { - while (*pamenv) { - /* Don't add strings unless they set a var */ - if (strchr(*pamenv, '=')) - putenv(xstrdup(*pamenv)); - else - unsetenv(*pamenv); - pamenv++; - } - } - } -#endif - TAILQ_FOREACH(env, env_list, entries) { if (strncmp(env->value, "RC_", 3) == 0 && strncmp(env->value, "SSD_", 4) == 0) { *strchr(env->value, '=') = '\0'; @@ -521,781 +101,165 @@ RC_NORETURN static void child_process(char *exec, char **argv) rc_stringlist_free(env_list); /* For the path, remove the rcscript bin dir from it */ - if ((token = getenv("PATH"))) { - len = strlen(token); - newpath = np = xmalloc(len + 1); - while (token && *token) { - p = strchr(token, ':'); - if (p) { - *p++ = '\0'; - while (*p == ':') - p++; - } - if (strcmp(token, RC_LIBEXECDIR "/bin") != 0 && - strcmp(token, RC_LIBEXECDIR "/sbin") != 0) - { - len = strlen(token); - if (np != newpath) - *np++ = ':'; - memcpy(np, token, len); - np += len; - } - token = p; - } - *np = '\0'; - unsetenv("PATH"); - setenv("PATH", newpath, 1); - } + if ((path = getenv("PATH"))) { + const char *entry; + char *newpath; + size_t len; - stdin_fd = devnull_fd; - stdout_fd = devnull_fd; - stderr_fd = devnull_fd; - if (redirect_stdin) { - if ((stdin_fd = open(redirect_stdin, - O_RDONLY)) == -1) - eerrorx("%s: unable to open the input file" - " for stdin `%s': %s", - applet, redirect_stdin, strerror(errno)); - } - if (redirect_stdout) { - if ((stdout_fd = open(redirect_stdout, - O_WRONLY | O_CREAT | O_APPEND, - S_IRUSR | S_IWUSR)) == -1) - eerrorx("%s: unable to open the logfile" - " for stdout `%s': %s", - applet, redirect_stdout, strerror(errno)); - } else if (stdout_process) { - stdout_fd = rc_pipe_command(stdout_process, devnull_fd); - if (stdout_fd == -1) - eerrorx("%s: unable to open the logging process" - " for stdout `%s': %s", - applet, stdout_process, strerror(errno)); - } - if (redirect_stderr) { - if ((stderr_fd = open(redirect_stderr, - O_WRONLY | O_CREAT | O_APPEND, - S_IRUSR | S_IWUSR)) == -1) - eerrorx("%s: unable to open the logfile" - " for stderr `%s': %s", - applet, redirect_stderr, strerror(errno)); - } else if (stderr_process) { - stderr_fd = rc_pipe_command(stderr_process, devnull_fd); - if (stderr_fd == -1) - eerrorx("%s: unable to open the logging process" - " for stderr `%s': %s", - applet, stderr_process, strerror(errno)); - } + FILE *fp = xopen_memstream(&newpath, &len); - dup2(stdin_fd, STDIN_FILENO); - if (redirect_stdout || stdout_process || rc_yesno(getenv("EINFO_QUIET"))) - dup2(stdout_fd, STDOUT_FILENO); - if (redirect_stderr || stderr_process || rc_yesno(getenv("EINFO_QUIET"))) - dup2(stderr_fd, STDERR_FILENO); + while ((entry = strchr(path, ':'))) { + if (strncmp(RC_LIBEXECDIR "/bin", path, entry - path) != 0 + || strncmp(RC_LIBEXECDIR "/sbin", path, entry - path) != 0) { + fprintf(fp, "%*.s:", (int)(entry - path), path); + } + while (*entry == ':') + entry++; + path = entry; + } - cloexec_fds_from(3); + xclose_memstream(fp); + if (newpath[len - 1] == ':') + newpath[len - 1] = '\0'; - if (notify.type == NOTIFY_FD && dup2(notify.pipe[1], notify.fd) == -1) - eerrorx("Failed to initialize ready fd."); + setenv("PATH", newpath, true); + } - cmdline = make_cmdline(argv); - syslog(LOG_INFO, "Child command line: %s", cmdline); - free(cmdline); - execvp(exec, argv); + umask(022); -#ifdef HAVE_PAM - if (changeuser != NULL && pamr == PAM_SUCCESS) - pam_close_session(pamh, PAM_SILENT); +#ifdef TIOCNOTTY + /* remove the controlling tty */ + fd = open("/dev/tty", O_RDWR); + ioctl(fd, TIOCNOTTY, 0); + close(fd); #endif - eerrorx("%s: failed to exec `%s': %s", applet, exec,strerror(errno)); -} -RC_NORETURN static void supervisor(char *exec, char **argv) -{ - FILE *fp; - char buf[2048]; - char cmd[2048]; - int count; - int failing; - int health_status; - int healthcheck_respawn; - int i; - int nkilled; - int sig_send; - pid_t health_pid; - pid_t wait_pid; - sigset_t old_signals; - sigset_t signals; - struct sigaction sa; - int64_t respawn_now = 0; - int64_t first_spawn = 0; + fd = open("/dev/null", O_RDWR); + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); - /* block all signals we do not handle */ sigfillset(&signals); - sigdelset(&signals, SIGALRM); sigdelset(&signals, SIGCHLD); - sigdelset(&signals, SIGTERM); - sigprocmask(SIG_SETMASK, &signals, &old_signals); - - /* install signal handler */ - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = handle_signal; - sigaction(SIGALRM, &sa, NULL); + sigprocmask(SIG_SETMASK, &signals, NULL); sigaction(SIGCHLD, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - fp = fopen(pidfile, "w"); - if (!fp) - eerrorx("%s: fopen `%s': %s", applet, pidfile, strerror(errno)); - fprintf(fp, "%d\n", getpid()); - fclose(fp); - - if (svcname) - rc_service_daemon_set(svcname, exec, (const char * const *) argv, - pidfile, true); - - /* remove the controlling tty */ -#ifdef TIOCNOTTY - ioctl(tty_fd, TIOCNOTTY, 0); - close(tty_fd); -#endif - - /* - * Supervisor main loop - */ - if (healthcheckdelay) - alarm(healthcheckdelay); - else if (healthchecktimer) - alarm(healthchecktimer); - failing = 0; - while (!exiting) { - healthcheck_respawn = 0; - fifo_fd = open(fifopath, O_RDONLY); - if (fifo_fd > 0) { - memset(buf, 0, sizeof(buf)); - count = read(fifo_fd, buf, sizeof(buf) - 1); - close(fifo_fd); - if (count != -1) - buf[count] = 0; - if (count == 0) - continue; - if (verbose) - syslog(LOG_DEBUG, "Received %s from fifo", buf); - if (strncasecmp(buf, "sig", 3) == 0) { - if ((sscanf(buf, "%s %d", cmd, &sig_send) == 2) - && (sig_send >= 0 && sig_send < NSIG)) { - syslog(LOG_INFO, "Sending signal %d to %d", sig_send, - child_pid); - if (kill(child_pid, sig_send) == -1) - syslog(LOG_ERR, "Unable to send signal %d to %d", - sig_send, child_pid); - } - } - continue; - } - if (do_healthcheck) { - do_healthcheck = 0; - alarm(0); - if (verbose) - syslog(LOG_DEBUG, "running health check for %s", svcname); - health_pid = exec_command("healthcheck"); - health_status = rc_waitpid(health_pid); - if (WIFEXITED(health_status) && WEXITSTATUS(health_status) == 0) - alarm(healthchecktimer); - else { - syslog(LOG_WARNING, "health check for %s failed", svcname); - health_pid = exec_command("unhealthy"); - rc_waitpid(health_pid); - syslog(LOG_INFO, "stopping %s, pid %d", exec, child_pid); - nkilled = run_stop_schedule(applet, NULL, NULL, child_pid, 0, - false, false, true); - if (nkilled < 0) - syslog(LOG_INFO, "Unable to kill %d: %s", - child_pid, strerror(errno)); - else - healthcheck_respawn = 1; - } - } - if (exiting) { - alarm(0); - syslog(LOG_INFO, "stopping %s, pid %d", exec, child_pid); - nkilled = run_stop_schedule(applet, NULL, NULL, child_pid, 0, - false, false, true); - if (nkilled > 0) - syslog(LOG_INFO, "killed %d processes", nkilled); - continue; - } - wait_pid = waitpid(child_pid, &i, WNOHANG); - if (wait_pid == child_pid) { - if (WIFEXITED(i)) - syslog(LOG_WARNING, "%s, pid %d, exited with return code %d", - exec, child_pid, WEXITSTATUS(i)); - else if (WIFSIGNALED(i)) - syslog(LOG_WARNING, "%s, pid %d, terminated by signal %d", - exec, child_pid, WTERMSIG(i)); - } - if (wait_pid == child_pid || healthcheck_respawn) { - do_healthcheck = 0; - healthcheck_respawn = 0; - alarm(0); - respawn_now = tm_now(); - if (first_spawn == 0) - first_spawn = respawn_now; - if ((respawn_period > 0) - && (respawn_now - first_spawn > respawn_period)) { - respawn_count = 0; - first_spawn = 0; - } else - respawn_count++; - if (respawn_max > 0 && respawn_count > respawn_max) { - syslog(LOG_WARNING, "respawned \"%s\" too many times, exiting", - exec); - exiting = 1; - failing = 1; - continue; - } - tm_sleep(respawn_delay, TM_NO_EINTR); - if (exiting) - continue; - child_pid = fork(); - if (child_pid == -1) { - syslog(LOG_ERR, "%s: fork: %s", applet, strerror(errno)); - exit(EXIT_FAILURE); - } - if (child_pid == 0) { - sigprocmask(SIG_SETMASK, &old_signals, NULL); - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; - sigaction(SIGALRM, &sa, NULL); - sigaction(SIGCHLD, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - child_process(exec, argv); - } - if (healthcheckdelay) - alarm(healthcheckdelay); - else if (healthchecktimer) - alarm(healthchecktimer); - } - } - - if (svcname) { - rc_service_daemon_set(svcname, exec, (const char *const *)argv, - pidfile, false); - rc_service_value_set(svcname, "child_pid", NULL); - rc_service_mark(svcname, RC_SERVICE_STOPPED); - if (failing) - rc_service_mark(svcname, RC_SERVICE_FAILED); - } - if (pidfile && exists(pidfile)) - unlink(pidfile); - if (fifopath && exists(fifopath)) - unlink(fifopath); - exit(EXIT_SUCCESS); + fchdir(rc_dirfd(RC_DIR_DAEMONS)); } -int main(int argc, char **argv) +static int +setup_fifo(void) { - int opt; - char **c; - int x; - bool start = false; - bool stop = false; - bool reexec = false; - bool sendsig = false; - char *exec = NULL; - char *retry = NULL; - int sig = SIGTERM; - char *home = NULL; - int tid = 0; - pid_t pid; - char *tmp; - char *p; - char *token; - int i; - int n; - char *exec_file = NULL; - char *varbuf = NULL; - struct timespec ts; - struct passwd *pw; - struct group *gr; - FILE *fp; - mode_t numask = 022; - int child_argc = 0; - char **child_argv = NULL; - char *str = NULL; - char *cmdline = NULL; - const char *respawn_delay_str = "0"; - const char *respawn_period_str = "0"; - - applet = basename_c(argv[0]); - atexit(cleanup); - svcname = getenv("RC_SVCNAME"); - if (!svcname) - eerrorx("%s: The RC_SVCNAME environment variable is not set", applet); - if (rc_yesno(getenv("RC_USER_SERVICES"))) - rc_set_user(); - openlog(applet, LOG_PID, LOG_DAEMON); - - if (argc <= 1 || strcmp(argv[1], svcname)) - eerrorx("%s: the first argument is %s and must be %s", - applet, argv[1], svcname); - - if ((tmp = getenv("SSD_NICELEVEL"))) - if (sscanf(tmp, "%d", &nicelevel) != 1) - eerror("%s: invalid nice level `%s' (SSD_NICELEVEL)", - applet, tmp); - if ((tmp = getenv("SSD_IONICELEVEL"))) { - n = sscanf(tmp, "%d:%d", &ionicec, &ioniced); - if (n != 1 && n != 2) - eerror("%s: invalid ionice level `%s' (SSD_IONICELEVEL)", - applet, tmp); - if (ionicec == 0) - ioniced = 0; - else if (ionicec == 3) - ioniced = 7; - ionicec <<= 13; /* class shift */ + int fd; + + /* TODO: handle this better. */ + if (mkfifo(applet, 0700) == -1) { + if (errno != EEXIST) + exit(EXIT_FAILURE); + unlink(applet); + if (mkfifo(applet, 0700) == -1) + exit(EXIT_FAILURE); } - if ((tmp = getenv("SSD_OOM_SCORE_ADJ"))) - if (sscanf(tmp, "%d", &oom_score_adj) != 1) - eerror("%s: invalid oom_score_adj `%s' (SSD_OOM_SCORE_ADJ)", - applet, tmp); - - /* Get our user name and initial dir */ - p = getenv("USER"); - home = getenv("HOME"); - if (home == NULL || p == NULL) { - pw = getpwuid(getuid()); - if (pw != NULL) { - if (p == NULL) - setenv("USER", pw->pw_name, 1); - if (home == NULL) { - setenv("HOME", pw->pw_dir, 1); - home = pw->pw_dir; - } - } - } - - cmdline = make_cmdline(argv); - if (svcname) { - argc--; - argv++; - } - while ((opt = getopt_long(argc, argv, getoptstring, longopts, - (int *) 0)) != -1) - switch (opt) { - case 'a': /* --healthcheck-timer