3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
-THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*/
/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
-/* TODO: depends on runit_lib.c - review and reduce/eliminate */
-#include <sys/poll.h>
+//config:config RUNSV
+//config: bool "runsv (7.8 kb)"
+//config: default y
+//config: help
+//config: runsv starts and monitors a service and optionally an appendant log
+//config: service.
+
+//applet:IF_RUNSV(APPLET(runsv, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_RUNSV) += runsv.o
+
+//usage:#define runsv_trivial_usage
+//usage: "DIR"
+//usage:#define runsv_full_usage "\n\n"
+//usage: "Start and monitor a service and optionally an appendant log service"
+
#include <sys/file.h>
#include "libbb.h"
+#include "common_bufsiz.h"
#include "runit_lib.h"
#if ENABLE_MONOTONIC_SYSCALL
#else
static void gettimeofday_ns(struct timespec *ts)
{
- if (sizeof(struct timeval) == sizeof(struct timespec)
- && sizeof(((struct timeval*)ts)->tv_usec) == sizeof(ts->tv_nsec)
- ) {
- /* Cheat */
- gettimeofday((void*)ts, NULL);
- ts->tv_nsec *= 1000;
- } else {
- extern void BUG_need_to_implement_gettimeofday_ns(void);
- BUG_need_to_implement_gettimeofday_ns();
- }
+ BUILD_BUG_ON(sizeof(struct timeval) != sizeof(struct timespec));
+ BUILD_BUG_ON(sizeof(((struct timeval*)ts)->tv_usec) != sizeof(ts->tv_nsec));
+ /* Cheat */
+ gettimeofday((void*)ts, NULL);
+ ts->tv_nsec *= 1000;
}
#endif
int pid;
smallint state;
smallint ctrl;
- smallint want;
+ smallint sd_want;
smallint islog;
struct timespec start;
int fdlock;
int fdcontrol;
int fdcontrolwrite;
+ int wstat;
};
struct globals {
struct fd_pair logpipe;
char *dir;
struct svdir svd[2];
-};
-#define G (*(struct globals*)&bb_common_bufsiz1)
+} FIX_ALIASING;
+#define G (*(struct globals*)bb_common_bufsiz1)
#define haslog (G.haslog )
#define sigterm (G.sigterm )
#define pidchanged (G.pidchanged )
#define dir (G.dir )
#define svd (G.svd )
#define INIT_G() do { \
+ setup_common_bufsiz(); \
pidchanged = 1; \
} while (0)
static void fatal2_cannot(const char *m1, const char *m2)
{
- bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
+ bb_perror_msg_and_die("%s: fatal: can't %s%s", dir, m1, m2);
/* was exiting 111 */
}
static void fatal_cannot(const char *m)
}
static void fatal2x_cannot(const char *m1, const char *m2)
{
- bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
+ bb_error_msg_and_die("%s: fatal: can't %s%s", dir, m1, m2);
/* was exiting 111 */
}
+static void warn2_cannot(const char *m1, const char *m2)
+{
+ bb_perror_msg("%s: warning: can't %s%s", dir, m1, m2);
+}
static void warn_cannot(const char *m)
{
- bb_perror_msg("%s: warning: cannot %s", dir, m);
+ warn2_cannot(m, "");
}
static void s_child(int sig_no UNUSED_PARAM)
write(selfpipe.wr, "", 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);
+ /* Why O_NDELAY? */
+ int fd = open(name, O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT, 0644);
if (fd < 0)
bb_perror_msg("%s: warning: cannot open %s",
dir, name);
ssize_t sz;
int fd;
svstatus_t status;
+ const char *fstatus ="log/supervise/status";
+ const char *fstatusnew ="log/supervise/status.new";
+ const char *f_stat ="log/supervise/stat";
+ const char *fstatnew ="log/supervise/stat.new";
+ const char *fpid ="log/supervise/pid";
+ const char *fpidnew ="log/supervise/pid.new";
+
+ if (!s->islog) {
+ fstatus += 4;
+ fstatusnew += 4;
+ f_stat += 4;
+ fstatnew += 4;
+ fpid += 4;
+ fpidnew += 4;
+ }
/* pid */
if (pidchanged) {
- fd = open_trunc_or_warn("supervise/pid.new");
+ fd = open_trunc_or_warn(fpidnew);
if (fd < 0)
return;
if (s->pid) {
write(fd, spid, size);
}
close(fd);
- if (rename_or_warn("supervise/pid.new",
- s->islog ? "log/supervise/pid" : "log/supervise/pid"+4))
+ if (rename_or_warn(fpidnew, fpid))
return;
pidchanged = 0;
}
/* stat */
- fd = open_trunc_or_warn("supervise/stat.new");
+ fd = open_trunc_or_warn(fstatnew);
if (fd < -1)
return;
char *p = stat_buf;
switch (s->state) {
case S_DOWN:
- p = add_str(p, "down");
+ p = stpcpy(p, "down");
break;
case S_RUN:
- p = add_str(p, "run");
+ p = stpcpy(p, "run");
break;
case S_FINISH:
- p = add_str(p, "finish");
+ p = stpcpy(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->ctrl & C_PAUSE)
+ p = stpcpy(p, ", paused");
+ if (s->ctrl & C_TERM)
+ p = stpcpy(p, ", got TERM");
if (s->state != S_DOWN)
- switch (s->want) {
+ switch (s->sd_want) {
case W_DOWN:
- p = add_str(p, ", want down");
+ p = stpcpy(p, ", want down");
break;
case W_EXIT:
- p = add_str(p, ", want exit");
+ p = stpcpy(p, ", want exit");
break;
}
*p++ = '\n';
close(fd);
}
- rename_or_warn("supervise/stat.new",
- s->islog ? "log/supervise/stat" : "log/supervise/stat"+4);
+ rename_or_warn(fstatnew, f_stat);
/* supervise compatibility */
memset(&status, 0, sizeof(status));
status.pid_le32 = SWAP_LE32(s->pid);
if (s->ctrl & C_PAUSE)
status.paused = 1;
- if (s->want == W_UP)
+ if (s->sd_want == W_UP)
status.want = 'u';
else
status.want = 'd';
if (s->ctrl & C_TERM)
status.got_term = 1;
status.run_or_finish = s->state;
- fd = open_trunc_or_warn("supervise/status.new");
+ fd = open_trunc_or_warn(fstatusnew);
if (fd < 0)
return;
sz = write(fd, &status, sizeof(status));
close(fd);
if (sz != sizeof(status)) {
- warn_cannot("write supervise/status.new");
- unlink("supervise/status.new");
+ warn2_cannot("write ", fstatusnew);
+ unlink(fstatusnew);
return;
}
- rename_or_warn("supervise/status.new",
- s->islog ? "log/supervise/status" : "log/supervise/status"+4);
+ rename_or_warn(fstatusnew, fstatus);
}
static unsigned custom(struct svdir *s, char c)
int w;
char a[10];
struct stat st;
- char *prog[2];
- if (s->islog) return 0;
+ if (s->islog)
+ return 0;
strcpy(a, "control/?");
a[8] = c; /* replace '?' */
if (stat(a, &st) == 0) {
if (st.st_mode & S_IXUSR) {
pid = vfork();
if (pid == -1) {
- warn_cannot("vfork for control/?");
+ warn2_cannot("vfork for ", a);
return 0;
}
- if (!pid) {
+ if (pid == 0) {
/* child */
if (haslog && dup2(logpipe.wr, 1) == -1)
- warn_cannot("setup stdout for control/?");
- prog[0] = a;
- prog[1] = NULL;
- execv(a, prog);
- fatal_cannot("run control/?");
+ warn2_cannot("setup stdout for ", a);
+ execl(a, a, (char *) NULL);
+ fatal2_cannot("run ", a);
}
/* parent */
- while (safe_waitpid(pid, &w, 0) == -1) {
- warn_cannot("wait for child control/?");
+ if (safe_waitpid(pid, &w, 0) == -1) {
+ warn2_cannot("wait for child ", a);
return 0;
}
- return !wait_exitcode(w);
+ return WEXITSTATUS(w) == 0;
}
} else {
if (errno != ENOENT)
- warn_cannot("stat control/?");
+ warn2_cannot("stat ", a);
}
return 0;
}
s->ctrl |= C_TERM;
update_status(s);
}
- if (s->want == W_DOWN) {
+ if (s->sd_want == W_DOWN) {
kill(s->pid, SIGCONT);
custom(s, 'd');
return;
}
- if (s->want == W_EXIT) {
+ if (s->sd_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] = (char*)"./finish";
- else {
- run[0] = (char*)"./run";
+ const char *arg[4];
+ char exitcode[sizeof(int)*3 + 2];
+
+ if (s->state == S_FINISH) {
+/* Two arguments are given to ./finish. The first one is ./run exit code,
+ * or -1 if ./run didnt exit normally. The second one is
+ * the least significant byte of the exit status as determined by waitpid;
+ * for instance it is 0 if ./run exited normally, and the signal number
+ * if ./run was terminated by a signal. If runsv cannot start ./run
+ * for some reason, the exit code is 111 and the status is 0.
+ */
+ arg[0] = "./finish";
+ arg[1] = "-1";
+ if (WIFEXITED(s->wstat)) {
+ *utoa_to_buf(WEXITSTATUS(s->wstat), exitcode, sizeof(exitcode)) = '\0';
+ arg[1] = exitcode;
+ }
+ //arg[2] = "0";
+ //if (WIFSIGNALED(s->wstat)) {
+ arg[2] = utoa(WTERMSIG(s->wstat));
+ //}
+ arg[3] = NULL;
+ } else {
+ arg[0] = "./run";
+ arg[1] = NULL;
custom(s, 'u');
}
- run[1] = NULL;
if (s->pid != 0)
stopservice(s); /* should never happen */
, SIG_DFL);*/
sig_unblock(SIGCHLD);
sig_unblock(SIGTERM);
- execvp(*run, run);
- fatal2_cannot(s->islog ? "start log/" : "start ", *run);
+ execv(arg[0], (char**) arg);
+ fatal2_cannot(s->islog ? "start log/" : "start ", arg[0]);
}
/* parent */
if (s->state != S_FINISH) {
switch (c) {
case 'd': /* down */
- s->want = W_DOWN;
+ s->sd_want = W_DOWN;
update_status(s);
- if (s->pid && s->state != S_FINISH)
+ if (s->state == S_RUN)
stopservice(s);
break;
case 'u': /* up */
- s->want = W_UP;
+ s->sd_want = W_UP;
update_status(s);
- if (s->pid == 0)
+ if (s->state == S_DOWN)
startservice(s);
break;
case 'x': /* exit */
if (s->islog)
break;
- s->want = W_EXIT;
+ s->sd_want = W_EXIT;
update_status(s);
/* FALLTHROUGH */
case 't': /* sig term */
- if (s->pid && s->state != S_FINISH)
+ if (s->state == S_RUN)
stopservice(s);
break;
case 'k': /* sig kill */
- if (s->pid && !custom(s, c))
+ if ((s->state == S_RUN) && !custom(s, c))
kill(s->pid, SIGKILL);
s->state = S_DOWN;
break;
case 'p': /* sig pause */
- if (s->pid && !custom(s, c))
+ if ((s->state == S_RUN) && !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))
+ if ((s->state == S_RUN) && !custom(s, c))
kill(s->pid, SIGCONT);
- if (s->ctrl & C_PAUSE)
- s->ctrl &= ~C_PAUSE;
+ s->ctrl &= ~C_PAUSE;
update_status(s);
break;
case 'o': /* once */
- s->want = W_DOWN;
+ s->sd_want = W_DOWN;
update_status(s);
- if (!s->pid)
+ if (s->state == S_DOWN)
startservice(s);
break;
case 'a': /* sig alarm */
}
return 1;
sendsig:
- if (s->pid && !custom(s, c))
+ if ((s->state == S_RUN) && !custom(s, c))
kill(s->pid, sig);
return 1;
}
+static void open_control(const char *f, struct svdir *s)
+{
+ struct stat st;
+ mkfifo(f, 0600);
+ if (stat(f, &st) == -1)
+ fatal2_cannot("stat ", f);
+ if (!S_ISFIFO(st.st_mode))
+ bb_error_msg_and_die("%s: fatal: %s exists but is not a fifo", dir, f);
+ s->fdcontrol = xopen(f, O_RDONLY|O_NDELAY);
+ close_on_exec_on(s->fdcontrol);
+ s->fdcontrolwrite = xopen(f, O_WRONLY|O_NDELAY);
+ close_on_exec_on(s->fdcontrolwrite);
+ update_status(s);
+}
+
int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int runsv_main(int argc UNUSED_PARAM, char **argv)
{
INIT_G();
- if (!argv[1] || argv[2])
- bb_show_usage();
- dir = argv[1];
+ dir = single_argv(argv);
xpiped_pair(selfpipe);
close_on_exec_on(selfpipe.rd);
/* bss: svd[0].pid = 0; */
if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
if (C_NOOP) svd[0].ctrl = C_NOOP;
- if (W_UP) svd[0].want = W_UP;
+ if (W_UP) svd[0].sd_want = W_UP;
/* bss: svd[0].islog = 0; */
/* bss: svd[1].pid = 0; */
gettimeofday_ns(&svd[0].start);
- if (stat("down", &s) != -1) svd[0].want = W_DOWN;
+ if (stat("down", &s) != -1)
+ svd[0].sd_want = W_DOWN;
if (stat("log", &s) == -1) {
if (errno != ENOENT)
haslog = 1;
svd[1].state = S_DOWN;
svd[1].ctrl = C_NOOP;
- svd[1].want = W_UP;
+ svd[1].sd_want = W_UP;
svd[1].islog = 1;
gettimeofday_ns(&svd[1].start);
if (stat("log/down", &s) != -1)
- svd[1].want = W_DOWN;
+ svd[1].sd_want = W_DOWN;
xpiped_pair(logpipe);
close_on_exec_on(logpipe.rd);
close_on_exec_on(logpipe.wr);
}
svd[0].fdlock = xopen3("log/supervise/lock"+4,
O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
- if (lock_exnb(svd[0].fdlock) == -1)
+ if (flock(svd[0].fdlock, LOCK_EX | LOCK_NB) == -1)
fatal_cannot("lock supervise/lock");
close_on_exec_on(svd[0].fdlock);
if (haslog) {
}
svd[1].fdlock = xopen3("log/supervise/lock",
O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
- if (lock_ex(svd[1].fdlock) == -1)
+ if (flock(svd[1].fdlock, LOCK_EX) == -1)
fatal_cannot("lock log/supervise/lock");
close_on_exec_on(svd[1].fdlock);
}
- mkfifo("log/supervise/control"+4, 0600);
- svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
- close_on_exec_on(svd[0].fdcontrol);
- svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
- close_on_exec_on(svd[0].fdcontrolwrite);
- update_status(&svd[0]);
+ open_control("log/supervise/control"+4, &svd[0]);
if (haslog) {
- mkfifo("log/supervise/control", 0600);
- svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
- close_on_exec_on(svd[1].fdcontrol);
- svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
- close_on_exec_on(svd[1].fdcontrolwrite);
- update_status(&svd[1]);
+ open_control("log/supervise/control", &svd[1]);
}
mkfifo("log/supervise/ok"+4, 0600);
fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
char ch;
if (haslog)
- if (!svd[1].pid && svd[1].want == W_UP)
+ if (!svd[1].pid && svd[1].sd_want == W_UP)
startservice(&svd[1]);
if (!svd[0].pid)
- if (svd[0].want == W_UP || svd[0].state == S_FINISH)
+ if (svd[0].sd_want == W_UP || svd[0].state == S_FINISH)
startservice(&svd[0]);
x[0].fd = selfpipe.rd;
if ((child == -1) && (errno != EINTR))
break;
if (child == svd[0].pid) {
+ svd[0].wstat = wstat;
svd[0].pid = 0;
pidchanged = 1;
- svd[0].ctrl &=~ C_TERM;
+ svd[0].ctrl &= ~C_TERM;
if (svd[0].state != S_FINISH) {
- fd = open_read("finish");
+ fd = open("finish", O_RDONLY|O_NDELAY);
if (fd != -1) {
close(fd);
svd[0].state = S_FINISH;
}
if (haslog) {
if (child == svd[1].pid) {
+ svd[0].wstat = wstat;
svd[1].pid = 0;
pidchanged = 1;
svd[1].state = S_DOWN;
sigterm = 0;
}
- if (svd[0].want == W_EXIT && svd[0].state == S_DOWN) {
+ if (svd[0].sd_want == W_EXIT && svd[0].state == S_DOWN) {
if (svd[1].pid == 0)
_exit(EXIT_SUCCESS);
- if (svd[1].want != W_EXIT) {
- svd[1].want = W_EXIT;
+ if (svd[1].sd_want != W_EXIT) {
+ svd[1].sd_want = W_EXIT;
/* stopservice(&svd[1]); */
update_status(&svd[1]);
close(logpipe.wr);