ash: builtin: Mark more regular built-ins
[oweals/busybox.git] / runit / sv.c
index d52eb6d228f8fe51f442ded82728e1722847ce01..5c249ff95f46200b1aa02179bdd206a36d842ccf 100644 (file)
@@ -13,7 +13,7 @@ modification, are permitted provided that the following conditions are met:
    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,
@@ -25,7 +25,7 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
-/* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
+/* Taken from http://smarden.org/runit/sv.8.html:
 
 sv - control and manage services monitored by runsv
 
@@ -36,17 +36,13 @@ The sv program reports the current status and controls the state of services
 monitored by the runsv(8) supervisor.
 
 services consists of one or more arguments, each argument naming a directory
-service used by runsv(8). If service doesn?t start with a dot or slash,
-it is searched in the default services directory /var/service/, otherwise
-relative to the current directory.
+service used by runsv(8). If service doesn't start with a dot or slash and
+doesn't end with a slash, it is searched in the default services directory
+/var/service/, otherwise relative to the current directory.
 
 command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
-1, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
-force-reload, force-restart, force-shutdown.
-
-The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
-script interface. The service to be controlled then is specified by the
-base name of the "init script".
+1, 2, term, kill, or exit, or start, stop, reload, restart, shutdown,
+force-stop, force-reload, force-restart, force-shutdown, try-restart.
 
 status
     Report the current status of the service, and the appendant log service
@@ -66,12 +62,14 @@ exit
     If the service is running, send it the TERM signal, and the CONT signal.
     Do not restart the service. If the service is down, and no log service
     exists, runsv(8) exits. If the service is down and a log service exists,
-    send the TERM signal to the log service. If the log service is down,
-    runsv(8) exits. This command is ignored if it is given to an appendant
-    log service.
+    runsv(8) closes the standard input of the log service and waits for it to
+    terminate. If the log service is down, runsv(8) exits. This command is
+    ignored if it is given to an appendant log service.
 
 sv actually looks only at the first character of above commands.
 
+Commands compatible to LSB init script actions:
+
 status
     Same as status.
 start
@@ -83,6 +81,8 @@ start
 stop
     Same as down, but wait up to 7 seconds for the service to become down.
     Then report the status or timeout.
+reload
+    Same as hup, and additionally report the status afterwards.
 restart
     Send the commands term, cont, and up to the service, and wait up to
     7 seconds for the service to restart. Then report the status or timeout.
@@ -104,12 +104,15 @@ force-restart
     7 seconds for the service to restart. Then report the status, and
     on timeout send the service the kill command. If the script ./check
     exists in the service directory, sv runs this script to check whether
-    the service is up and available again; it?s considered to be available
+    the service is up and available again; it's considered to be available
     if ./check exits with 0.
 force-shutdown
     Same as exit, but wait up to 7 seconds for the runsv(8) process to
     terminate. Then report the status, and on timeout send the service
     the kill command.
+try-restart
+    if the service is running, send it the term and cont commands, and wait up to
+    7 seconds for the service to restart. Then report the status or timeout.
 
 Additional Commands
 
@@ -124,8 +127,8 @@ check
 Options
 
 -v
-    wait up to 7 seconds for the command to take effect.
-    Then report the status or timeout.
+    If the command is up, down, term, once, cont, or exit, then wait up to 7
+    seconds for the command to take effect. Then report the status or timeout.
 -w sec
     Override the default timeout of 7 seconds with sec seconds. Implies -v.
 
@@ -148,58 +151,115 @@ Exit Codes
     is 99. sv exits 100 on error.
 */
 
-/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
-/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+
+//config:config SV
+//config:      bool "sv (8.5 kb)"
+//config:      default y
+//config:      help
+//config:      sv reports the current status and controls the state of services
+//config:      monitored by the runsv supervisor.
+//config:
+//config:config SV_DEFAULT_SERVICE_DIR
+//config:      string "Default directory for services"
+//config:      default "/var/service"
+//config:      depends on SV || SVC || SVOK
+//config:      help
+//config:      Default directory for services.
+//config:      Defaults to "/var/service"
+//config:
+//config:config SVC
+//config:      bool "svc (8.4 kb)"
+//config:      default y
+//config:      help
+//config:      svc controls the state of services monitored by the runsv supervisor.
+//config:      It is compatible with daemontools command with the same name.
+//config:
+//config:config SVOK
+//config:      bool "svok (1.5 kb)"
+//config:      default y
+//config:      help
+//config:      svok checks whether runsv supervisor is running.
+//config:      It is compatible with daemontools command with the same name.
+
+//applet:IF_SV(  APPLET_NOEXEC(sv,   sv,   BB_DIR_USR_BIN, BB_SUID_DROP, sv  ))
+//applet:IF_SVC( APPLET_NOEXEC(svc,  svc,  BB_DIR_USR_BIN, BB_SUID_DROP, svc ))
+//applet:IF_SVOK(APPLET_NOEXEC(svok, svok, BB_DIR_USR_BIN, BB_SUID_DROP, svok))
+
+//kbuild:lib-$(CONFIG_SV) += sv.o
+//kbuild:lib-$(CONFIG_SVC) += sv.o
+//kbuild:lib-$(CONFIG_SVOK) += sv.o
 
