randomconfig fixes
[oweals/busybox.git] / runit / sv.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, 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
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
29
30 sv - control and manage services monitored by runsv
31
32 sv [-v] [-w sec] command services
33 /etc/init.d/service [-w sec] command
34
35 The sv program reports the current status and controls the state of services
36 monitored by the runsv(8) supervisor.
37
38 services consists of one or more arguments, each argument naming a directory
39 service used by runsv(8). If service doesn't start with a dot or slash,
40 it is searched in the default services directory /var/service/, otherwise
41 relative to the current directory.
42
43 command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
44 1, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
45 force-reload, force-restart, force-shutdown.
46
47 The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
48 script interface. The service to be controlled then is specified by the
49 base name of the "init script".
50
51 status
52     Report the current status of the service, and the appendant log service
53     if available, to standard output.
54 up
55     If the service is not running, start it. If the service stops, restart it.
56 down
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.
60 once
61     If the service is not running, start it. Do not restart it if it stops.
62 pause 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.
65 exit
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
73 sv actually looks only at the first character of above commands.
74
75 Commands compatible to LSB init script actions:
76
77 status
78     Same as status.
79 start
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.
85 stop
86     Same as down, but wait up to 7 seconds for the service to become down.
87     Then report the status or timeout.
88 restart
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.
94 shutdown
95     Same as exit, but wait up to 7 seconds for the runsv(8) process
96     to terminate. Then report the status or timeout.
97 force-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.
100 force-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.
104 force-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.
111 force-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
116 Additional Commands
117
118 check
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
126 Options
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
134 Environment
135
136 SVDIR
137     The environment variable $SVDIR overrides the default services directory
138     /var/service.
139 SVWAIT
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
143 Exit 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
155 //config:config SV
156 //config:       bool "sv"
157 //config:       default y
158 //config:       help
159 //config:         sv reports the current status and controls the state of services
160 //config:         monitored by the runsv supervisor.
161 //config:
162 //config:config SV_DEFAULT_SERVICE_DIR
163 //config:       string "Default directory for services"
164 //config:       default "/var/service"
165 //config:       depends on SV
166 //config:       help
167 //config:         Default directory for services.
168 //config:         Defaults to "/var/service"
169 //config:
170 //config:config SVC
171 //config:       bool "svc"
172 //config:       default y
173 //config:       help
174 //config:         svc controls the state of services monitored by the runsv supervisor.
175 //config:         It is comaptible with daemontools command with the same name.
176
177 //applet:IF_SV(APPLET(sv, BB_DIR_USR_BIN, BB_SUID_DROP))
178 //applet:IF_SVC(APPLET(svc, BB_DIR_USR_BIN, BB_SUID_DROP))
179
180 //kbuild:lib-$(CONFIG_SV) += sv.o
181 //kbuild:lib-$(CONFIG_SVC) += sv.o
182
183 #include <sys/file.h>
184 #include "libbb.h"
185 #include "common_bufsiz.h"
186 #include "runit_lib.h"
187
188 struct globals {
189         const char *acts;
190         char **service;
191         unsigned rc;
192 /* "Bernstein" time format: unix + 0x400000000000000aULL */
193         uint64_t tstart, tnow;
194         svstatus_t svstatus;
195 } FIX_ALIASING;
196 #define G (*(struct globals*)bb_common_bufsiz1)
197 #define acts         (G.acts        )
198 #define service      (G.service     )
199 #define rc           (G.rc          )
200 #define tstart       (G.tstart      )
201 #define tnow         (G.tnow        )
202 #define svstatus     (G.svstatus    )
203 #define INIT_G() do { setup_common_bufsiz(); } while (0)
204
205
206 #define str_equal(s,t) (strcmp((s), (t)) == 0)
207
208
209 static void fatal_cannot(const char *m1) NORETURN;
210 static void fatal_cannot(const char *m1)
211 {
212         bb_perror_msg("fatal: can't %s", m1);
213         _exit(151);
214 }
215
216 static void out(const char *p, const char *m1)
217 {
218         printf("%s%s: %s", p, *service, m1);
219         if (errno) {
220                 printf(": %s", strerror(errno));
221         }
222         bb_putchar('\n'); /* will also flush the output */
223 }
224
225 #define WARN    "warning: "
226 #define OK      "ok: "
227
228 static void fail(const char *m1)
229 {
230         ++rc;
231         out("fail: ", m1);
232 }
233 static void failx(const char *m1)
234 {
235         errno = 0;
236         fail(m1);
237 }
238 static void warn(const char *m1)
239 {
240         ++rc;
241         /* "warning: <service>: <m1>\n" */
242         out("warning: ", m1);
243 }
244 static void ok(const char *m1)
245 {
246         errno = 0;
247         out(OK, m1);
248 }
249
250 static int svstatus_get(void)
251 {
252         int fd, r;
253
254         fd = open("supervise/ok", O_WRONLY|O_NDELAY);
255         if (fd == -1) {
256                 if (errno == ENODEV) {
257                         *acts == 'x' ? ok("runsv not running")
258                                      : failx("runsv not running");
259                         return 0;
260                 }
261                 warn("can't open supervise/ok");
262                 return -1;
263         }
264         close(fd);
265         fd = open("supervise/status", O_RDONLY|O_NDELAY);
266         if (fd == -1) {
267                 warn("can't open supervise/status");
268                 return -1;
269         }
270         r = read(fd, &svstatus, 20);
271         close(fd);
272         switch (r) {
273         case 20:
274                 break;
275         case -1:
276                 warn("can't read supervise/status");
277                 return -1;
278         default:
279                 errno = 0;
280                 warn("can't read supervise/status: bad format");
281                 return -1;
282         }
283         return 1;
284 }
285
286 static unsigned svstatus_print(const char *m)
287 {
288         int diff;
289         int pid;
290         int normallyup = 0;
291         struct stat s;
292         uint64_t timestamp;
293
294         if (stat("down", &s) == -1) {
295                 if (errno != ENOENT) {
296                         bb_perror_msg(WARN"can't stat %s/down", *service);
297                         return 0;
298                 }
299                 normallyup = 1;
300         }
301         pid = SWAP_LE32(svstatus.pid_le32);
302         timestamp = SWAP_BE64(svstatus.time_be64);
303         if (pid) {
304                 switch (svstatus.run_or_finish) {
305                 case 1: printf("run: "); break;
306                 case 2: printf("finish: "); break;
307                 }
308                 printf("%s: (pid %d) ", m, pid);
309         } else {
310                 printf("down: %s: ", m);
311         }
312         diff = tnow - timestamp;
313         printf("%us", (diff < 0 ? 0 : diff));
314         if (pid) {
315                 if (!normallyup) printf(", normally down");
316                 if (svstatus.paused) printf(", paused");
317                 if (svstatus.want == 'd') printf(", want down");
318                 if (svstatus.got_term) printf(", got TERM");
319         } else {
320                 if (normallyup) printf(", normally up");
321                 if (svstatus.want == 'u') printf(", want up");
322         }
323         return pid ? 1 : 2;
324 }
325
326 static int status(const char *unused UNUSED_PARAM)
327 {
328         int r;
329
330         if (svstatus_get() <= 0)
331                 return 0;
332
333         r = svstatus_print(*service);
334         if (chdir("log") == -1) {
335                 if (errno != ENOENT) {
336                         printf("; log: "WARN"can't change to log service directory: %s",
337                                         strerror(errno));
338                 }
339         } else if (svstatus_get()) {
340                 printf("; ");
341                 svstatus_print("log");
342         }
343         bb_putchar('\n'); /* will also flush the output */
344         return r;
345 }
346
347 static int checkscript(void)
348 {
349         char *prog[2];
350         struct stat s;
351         int pid, w;
352
353         if (stat("check", &s) == -1) {
354                 if (errno == ENOENT) return 1;
355                 bb_perror_msg(WARN"can't stat %s/check", *service);
356                 return 0;
357         }
358         /* if (!(s.st_mode & S_IXUSR)) return 1; */
359         prog[0] = (char*)"./check";
360         prog[1] = NULL;
361         pid = spawn(prog);
362         if (pid <= 0) {
363                 bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
364                 return 0;
365         }
366         while (safe_waitpid(pid, &w, 0) == -1) {
367                 bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
368                 return 0;
369         }
370         return WEXITSTATUS(w) == 0;
371 }
372
373 static int check(const char *a)
374 {
375         int r;
376         unsigned pid_le32;
377         uint64_t timestamp;
378
379         r = svstatus_get();
380         if (r == -1)
381                 return -1;
382         if (r == 0) {
383                 if (*a == 'x')
384                         return 1;
385                 return -1;
386         }
387         pid_le32 = svstatus.pid_le32;
388         switch (*a) {
389         case 'x':
390                 return 0;
391         case 'u':
392                 if (!pid_le32 || svstatus.run_or_finish != 1) return 0;
393                 if (!checkscript()) return 0;
394                 break;
395         case 'd':
396                 if (pid_le32) return 0;
397                 break;
398         case 'c':
399                 if (pid_le32 && !checkscript()) return 0;
400                 break;
401         case 't':
402                 if (!pid_le32 && svstatus.want == 'd') break;
403                 timestamp = SWAP_BE64(svstatus.time_be64);
404                 if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
405                         return 0;
406                 break;
407         case 'o':
408                 timestamp = SWAP_BE64(svstatus.time_be64);
409                 if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
410                         return 0;
411         }
412         printf(OK);
413         svstatus_print(*service);
414         bb_putchar('\n'); /* will also flush the output */
415         return 1;
416 }
417
418 static int control(const char *a)
419 {
420         int fd, r, l;
421
422 /* Is it an optimization?
423    It causes problems with "sv o SRV; ...; sv d SRV"
424    ('d' is not passed to SRV because its .want == 'd'):
425         if (svstatus_get() <= 0)
426                 return -1;
427         if (svstatus.want == *a)
428                 return 0;
429 */
430         fd = open("supervise/control", O_WRONLY|O_NDELAY);
431         if (fd == -1) {
432                 if (errno != ENODEV)
433                         warn("can't open supervise/control");
434                 else
435                         *a == 'x' ? ok("runsv not running") : failx("runsv not running");
436                 return -1;
437         }
438         l = strlen(a);
439         r = write(fd, a, l);
440         close(fd);
441         if (r != l) {
442                 warn("can't write to supervise/control");
443                 return -1;
444         }
445         return 1;
446 }
447
448 //usage:#define sv_trivial_usage
449 //usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
450 //usage:#define sv_full_usage "\n\n"
451 //usage:       "Control services monitored by runsv supervisor.\n"
452 //usage:       "Commands (only first character is enough):\n"
453 //usage:       "\n"
454 //usage:       "status: query service status\n"
455 //usage:       "up: if service isn't running, start it. If service stops, restart it\n"
456 //usage:       "once: like 'up', but if service stops, don't restart it\n"
457 //usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
458 //usage:       "        if it exists. After it stops, don't restart service\n"
459 //usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
460 //usage:       "        runsv exits too\n"
461 //usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
462 //usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
463 static int sv(char **argv)
464 {
465         char *x;
466         char *action;
467         const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
468         unsigned waitsec = 7;
469         smallint kll = 0;
470         int verbose = 0;
471         int (*act)(const char*);
472         int (*cbk)(const char*);
473         int curdir;
474
475         INIT_G();
476
477         xfunc_error_retval = 100;
478
479         x = getenv("SVDIR");
480         if (x) varservice = x;
481         x = getenv("SVWAIT");
482         if (x) waitsec = xatou(x);
483
484         opt_complementary = "vv"; /* -w N, -v is a counter */
485         getopt32(argv, "w:+v", &waitsec, &verbose);
486         argv += optind;
487         action = *argv++;
488         if (!action || !*argv) bb_show_usage();
489
490         tnow = time(NULL) + 0x400000000000000aULL;
491         tstart = tnow;
492         curdir = open(".", O_RDONLY|O_NDELAY);
493         if (curdir == -1)
494                 fatal_cannot("open current directory");
495
496         act = &control;
497         acts = "s";
498         cbk = &check;
499
500         switch (*action) {
501         case 'x':
502         case 'e':
503                 acts = "x";
504                 if (!verbose) cbk = NULL;
505                 break;
506         case 'X':
507         case 'E':
508                 acts = "x";
509                 kll = 1;
510                 break;
511         case 'D':
512                 acts = "d";
513                 kll = 1;
514                 break;
515         case 'T':
516                 acts = "tc";
517                 kll = 1;
518                 break;
519         case 'c':
520                 if (str_equal(action, "check")) {
521                         act = NULL;
522                         acts = "c";
523                         break;
524                 }
525         case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
526         case 'a': case 'i': case 'k': case 'q': case '1': case '2':
527                 action[1] = '\0';
528                 acts = action;
529                 if (!verbose) cbk = NULL;
530                 break;
531         case 's':
532                 if (str_equal(action, "shutdown")) {
533                         acts = "x";
534                         break;
535                 }
536                 if (str_equal(action, "start")) {
537                         acts = "u";
538                         break;
539                 }
540                 if (str_equal(action, "stop")) {
541                         acts = "d";
542                         break;
543                 }
544                 /* "status" */
545                 act = &status;
546                 cbk = NULL;
547                 break;
548         case 'r':
549                 if (str_equal(action, "restart")) {
550                         acts = "tcu";
551                         break;
552                 }
553                 bb_show_usage();
554         case 'f':
555                 if (str_equal(action, "force-reload")) {
556                         acts = "tc";
557                         kll = 1;
558                         break;
559                 }
560                 if (str_equal(action, "force-restart")) {
561                         acts = "tcu";
562                         kll = 1;
563                         break;
564                 }
565                 if (str_equal(action, "force-shutdown")) {
566                         acts = "x";
567                         kll = 1;
568                         break;
569                 }
570                 if (str_equal(action, "force-stop")) {
571                         acts = "d";
572                         kll = 1;
573                         break;
574                 }
575         default:
576                 bb_show_usage();
577         }
578
579         service = argv;
580         while ((x = *service) != NULL) {
581                 if (x[0] != '/' && x[0] != '.') {
582                         if (chdir(varservice) == -1)
583                                 goto chdir_failed_0;
584                 }
585                 if (chdir(x) == -1) {
586  chdir_failed_0:
587                         fail("can't change to service directory");
588                         goto nullify_service_0;
589                 }
590                 if (act && (act(acts) == -1)) {
591  nullify_service_0:
592                         *service = (char*) -1L; /* "dead" */
593                 }
594                 if (fchdir(curdir) == -1)
595                         fatal_cannot("change to original directory");
596                 service++;
597         }
598
599         if (cbk) while (1) {
600                 int want_exit;
601                 int diff;
602
603                 diff = tnow - tstart;
604                 service = argv;
605                 want_exit = 1;
606                 while ((x = *service) != NULL) {
607                         if (x == (char*) -1L) /* "dead" */
608                                 goto next;
609                         if (x[0] != '/' && x[0] != '.') {
610                                 if (chdir(varservice) == -1)
611                                         goto chdir_failed;
612                         }
613                         if (chdir(x) == -1) {
614  chdir_failed:
615                                 fail("can't change to service directory");
616                                 goto nullify_service;
617                         }
618                         if (cbk(acts) != 0)
619                                 goto nullify_service;
620                         want_exit = 0;
621                         if (diff >= waitsec) {
622                                 printf(kll ? "kill: " : "timeout: ");
623                                 if (svstatus_get() > 0) {
624                                         svstatus_print(x);
625                                         ++rc;
626                                 }
627                                 bb_putchar('\n'); /* will also flush the output */
628                                 if (kll)
629                                         control("k");
630  nullify_service:
631                                 *service = (char*) -1L; /* "dead" */
632                         }
633                         if (fchdir(curdir) == -1)
634                                 fatal_cannot("change to original directory");
635  next:
636                         service++;
637                 }
638                 if (want_exit) break;
639                 usleep(420000);
640                 tnow = time(NULL) + 0x400000000000000aULL;
641         }
642         return rc > 99 ? 99 : rc;
643 }
644
645 #if ENABLE_SV
646 int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
647 int sv_main(int argc UNUSED_PARAM, char **argv)
648 {
649         return sv(argv);
650 }
651 #endif
652
653 //usage:#define svc_trivial_usage
654 //usage:       "[-udopchaitkx] SERVICE_DIR..."
655 //usage:#define svc_full_usage "\n\n"
656 //usage:       "Control services monitored by runsv supervisor"
657 //usage:   "\n"
658 //usage:   "\n""        -u      If service is not running, start it; restart if it stops"
659 //usage:   "\n""        -d      If service is running, send TERM+CONT signals; do not restart it"
660 //usage:   "\n""        -o      Once: if service is not running, start it; do not restart it"
661 //usage:   "\n""        -pchaitk Send STOP, CONT, HUP, ALRM, INT, TERM, KILL signal to service"
662 //usage:   "\n""        -x      Exit: runsv will exit as soon as the service is down"
663 #if ENABLE_SVC
664 int svc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
665 int svc_main(int argc UNUSED_PARAM, char **argv)
666 {
667         char command[2];
668         const char *optstring;
669         unsigned opts;
670
671         INIT_G();
672
673         optstring = "udopchaitkx";
674         opts = getopt32(argv, optstring);
675         argv += optind;
676         if (!argv[0] || !opts)
677                 bb_show_usage();
678
679         argv -= 2;
680         if (optind > 2) {
681                 argv--;
682                 argv[2] = (char*)"--";
683         }
684         argv[0] = (char*)"sv";
685         argv[1] = command;
686         command[1] = '\0';
687
688         /* getopt32() was already called:
689          * reset the libc getopt() function, which keeps internal state.
690          */
691 #ifdef __GLIBC__
692         optind = 0;
693 #else /* BSD style */
694         optind = 1;
695         /* optreset = 1; */
696 #endif
697
698         do {
699                 if (opts & 1) {
700                         int r;
701                         command[0] = *optstring;
702                         r = sv(argv);
703                         if (r)
704                                 return 1;
705                 }
706                 optstring++;
707                 opts >>= 1;
708         } while (opts);
709
710         return 0;
711 }
712 #endif