ash: builtin: Mark more regular built-ins
[oweals/busybox.git] / runit / sv.c
index c420a91a6df41bd3c3808b373b163b77295c4131..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,9 +62,9 @@ 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.
 
@@ -85,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.
@@ -112,6 +110,9 @@ 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
 
@@ -126,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.
 
@@ -151,11 +152,47 @@ Exit Codes
 */
 
 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
-/* TODO: depends on runit_lib.c - review and reduce/eliminate */
 
-#include <sys/poll.h>
+//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/file.h>
 #include "libbb.h"
+#include "common_bufsiz.h"
 #include "runit_lib.h"
 
 struct globals {
@@ -165,20 +202,27 @@ struct globals {
 /* "Bernstein" time format: unix + 0x400000000000000aULL */
        uint64_t tstart, tnow;
        svstatus_t svstatus;
+       smallint islog;
 } FIX_ALIASING;
-#define G (*(struct globals*)&bb_common_bufsiz1)
+#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 INIT_G() do { } while (0)
+#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)))
+#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)
 {
@@ -188,9 +232,9 @@ static void fatal_cannot(const char *m1)
 
 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);
        }
        bb_putchar('\n'); /* will also flush the output */
 }
@@ -273,15 +317,14 @@ static unsigned svstatus_print(const char *m)
        }
        pid = SWAP_LE32(svstatus.pid_le32);
        timestamp = SWAP_BE64(svstatus.time_be64);
-       if (pid) {
-               switch (svstatus.run_or_finish) {
+       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);
        }
+       printf("%s: ", m);
+       if (svstatus.run_or_finish)
+               printf("(pid %d) ", pid);
        diff = tnow - timestamp;
        printf("%us", (diff < 0 ? 0 : diff));
        if (pid) {
@@ -304,16 +347,21 @@ static int status(const char *unused UNUSED_PARAM)
                return 0;
 
        r = svstatus_print(*service);
+       islog = 1;
        if (chdir("log") == -1) {
                if (errno != ENOENT) {
-                       printf("; log: "WARN"can't 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');
+               }
        }
-       bb_putchar('\n'); /* will also flush the output */
+       islog = 0;
        return r;
 }
 
@@ -352,35 +400,53 @@ static int check(const char *a)
        r = svstatus_get();
        if (r == -1)
                return -1;
-       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) return 0;
-               break;
-       case 'c':
-               if (pid_le32 && !checkscript()) return 0;
-               break;
-       case 't':
-               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'))
+       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);
@@ -392,14 +458,10 @@ static int control(const char *a)
 {
        int fd, r, l;
 
-/* Is it an optimization?
-   It causes problems with "sv o SRV; ...; sv d SRV"
-   ('d' is not passed to SRV because its .want == 'd'):
        if (svstatus_get() <= 0)
                return -1;
-       if (svstatus.want == *a)
+       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)
@@ -418,10 +480,23 @@ static int control(const char *a)
        return 1;
 }
 
-int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int sv_main(int argc UNUSED_PARAM, 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;
        char *x;
        char *action;
        const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
@@ -441,8 +516,9 @@ int sv_main(int argc UNUSED_PARAM, char **argv)
        x = getenv("SVWAIT");
        if (x) waitsec = xatou(x);
 
-       opt_complementary = "w+:vv"; /* -w N, -v is a counter */
-       opt = getopt32(argv, "w:v", &waitsec, &verbose);
+       getopt32(argv, "^" "w:+v" "\0" "vv" /* -w N, -v is a counter */,
+                       &waitsec, &verbose
+       );
        argv += optind;
        action = *argv++;
        if (!action || !*argv) bb_show_usage();
@@ -476,17 +552,23 @@ int sv_main(int argc UNUSED_PARAM, char **argv)
                acts = "tc";
                kll = 1;
                break;
+       case 't':
+               if (str_equal(action, "try-restart")) {
+                       acts = "tc";
+                       break;
+               }
        case 'c':
                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_equal(action, "shutdown")) {
@@ -510,6 +592,10 @@ int sv_main(int argc UNUSED_PARAM, char **argv)
                        acts = "tcu";
                        break;
                }
+               if (str_equal(action, "reload")) {
+                       acts = "h";
+                       break;
+               }
                bb_show_usage();
        case 'f':
                if (str_equal(action, "force-reload")) {
@@ -538,7 +624,9 @@ int sv_main(int argc UNUSED_PARAM, char **argv)
 
        service = argv;
        while ((x = *service) != NULL) {
-               if (x[0] != '/' && x[0] != '.') {
+               if (x[0] != '/' && x[0] != '.'
+                && !last_char_is(x, '/')
+               ) {
                        if (chdir(varservice) == -1)
                                goto chdir_failed_0;
                }
@@ -601,3 +689,105 @@ int sv_main(int argc UNUSED_PARAM, char **argv)
        }
        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