ash: builtin: Mark more regular built-ins
[oweals/busybox.git] / runit / sv.c
index 9e2132259be760f3a09310bdbc63685df0a434eb..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.
 
@@ -153,32 +154,41 @@ Exit Codes
 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
 
 //config:config SV
-//config:      bool "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:      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
+//config:      depends on SV || SVC || SVOK
 //config:      help
-//config:        Default directory for services.
-//config:        Defaults to "/var/service"
+//config:      Default directory for services.
+//config:      Defaults to "/var/service"
 //config:
 //config:config SVC
-//config:      bool "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:        svc controls the state of services monitored by the runsv supervisor.
-//config:        It is comaptible with daemontools command with the same name.
+//config:      svok checks whether runsv supervisor is running.
+//config:      It is compatible with daemontools command with the same name.
 
-//applet:IF_SV(APPLET(sv, BB_DIR_USR_BIN, BB_SUID_DROP))
-//applet:IF_SVC(APPLET(svc, BB_DIR_USR_BIN, BB_SUID_DROP))
+//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"
@@ -192,6 +202,7 @@ 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 acts         (G.acts        )
@@ -200,12 +211,18 @@ struct globals {
 #define tstart       (G.tstart      )
 #define tnow         (G.tnow        )
 #define svstatus     (G.svstatus    )
-#define INIT_G() do { setup_common_bufsiz(); } 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)) == 0)
 
 
+#if ENABLE_SV || ENABLE_SVC
 static void fatal_cannot(const char *m1) NORETURN;
 static void fatal_cannot(const char *m1)
 {
@@ -215,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 */
 }
@@ -300,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) {
@@ -331,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;
 }
 
@@ -379,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);
@@ -419,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)
@@ -481,8 +516,9 @@ static int sv(char **argv)
        x = getenv("SVWAIT");
        if (x) waitsec = xatou(x);
 
-       opt_complementary = "vv"; /* -w N, -v is a counter */
-       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();
@@ -516,17 +552,23 @@ static int sv(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")) {
@@ -550,6 +592,10 @@ static int sv(char **argv)
                        acts = "tcu";
                        break;
                }
+               if (str_equal(action, "reload")) {
+                       acts = "h";
+                       break;
+               }
                bb_show_usage();
        case 'f':
                if (str_equal(action, "force-reload")) {
@@ -578,7 +624,9 @@ static int sv(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;
                }
@@ -641,6 +689,7 @@ static int sv(char **argv)
        }
        return rc > 99 ? 99 : rc;
 }
+#endif
 
 #if ENABLE_SV
 int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -668,8 +717,6 @@ int svc_main(int argc UNUSED_PARAM, char **argv)
        const char *optstring;
        unsigned opts;
 
-       INIT_G();
-
        optstring = "udopchaitkx";
        opts = getopt32(argv, optstring);
        argv += optind;
@@ -685,20 +732,16 @@ int svc_main(int argc UNUSED_PARAM, char **argv)
        argv[1] = command;
        command[1] = '\0';
 
-       /* getopt32() was already called:
-        * reset the libc getopt() function, which keeps internal state.
-        */
-#ifdef __GLIBC__
-       optind = 0;
-#else /* BSD style */
-       optind = 1;
-       /* optreset = 1; */
-#endif
-
        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;
@@ -710,3 +753,41 @@ int svc_main(int argc UNUSED_PARAM, char **argv)
        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