forgot about avn add... :(
No binary (build failed)
—
(log yok)
| @@ -0,0 +1,613 @@ | |
| + | /* Busyboxed by Denis Vlasenko <[email protected]> */ |
| + | /* TODO: depends on runit_lib.c - review and reduce/eliminate */ |
| + | |
| + | #include <sys/poll.h> |
| + | #include <sys/file.h> |
| + | #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; |
| + | } |
| @@ -0,0 +1,306 @@ | |
| + | /* Busyboxed by Denis Vlasenko <[email protected]> */ |
| + | /* TODO: depends on runit_lib.c - review and reduce/eliminate */ |
| + | |
| + | #include <sys/poll.h> |
| + | #include <sys/file.h> |
| + | #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; |
| + | } |
| @@ -0,0 +1,360 @@ | |
| + | /* Busyboxed by Denis Vlasenko <[email protected]> */ |
| + | /* TODO: depends on runit_lib.c - review and reduce/eliminate */ |
| + | |
| + | #include <sys/poll.h> |
| + | #include <sys/file.h> |
| + | #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; |
| + | } |