#include #include #include #include #include #include #include #include #include #include #include #include #include "svstat.h" #include "iolib.h" #include "freedt.h" #include "config.h" const char *progname = "supervise"; const char *proghelp = "Usage: supervise [OPTIONS] servicedir\n" "Supervise a service directory.\n\n"; struct svstat svstatus; int die_with_child = 0; int restart_on_death = 1; int statusfd, control, lockfd; int selfpipe[2]; void sigchld_handler(int value) { /* We ignore errors here -- the only relevant error is EAGAIN for the pipe being full, and that doesn't matter. */ int saved_errno = errno; write(selfpipe[1], "x", 1); errno = saved_errno; } void start_child() { if (svstatus.pid != -1) return; svstatus.pid = fork(); if (svstatus.pid < 0) die("fork failed"); else if (svstatus.pid == 0) { sigset_t empty; sigemptyset(&empty); if (sigprocmask(SIG_SETMASK, &empty, NULL) < 0) _exit(111); if (setsid() < 0) _exit(111); execl("./run", "run", NULL); _exit(111); } svstatus.up = 1; svstatus.starttime = time(NULL); reliable_sleep(1); } void kill_child(int signal) { if (svstatus.pid == -1) return; if (kill(svstatus.pid, signal) < 0) { if (errno == EPERM) { warn("unable to send signal to process (EPERM)"); } else if (errno == ESRCH) { /* The process has exited -- no problem. */ } else { die("unable to send signal to process"); } } } void write_status() { if (lseek(statusfd, 0, SEEK_SET) < 0) die("unable to seek status file"); if (write(statusfd, &svstatus, sizeof svstatus) < 0) die("unable to write to status file"); } int main(int argc, char **argv) { struct sigaction sa; sigset_t sig_chld; int fd; const char *name; get_default_args(argc, argv); if ((argc - optind) != 1) help(); name = argv[optind]; svstatus.pid = -1; svstatus.starttime = 0; svstatus.up = 0; if (chdir(name) < 0) die2(name, "unable to chdir to service directory"); if (mkdir("supervise", 02700) < 0 && errno != EEXIST) die2(name, "unable to create supervise directory"); lockfd = open("supervise/lock", O_WRONLY | O_CREAT, 0600); if (lockfd < 0) die2(name, "unable to open supervise/lock"); if (lock_fd(lockfd, 0) < 0) die2(name, "unable to obtain lock on supervise/lock"); set_fd_cloexec(lockfd); if (mkfifo("supervise/control", 0600) < 0 && errno != EEXIST) die2(name, "unable to create fifo supervise/control"); control = open("supervise/control", O_RDWR); if (control < 0) die2(name, "unable to open fifo supervise/control"); set_fd_cloexec(control); statusfd = open("supervise/status", O_WRONLY | O_CREAT, 0644); if (statusfd < 0) die2(name, "unable to open supervise/status"); set_fd_cloexec(statusfd); if (pipe(selfpipe) < 0) die("cannot create self-pipe"); change_fd_flags(selfpipe[1], O_NONBLOCK, 0); set_fd_cloexec(selfpipe[0]); set_fd_cloexec(selfpipe[1]); sigemptyset(&sig_chld); sigaddset(&sig_chld, SIGCHLD); if (sigprocmask(SIG_BLOCK, &sig_chld, NULL) < 0) die("unable to block SIGCHLD"); sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) < 0) die("unable to install SIGCHLD handler"); fd = open("down", O_RDONLY); if (fd < 0) { start_child(); } else { svstatus.starttime = time(NULL); close(fd); } write_status(); while (1) { int rc; fd_set fds; sigset_t old_sigs; FD_ZERO(&fds); FD_SET(control, &fds); FD_SET(selfpipe[0], &fds); if (sigprocmask(SIG_UNBLOCK, &sig_chld, &old_sigs) < 0) die("unable to unblock SIGCHLD"); do { rc = select(MAX(control, selfpipe[0]) + 1, &fds, NULL, NULL, NULL); } while (rc < 0 && errno == EINTR); if (rc < 0) die("select failed"); if (sigprocmask(SIG_SETMASK, &old_sigs, NULL) < 0) die("unable to restore signal mask"); if (FD_ISSET(control, &fds)) { /* Command from svc. */ char c; if (read(control, &c, 1) < 0) die("read from control failed"); switch (c) { case 'u': restart_on_death = 1; start_child(); write_status(); break; case 'o': restart_on_death = 0; start_child(); write_status(); break; case 'd': restart_on_death = 0; kill_child(SIGTERM); kill_child(SIGCONT); break; case 'p': kill_child(SIGSTOP); break; case 'c': kill_child(SIGCONT); break; case 'h': kill_child(SIGHUP); break; case 'a': kill_child(SIGALRM); break; case 'i': kill_child(SIGINT); break; case 't': kill_child(SIGTERM); break; case 'k': kill_child(SIGKILL); break; case 'X': kill_child(SIGTERM); kill_child(SIGCONT); exit(0); case 'x': if (svstatus.up == 1) die_with_child = 1; else exit(0); break; } } if (FD_ISSET(selfpipe[0], &fds)) { /* SIGCHLD */ char c; int wrc; if (read(selfpipe[0], &c, 1) < 0) die("read from self-pipe failed"); wrc = waitpid(svstatus.pid, NULL, WNOHANG); if (wrc < 0) { die("waitpid failed"); } else if (wrc == 0) { /* The child has been SIGSTOPped, which * we don't care about. */ } else { /* The child has exited. */ svstatus.pid = -1; if (die_with_child) exit(0); if (restart_on_death) start_child(); else svstatus.up = 0; svstatus.starttime = time(NULL); write_status(); } } } return 0; /* NOTREACHED */ }