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#include <sys/poll.h>
 157#include <sys/file.h>
 158#include "libbb.h"
 159#include "runit_lib.h"
 160
 161struct globals {
 162        const char *acts;
 163        char **service;
 164        unsigned rc;
 165/* "Bernstein" time format: unix + 0x400000000000000aULL */
 166        uint64_t tstart, tnow;
 167        svstatus_t svstatus;
 168};
 169#define G (*(struct globals*)&bb_common_bufsiz1)
 170#define acts         (G.acts        )
 171#define service      (G.service     )
 172#define rc           (G.rc          )
 173#define tstart       (G.tstart      )
 174#define tnow         (G.tnow        )
 175#define svstatus     (G.svstatus    )
 176#define INIT_G() do { } while (0)
 177
 178
 179static void fatal_cannot(const char *m1) NORETURN;
 180static void fatal_cannot(const char *m1)
 181{
 182        bb_perror_msg("fatal: can't %s", m1);
 183        _exit(151);
 184}
 185
 186static void out(const char *p, const char *m1)
 187{
 188        printf("%s%s: %s", p, *service, m1);
 189        if (errno) {
 190                printf(": %s", strerror(errno));
 191        }
 192        bb_putchar('\n'); /* will also flush the output */
 193}
 194
 195#define WARN    "warning: "
 196#define OK      "ok: "
 197
 198static void fail(const char *m1)
 199{
 200        ++rc;
 201        out("fail: ", m1);
 202}
 203static void failx(const char *m1)
 204{
 205        errno = 0;
 206        fail(m1);
 207}
 208static void warn(const char *m1)
 209{
 210        ++rc;
 211        /* "warning: <service>: <m1>\n" */
 212        out("warning: ", m1);
 213}
 214static void ok(const char *m1)
 215{
 216        errno = 0;
 217        out(OK, m1);
 218}
 219
 220static int svstatus_get(void)
 221{
 222        int fd, r;
 223
 224        fd = open_write("supervise/ok");
 225        if (fd == -1) {
 226                if (errno == ENODEV) {
 227                        *acts == 'x' ? ok("runsv not running")
 228                                     : failx("runsv not running");
 229                        return 0;
 230                }
 231                warn("cannot open supervise/ok");
 232                return -1;
 233        }
 234        close(fd);
 235        fd = open_read("supervise/status");
 236        if (fd == -1) {
 237                warn("cannot open supervise/status");
 238                return -1;
 239        }
 240        r = read(fd, &svstatus, 20);
 241        close(fd);
 242        switch (r) {
 243        case 20:
 244                break;
 245        case -1:
 246                warn("cannot read supervise/status");
 247                return -1;
 248        default:
 249                errno = 0;
 250                warn("cannot read supervise/status: bad format");
 251                return -1;
 252        }
 253        return 1;
 254}
 255
 256static unsigned svstatus_print(const char *m)
 257{
 258        int diff;
 259        int pid;
 260        int normallyup = 0;
 261        struct stat s;
 262        uint64_t timestamp;
 263
 264        if (stat("down", &s) == -1) {
 265                if (errno != ENOENT) {
 266                        bb_perror_msg(WARN"cannot stat %s/down", *service);
 267                        return 0;
 268                }
 269                normallyup = 1;
 270        }
 271        pid = SWAP_LE32(svstatus.pid_le32);
 272        timestamp = SWAP_BE64(svstatus.time_be64);
 273        if (pid) {
 274                switch (svstatus.run_or_finish) {
 275                case 1: printf("run: "); break;
 276                case 2: printf("finish: "); break;
 277                }
 278                printf("%s: (pid %d) ", m, pid);
 279        } else {
 280                printf("down: %s: ", m);
 281        }
 282        diff = tnow - timestamp;
 283        printf("%us", (diff < 0 ? 0 : diff));
 284        if (pid) {
 285                if (!normallyup) printf(", normally down");
 286                if (svstatus.paused) printf(", paused");
 287                if (svstatus.want == 'd') printf(", want down");
 288                if (svstatus.got_term) printf(", got TERM");
 289        } else {
 290                if (normallyup) printf(", normally up");
 291                if (svstatus.want == 'u') printf(", want up");
 292        }
 293        return pid ? 1 : 2;
 294}
 295
 296static int status(const char *unused UNUSED_PARAM)
 297{
 298        int r;
 299
 300        r = svstatus_get();
 301        switch (r) { case -1: case 0: return 0; }
 302
 303        r = svstatus_print(*service);
 304        if (chdir("log") == -1) {
 305                if (errno != ENOENT) {
 306                        printf("; log: "WARN"cannot change to log service directory: %s",
 307                                        strerror(errno));
 308                }
 309        } else if (svstatus_get()) {
 310                printf("; ");
 311                svstatus_print("log");
 312        }
 313        bb_putchar('\n'); /* will also flush the output */
 314        return r;
 315}
 316
 317static int checkscript(void)
 318{
 319        char *prog[2];
 320        struct stat s;
 321        int pid, w;
 322
 323        if (stat("check", &s) == -1) {
 324                if (errno == ENOENT) return 1;
 325                bb_perror_msg(WARN"cannot stat %s/check", *service);
 326                return 0;
 327        }
 328        /* if (!(s.st_mode & S_IXUSR)) return 1; */
 329        prog[0] = (char*)"./check";
 330        prog[1] = NULL;
 331        pid = spawn(prog);
 332        if (pid <= 0) {
 333                bb_perror_msg(WARN"cannot %s child %s/check", "run", *service);
 334                return 0;
 335        }
 336        while (safe_waitpid(pid, &w, 0) == -1) {
 337                bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
 338                return 0;
 339        }
 340        return !wait_exitcode(w);
 341}
 342
 343static int check(const char *a)
 344{
 345        int r;
 346        unsigned pid;
 347        uint64_t timestamp;
 348
 349        r = svstatus_get();
 350        if (r == -1)
 351                return -1;
 352        if (r == 0) {
 353                if (*a == 'x')
 354                        return 1;
 355                return -1;
 356        }
 357        pid = SWAP_LE32(svstatus.pid_le32);
 358        switch (*a) {
 359        case 'x':
 360                return 0;
 361        case 'u':
 362                if (!pid || svstatus.run_or_finish != 1) return 0;
 363                if (!checkscript()) return 0;
 364                break;
 365        case 'd':
 366                if (pid) return 0;
 367                break;
 368        case 'c':
 369                if (pid && !checkscript()) return 0;
 370                break;
 371        case 't':
 372                if (!pid && svstatus.want == 'd') break;
 373                timestamp = SWAP_BE64(svstatus.time_be64);
 374                if ((tstart > timestamp) || !pid || svstatus.got_term || !checkscript())
 375                        return 0;
 376                break;
 377        case 'o':
 378                timestamp = SWAP_BE64(svstatus.time_be64);
 379                if ((!pid && tstart > timestamp) || (pid && svstatus.want != 'd'))
 380                        return 0;
 381        }
 382        printf(OK);
 383        svstatus_print(*service);
 384        bb_putchar('\n'); /* will also flush the output */
 385        return 1;
 386}
 387
 388static int control(const char *a)
 389{
 390        int fd, r;
 391
 392        if (svstatus_get() <= 0)
 393                return -1;
 394        if (svstatus.want == *a)
 395                return 0;
 396        fd = open_write("supervise/control");
 397        if (fd == -1) {
 398                if (errno != ENODEV)
 399                        warn("cannot open supervise/control");
 400                else
 401                        *a == 'x' ? ok("runsv not running") : failx("runsv not running");
 402                return -1;
 403        }
 404        r = write(fd, a, strlen(a));
 405        close(fd);
 406        if (r != strlen(a)) {
 407                warn("cannot write to supervise/control");
 408                return -1;
 409        }
 410        return 1;
 411}
 412
 413int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 414int sv_main(int argc, char **argv)
 415{
 416        unsigned opt;
 417        unsigned i, want_exit;
 418        char *x;
 419        char *action;
 420        const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
 421        unsigned services;
 422        char **servicex;
 423        unsigned waitsec = 7;
 424        smallint kll = 0;
 425        int verbose = 0;
 426        int (*act)(const char*);
 427        int (*cbk)(const char*);
 428        int curdir;
 429
 430        INIT_G();
 431
 432        xfunc_error_retval = 100;
 433
 434        x = getenv("SVDIR");
 435        if (x) varservice = x;
 436        x = getenv("SVWAIT");
 437        if (x) waitsec = xatou(x);
 438
 439        opt_complementary = "w+:vv"; /* -w N, -v is a counter */
 440        opt = getopt32(argv, "w:v", &waitsec, &verbose);
 441        argc -= optind;
 442        argv += optind;
 443        action = *argv++;
 444        if (!action || !*argv) bb_show_usage();
 445        service = argv;
 446        services = argc - 1;
 447
 448        tnow = time(0) + 0x400000000000000aULL;
 449        tstart = tnow;
 450        curdir = open_read(".");
 451        if (curdir == -1)
 452                fatal_cannot("open current directory");
 453
 454        act = &control;
 455        acts = "s";
 456        cbk = &check;
 457
 458        switch (*action) {
 459        case 'x':
 460        case 'e':
 461                acts = "x";
 462                if (!verbose) cbk = NULL;
 463                break;
 464        case 'X':
 465        case 'E':
 466                acts = "x";
 467                kll = 1;
 468                break;
 469        case 'D':
 470                acts = "d";
 471                kll = 1;
 472                break;
 473        case 'T':
 474                acts = "tc";
 475                kll = 1;
 476                break;
 477        case 'c':
 478                if (str_equal(action, "check")) {
 479                        act = NULL;
 480                        acts = "c";
 481                        break;
 482                }
 483        case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
 484        case 'a': case 'i': case 'k': case 'q': case '1': case '2':
 485                action[1] = '\0';
 486                acts = action;
 487                if (!verbose) cbk = NULL;
 488                break;
 489        case 's':
 490                if (str_equal(action, "shutdown")) {
 491                        acts = "x";
 492                        break;
 493                }
 494                if (str_equal(action, "start")) {
 495                        acts = "u";
 496                        break;
 497                }
 498                if (str_equal(action, "stop")) {
 499                        acts = "d";
 500                        break;
 501                }
 502                /* "status" */
 503                act = &status;
 504                cbk = NULL;
 505                break;
 506        case 'r':
 507                if (str_equal(action, "restart")) {
 508                        acts = "tcu";
 509                        break;
 510                }
 511                bb_show_usage();
 512        case 'f':
 513                if (str_equal(action, "force-reload")) {
 514                        acts = "tc";
 515                        kll = 1;
 516                        break;
 517                }
 518                if (str_equal(action, "force-restart")) {
 519                        acts = "tcu";
 520                        kll = 1;
 521                        break;
 522                }
 523                if (str_equal(action, "force-shutdown")) {
 524                        acts = "x";
 525                        kll = 1;
 526                        break;
 527                }
 528                if (str_equal(action, "force-stop")) {
 529                        acts = "d";
 530                        kll = 1;
 531                        break;
 532                }
 533        default:
 534                bb_show_usage();
 535        }
 536
 537        servicex = service;
 538        for (i = 0; i < services; ++i) {
 539                if ((**service != '/') && (**service != '.')) {
 540                        if (chdir(varservice) == -1)
 541                                goto chdir_failed_0;
 542                }
 543                if (chdir(*service) == -1) {
 544 chdir_failed_0:
 545                        fail("cannot change to service directory");
 546                        goto nullify_service_0;
 547                }
 548                if (act && (act(acts) == -1)) {
 549 nullify_service_0:
 550                        *service = NULL;
 551                }
 552                if (fchdir(curdir) == -1)
 553                        fatal_cannot("change to original directory");
 554                service++;
 555        }
 556
 557        if (cbk) while (1) {
 558                int diff;
 559
 560                diff = tnow - tstart;
 561                service = servicex;
 562                want_exit = 1;
 563                for (i = 0; i < services; ++i, ++service) {
 564                        if (!*service)
 565                                continue;
 566                        if ((**service != '/') && (**service != '.')) {
 567                                if (chdir(varservice) == -1)
 568                                        goto chdir_failed;
 569                        }
 570                        if (chdir(*service) == -1) {
 571 chdir_failed:
 572                                fail("cannot change to service directory");
 573                                goto nullify_service;
 574                        }
 575                        if (cbk(acts) != 0)
 576                                goto nullify_service;
 577                        want_exit = 0;
 578                        if (diff >= waitsec) {
 579                                printf(kll ? "kill: " : "timeout: ");
 580                                if (svstatus_get() > 0) {
 581                                        svstatus_print(*service);
 582                                        ++rc;
 583                                }
 584                                bb_putchar('\n'); /* will also flush the output */
 585                                if (kll)
 586                                        control("k");
 587 nullify_service:
 588                                *service = NULL;
 589                        }
 590                        if (fchdir(curdir) == -1)
 591                                fatal_cannot("change to original directory");
 592                }
 593                if (want_exit) break;
 594                usleep(420000);
 595                tnow = time(0) + 0x400000000000000aULL;
 596        }
 597        return rc > 99 ? 99 : rc;
 598}
 599