source: branches/3.0/mindi-busybox/runit/sv.c @ 3085

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