diff --git a/runit/runsv.c b/runit/runsv.c new file mode 100644 index 000000000..e1b5459fb --- /dev/null +++ b/runit/runsv.c @@ -0,0 +1,613 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static int selfpipe[2]; + +/* state */ +#define S_DOWN 0 +#define S_RUN 1 +#define S_FINISH 2 +/* ctrl */ +#define C_NOOP 0 +#define C_TERM 1 +#define C_PAUSE 2 +/* want */ +#define W_UP 0 +#define W_DOWN 1 +#define W_EXIT 2 + +struct svdir { + int pid; + int state; + int ctrl; + int want; + struct taia start; + int fdlock; + int fdcontrol; + int fdcontrolwrite; + int islog; +}; +static struct svdir svd[2]; + +static int sigterm = 0; +static int haslog = 0; +static int pidchanged = 1; +static int logpipe[2]; +static char *dir; + +#define usage() bb_show_usage() + +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void fatal_cannot(char *m) +{ + fatal2_cannot(m, ""); + /* was exiting 111 */ +} +static void fatal2x_cannot(char *m1, char *m2) +{ + bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void warn_cannot(char *m) +{ + bb_perror_msg("%s: warning: cannot %s", dir, m); +} +static void warnx_cannot(char *m) +{ + bb_error_msg("%s: warning: cannot %s", dir, m); +} + +static void stopservice(struct svdir *); + +static void s_child(int sig_no) +{ + write(selfpipe[1], "", 1); +} + +static void s_term(int sig_no) +{ + sigterm = 1; + write(selfpipe[1], "", 1); /* XXX */ +} + +static char *add_str(char *p, const char *to_add) +{ + while ((*p = *to_add) != '\0') { + p++; + to_add++; + } + return p; +} + +static int open_trunc_or_warn(const char *name) +{ + int fd = open_trunc(name); + if (fd < 0) + bb_perror_msg("%s: warning: cannot open %s", + dir, name); + return fd; +} + +static int rename_or_warn(const char *old, const char *new) +{ + if (rename(old, new) == -1) { + bb_perror_msg("%s: warning: cannot rename %s to %s", + dir, old, new); + return -1; + } + return 0; +} + +static void update_status(struct svdir *s) +{ + unsigned long l; + int fd; + char status[20]; + + /* pid */ + if (pidchanged) { + fd = open_trunc_or_warn("supervise/pid.new"); + if (fd < 0) + return; + if (s->pid) { + char spid[sizeof(s->pid)*3 + 2]; + int size = sprintf(spid, "%d\n", s->pid); + write(fd, spid, size); + } + close(fd); + if (s->islog) { + if (rename_or_warn("supervise/pid.new", "log/supervise/pid")) + return; + } else if (rename_or_warn("supervise/pid.new", "supervise/pid")) { + return; + } + pidchanged = 0; + } + + /* stat */ + fd = open_trunc_or_warn("supervise/stat.new"); + if (fd < -1) + return; + + { + char stat_buf[sizeof("finish, paused, got TERM, want down\n")]; + char *p = stat_buf; + switch (s->state) { + case S_DOWN: + p = add_str(p, "down"); + break; + case S_RUN: + p = add_str(p, "run"); + break; + case S_FINISH: + p = add_str(p, "finish"); + break; + } + if (s->ctrl & C_PAUSE) p = add_str(p, ", paused"); + if (s->ctrl & C_TERM) p = add_str(p, ", got TERM"); + if (s->state != S_DOWN) + switch(s->want) { + case W_DOWN: + p = add_str(p, ", want down"); + break; + case W_EXIT: + p = add_str(p, ", want exit"); + break; + } + *p++ = '\n'; + write(fd, stat_buf, p - stat_buf); + close(fd); + } + + if (s->islog) { + rename_or_warn("supervise/stat.new", "log/supervise/stat"); + } else { + rename_or_warn("supervise/stat.new", "log/supervise/stat"+4); + } + + /* supervise compatibility */ + taia_pack(status, &s->start); + l = (unsigned long)s->pid; + status[12] = l; l >>=8; + status[13] = l; l >>=8; + status[14] = l; l >>=8; + status[15] = l; + if (s->ctrl & C_PAUSE) + status[16] = 1; + else + status[16] = 0; + if (s->want == W_UP) + status[17] = 'u'; + else + status[17] = 'd'; + if (s->ctrl & C_TERM) + status[18] = 1; + else + status[18] = 0; + status[19] = s->state; + fd = open_trunc_or_warn("supervise/status.new"); + if (fd < 0) + return; + l = write(fd, status, sizeof status); + if (l < 0) { + warn_cannot("write supervise/status.new"); + close(fd); + unlink("supervise/status.new"); + return; + } + close(fd); + if (l < sizeof status) { + warnx_cannot("write supervise/status.new: partial write"); + return; + } + if (s->islog) { + rename_or_warn("supervise/status.new", "log/supervise/status"); + } else { + rename_or_warn("supervise/status.new", "log/supervise/status"+4); + } +} + +static unsigned custom(struct svdir *s, char c) +{ + int pid; + int w; + char a[10]; + struct stat st; + char *prog[2]; + + if (s->islog) return 0; + memcpy(a, "control/?", 10); + a[8] = c; + if (stat(a, &st) == 0) { + if (st.st_mode & S_IXUSR) { + pid = fork(); + if (pid == -1) { + warn_cannot("fork for control/?"); + return 0; + } + if (!pid) { + if (haslog && fd_copy(1, logpipe[1]) == -1) + warn_cannot("setup stdout for control/?"); + prog[0] = a; + prog[1] = 0; + execve(a, prog, environ); + fatal_cannot("run control/?"); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + warn_cannot("wait for child control/?"); + return 0; + } + return !wait_exitcode(w); + } + } + else { + if (errno == ENOENT) return 0; + warn_cannot("stat control/?"); + } + return 0; +} + +static void stopservice(struct svdir *s) +{ + if (s->pid && ! custom(s, 't')) { + kill(s->pid, SIGTERM); + s->ctrl |=C_TERM; + update_status(s); + } + if (s->want == W_DOWN) { + kill(s->pid, SIGCONT); + custom(s, 'd'); return; + } + if (s->want == W_EXIT) { + kill(s->pid, SIGCONT); + custom(s, 'x'); + } +} + +static void startservice(struct svdir *s) +{ + int p; + char *run[2]; + + if (s->state == S_FINISH) + run[0] = "./finish"; + else { + run[0] = "./run"; + custom(s, 'u'); + } + run[1] = 0; + + if (s->pid != 0) stopservice(s); /* should never happen */ + while ((p = fork()) == -1) { + warn_cannot("fork, sleeping"); + sleep(5); + } + if (p == 0) { + /* child */ + if (haslog) { + if (s->islog) { + if (fd_copy(0, logpipe[0]) == -1) + fatal_cannot("setup filedescriptor for ./log/run"); + close(logpipe[1]); + if (chdir("./log") == -1) + fatal_cannot("change directory to ./log"); + } else { + if (fd_copy(1, logpipe[1]) == -1) + fatal_cannot("setup filedescriptor for ./run"); + close(logpipe[0]); + } + } + sig_uncatch(sig_child); + sig_unblock(sig_child); + sig_uncatch(sig_term); + sig_unblock(sig_term); + execve(*run, run, environ); + if (s->islog) + fatal2_cannot("start log/", *run); + else + fatal2_cannot("start ", *run); + } + if (s->state != S_FINISH) { + taia_now(&s->start); + s->state = S_RUN; + } + s->pid = p; + pidchanged = 1; + s->ctrl = C_NOOP; + update_status(s); +} + +static int ctrl(struct svdir *s, char c) +{ + switch(c) { + case 'd': /* down */ + s->want = W_DOWN; + update_status(s); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 'u': /* up */ + s->want = W_UP; + update_status(s); + if (s->pid == 0) startservice(s); + break; + case 'x': /* exit */ + if (s->islog) break; + s->want = W_EXIT; + update_status(s); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 't': /* sig term */ + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 'k': /* sig kill */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGKILL); + s->state = S_DOWN; + break; + case 'p': /* sig pause */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGSTOP); + s->ctrl |=C_PAUSE; + update_status(s); + break; + case 'c': /* sig cont */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGCONT); + if (s->ctrl & C_PAUSE) s->ctrl &=~C_PAUSE; + update_status(s); + break; + case 'o': /* once */ + s->want = W_DOWN; + update_status(s); + if (!s->pid) startservice(s); + break; + case 'a': /* sig alarm */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGALRM); + break; + case 'h': /* sig hup */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGHUP); + break; + case 'i': /* sig int */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGINT); + break; + case 'q': /* sig quit */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGQUIT); + break; + case '1': /* sig usr1 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR1); + break; + case '2': /* sig usr2 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR2); + break; + } + return 1; +} + +int runsv_main(int argc, char **argv) +{ + struct stat s; + int fd; + int r; + char buf[256]; + + if (!argv[1] || argv[2]) usage(); + dir = argv[1]; + + if (pipe(selfpipe) == -1) fatal_cannot("create selfpipe"); + coe(selfpipe[0]); + coe(selfpipe[1]); + ndelay_on(selfpipe[0]); + ndelay_on(selfpipe[1]); + + sig_block(sig_child); + sig_catch(sig_child, s_child); + sig_block(sig_term); + sig_catch(sig_term, s_term); + + xchdir(dir); + svd[0].pid = 0; + svd[0].state = S_DOWN; + svd[0].ctrl = C_NOOP; + svd[0].want = W_UP; + svd[0].islog = 0; + svd[1].pid = 0; + taia_now(&svd[0].start); + if (stat("down", &s) != -1) svd[0].want = W_DOWN; + + if (stat("log", &s) == -1) { + if (errno != ENOENT) + warn_cannot("stat ./log"); + } else { + if (!S_ISDIR(s.st_mode)) + warnx_cannot("stat log/down: log is not a directory"); + else { + haslog = 1; + svd[1].state = S_DOWN; + svd[1].ctrl = C_NOOP; + svd[1].want = W_UP; + svd[1].islog = 1; + taia_now(&svd[1].start); + if (stat("log/down", &s) != -1) + svd[1].want = W_DOWN; + if (pipe(logpipe) == -1) + fatal_cannot("create log pipe"); + coe(logpipe[0]); + coe(logpipe[1]); + } + } + + if (mkdir("supervise", 0700) == -1) { + r = readlink("supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./supervise: ", "name too long"); + buf[r] = 0; + mkdir(buf, 0700); + } else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./supervise"); + } + } + svd[0].fdlock = xopen3("log/supervise/lock"+4, + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_exnb(svd[0].fdlock) == -1) + fatal_cannot("lock supervise/lock"); + coe(svd[0].fdlock); + if (haslog) { + if (mkdir("log/supervise", 0700) == -1) { + r = readlink("log/supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./log/supervise: ", "name too long"); + buf[r] = 0; + fd = xopen(".", O_RDONLY|O_NDELAY); + xchdir("./log"); + mkdir(buf, 0700); + if (fchdir(fd) == -1) + fatal_cannot("change back to service directory"); + close(fd); + } + else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./log/supervise"); + } + } + svd[1].fdlock = xopen3("log/supervise/lock", + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_ex(svd[1].fdlock) == -1) + fatal_cannot("lock log/supervise/lock"); + coe(svd[1].fdlock); + } + + fifo_make("log/supervise/control"+4, 0600); + svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY); + coe(svd[0].fdcontrol); + svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY); + coe(svd[0].fdcontrolwrite); + update_status(&svd[0]); + if (haslog) { + fifo_make("log/supervise/control", 0600); + svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY); + coe(svd[1].fdcontrol); + svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY); + coe(svd[1].fdcontrolwrite); + update_status(&svd[1]); + } + fifo_make("log/supervise/ok"+4, 0600); + fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY); + coe(fd); + if (haslog) { + fifo_make("log/supervise/ok", 0600); + fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY); + coe(fd); + } + for (;;) { + iopause_fd x[3]; + struct taia deadline; + struct taia now; + char ch; + + if (haslog) + if (!svd[1].pid && svd[1].want == W_UP) + startservice(&svd[1]); + if (!svd[0].pid) + if (svd[0].want == W_UP || svd[0].state == S_FINISH) + startservice(&svd[0]); + + x[0].fd = selfpipe[0]; + x[0].events = IOPAUSE_READ; + x[1].fd = svd[0].fdcontrol; + x[1].events = IOPAUSE_READ; + if (haslog) { + x[2].fd = svd[1].fdcontrol; + x[2].events = IOPAUSE_READ; + } + taia_now(&now); + taia_uint(&deadline, 3600); + taia_add(&deadline, &now, &deadline); + + sig_unblock(sig_term); + sig_unblock(sig_child); + iopause(x, 2+haslog, &deadline, &now); + sig_block(sig_term); + sig_block(sig_child); + + while (read(selfpipe[0], &ch, 1) == 1) + ; + for (;;) { + int child; + int wstat; + + child = wait_nohang(&wstat); + if (!child) break; + if ((child == -1) && (errno != EINTR)) break; + if (child == svd[0].pid) { + svd[0].pid = 0; + pidchanged = 1; + svd[0].ctrl &=~C_TERM; + if (svd[0].state != S_FINISH) + fd = open_read("finish"); + if (fd != -1) { + close(fd); + svd[0].state = S_FINISH; + update_status(&svd[0]); + continue; + } + svd[0].state = S_DOWN; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[0].start, &deadline); + taia_now(&svd[0].start); + update_status(&svd[0]); + if (taia_less(&svd[0].start, &deadline)) sleep(1); + } + if (haslog) { + if (child == svd[1].pid) { + svd[1].pid = 0; + pidchanged = 1; + svd[1].state = S_DOWN; + svd[1].ctrl &=~C_TERM; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[1].start, &deadline); + taia_now(&svd[1].start); + update_status(&svd[1]); + if (taia_less(&svd[1].start, &deadline)) sleep(1); + } + } + } + if (read(svd[0].fdcontrol, &ch, 1) == 1) + ctrl(&svd[0], ch); + if (haslog) + if (read(svd[1].fdcontrol, &ch, 1) == 1) + ctrl(&svd[1], ch); + + if (sigterm) { + ctrl(&svd[0], 'x'); + sigterm = 0; + } + + if (svd[0].want == W_EXIT && svd[0].state == S_DOWN) { + if (svd[1].pid == 0) + _exit(0); + if (svd[1].want != W_EXIT) { + svd[1].want = W_EXIT; + /* stopservice(&svd[1]); */ + update_status(&svd[1]); + close(logpipe[1]); + close(logpipe[0]); + //if (close(logpipe[1]) == -1) + // warn_cannot("close logpipe[1]"); + //if (close(logpipe[0]) == -1) + // warn_cannot("close logpipe[0]"); + } + } + } + /* not reached */ + return 0; +} diff --git a/runit/runsvdir.c b/runit/runsvdir.c new file mode 100644 index 000000000..9238eec82 --- /dev/null +++ b/runit/runsvdir.c @@ -0,0 +1,306 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +#define MAXSERVICES 1000 + +static char *svdir; +static unsigned long dev; +static unsigned long ino; +static struct service { + unsigned long dev; + unsigned long ino; + int pid; + int isgone; +} *sv; +static int svnum; +static int check = 1; +static char *rplog; +static int rploglen; +static int logpipe[2]; +static iopause_fd io[1]; +static struct taia stamplog; +static int exitsoon; +static int pgrp; + +#define usage() bb_show_usage() +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2); + /* was exiting 100 */ +} +static void warn3x(char *m1, char *m2, char *m3) +{ + bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3); +} +static void warn2_cannot(char *m1, char *m2) +{ + warn3x("cannot ", m1, m2); +} +static void warnx(char *m1) +{ + warn3x(m1, "", ""); +} + +static void s_term(int sig_no) +{ + exitsoon = 1; +} +static void s_hangup(int sig_no) +{ + exitsoon = 2; +} + +static void runsv(int no, char *name) +{ + int pid = fork(); + + if (pid == -1) { + warn2_cannot("fork for ", name); + return; + } + if (pid == 0) { + /* child */ + char *prog[3]; + + prog[0] = "runsv"; + prog[1] = name; + prog[2] = 0; + sig_uncatch(sig_hangup); + sig_uncatch(sig_term); + if (pgrp) setsid(); + execvp(prog[0], prog); + //pathexec_run(*prog, prog, (char* const*)environ); + fatal2_cannot("start runsv ", name); + } + sv[no].pid = pid; +} + +static void runsvdir(void) +{ + DIR *dir; + direntry *d; + int i; + struct stat s; + + dir = opendir("."); + if (!dir) { + warn2_cannot("open directory ", svdir); + return; + } + for (i = 0; i < svnum; i++) + sv[i].isgone = 1; + errno = 0; + while ((d = readdir(dir))) { + if (d->d_name[0] == '.') continue; + if (stat(d->d_name, &s) == -1) { + warn2_cannot("stat ", d->d_name); + errno = 0; + continue; + } + if (!S_ISDIR(s.st_mode)) continue; + for (i = 0; i < svnum; i++) { + if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) { + sv[i].isgone = 0; + if (!sv[i].pid) + runsv(i, d->d_name); + break; + } + } + if (i == svnum) { + /* new service */ + struct service *svnew = realloc(sv, (i+1) * sizeof(*sv)); + if (!svnew) { + warn3x("cannot start runsv ", d->d_name, + " too many services"); + continue; + } + sv = svnew; + svnum++; + memset(&sv[i], 0, sizeof(sv[i])); + sv[i].ino = s.st_ino; + sv[i].dev = s.st_dev; + //sv[i].pid = 0; + //sv[i].isgone = 0; + runsv(i, d->d_name); + check = 1; + } + } + if (errno) { + warn2_cannot("read directory ", svdir); + closedir(dir); + check = 1; + return; + } + closedir(dir); + + /* SIGTERM removed runsv's */ + for (i = 0; i < svnum; i++) { + if (!sv[i].isgone) + continue; + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + sv[i] = sv[--svnum]; + check = 1; + } +} + +static int setup_log(void) +{ + rploglen = strlen(rplog); + if (rploglen < 7) { + warnx("log must have at least seven characters"); + return 0; + } + if (pipe(logpipe) == -1) { + warnx("cannot create pipe for log"); + return -1; + } + coe(logpipe[1]); + coe(logpipe[0]); + ndelay_on(logpipe[0]); + ndelay_on(logpipe[1]); + if (fd_copy(2, logpipe[1]) == -1) { + warnx("cannot set filedescriptor for log"); + return -1; + } + io[0].fd = logpipe[0]; + io[0].events = IOPAUSE_READ; + taia_now(&stamplog); + return 1; +} + +int runsvdir_main(int argc, char **argv) +{ + struct stat s; + time_t mtime = 0; + int wstat; + int curdir; + int pid; + struct taia deadline; + struct taia now; + struct taia stampcheck; + char ch; + int i; + + argv++; + if (!argv || !*argv) usage(); + if (**argv == '-') { + switch (*(*argv + 1)) { + case 'P': pgrp = 1; + case '-': ++argv; + } + if (!argv || !*argv) usage(); + } + + sig_catch(sig_term, s_term); + sig_catch(sig_hangup, s_hangup); + svdir = *argv++; + if (argv && *argv) { + rplog = *argv; + if (setup_log() != 1) { + rplog = 0; + warnx("log service disabled"); + } + } + curdir = open_read("."); + if (curdir == -1) + fatal2_cannot("open current directory", ""); + coe(curdir); + + taia_now(&stampcheck); + + for (;;) { + /* collect children */ + for (;;) { + pid = wait_nohang(&wstat); + if (pid <= 0) break; + for (i = 0; i < svnum; i++) { + if (pid == sv[i].pid) { + /* runsv has gone */ + sv[i].pid = 0; + check = 1; + break; + } + } + } + + taia_now(&now); + if (now.sec.x < (stampcheck.sec.x - 3)) { + /* time warp */ + warnx("time warp: resetting time stamp"); + taia_now(&stampcheck); + taia_now(&now); + if (rplog) taia_now(&stamplog); + } + if (taia_less(&now, &stampcheck) == 0) { + /* wait at least a second */ + taia_uint(&deadline, 1); + taia_add(&stampcheck, &now, &deadline); + + if (stat(svdir, &s) != -1) { + if (check || s.st_mtime != mtime + || s.st_ino != ino || s.st_dev != dev + ) { + /* svdir modified */ + if (chdir(svdir) != -1) { + mtime = s.st_mtime; + dev = s.st_dev; + ino = s.st_ino; + check = 0; + if (now.sec.x <= (4611686018427387914ULL + (uint64_t)mtime)) + sleep(1); + runsvdir(); + while (fchdir(curdir) == -1) { + warn2_cannot("change directory, pausing", ""); + sleep(5); + } + } else + warn2_cannot("change directory to ", svdir); + } + } else + warn2_cannot("stat ", svdir); + } + + if (rplog) { + if (taia_less(&now, &stamplog) == 0) { + write(logpipe[1], ".", 1); + taia_uint(&deadline, 900); + taia_add(&stamplog, &now, &deadline); + } + } + taia_uint(&deadline, check ? 1 : 5); + taia_add(&deadline, &now, &deadline); + + sig_block(sig_child); + if (rplog) + iopause(io, 1, &deadline, &now); + else + iopause(0, 0, &deadline, &now); + sig_unblock(sig_child); + + if (rplog && (io[0].revents | IOPAUSE_READ)) + while (read(logpipe[0], &ch, 1) > 0) + if (ch) { + for (i = 6; i < rploglen; i++) + rplog[i-1] = rplog[i]; + rplog[rploglen-1] = ch; + } + + switch (exitsoon) { + case 1: + _exit(0); + case 2: + for (i = 0; i < svnum; i++) + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + _exit(111); + } + } + /* not reached */ + return 0; +} diff --git a/runit/sv.c b/runit/sv.c new file mode 100644 index 000000000..819f31419 --- /dev/null +++ b/runit/sv.c @@ -0,0 +1,360 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static char *action; +static char *acts; +static char *varservice = "/var/service/"; +static char **service; +static char **servicex; +static unsigned services; +static unsigned rc = 0; +static unsigned verbose = 0; +static unsigned long waitsec = 7; +static unsigned kll = 0; +static struct taia tstart, tnow, tdiff; +static struct tai tstatus; + +static int (*act)(char*) = 0; +static int (*cbk)(char*) = 0; + +static int curdir, fd, r; +static char svstatus[20]; + +#define usage() bb_show_usage() + +static void fatal_cannot(char *m1) +{ + bb_perror_msg("fatal: cannot %s", m1); + _exit(151); +} + +static void out(char *p, char *m1) +{ + printf("%s%s: %s", p, *service, m1); + if (errno) { + printf(": %s", strerror(errno)); + } + puts(""); /* will also flush the output */ +} + +#define FAIL "fail: " +#define WARN "warning: " +#define OK "ok: " +#define RUN "run: " +#define FINISH "finish: " +#define DOWN "down: " +#define TIMEOUT "timeout: " +#define KILL "kill: " + +static void fail(char *m1) { ++rc; out(FAIL, m1); } +static void failx(char *m1) { errno = 0; fail(m1); } +static void warn_cannot(char *m1) { ++rc; out("warning: cannot ", m1); } +static void warnx_cannot(char *m1) { errno = 0; warn_cannot(m1); } +static void ok(char *m1) { errno = 0; out(OK, m1); } + +static int svstatus_get(void) +{ + if ((fd = open_write("supervise/ok")) == -1) { + if (errno == ENODEV) { + *acts == 'x' ? ok("runsv not running") + : failx("runsv not running"); + return 0; + } + warn_cannot("open supervise/ok"); + return -1; + } + close(fd); + if ((fd = open_read("supervise/status")) == -1) { + warn_cannot("open supervise/status"); + return -1; + } + r = read(fd, svstatus, 20); + close(fd); + switch(r) { + case 20: break; + case -1: warn_cannot("read supervise/status"); return -1; + default: warnx_cannot("read supervise/status: bad format"); return -1; + } + return 1; +} + +static unsigned svstatus_print(char *m) +{ + int pid; + int normallyup = 0; + struct stat s; + + if (stat("down", &s) == -1) { + if (errno != ENOENT) { + bb_perror_msg(WARN"cannot stat %s/down", *service); + return 0; + } + normallyup = 1; + } + pid = (unsigned char) svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + tai_unpack(svstatus, &tstatus); + if (pid) { + switch (svstatus[19]) { + case 1: printf(RUN); break; + case 2: printf(FINISH); break; + } + printf("%s: (pid %d) ", m, pid); + } + else { + printf(DOWN"%s: ", m); + } + printf("%lus", (unsigned long)(tnow.sec.x < tstatus.x ? 0 : tnow.sec.x-tstatus.x)); + if (pid && !normallyup) printf(", normally down"); + if (!pid && normallyup) printf(", normally up"); + if (pid && svstatus[16]) printf(", paused"); + if (!pid && (svstatus[17] == 'u')) printf(", want up"); + if (pid && (svstatus[17] == 'd')) printf(", want down"); + if (pid && svstatus[18]) printf(", got TERM"); + return pid ? 1 : 2; +} + +static int status(char *unused) +{ + r = svstatus_get(); + switch(r) { case -1: case 0: return 0; } + r = svstatus_print(*service); + if (chdir("log") == -1) { + if (errno != ENOENT) { + printf("; log: "WARN"cannot change to log service directory: %s", + strerror(errno)); + } + } else if (svstatus_get()) { + printf("; "); + svstatus_print("log"); + } + puts(""); /* will also flush the output */ + return r; +} + +static int checkscript(void) +{ + char *prog[2]; + struct stat s; + int pid, w; + + if (stat("check", &s) == -1) { + if (errno == ENOENT) return 1; + bb_perror_msg(WARN"cannot stat %s/check", *service); + return 0; + } + /* if (!(s.st_mode & S_IXUSR)) return 1; */ + if ((pid = fork()) == -1) { + bb_perror_msg(WARN"cannot fork for %s/check", *service); + return 0; + } + if (!pid) { + prog[0] = "./check"; + prog[1] = 0; + close(1); + execve("check", prog, environ); + bb_perror_msg(WARN"cannot run %s/check", *service); + _exit(0); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + bb_perror_msg(WARN"cannot wait for child %s/check", *service); + return 0; + } + return !wait_exitcode(w); +} + +static int check(char *a) +{ + unsigned pid; + + if ((r = svstatus_get()) == -1) return -1; + if (r == 0) { if (*a == 'x') return 1; return -1; } + pid = (unsigned char)svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + switch (*a) { + case 'x': return 0; + case 'u': + if (!pid || svstatus[19] != 1) return 0; + if (!checkscript()) return 0; + break; + case 'd': if (pid) return 0; break; + case 'c': if (pid) if (!checkscript()) return 0; break; + case 't': + if (!pid && svstatus[17] == 'd') break; + tai_unpack(svstatus, &tstatus); + if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript()) + return 0; + break; + case 'o': + tai_unpack(svstatus, &tstatus); + if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd')) + return 0; + } + printf(OK); svstatus_print(*service); puts(""); /* will also flush the output */ + return 1; +} + +static int control(char *a) +{ + if (svstatus_get() <= 0) return -1; + if (svstatus[17] == *a) return 0; + if ((fd = open_write("supervise/control")) == -1) { + if (errno != ENODEV) + warn_cannot("open supervise/control"); + else + *a == 'x' ? ok("runsv not running") : failx("runsv not running"); + return -1; + } + r = write(fd, a, strlen(a)); + close(fd); + if (r != strlen(a)) { + warn_cannot("write to supervise/control"); + return -1; + } + return 1; +} + +int sv_main(int argc, char **argv) +{ + unsigned opt; + unsigned i, want_exit; + char *x; + + for (i = strlen(*argv); i; --i) + if ((*argv)[i-1] == '/') + break; + *argv += i; + service = argv; + services = 1; + if ((x = getenv("SVDIR"))) varservice = x; + if ((x = getenv("SVWAIT"))) waitsec = xatoul(x); + /* TODO: V can be handled internally by getopt_ulflags */ + opt = getopt32(argc, argv, "w:vV", &x); + if (opt & 1) waitsec = xatoul(x); + if (opt & 2) verbose = 1; + if (opt & 4) usage(); + if (!(action = *argv++)) usage(); + --argc; + service = argv; services = argc; + if (!*service) usage(); + + taia_now(&tnow); tstart = tnow; + if ((curdir = open_read(".")) == -1) + fatal_cannot("open current directory"); + + act = &control; acts = "s"; + if (verbose) cbk = ✓ + switch (*action) { + case 'x': case 'e': + acts = "x"; break; + case 'X': case 'E': + acts = "x"; kll = 1; cbk = ✓ break; + case 'D': + acts = "d"; kll = 1; cbk = ✓ break; + case 'T': + acts = "tc"; kll = 1; cbk = ✓ break; + case 'c': + if (!str_diff(action, "check")) { + act = 0; + acts = "c"; + cbk = ✓ + break; + } + case 'u': case 'd': case 'o': case 't': case 'p': case 'h': + case 'a': case 'i': case 'k': case 'q': case '1': case '2': + action[1] = 0; acts = action; break; + case 's': + if (!str_diff(action, "shutdown")) { + acts = "x"; + cbk = ✓ + break; + } + if (!str_diff(action, "start")) { + acts = "u"; + cbk = ✓ + break; + } + if (!str_diff(action, "stop")) { + acts = "d"; + cbk = ✓ + break; + } + act = &status; cbk = 0; break; + case 'r': + if (!str_diff(action, "restart")) { + acts = "tcu"; + cbk = ✓ + break; + } + usage(); + case 'f': + if (!str_diff(action, "force-reload")) + { acts = "tc"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-restart")) + { acts = "tcu"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-shutdown")) + { acts = "x"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-stop")) + { acts = "d"; kll = 1; cbk = ✓ break; } + default: + usage(); + } + + servicex = service; + for (i = 0; i < services; ++i) { + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) if (act && (act(acts) == -1)) *service = 0; + if (fchdir(curdir) == -1) fatal_cannot("change to original directory"); + service++; + } + + if (*cbk) + for (;;) { + taia_sub(&tdiff, &tnow, &tstart); + service = servicex; want_exit = 1; + for (i = 0; i < services; ++i, ++service) { + if (!*service) continue; + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) { if (cbk(acts) != 0) *service = 0; else want_exit = 0; } + if (*service && taia_approx(&tdiff) > waitsec) { + kll ? printf(KILL) : printf(TIMEOUT); + if (svstatus_get() > 0) { svstatus_print(*service); ++rc; } + puts(""); /* will also flush the output */ + if (kll) control("k"); + *service = 0; + } + if (fchdir(curdir) == -1) + fatal_cannot("change to original directory"); + } + if (want_exit) break; + usleep(420000); + taia_now(&tnow); + } + return rc > 99 ? 99 : rc; +}