#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/select.h>
#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 */
}
syntax highlighted by Code2HTML, v. 0.9.1