-#include <sys/poll.h>
 #include <sys/file.h>
-#include "busybox.h"
+#include "libbb.h"
+#include "common_bufsiz.h"
 #include "runit_lib.h"
 
-static const char *acts;
-static char **service;
-static unsigned rc;
-static struct taia tstart, tnow;
-static char svstatus[20];
-
-#define usage() bb_show_usage()
-
-static void fatal_cannot(const char *m1) ATTRIBUTE_NORETURN;
+struct globals {
+       const char *acts;
+       char **service;
+       unsigned rc;
+/* "Bernstein" time format: unix + 0x400000000000000aULL */
+       uint64_t tstart, tnow;
+       svstatus_t svstatus;
+       smallint islog;
+} FIX_ALIASING;
+#define G (*(struct globals*)bb_common_bufsiz1)
+#define acts         (G.acts        )
+#define service      (G.service     )
+#define rc           (G.rc          )
+#define tstart       (G.tstart      )
+#define tnow         (G.tnow        )
+#define svstatus     (G.svstatus    )
+#define islog        (G.islog       )
+#define INIT_G() do { \
+       setup_common_bufsiz(); \
+       /* need to zero out, svc calls sv() repeatedly */ \
+       memset(&G, 0, sizeof(G)); \
+} while (0)
+
+
+#define str_equal(s,t) (strcmp((s), (t)) == 0)
+
+
+#if ENABLE_SV || ENABLE_SVC
+static void fatal_cannot(const char *m1) NORETURN;
 static void fatal_cannot(const char *m1)
 {
-       bb_perror_msg("fatal: cannot %s", m1);
+       bb_perror_msg("fatal: can't %s", m1);
        _exit(151);
 }
 
 static void out(const char *p, const char *m1)
 {
-       printf("%s%s: %s", p, *service, m1);
+       printf("%s%s%s: %s", p, *service, islog ? "/log" : "", m1);
        if (errno) {
-               printf(": %s", strerror(errno));
+               printf(": "STRERROR_FMT STRERROR_ERRNO);
        }
-       puts(""); /* will also flush the output */
+       bb_putchar('\n'); /* will also flush the output */
 }
 
 #define WARN    "warning: "
 #define OK      "ok: "
 
