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        if (svstatus_get() <= 0)
 301                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 WEXITSTATUS(w) == 0;
 341}
 342
 343static int check(const char *a)
 344{
 345        int r;
 346        unsigned pid_le32;
 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_le32 = svstatus.pid_le32;
 358        switch (*a) {
 359        case 'x':
 360                return 0;
 361        case 'u':
 362                if (!pid_le32 || svstatus.run_or_finish != 1) return 0;
 363                if (!checkscript()) return 0;
 364                break;
 365        case 'd':
 366                if (pid_le32) return 0;
 367                break;
 368        case 'c':
 369                if (pid_le32 && !checkscript()) return 0;
 370                break;
 371        case 't':
 372                if (!pid_le32 && svstatus.want == 'd') break;
 373                timestamp = SWAP_BE64(svstatus.time_be64);
 374                if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
 375                        return 0;
 376                break;
 377        case 'o':
 378                timestamp = SWAP_BE64(svstatus.time_be64);
 379                if ((!pid_le32 && tstart > timestamp) || (pid_le32 && 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, l;
 391
 392/* Is it an optimization?
 393   It causes problems with "sv o SRV; ...; sv d SRV"
 394   ('d' is not passed to SRV because its .want == 'd'):
 395        if (svstatus_get() <= 0)
 396                return -1;
 397        if (svstatus.want == *a)
 398                return 0;
 399*/
 400        fd = open_write("supervise/control");
 401        if (fd == -1) {
 402                if (errno != ENODEV)
 403                        warn("cannot open supervise/control");
 404                else
 405                        *a == 'x' ? ok("runsv not running") : failx("runsv not running");
 406                return -1;
 407        }
 408        l = strlen(a);
 409        r = write(fd, a, l);
 410        close(fd);
 411        if (r != l) {
 412                warn("cannot write to supervise/control");
 413                return -1;
 414        }
 415        return 1;
 416}
 417
 418int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 419int sv_main(int argc UNUSED_PARAM, char **argv)
 420{
 421        unsigned opt;
 422        char *x;
 423        char *action;
 424        const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
 425        unsigned waitsec = 7;
 426        smallint kll = 0;
 427        int verbose = 0;
 428        int (*act)(const char*);
 429        int (*cbk)(const char*);
 430        int curdir;
 431
 432        INIT_G();
 433
 434        xfunc_error_retval = 100;
 435
 436        x = getenv("SVDIR");
 437        if (x) varservice = x;
 438        x = getenv("SVWAIT");
 439        if (x) waitsec = xatou(x);
 440
 441        opt_complementary = "w+:vv"; /* -w N, -v is a counter */
 442        opt = getopt32(argv, "w:v", &waitsec, &verbose);
 443        argv += optind;
 444        action = *argv++;
 445        if (!action || !*argv) bb_show_usage();
 446
 447        tnow = time(NULL) + 0x400000000000000aULL;
 448        tstart = tnow;
 449        curdir = open_read(".");
 450        if (curdir == -1)
 451                fatal_cannot("open current directory");
 452
 453        act = &control;
 454        acts = "s";
 455        cbk = &check;
 456
 457        switch (*action) {
 458        case 'x':
 459        case 'e':
 460                acts = "x";
 461                if (!verbose) cbk = NULL;
 462                break;
 463        case 'X':
 464        case 'E':
 465                acts = "x";
 466                kll = 1;
 467                break;
 468        case 'D':
 469                acts = "d";
 470                kll = 1;
 471                break;
 472        case 'T':
 473                acts = "tc";
 474                kll = 1;
 475                break;
 476        case 'c':
 477                if (str_equal(action, "check")) {
 478                        act = NULL;
 479                        acts = "c";
 480                        break;
 481                }
 482        case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
 483        case 'a': case 'i': case 'k': case 'q': case '1': case '2':
 484                action[1] = '\0';
 485                acts = action;
 486                if (!verbose) cbk = NULL;
 487                break;
 488        case 's':
 489                if (str_equal(action, "shutdown")) {
 490                        acts = "x";
 491                        break;
 492                }
 493                if (str_equal(action, "start")) {
 494                        acts = "u";
 495                        break;
 496                }
 497                if (str_equal(action, "stop")) {
 498                        acts = "d";
 499                        break;
 500                }
 501                /* "status" */
 502                act = &status;
 503                cbk = NULL;
 504                break;
 505        case 'r':
 506                if (str_equal(action, "restart")) {
 507                        acts = "tcu";
 508                        break;
 509                }
 510                bb_show_usage();
 511        case 'f':
 512                if (str_equal(action, "force-reload")) {
 513                        acts = "tc";
 514                        kll = 1;
 515                        break;
 516                }
 517                if (str_equal(action, "force-restart")) {
 518                        acts = "tcu";
 519                        kll = 1;
 520                        break;
 521                }
 522                if (str_equal(action, "force-shutdown")) {
 523                        acts = "x";
 524                        kll = 1;
 525                        break;
 526                }
 527                if (str_equal(action, "force-stop")) {
 528                        acts = "d";
 529                        kll = 1;
 530                        break;
 531                }
 532        default:
 533                bb_show_usage();
 534        }
 535
 536        service = argv;
 537        while ((x = *service) != NULL) {
 538                if (x[0] != '/' && x[0] != '.') {
 539                        if (chdir(varservice) == -1)
 540                                goto chdir_failed_0;
 541                }
 542                if (chdir(x) == -1) {
 543 chdir_failed_0:
 544                        fail("cannot change to service directory");
 545                        goto nullify_service_0;
 546                }
 547                if (act && (act(acts) == -1)) {
 548 nullify_service_0:
 549                        *service = (char*) -1L; /* "dead" */
 550                }
 551                if (fchdir(curdir) == -1)
 552                        fatal_cannot("change to original directory");
 553                service++;
 554        }
 555
 556        if (cbk) while (1) {
 557                int want_exit;
 558                int diff;
 559
 560                diff = tnow - tstart;
 561                service = argv;
 562                want_exit = 1;
 563                while ((x = *service) != NULL) {
 564                        if (x == (char*) -1L) /* "dead" */
 565                                goto next;
 566                        if (x[0] != '/' && x[0] != '.') {
 567                                if (chdir(varservice) == -1)
 568                                        goto chdir_failed;
 569                        }
 570                        if (chdir(x) == -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(x);
 582                                        ++rc;
 583                                }
 584                                bb_putchar('\n'); /* will also flush the output */
 585                                if (kll)
 586                                        control("k");
 587 nullify_service:
 588                                *service = (char*) -1L; /* "dead" */
 589                        }
 590                        if (fchdir(curdir) == -1)
 591                                fatal_cannot("change to original directory");
 592 next:
 593                        service++;
 594                }
 595                if (want_exit) break;
 596                usleep(420000);
 597                tnow = time(NULL) + 0x400000000000000aULL;
 598        }
 599        return rc > 99 ? 99 : rc;
 600}
 601