/** * Copyright © 2015, 2016 Mattias Andrée * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "daemon.h" #include #include #include #include #include #include #include #include #include #include /** * Macro that adds the common beginning of for * all daemon pathnames to a string literal. * * @param name:string literal The unique part of the pathname. */ #define DAEMON_IMAGE(name) LIBEXECDIR "/" PACKAGE "/satd-" name /** * The value on `timer_pid` when there is not "timer" image running. */ #define NO_TIMER_SPAWNED -2 /* Must be negative, but not -1. */ /** * Signal that has been received, 0 if none. */ static volatile sig_atomic_t received_signo = SIGCHLD; /* Forces the "timer" image to run. */ /** * The PID the "timer" fork, `NO_TIMER_SPAWNED` if not running. * * It does not matter that this is lost on a successful SIGHUP. */ static volatile pid_t timer_pid = NO_TIMER_SPAWNED; /** * Child counter. */ static volatile pid_t child_count = 0; /** * Timer specification for an unset timer. */ static const struct itimerspec nilspec = { .it_interval.tv_sec = 0, .it_value.tv_sec = 0, .it_interval.tv_nsec = 0, .it_value.tv_nsec = 0, }; /** * Invoked when a signal is received. * * @param int The signal. */ static void sighandler(int signo) { int saved_errno = errno; pid_t pid; if (signo == SIGCHLD) { for (; (pid = waitpid(-1, NULL, WNOHANG)) > 0; child_count--) { if (pid == timer_pid) timer_pid = NO_TIMER_SPAWNED; else received_signo = (sig_atomic_t)signo; } } else { received_signo = (sig_atomic_t)signo; } errno = saved_errno; } /** * Spawn a libexec. * * @param command The command to spawn, -1 for "timer". * @param fd File descriptor to the socket, -1 iff `command` is -1. * @param argv `argv` from `main`. * @param envp `envp` from `main`. * @return 0 on success, -1 on error. */ static int spawn(int command, int fd, char *argv[], char *envp[]) { #define IMAGE(CASE, NAME) case CASE: image = DAEMON_IMAGE(NAME); break const char *image; pid_t pid; /* Try forking until we success. */ while ((pid = fork()) == -1) { if (errno != EAGAIN) return -1; (void) sleep(1); /* Possibly shorter because of SIGCHLD. */ } /* Parent. */ if (pid) { child_count++; if (command < 0) timer_pid = pid; return 0; } /* Child. */ switch (command) { IMAGE(SAT_QUEUE, "add"); IMAGE(SAT_REMOVE, "rm"); IMAGE(SAT_RUN, "run"); IMAGE(-1, "timer"); default: fprintf(stderr, "%s: invalid command received.\n", argv[0]); goto silent_fail; } if (command < 0) { close(LOCK_FILENO), close(SOCK_FILENO); } else { close(LOCK_FILENO), close(BOOT_FILENO), close(REAL_FILENO); t (DUP2_AND_CLOSE(fd, SOCK_FILENO) == -1); fd = SOCK_FILENO; } execve(image, argv, envp); fail: perror(argv[0]); silent_fail: close(fd); exit(1); } /** * Determine whether a timer is set. * * @param fd The file descriptor of the timer. * @return 1 if set, 0 if unset, -1 on error. */ static int is_timer_set(int fd) { struct itimerspec spec; if (timerfd_gettime(fd, &spec)) return -1; return (spec.it_interval.tv_sec || spec.it_value.tv_sec || spec.it_interval.tv_nsec || spec.it_value.tv_nsec); } /** * If a timer has expired, unset it. * * @param fd The file descriptor of the timer. * @param fdset Set that shall contain `fd` iff it has expired. * @return 1 if the timer expired, 0 otherwise, -1 on error. */ static int test_timer(int fd, const fd_set *fdset) { int64_t _overrun; if (!FD_ISSET(fd, fdset)) return 0; if (read(fd, &_overrun, (size_t)8) < 8) return -1; if (timer_pid == NO_TIMER_SPAWNED) return 1; return timerfd_settime(fd, TFD_TIMER_ABSTIME, &nilspec, NULL) * 2 + 1; } /** * The sat daemon. * * @param argc Should be 3. * @param argv The name of the process, the pathname of the socket, * and the pathname to the state file. * @param envp The environment. * @return 0 The process was successful. * @return 1 The process failed queuing the job. */ int main(int argc, char *argv[], char *envp[]) { int fd = -1, rc = 0, accepted = 0, r, expired = 0; unsigned char type; fd_set fdset; struct stat attr; /* Set up signal handlers. */ t (signal(SIGHUP, sighandler) == SIG_ERR); t (signal(SIGCHLD, sighandler) == SIG_ERR); /* The magnificent loop. */ again: /* Update the a newer version of the daemon? */ if (received_signo == SIGHUP) { execve(DAEMON_IMAGE("diminished"), argv, envp); perror(argv[0]); } /* Need to set new timer values? */ if (expired || ((received_signo == SIGCHLD) && (timer_pid == NO_TIMER_SPAWNED))) t (expired = 0, spawn(-1, -1, argv, envp)); received_signo = 0; #if 1 || !defined(DEBUG) /* Can we quit yet? */ if (accepted && !child_count) { t (r = is_timer_set(BOOT_FILENO), r < 0); if (r) goto not_done; t (r = is_timer_set(REAL_FILENO), r < 0); if (r) goto not_done; t (fstat(STATE_FILENO, &attr)); if (attr.st_size > (off_t)sizeof(size_t)) t (spawn(-1, -1, argv, envp)); else goto done; } #endif not_done: /* Wait for something to happen. */ FD_ZERO(&fdset); FD_SET(SOCK_FILENO, &fdset); FD_SET(BOOT_FILENO, &fdset); FD_SET(REAL_FILENO, &fdset); /* This is the highest one. */ if (select(REAL_FILENO + 1, &fdset, NULL, NULL, NULL) == -1) { t (errno != EINTR); goto again; } /* Was any jobs expired? */ t ((expired |= test_timer(BOOT_FILENO, &fdset)) < 0); t ((expired |= test_timer(REAL_FILENO, &fdset)) < 0); /* Accept connections. */ if (!FD_ISSET(SOCK_FILENO, &fdset)) goto again; if (fd = accept(SOCK_FILENO, NULL, NULL), fd == -1) { t ((errno != ECONNABORTED) && (errno != EINTR)); /* Including EMFILE, ENFILE, and ENOMEM because of potential resource leak. */ goto again; } accepted = 1; if (read(fd, &type, sizeof(type)) <= 0) perror(argv[0]); else t (spawn((int)type, fd, argv, envp)); close(fd), fd = -1; goto again; fail: perror(argv[0]); if (fd >= 0) close(fd); rc = 1; done: while (waitpid(-1, NULL, 0) > 0); unlink(argv[1]); if (!rc) unlink(argv[2]); close(SOCK_FILENO); close(STATE_FILENO); return rc; (void) argc; }