-static void fail(const char *m1) {
+static void fail(const char *m1)
+{
        ++rc;
        out("fail: ", m1);
 }
-static void failx(const char *m1) {
+static void failx(const char *m1)
+{
        errno = 0;
        fail(m1);
 }
-static void warn_cannot(const char *m1) {
+static void warn(const char *m1)
+{
        ++rc;
-       out("warning: cannot ", m1);
-}
-static void warnx_cannot(const char *m1) {
-       errno = 0;
-       warn_cannot(m1);
+       /* "warning: <service>: <m1>\n" */
+       out("warning: ", m1);
 }
-static void ok(const char *m1) {
+static void ok(const char *m1)
+{
        errno = 0;
        out(OK, m1);
 }
@@ -208,93 +268,100 @@ static int svstatus_get(void)
 {
        int fd, r;
 
-       fd = open_write("supervise/ok");
+       fd = open("supervise/ok", O_WRONLY|O_NDELAY);
        if (fd == -1) {
                if (errno == ENODEV) {
                        *acts == 'x' ? ok("runsv not running")
                                     : failx("runsv not running");
                        return 0;
                }
-               warn_cannot("open supervise/ok");
+               warn("can't open supervise/ok");
                return -1;
        }
        close(fd);
-       fd = open_read("supervise/status");
+       fd = open("supervise/status", O_RDONLY|O_NDELAY);
        if (fd == -1) {
-               warn_cannot("open supervise/status");
+               warn("can't open supervise/status");
                return -1;
        }
-       r = read(fd, svstatus, 20);
+       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;
+       case 20:
+               break;
+       case -1:
+               warn("can't read supervise/status");
+               return -1;
+       default:
+               errno = 0;
+               warn("can't read supervise/status: bad format");
+               return -1;
        }
        return 1;
 }
 
 static unsigned svstatus_print(const char *m)
 {
-       long diff;
+       int diff;
        int pid;
        int normallyup = 0;
        struct stat s;
-       struct tai tstatus;
+       uint64_t timestamp;
 
        if (stat("down", &s) == -1) {
                if (errno != ENOENT) {
-                       bb_perror_msg(WARN"cannot stat %s/down", *service);
+                       bb_perror_msg(WARN"can't 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]) {
+       pid = SWAP_LE32(svstatus.pid_le32);
+       timestamp = SWAP_BE64(svstatus.time_be64);
+       switch (svstatus.run_or_finish) {
+               case 0: printf("down: "); break;
                case 1: printf("run: "); break;
                case 2: printf("finish: "); break;
-               }
-               printf("%s: (pid %d) ", m, pid);
-       } else {
-               printf("down: %s: ", m);
        }
-       diff = tnow.sec.x - tstatus.x;
-       printf("%lds", (diff < 0 ? 0L : diff));
+       printf("%s: ", m);
+       if (svstatus.run_or_finish)
+               printf("(pid %d) ", pid);
+       diff = tnow - timestamp;
+       printf("%us", (diff < 0 ? 0 : diff));
        if (pid) {
                if (!normallyup) printf(", normally down");
-               if (svstatus[16]) printf(", paused");
-               if (svstatus[17] == 'd') printf(", want down");
-               if (svstatus[18]) printf(", got TERM");
+               if (svstatus.paused) printf(", paused");
+               if (svstatus.want == 'd') printf(", want down");
+               if (svstatus.got_term) printf(", got TERM");
        } else {
                if (normallyup) printf(", normally up");
-               if (svstatus[17] == 'u') printf(", want up");
+               if (svstatus.want == 'u') printf(", want up");
        }
        return pid ? 1 : 2;
 }
 
-static int status(const char *unused)
+static int status(const char *unused UNUSED_PARAM)
 {
        int r;
 
-       r = svstatus_get();
-       switch (r) { case -1: case 0: return 0; }
+       if (svstatus_get() <= 0)
+               return 0;
 
        r = svstatus_print(*service);
+       islog = 1;
        if (chdir("log") == -1) {
                if (errno != ENOENT) {
-                       printf("; log: "WARN"cannot change to log service directory: %s",
-                                       strerror(errno));
-               }
-       } else if (svstatus_get()) {
+                       printf("; ");
+                       warn("can't change directory");
+               } else
+                       bb_putchar('\n');
+       } else {
                printf("; ");
-               svstatus_print("log");
+               if (svstatus_get()) {
+                       r = svstatus_print("log");
+                       bb_putchar('\n');
+               }
        }
-       puts(""); /* will also flush the output */
+       islog = 0;
        return r;
 }
 
@@ -306,7 +373,7 @@ static int checkscript(void)
 
        if (stat("check", &s) == -1) {
                if (errno == ENOENT) return 1;
-               bb_perror_msg(WARN"cannot stat %s/check", *service);
+               bb_perror_msg(WARN"can't stat %s/check", *service);
                return 0;
        }
        /* if (!(s.st_mode & S_IXUSR)) return 1; */
@@ -314,125 +381,151 @@ static int checkscript(void)
        prog[1] = NULL;
        pid = spawn(prog);
        if (pid <= 0) {
-               bb_perror_msg(WARN"cannot %s child %s/check", "run", *service);
+               bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
                return 0;
        }
-       while (wait_pid(&w, pid) == -1) {
-               if (errno == EINTR) continue;
-               bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
+       while (safe_waitpid(pid, &w, 0) == -1) {
+               bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
                return 0;
        }
-       return !wait_exitcode(w);
+       return WEXITSTATUS(w) == 0;
 }
 
 static int check(const char *a)
 {
        int r;
-       unsigned pid;
-       struct tai tstatus;
+       unsigned pid_le32;
+       uint64_t timestamp;
 
        r = svstatus_get();
        if (r == -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 && !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'))
+       while (*a) {
+               if (r == 0) {
+                       if (*a == 'x')
+                               return 1;
+                       return -1;
+               }
+               pid_le32 = svstatus.pid_le32;
+               switch (*a) {
+               case 'x':
                        return 0;
+               case 'u':
+                       if (!pid_le32 || svstatus.run_or_finish != 1)
+                               return 0;
+                       if (!checkscript())
+                               return 0;
+                       break;
+               case 'd':
+                       if (pid_le32 || svstatus.run_or_finish != 0)
+                               return 0;
+                       break;
+               case 'C':
+                       if (pid_le32 && !checkscript())
+                               return 0;
+                       break;
+               case 't':
+               case 'k':
+                       if (!pid_le32 && svstatus.want == 'd')
+                               break;
+                       timestamp = SWAP_BE64(svstatus.time_be64);
+                       if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
+                               return 0;
+                       break;
+               case 'o':
+                       timestamp = SWAP_BE64(svstatus.time_be64);
+                       if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
+                               return 0;
+                       break;
+               case 'p':
+                       if (pid_le32 && !svstatus.paused)
+                               return 0;
+                       break;
+               case 'c':
+                       if (pid_le32 && svstatus.paused)
+                               return 0;
+                       break;
+               }
+               ++a;
        }
        printf(OK);
        svstatus_print(*service);
-       puts(""); /* will also flush the output */
+       bb_putchar('\n'); /* will also flush the output */
        return 1;
 }
 
 static int control(const char *a)
 {
-       int fd, r;
+       int fd, r, l;
 
-       if (svstatus_get() <= 0) return -1;
-       if (svstatus[17] == *a) return 0;
-       fd = open_write("supervise/control");
+       if (svstatus_get() <= 0)
+               return -1;
+       if (svstatus.want == *a && (*a != 'd' || svstatus.got_term == 1))
+               return 0;
+       fd = open("supervise/control", O_WRONLY|O_NDELAY);
        if (fd == -1) {
                if (errno != ENODEV)
-                       warn_cannot("open supervise/control");
+                       warn("can't open supervise/control");
                else
                        *a == 'x' ? ok("runsv not running") : failx("runsv not running");
                return -1;
        }
-       r = write(fd, a, strlen(a));
+       l = strlen(a);
+       r = write(fd, a, l);
        close(fd);
-       if (r != strlen(a)) {
-               warn_cannot("write to supervise/control");
+       if (r != l) {
+               warn("can't write to supervise/control");
                return -1;
        }
        return 1;
 }
 
-int sv_main(int argc, char **argv);
-int sv_main(int argc, char **argv)
+//usage:#define sv_trivial_usage
+//usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
+//usage:#define sv_full_usage "\n\n"
+//usage:       "Control services monitored by runsv supervisor.\n"
+//usage:       "Commands (only first character is enough):\n"
+//usage:       "\n"
+//usage:       "status: query service status\n"
+//usage:       "up: if service isn't running, start it. If service stops, restart it\n"
+//usage:       "once: like 'up', but if service stops, don't restart it\n"
+//usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
+//usage:       "       if it exists. After it stops, don't restart service\n"
+//usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
+//usage:       "       runsv exits too\n"
+//usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
+//usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
+static int sv(char **argv)
 {
-       unsigned opt;
-       unsigned i, want_exit;
        char *x;
        char *action;
-       const char *varservice = "/var/service/";
-       unsigned services;
-       char **servicex;
-       unsigned long waitsec = 7;
+       const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
+       unsigned waitsec = 7;
        smallint kll = 0;
-       smallint verbose = 0;
+       int verbose = 0;
        int (*act)(const char*);
        int (*cbk)(const char*);
        int curdir;
 
+       INIT_G();
+
        xfunc_error_retval = 100;
 
        x = getenv("SVDIR");
        if (x) varservice = x;
        x = getenv("SVWAIT");
-       if (x) waitsec = xatoul(x);
+       if (x) waitsec = xatou(x);
 
-       opt = getopt32(argc, argv, "w:v", &x);
-       if (opt & 1) waitsec = xatoul(x); // -w
-       if (opt & 2) verbose = 1; // -v
-       argc -= optind;
+       getopt32(argv, "^" "w:+v" "\0" "vv" /* -w N, -v is a counter */,
+                       &waitsec, &verbose
+       );
        argv += optind;
        action = *argv++;
-       if (!action || !*argv) usage();
-       service = argv;
-       services = argc - 1;
+       if (!action || !*argv) bb_show_usage();
 
-       taia_now(&tnow);
+       tnow = time(NULL) + 0x400000000000000aULL;
        tstart = tnow;
-       curdir = open_read(".");
+       curdir = open(".", O_RDONLY|O_NDELAY);
        if (curdir == -1)
                fatal_cannot("open current directory");
 
@@ -459,28 +552,34 @@ int sv_main(int argc, char **argv)
                acts = "tc";
                kll = 1;
                break;
+       case 't':
+               if (str_equal(action, "try-restart")) {
+                       acts = "tc";
+                       break;
+               }
        case 'c':
-               if (!str_diff(action, "check")) {
+               if (str_equal(action, "check")) {
                        act = NULL;
-                       acts = "c";
+                       acts = "C";
                        break;
                }
-       case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
+       case 'u': case 'd': case 'o': case 'p': case 'h':
        case 'a': case 'i': case 'k': case 'q': case '1': case '2':
                action[1] = '\0';
                acts = action;
-               if (!verbose) cbk = NULL;
+               if (!verbose)
+                       cbk = NULL;
                break;
        case 's':
-               if (!str_diff(action, "shutdown")) {
+               if (str_equal(action, "shutdown")) {
                        acts = "x";
                        break;
                }
-               if (!str_diff(action, "start")) {
+               if (str_equal(action, "start")) {
                        acts = "u";
                        break;
                }
-               if (!str_diff(action, "stop")) {
+               if (str_equal(action, "stop")) {
                        acts = "d";
                        break;
                }
@@ -489,50 +588,56 @@ int sv_main(int argc, char **argv)
                cbk = NULL;
                break;
        case 'r':
-               if (!str_diff(action, "restart")) {
+               if (str_equal(action, "restart")) {
                        acts = "tcu";
                        break;
                }
-               usage();
+               if (str_equal(action, "reload")) {
+                       acts = "h";
+                       break;
+               }
+               bb_show_usage();
        case 'f':
-               if (!str_diff(action, "force-reload")) {
+               if (str_equal(action, "force-reload")) {
                        acts = "tc";
                        kll = 1;
                        break;
                }
-               if (!str_diff(action, "force-restart")) {
+               if (str_equal(action, "force-restart")) {
                        acts = "tcu";
                        kll = 1;
                        break;
                }
-               if (!str_diff(action, "force-shutdown")) {
+               if (str_equal(action, "force-shutdown")) {
                        acts = "x";
                        kll = 1;
                        break;
                }
-               if (!str_diff(action, "force-stop")) {
+               if (str_equal(action, "force-stop")) {
                        acts = "d";
                        kll = 1;
                        break;
                }
        default:
-               usage();
+               bb_show_usage();
        }
 
-       servicex = service;
-       for (i = 0; i < services; ++i) {
-               if ((**service != '/') && (**service != '.')) {
+       service = argv;
+       while ((x = *service) != NULL) {
+               if (x[0] != '/' && x[0] != '.'
+                && !last_char_is(x, '/')
+               ) {
                        if (chdir(varservice) == -1)
                                goto chdir_failed_0;
                }
-               if (chdir(*service) == -1) {
+               if (chdir(x) == -1) {
  chdir_failed_0:
-                       fail("cannot change to service directory");
+                       fail("can't change to service directory");
                        goto nullify_service_0;
                }
                if (act && (act(acts) == -1)) {
  nullify_service_0:
-                       *service = NULL;
+                       *service = (char*) -1L; /* "dead" */
                }
                if (fchdir(curdir) == -1)
                        fatal_cannot("change to original directory");
@@ -540,23 +645,22 @@ int sv_main(int argc, char **argv)
        }
 
        if (cbk) while (1) {
-               //struct taia tdiff;
-               long diff;
+               int want_exit;
+               int diff;
 
-               //taia_sub(&tdiff, &tnow, &tstart);
-               diff = tnow.sec.x - tstart.sec.x;
-               service = servicex;
+               diff = tnow - tstart;
+               service = argv;
                want_exit = 1;
-               for (i = 0; i < services; ++i, ++service) {
-                       if (!*service)
-                               continue;
-                       if ((**service != '/') && (**service != '.')) {
+               while ((x = *service) != NULL) {
+                       if (x == (char*) -1L) /* "dead" */
+                               goto next;
+                       if (x[0] != '/' && x[0] != '.') {
                                if (chdir(varservice) == -1)
                                        goto chdir_failed;
                        }
-                       if (chdir(*service) == -1) {
+                       if (chdir(x) == -1) {
  chdir_failed:
-                               fail("cannot change to service directory");
+                               fail("can't change to service directory");
                                goto nullify_service;
                        }
                        if (cbk(acts) != 0)
@@ -565,21 +669,125 @@ int sv_main(int argc, char **argv)
                        if (diff >= waitsec) {
                                printf(kll ? "kill: " : "timeout: ");
                                if (svstatus_get() > 0) {
-                                       svstatus_print(*service);
+                                       svstatus_print(x);
                                        ++rc;
                                }
-                               puts(""); /* will also flush the output */
+                               bb_putchar('\n'); /* will also flush the output */
                                if (kll)
                                        control("k");
  nullify_service:
-                               *service = NULL;
+                               *service = (char*) -1L; /* "dead" */
                        }
                        if (fchdir(curdir) == -1)
                                fatal_cannot("change to original directory");
+ next:
+                       service++;
                }
                if (want_exit) break;
                usleep(420000);
-               taia_now(&tnow);
+               tnow = time(NULL) + 0x400000000000000aULL;
        }
        return rc > 99 ? 99 : rc;
 }
+#endif
+
+#if ENABLE_SV
+int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sv_main(int argc UNUSED_PARAM, char **argv)
+{
+       return sv(argv);
+}
+#endif
+
+//usage:#define svc_trivial_usage
+//usage:       "[-udopchaitkx] SERVICE_DIR..."
+//usage:#define svc_full_usage "\n\n"
+//usage:       "Control services monitored by runsv supervisor"
+//usage:   "\n"
+//usage:   "\n""       -u      If service is not running, start it; restart if it stops"
+//usage:   "\n""       -d      If service is running, send TERM+CONT signals; do not restart it"
+//usage:   "\n""       -o      Once: if service is not running, start it; do not restart it"
+//usage:   "\n""       -pchaitk Send STOP, CONT, HUP, ALRM, INT, TERM, KILL signal to service"
+//usage:   "\n""       -x      Exit: runsv will exit as soon as the service is down"
+#if ENABLE_SVC
+int svc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int svc_main(int argc UNUSED_PARAM, char **argv)
+{
+       char command[2];
+       const char *optstring;
+       unsigned opts;
+
+       optstring = "udopchaitkx";
+       opts = getopt32(argv, optstring);
+       argv += optind;
+       if (!argv[0] || !opts)
+               bb_show_usage();
+
+       argv -= 2;
+       if (optind > 2) {
+               argv--;
+               argv[2] = (char*)"--";
+       }
+       argv[0] = (char*)"sv";
+       argv[1] = command;
+       command[1] = '\0';
+
+       do {
+               if (opts & 1) {
+                       int r;
+
+                       command[0] = *optstring;
+
+                       /* getopt() was already called by getopt32():
+                        * reset the libc getopt() function's internal state.
+                        */
+                       GETOPT_RESET();
+                       r = sv(argv);
+                       if (r)
+                               return 1;
+               }
+               optstring++;
+               opts >>= 1;
+       } while (opts);
+
+       return 0;
+}
+#endif
+
+//usage:#define svok_trivial_usage
+//usage:       "SERVICE_DIR"
+//usage:#define svok_full_usage "\n\n"
+//usage:       "Check whether runsv supervisor is running.\n"
+//usage:       "Exit code is 0 if it does, 100 if it does not,\n"
+//usage:       "111 (with error message) if SERVICE_DIR does not exist."
+#if ENABLE_SVOK
+int svok_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int svok_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *dir = argv[1];
+
+       if (!dir)
+               bb_show_usage();
+
+       xfunc_error_retval = 111;
+
+       /*
+        * daemontools has no concept of "default service dir", runit does.
+        * Let's act as runit.
+        */
+       if (dir[0] != '/' && dir[0] != '.'
+        && !last_char_is(dir, '/')
+       ) {
+               xchdir(CONFIG_SV_DEFAULT_SERVICE_DIR);
+       }
+
+       xchdir(dir);
+       if (open("supervise/ok", O_WRONLY) < 0) {
+               if (errno == ENOENT || errno == ENXIO)
+                       return 100;
+               bb_perror_msg_and_die("can't open '%s'", "supervise/ok");
+       }
+
+       return 0;
+}
+#endif