busybox/runit/sv.c
<<
>>
Prefs
   1/*
   2Copyright (c) 2001-2006, Gerrit Pape
   3All rights reserved.
   4
   5Redistribution and use in source and binary forms, with or without
   6modification, are permitted provided that the following conditions are met:
   7
   8   1. Redistributions of source code must retain the above copyright notice,
   9      this list of conditions and the following disclaimer.
  10   2. Redistributions in binary form must reproduce the above copyright
  11      notice, this list of conditions and the following disclaimer in the
  12      documentation and/or other materials provided with the distribution.
  13   3. The name of the author may not be used to endorse or promote products
  14      derived from this software without specific prior written permission.
  15
  16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
  17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  25ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26*/
  27
  28/* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
  29
  30sv - control and manage services monitored by runsv
  31
  32sv [-v] [-w sec] command services
  33/etc/init.d/service [-w sec] command
  34
  35The sv program reports the current status and controls the state of services
  36monitored by the runsv(8) supervisor.
  37
  38services consists of one or more arguments, each argument naming a directory
  39service used by runsv(8). If service doesn't start with a dot or slash,
  40it is searched in the default services directory /var/service/, otherwise
  41relative to the current directory.
  42
  43command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
  441, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
  45force-reload, force-restart, force-shutdown.
  46
  47The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
  48script interface. The service to be controlled then is specified by the
  49base name of the "init script".
  50
  51status
  52    Report the current status of the service, and the appendant log service
  53    if available, to standard output.
  54up
  55    If the service is not running, start it. If the service stops, restart it.
  56down
  57    If the service is running, send it the TERM signal, and the CONT signal.
  58    If ./run exits, start ./finish if it exists. After it stops, do not
  59    restart service.
  60once
  61    If the service is not running, start it. Do not restart it if it stops.
  62pause cont hup alarm interrupt quit 1 2 term kill
  63    If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
  64    USR1, USR2, TERM, or KILL signal respectively.
  65exit
  66    If the service is running, send it the TERM signal, and the CONT signal.
  67    Do not restart the service. If the service is down, and no log service
  68    exists, runsv(8) exits. If the service is down and a log service exists,
  69    send the TERM signal to the log service. If the log service is down,
  70    runsv(8) exits. This command is ignored if it is given to an appendant
  71    log service.
  72
  73sv actually looks only at the first character of above commands.
  74
  75Commands compatible to LSB init script actions:
  76
  77status
  78    Same as status.
  79start
  80    Same as up, but wait up to 7 seconds for the command to take effect.
  81    Then report the status or timeout. If the script ./check exists in
  82    the service directory, sv runs this script to check whether the service
  83    is up and available; it's considered to be available if ./check exits
  84    with 0.
  85stop
  86    Same as down, but wait up to 7 seconds for the service to become down.
  87    Then report the status or timeout.
  88restart
  89    Send the commands term, cont, and up to the service, and wait up to
  90    7 seconds for the service to restart. Then report the status or timeout.
  91    If the script ./check exists in the service directory, sv runs this script
  92    to check whether the service is up and available again; it's considered
  93    to be available if ./check exits with 0.
  94shutdown
  95    Same as exit, but wait up to 7 seconds for the runsv(8) process
  96    to terminate. Then report the status or timeout.
  97force-stop
  98    Same as down, but wait up to 7 seconds for the service to become down.
  99    Then report the status, and on timeout send the service the kill command.
 100force-reload
 101    Send the service the term and cont commands, and wait up to
 102    7 seconds for the service to restart. Then report the status,
 103    and on timeout send the service the kill command.
 104force-restart
 105    Send the service the term, cont and up commands, and wait up to
 106    7 seconds for the service to restart. Then report the status, and
 107    on timeout send the service the kill command. If the script ./check
 108    exists in the service directory, sv runs this script to check whether
 109    the service is up and available again; it's considered to be available
 110    if ./check exits with 0.
 111force-shutdown
 112    Same as exit, but wait up to 7 seconds for the runsv(8) process to
 113    terminate. Then report the status, and on timeout send the service
 114    the kill command.
 115
 116Additional Commands
 117
 118check
 119    Check for the service to be in the state that's been requested. Wait up to
 120    7 seconds for the service to reach the requested state, then report
 121    the status or timeout. If the requested state of the service is up,
 122    and the script ./check exists in the service directory, sv runs
 123    this script to check whether the service is up and running;
 124    it's considered to be up if ./check exits with 0.
 125
 126Options
 127
 128-v
 129    wait up to 7 seconds for the command to take effect.
 130    Then report the status or timeout.
 131-w sec
 132    Override the default timeout of 7 seconds with sec seconds. Implies -v.
 133
 134Environment
 135
 136SVDIR
 137    The environment variable $SVDIR overrides the default services directory
 138    /var/service.
 139SVWAIT
 140    The environment variable $SVWAIT overrides the default 7 seconds to wait
 141    for a command to take effect. It is overridden by the -w option.
 142
 143Exit Codes
 144    sv exits 0, if the command was successfully sent to all services, and,
 145    if it was told to wait, the command has taken effect to all services.
 146
 147    For each service that caused an error (e.g. the directory is not
 148    controlled by a runsv(8) process, or sv timed out while waiting),
 149    sv increases the exit code by one and exits non zero. The maximum
 150    is 99. sv exits 100 on error.
 151*/
 152
 153/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
 154/* TODO: depends on runit_lib.c - review and reduce/eliminate */
 155
 156//usage:#define sv_trivial_usage
 157//usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
 158//usage:#define sv_full_usage "\n\n"
 159//usage:       "Control services monitored by runsv supervisor.\n"
 160//usage:       "Commands (only first character is enough):\n"
 161//usage:       "\n"
 162//usage:       "status: query service status\n"
 163//usage:       "up: if service isn't running, start it. If service stops, restart it\n"
 164//usage:       "once: like 'up', but if service stops, don't restart it\n"
 165//usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
 166//usage:       "        if it exists. After it stops, don't restart service\n"
 167//usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
 168//usage:       "        runsv exits too\n"
 169//usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
 170//usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
 171
 172#include <sys/file.h>
 173#include "libbb.h"
 174#include "runit_lib.h"
 175
 176struct globals {
 177        const char *acts;
 178        char **service;
 179        unsigned rc;
 180/* "Bernstein" time format: unix + 0x400000000000000aULL */
 181        uint64_t tstart, tnow;
 182        svstatus_t svstatus;
 183} FIX_ALIASING;
 184#define G (*(struct globals*)&bb_common_bufsiz1)
 185#define acts         (G.acts        )
 186#define service      (G.service     )
 187#define rc           (G.rc          )
 188#define tstart       (G.tstart      )
 189#define tnow         (G.tnow        )
 190#define svstatus     (G.svstatus    )
 191#define INIT_G() do { } while (0)
 192
 193
 194#define str_equal(s,t) (!strcmp((s), (t)))
 195
 196
 197static void fatal_cannot(const char *m1) NORETURN;
 198static void fatal_cannot(const char *m1)
 199{
 200        bb_perror_msg("fatal: can't %s", m1);
 201        _exit(151);
 202}
 203
 204static void out(const char *p, const char *m1)
 205{
 206        printf("%s%s: %s", p, *service, m1);
 207        if (errno) {
 208                printf(": %s", strerror(errno));
 209        }
 210        bb_putchar('\n'); /* will also flush the output */
 211}
 212
 213#define WARN    "warning: "
 214#define OK      "ok: "
 215
 216static void fail(const char *m1)
 217{
 218        ++rc;
 219        out("fail: ", m1);
 220}
 221static void failx(const char *m1)
 222{
 223        errno = 0;
 224        fail(m1);
 225}
 226static void warn(const char *m1)
 227{
 228        ++rc;
 229        /* "warning: <service>: <m1>\n" */
 230        out("warning: ", m1);
 231}
 232static void ok(const char *m1)
 233{
 234        errno = 0;
 235        out(OK, m1);
 236}
 237
 238static int svstatus_get(void)
 239{
 240        int fd, r;
 241
 242        fd = open("supervise/ok", O_WRONLY|O_NDELAY);
 243        if (fd == -1) {
 244                if (errno == ENODEV) {
 245                        *acts == 'x' ? ok("runsv not running")
 246                                     : failx("runsv not running");
 247                        return 0;
 248                }
 249                warn("can't open supervise/ok");
 250                return -1;
 251        }
 252        close(fd);
 253        fd = open("supervise/status", O_RDONLY|O_NDELAY);
 254        if (fd == -1) {
 255                warn("can't open supervise/status");
 256                return -1;
 257        }
 258        r = read(fd, &svstatus, 20);
 259        close(fd);
 260        switch (r) {
 261        case 20:
 262                break;
 263        case -1:
 264                warn("can't read supervise/status");
 265                return -1;
 266        default:
 267                errno = 0;
 268                warn("can't read supervise/status: bad format");
 269                return -1;
 270        }
 271        return 1;
 272}
 273
 274static unsigned svstatus_print(const char *m)
 275{
 276        int diff;
 277        int pid;
 278        int normallyup = 0;
 279        struct stat s;
 280        uint64_t timestamp;
 281
 282        if (stat("down", &s) == -1) {
 283                if (errno != ENOENT) {
 284                        bb_perror_msg(WARN"can't stat %s/down", *service);
 285                        return 0;
 286                }
 287                normallyup = 1;
 288        }
 289        pid = SWAP_LE32(svstatus.pid_le32);
 290        timestamp = SWAP_BE64(svstatus.time_be64);
 291        if (pid) {
 292                switch (svstatus.run_or_finish) {
 293                case 1: printf("run: "); break;
 294                case 2: printf("finish: "); break;
 295                }
 296                printf("%s: (pid %d) ", m, pid);
 297        } else {
 298                printf("down: %s: ", m);
 299        }
 300        diff = tnow - timestamp;
 301        printf("%us", (diff < 0 ? 0 : diff));
 302        if (pid) {
 303                if (!normallyup) printf(", normally down");
 304                if (svstatus.paused) printf(", paused");
 305                if (svstatus.want == 'd') printf(", want down");
 306                if (svstatus.got_term) printf(", got TERM");
 307        } else {
 308                if (normallyup) printf(", normally up");
 309                if (svstatus.want == 'u') printf(", want up");
 310        }
 311        return pid ? 1 : 2;
 312}
 313
 314static int status(const char *unused UNUSED_PARAM)
 315{
 316        int r;
 317
 318        if (svstatus_get() <= 0)
 319                return 0;
 320
 321        r = svstatus_print(*service);
 322        if (chdir("log") == -1) {
 323                if (errno != ENOENT) {
 324                        printf("; log: "WARN"can't change to log service directory: %s",
 325                                        strerror(errno));
 326                }
 327        } else if (svstatus_get()) {
 328                printf("; ");
 329                svstatus_print("log");
 330        }
 331        bb_putchar('\n'); /* will also flush the output */
 332        return r;
 333}
 334
 335static int checkscript(void)
 336{
 337        char *prog[2];
 338        struct stat s;
 339        int pid, w;
 340
 341        if (stat("check", &s) == -1) {
 342                if (errno == ENOENT) return 1;
 343                bb_perror_msg(WARN"can't stat %s/check", *service);
 344                return 0;
 345        }
 346        /* if (!(s.st_mode & S_IXUSR)) return 1; */
 347        prog[0] = (char*)"./check";
 348        prog[1] = NULL;
 349        pid = spawn(prog);
 350        if (pid <= 0) {
 351                bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
 352                return 0;
 353        }
 354        while (safe_waitpid(pid, &w, 0) == -1) {
 355                bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
 356                return 0;
 357        }
 358        return WEXITSTATUS(w) == 0;
 359}
 360
 361static int check(const char *a)
 362{
 363        int r;
 364        unsigned pid_le32;
 365        uint64_t timestamp;
 366
 367        r = svstatus_get();
 368        if (r == -1)
 369                return -1;
 370        if (r == 0) {
 371                if (*a == 'x')
 372                        return 1;
 373                return -1;
 374        }
 375        pid_le32 = svstatus.pid_le32;
 376        switch (*a) {
 377        case 'x':
 378                return 0;
 379        case 'u':
 380                if (!pid_le32 || svstatus.run_or_finish != 1) return 0;
 381                if (!checkscript()) return 0;
 382                break;
 383        case 'd':
 384                if (pid_le32) return 0;
 385                break;
 386        case 'c':
 387                if (pid_le32 && !checkscript()) return 0;
 388                break;
 389        case 't':
 390                if (!pid_le32 && svstatus.want == 'd') break;
 391                timestamp = SWAP_BE64(svstatus.time_be64);
 392                if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
 393                        return 0;
 394                break;
 395        case 'o':
 396                timestamp = SWAP_BE64(svstatus.time_be64);
 397                if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
 398                        return 0;
 399        }
 400        printf(OK);
 401        svstatus_print(*service);
 402        bb_putchar('\n'); /* will also flush the output */
 403        return 1;
 404}
 405
 406static int control(const char *a)
 407{
 408        int fd, r, l;
 409
 410/* Is it an optimization?
 411   It causes problems with "sv o SRV; ...; sv d SRV"
 412   ('d' is not passed to SRV because its .want == 'd'):
 413        if (svstatus_get() <= 0)
 414                return -1;
 415        if (svstatus.want == *a)
 416                return 0;
 417*/
 418        fd = open("supervise/control", O_WRONLY|O_NDELAY);
 419        if (fd == -1) {
 420                if (errno != ENODEV)
 421                        warn("can't open supervise/control");
 422                else
 423                        *a == 'x' ? ok("runsv not running") : failx("runsv not running");
 424                return -1;
 425        }
 426        l = strlen(a);
 427        r = write(fd, a, l);
 428        close(fd);
 429        if (r != l) {
 430                warn("can't write to supervise/control");
 431                return -1;
 432        }
 433        return 1;
 434}
 435
 436int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 437int sv_main(int argc UNUSED_PARAM, char **argv)
 438{
 439        char *x;
 440        char *action;
 441        const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
 442        unsigned waitsec = 7;
 443        smallint kll = 0;
 444        int verbose = 0;
 445        int (*act)(const char*);
 446        int (*cbk)(const char*);
 447        int curdir;
 448
 449        INIT_G();
 450
 451        xfunc_error_retval = 100;
 452
 453        x = getenv("SVDIR");
 454        if (x) varservice = x;
 455        x = getenv("SVWAIT");
 456        if (x) waitsec = xatou(x);
 457
 458        opt_complementary = "w+:vv"; /* -w N, -v is a counter */
 459        getopt32(argv, "w:v", &waitsec, &verbose);
 460        argv += optind;
 461        action = *argv++;
 462        if (!action || !*argv) bb_show_usage();
 463
 464        tnow = time(NULL) + 0x400000000000000aULL;
 465        tstart = tnow;
 466        curdir = open(".", O_RDONLY|O_NDELAY);
 467        if (curdir == -1)
 468                fatal_cannot("open current directory");
 469
 470        act = &control;
 471        acts = "s";
 472        cbk = &check;
 473
 474        switch (*action) {
 475        case 'x':
 476        case 'e':
 477                acts = "x";
 478                if (!verbose) cbk = NULL;
 479                break;
 480        case 'X':
 481        case 'E':
 482                acts = "x";
 483                kll = 1;
 484                break;
 485        case 'D':
 486                acts = "d";
 487                kll = 1;
 488                break;
 489        case 'T':
 490                acts = "tc";
 491                kll = 1;
 492                break;
 493        case 'c':
 494                if (str_equal(action, "check")) {
 495                        act = NULL;
 496                        acts = "c";
 497                        break;
 498                }
 499        case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
 500        case 'a': case 'i': case 'k': case 'q': case '1': case '2':
 501                action[1] = '\0';
 502                acts = action;
 503                if (!verbose) cbk = NULL;
 504                break;
 505        case 's':
 506                if (str_equal(action, "shutdown")) {
 507                        acts = "x";
 508                        break;
 509                }
 510                if (str_equal(action, "start")) {
 511                        acts = "u";
 512                        break;
 513                }
 514                if (str_equal(action, "stop")) {
 515                        acts = "d";
 516                        break;
 517                }
 518                /* "status" */
 519                act = &status;
 520                cbk = NULL;
 521                break;
 522        case 'r':
 523                if (str_equal(action, "restart")) {
 524                        acts = "tcu";
 525                        break;
 526                }
 527                bb_show_usage();
 528        case 'f':
 529                if (str_equal(action, "force-reload")) {
 530                        acts = "tc";
 531                        kll = 1;
 532                        break;
 533                }
 534                if (str_equal(action, "force-restart")) {
 535                        acts = "tcu";
 536                        kll = 1;
 537                        break;
 538                }
 539                if (str_equal(action, "force-shutdown")) {
 540                        acts = "x";
 541                        kll = 1;
 542                        break;
 543                }
 544                if (str_equal(action, "force-stop")) {
 545                        acts = "d";
 546                        kll = 1;
 547                        break;
 548                }
 549        default:
 550                bb_show_usage();
 551        }
 552
 553        service = argv;
 554        while ((x = *service) != NULL) {
 555                if (x[0] != '/' && x[0] != '.') {
 556                        if (chdir(varservice) == -1)
 557                                goto chdir_failed_0;
 558                }
 559                if (chdir(x) == -1) {
 560 chdir_failed_0:
 561                        fail("can't change to service directory");
 562                        goto nullify_service_0;
 563                }
 564                if (act && (act(acts) == -1)) {
 565 nullify_service_0:
 566                        *service = (char*) -1L; /* "dead" */
 567                }
 568                if (fchdir(curdir) == -1)
 569                        fatal_cannot("change to original directory");
 570                service++;
 571        }
 572
 573        if (cbk) while (1) {
 574                int want_exit;
 575                int diff;
 576
 577                diff = tnow - tstart;
 578                service = argv;
 579                want_exit = 1;
 580                while ((x = *service) != NULL) {
 581                        if (x == (char*) -1L) /* "dead" */
 582                                goto next;
 583                        if (x[0] != '/' && x[0] != '.') {
 584                                if (chdir(varservice) == -1)
 585                                        goto chdir_failed;
 586                        }
 587                        if (chdir(x) == -1) {
 588 chdir_failed:
 589                                fail("can't change to service directory");
 590                                goto nullify_service;
 591                        }
 592                        if (cbk(acts) != 0)
 593                                goto nullify_service;
 594                        want_exit = 0;
 595                        if (diff >= waitsec) {
 596                                printf(kll ? "kill: " : "timeout: ");
 597                                if (svstatus_get() > 0) {
 598                                        svstatus_print(x);
 599                                        ++rc;
 600                                }
 601                                bb_putchar('\n'); /* will also flush the output */
 602                                if (kll)
 603                                        control("k");
 604 nullify_service:
 605                                *service = (char*) -1L; /* "dead" */
 606                        }
 607                        if (fchdir(curdir) == -1)
 608                                fatal_cannot("change to original directory");
 609 next:
 610                        service++;
 611                }
 612                if (want_exit) break;
 613                usleep(420000);
 614                tnow = time(NULL) + 0x400000000000000aULL;
 615        }
 616        return rc > 99 ? 99 : rc;
 617}
 618