sv: update to match version 2.1.2 of runit
[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.org/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 and
40 doesn't end with a slash, it is searched in the default services directory
41 /var/service/, otherwise 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, reload, restart, shutdown,
45 force-stop, force-reload, force-restart, force-shutdown, try-restart.
46
47 status
48     Report the current status of the service, and the appendant log service
49     if available, to standard output.
50 up
51     If the service is not running, start it. If the service stops, restart it.
52 down
53     If the service is running, send it the TERM signal, and the CONT signal.
54     If ./run exits, start ./finish if it exists. After it stops, do not
55     restart service.
56 once
57     If the service is not running, start it. Do not restart it if it stops.
58 pause cont hup alarm interrupt quit 1 2 term kill
59     If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
60     USR1, USR2, TERM, or KILL signal respectively.
61 exit
62     If the service is running, send it the TERM signal, and the CONT signal.
63     Do not restart the service. If the service is down, and no log service
64     exists, runsv(8) exits. If the service is down and a log service exists,
65     runsv(8) closes the standard input of the log service and waits for it to
66     terminate. If the log service is down, runsv(8) exits. This command is
67     ignored if it is given to an appendant log service.
68
69 sv actually looks only at the first character of above commands.
70
71 Commands compatible to LSB init script actions:
72
73 status
74     Same as status.
75 start
76     Same as up, but wait up to 7 seconds for the command to take effect.
77     Then report the status or timeout. If the script ./check exists in
78     the service directory, sv runs this script to check whether the service
79     is up and available; it's considered to be available if ./check exits
80     with 0.
81 stop
82     Same as down, but wait up to 7 seconds for the service to become down.
83     Then report the status or timeout.
84 reload
85     Same as hup, and additionally report the status afterwards.
86 restart
87     Send the commands term, cont, and up to the service, and wait up to
88     7 seconds for the service to restart. Then report the status or timeout.
89     If the script ./check exists in the service directory, sv runs this script
90     to check whether the service is up and available again; it's considered
91     to be available if ./check exits with 0.
92 shutdown
93     Same as exit, but wait up to 7 seconds for the runsv(8) process
94     to terminate. Then report the status or timeout.
95 force-stop
96     Same as down, but wait up to 7 seconds for the service to become down.
97     Then report the status, and on timeout send the service the kill command.
98 force-reload
99     Send the service the term and cont commands, and wait up to
100     7 seconds for the service to restart. Then report the status,
101     and on timeout send the service the kill command.
102 force-restart
103     Send the service the term, cont and up commands, and wait up to
104     7 seconds for the service to restart. Then report the status, and
105     on timeout send the service the kill command. If the script ./check
106     exists in the service directory, sv runs this script to check whether
107     the service is up and available again; it's considered to be available
108     if ./check exits with 0.
109 force-shutdown
110     Same as exit, but wait up to 7 seconds for the runsv(8) process to
111     terminate. Then report the status, and on timeout send the service
112     the kill command.
113 try-restart
114     if the service is running, send it the term and cont commands, and wait up to
115     7 seconds for the service to restart. Then report the status or timeout.
116
117 Additional Commands
118
119 check
120     Check for the service to be in the state that's been requested. Wait up to
121     7 seconds for the service to reach the requested state, then report
122     the status or timeout. If the requested state of the service is up,
123     and the script ./check exists in the service directory, sv runs
124     this script to check whether the service is up and running;
125     it's considered to be up if ./check exits with 0.
126
127 Options
128
129 -v
130     If the command is up, down, term, once, cont, or exit, then wait up to 7
131     seconds for the command to take effect. Then report the status or timeout.
132 -w sec
133     Override the default timeout of 7 seconds with sec seconds. Implies -v.
134
135 Environment
136
137 SVDIR
138     The environment variable $SVDIR overrides the default services directory
139     /var/service.
140 SVWAIT
141     The environment variable $SVWAIT overrides the default 7 seconds to wait
142     for a command to take effect. It is overridden by the -w option.
143
144 Exit Codes
145     sv exits 0, if the command was successfully sent to all services, and,
146     if it was told to wait, the command has taken effect to all services.
147
148     For each service that caused an error (e.g. the directory is not
149     controlled by a runsv(8) process, or sv timed out while waiting),
150     sv increases the exit code by one and exits non zero. The maximum
151     is 99. sv exits 100 on error.
152 */
153
154 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
155
156 //config:config SV
157 //config:       bool "sv"
158 //config:       default y
159 //config:       help
160 //config:         sv reports the current status and controls the state of services
161 //config:         monitored by the runsv supervisor.
162 //config:
163 //config:config SV_DEFAULT_SERVICE_DIR
164 //config:       string "Default directory for services"
165 //config:       default "/var/service"
166 //config:       depends on SV
167 //config:       help
168 //config:         Default directory for services.
169 //config:         Defaults to "/var/service"
170 //config:
171 //config:config SVC
172 //config:       bool "svc"
173 //config:       default y
174 //config:       help
175 //config:         svc controls the state of services monitored by the runsv supervisor.
176 //config:         It is comaptible with daemontools command with the same name.
177
178 //applet:IF_SV(APPLET(sv, BB_DIR_USR_BIN, BB_SUID_DROP))
179 //applet:IF_SVC(APPLET(svc, BB_DIR_USR_BIN, BB_SUID_DROP))
180
181 //kbuild:lib-$(CONFIG_SV) += sv.o
182 //kbuild:lib-$(CONFIG_SVC) += sv.o
183
184 #include <sys/file.h>
185 #include "libbb.h"
186 #include "common_bufsiz.h"
187 #include "runit_lib.h"
188
189 struct globals {
190         const char *acts;
191         char **service;
192         unsigned rc;
193 /* "Bernstein" time format: unix + 0x400000000000000aULL */
194         uint64_t tstart, tnow;
195         svstatus_t svstatus;
196         unsigned islog;
197 } FIX_ALIASING;
198 #define G (*(struct globals*)bb_common_bufsiz1)
199 #define acts         (G.acts        )
200 #define service      (G.service     )
201 #define rc           (G.rc          )
202 #define tstart       (G.tstart      )
203 #define tnow         (G.tnow        )
204 #define svstatus     (G.svstatus    )
205 #define islog        (G.islog       )
206 #define INIT_G() do { setup_common_bufsiz(); } while (0)
207
208
209 #define str_equal(s,t) (strcmp((s), (t)) == 0)
210
211
212 static void fatal_cannot(const char *m1) NORETURN;
213 static void fatal_cannot(const char *m1)
214 {
215         bb_perror_msg("fatal: can't %s", m1);
216         _exit(151);
217 }
218
219 static void out(const char *p, const char *m1)
220 {
221         printf("%s%s%s: %s", p, *service, islog ? "/log" : "", m1);
222         if (errno) {
223                 printf(": %s", strerror(errno));
224         }
225         bb_putchar('\n'); /* will also flush the output */
226 }
227
228 #define WARN    "warning: "
229 #define OK      "ok: "
230
231 static void fail(const char *m1)
232 {
233         ++rc;
234         out("fail: ", m1);
235 }
236 static void failx(const char *m1)
237 {
238         errno = 0;
239         fail(m1);
240 }
241 static void warn(const char *m1)
242 {
243         ++rc;
244         /* "warning: <service>: <m1>\n" */
245         out("warning: ", m1);
246 }
247 static void ok(const char *m1)
248 {
249         errno = 0;
250         out(OK, m1);
251 }
252
253 static int svstatus_get(void)
254 {
255         int fd, r;
256
257         fd = open("supervise/ok", O_WRONLY|O_NDELAY);
258         if (fd == -1) {
259                 if (errno == ENODEV) {
260                         *acts == 'x' ? ok("runsv not running")
261                                      : failx("runsv not running");
262                         return 0;
263                 }
264                 warn("can't open supervise/ok");
265                 return -1;
266         }
267         close(fd);
268         fd = open("supervise/status", O_RDONLY|O_NDELAY);
269         if (fd == -1) {
270                 warn("can't open supervise/status");
271                 return -1;
272         }
273         r = read(fd, &svstatus, 20);
274         close(fd);
275         switch (r) {
276         case 20:
277                 break;
278         case -1:
279                 warn("can't read supervise/status");
280                 return -1;
281         default:
282                 errno = 0;
283                 warn("can't read supervise/status: bad format");
284                 return -1;
285         }
286         return 1;
287 }
288
289 static unsigned svstatus_print(const char *m)
290 {
291         int diff;
292         int pid;
293         int normallyup = 0;
294         struct stat s;
295         uint64_t timestamp;
296
297         if (stat("down", &s) == -1) {
298                 if (errno != ENOENT) {
299                         bb_perror_msg(WARN"can't stat %s/down", *service);
300                         return 0;
301                 }
302                 normallyup = 1;
303         }
304         pid = SWAP_LE32(svstatus.pid_le32);
305         timestamp = SWAP_BE64(svstatus.time_be64);
306         switch (svstatus.run_or_finish) {
307                 case 0: printf("down: "); break;
308                 case 1: printf("run: "); break;
309                 case 2: printf("finish: "); break;
310         }
311         printf("%s: ", m);
312         if (svstatus.run_or_finish)
313                 printf("(pid %d) ", pid);
314         diff = tnow - timestamp;
315         printf("%us", (diff < 0 ? 0 : diff));
316         if (pid) {
317                 if (!normallyup) printf(", normally down");
318                 if (svstatus.paused) printf(", paused");
319                 if (svstatus.want == 'd') printf(", want down");
320                 if (svstatus.got_term) printf(", got TERM");
321         } else {
322                 if (normallyup) printf(", normally up");
323                 if (svstatus.want == 'u') printf(", want up");
324         }
325         return pid ? 1 : 2;
326 }
327
328 static int status(const char *unused UNUSED_PARAM)
329 {
330         int r;
331
332         if (svstatus_get() <= 0)
333                 return 0;
334
335         r = svstatus_print(*service);
336         islog = 1;
337         if (chdir("log") == -1) {
338                 if (errno != ENOENT) {
339                         printf("; ");
340                         warn("can't change directory");
341                 } else
342                         bb_putchar('\n');
343         } else {
344                 printf("; ");
345                 if (svstatus_get()) {
346                         r = svstatus_print("log");
347                         bb_putchar('\n');
348                 }
349         }
350         islog = 0;
351         return r;
352 }
353
354 static int checkscript(void)
355 {
356         char *prog[2];
357         struct stat s;
358         int pid, w;
359
360         if (stat("check", &s) == -1) {
361                 if (errno == ENOENT) return 1;
362                 bb_perror_msg(WARN"can't stat %s/check", *service);
363                 return 0;
364         }
365         /* if (!(s.st_mode & S_IXUSR)) return 1; */
366         prog[0] = (char*)"./check";
367         prog[1] = NULL;
368         pid = spawn(prog);
369         if (pid <= 0) {
370                 bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
371                 return 0;
372         }
373         while (safe_waitpid(pid, &w, 0) == -1) {
374                 bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
375                 return 0;
376         }
377         return WEXITSTATUS(w) == 0;
378 }
379
380 static int check(const char *a)
381 {
382         int r;
383         unsigned pid_le32;
384         uint64_t timestamp;
385
386         r = svstatus_get();
387         if (r == -1)
388                 return -1;
389         while (*a) {
390                 if (r == 0) {
391                         if (*a == 'x')
392                                 return 1;
393                         return -1;
394                 }
395                 pid_le32 = svstatus.pid_le32;
396                 switch (*a) {
397                 case 'x':
398                         return 0;
399                 case 'u':
400                         if (!pid_le32 || svstatus.run_or_finish != 1)
401                                 return 0;
402                         if (!checkscript())
403                                 return 0;
404                         break;
405                 case 'd':
406                         if (pid_le32 || svstatus.run_or_finish != 0)
407                                 return 0;
408                         break;
409                 case 'C':
410                         if (pid_le32 && !checkscript())
411                                 return 0;
412                         break;
413                 case 't':
414                 case 'k':
415                         if (!pid_le32 && svstatus.want == 'd')
416                                 break;
417                         timestamp = SWAP_BE64(svstatus.time_be64);
418                         if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
419                                 return 0;
420                         break;
421                 case 'o':
422                         timestamp = SWAP_BE64(svstatus.time_be64);
423                         if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
424                                 return 0;
425                         break;
426                 case 'p':
427                         if (pid_le32 && !svstatus.paused)
428                                 return 0;
429                         break;
430                 case 'c':
431                         if (pid_le32 && svstatus.paused)
432                                 return 0;
433                         break;
434                 }
435                 ++a;
436         }
437         printf(OK);
438         svstatus_print(*service);
439         bb_putchar('\n'); /* will also flush the output */
440         return 1;
441 }
442
443 static int control(const char *a)
444 {
445         int fd, r, l;
446
447         if (svstatus_get() <= 0)
448                 return -1;
449         if (svstatus.want == *a && (*a != 'd' || svstatus.got_term == 1))
450                 return 0;
451         fd = open("supervise/control", O_WRONLY|O_NDELAY);
452         if (fd == -1) {
453                 if (errno != ENODEV)
454                         warn("can't open supervise/control");
455                 else
456                         *a == 'x' ? ok("runsv not running") : failx("runsv not running");
457                 return -1;
458         }
459         l = strlen(a);
460         r = write(fd, a, l);
461         close(fd);
462         if (r != l) {
463                 warn("can't write to supervise/control");
464                 return -1;
465         }
466         return 1;
467 }
468
469 //usage:#define sv_trivial_usage
470 //usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
471 //usage:#define sv_full_usage "\n\n"
472 //usage:       "Control services monitored by runsv supervisor.\n"
473 //usage:       "Commands (only first character is enough):\n"
474 //usage:       "\n"
475 //usage:       "status: query service status\n"
476 //usage:       "up: if service isn't running, start it. If service stops, restart it\n"
477 //usage:       "once: like 'up', but if service stops, don't restart it\n"
478 //usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
479 //usage:       "        if it exists. After it stops, don't restart service\n"
480 //usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
481 //usage:       "        runsv exits too\n"
482 //usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
483 //usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
484 static int sv(char **argv)
485 {
486         char *x;
487         char *action;
488         const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
489         unsigned waitsec = 7;
490         smallint kll = 0;
491         int verbose = 0;
492         int (*act)(const char*);
493         int (*cbk)(const char*);
494         int curdir;
495
496         INIT_G();
497
498         xfunc_error_retval = 100;
499
500         x = getenv("SVDIR");
501         if (x) varservice = x;
502         x = getenv("SVWAIT");
503         if (x) waitsec = xatou(x);
504
505         opt_complementary = "vv"; /* -w N, -v is a counter */
506         getopt32(argv, "w:+v", &waitsec, &verbose);
507         argv += optind;
508         action = *argv++;
509         if (!action || !*argv) bb_show_usage();
510
511         tnow = time(NULL) + 0x400000000000000aULL;
512         tstart = tnow;
513         curdir = open(".", O_RDONLY|O_NDELAY);
514         if (curdir == -1)
515                 fatal_cannot("open current directory");
516
517         act = &control;
518         acts = "s";
519         cbk = &check;
520
521         switch (*action) {
522         case 'x':
523         case 'e':
524                 acts = "x";
525                 if (!verbose) cbk = NULL;
526                 break;
527         case 'X':
528         case 'E':
529                 acts = "x";
530                 kll = 1;
531                 break;
532         case 'D':
533                 acts = "d";
534                 kll = 1;
535                 break;
536         case 'T':
537                 acts = "tc";
538                 kll = 1;
539                 break;
540         case 't':
541                 if (str_equal(action, "try-restart")) {
542                         acts = "tc";
543                         break;
544                 }
545         case 'c':
546                 if (str_equal(action, "check")) {
547                         act = NULL;
548                         acts = "C";
549                         break;
550                 }
551         case 'u': case 'd': case 'o': case 'p': case 'h':
552         case 'a': case 'i': case 'k': case 'q': case '1': case '2':
553                 action[1] = '\0';
554                 acts = action;
555                 if (!verbose)
556                         cbk = NULL;
557                 break;
558         case 's':
559                 if (str_equal(action, "shutdown")) {
560                         acts = "x";
561                         break;
562                 }
563                 if (str_equal(action, "start")) {
564                         acts = "u";
565                         break;
566                 }
567                 if (str_equal(action, "stop")) {
568                         acts = "d";
569                         break;
570                 }
571                 /* "status" */
572                 act = &status;
573                 cbk = NULL;
574                 break;
575         case 'r':
576                 if (str_equal(action, "restart")) {
577                         acts = "tcu";
578                         break;
579                 }
580                 if (str_equal(action, "reload")) {
581                         acts = "h";
582                         break;
583                 }
584                 bb_show_usage();
585         case 'f':
586                 if (str_equal(action, "force-reload")) {
587                         acts = "tc";
588                         kll = 1;
589                         break;
590                 }
591                 if (str_equal(action, "force-restart")) {
592                         acts = "tcu";
593                         kll = 1;
594                         break;
595                 }
596                 if (str_equal(action, "force-shutdown")) {
597                         acts = "x";
598                         kll = 1;
599                         break;
600                 }
601                 if (str_equal(action, "force-stop")) {
602                         acts = "d";
603                         kll = 1;
604                         break;
605                 }
606         default:
607                 bb_show_usage();
608         }
609
610         service = argv;
611         while ((x = *service) != NULL) {
612                 if (x[0] != '/' && x[0] != '.'
613                  && x[0] != '\0' && x[strlen(x) - 1] != '/'
614                 ) {
615                         if (chdir(varservice) == -1)
616                                 goto chdir_failed_0;
617                 }
618                 if (chdir(x) == -1) {
619  chdir_failed_0:
620                         fail("can't change to service directory");
621                         goto nullify_service_0;
622                 }
623                 if (act && (act(acts) == -1)) {
624  nullify_service_0:
625                         *service = (char*) -1L; /* "dead" */
626                 }
627                 if (fchdir(curdir) == -1)
628                         fatal_cannot("change to original directory");
629                 service++;
630         }
631
632         if (cbk) while (1) {
633                 int want_exit;
634                 int diff;
635
636                 diff = tnow - tstart;
637                 service = argv;
638                 want_exit = 1;
639                 while ((x = *service) != NULL) {
640                         if (x == (char*) -1L) /* "dead" */
641                                 goto next;
642                         if (x[0] != '/' && x[0] != '.') {
643                                 if (chdir(varservice) == -1)
644                                         goto chdir_failed;
645                         }
646                         if (chdir(x) == -1) {
647  chdir_failed:
648                                 fail("can't change to service directory");
649                                 goto nullify_service;
650                         }
651                         if (cbk(acts) != 0)
652                                 goto nullify_service;
653                         want_exit = 0;
654                         if (diff >= waitsec) {
655                                 printf(kll ? "kill: " : "timeout: ");
656                                 if (svstatus_get() > 0) {
657                                         svstatus_print(x);
658                                         ++rc;
659                                 }
660                                 bb_putchar('\n'); /* will also flush the output */
661                                 if (kll)
662                                         control("k");
663  nullify_service:
664                                 *service = (char*) -1L; /* "dead" */
665                         }
666                         if (fchdir(curdir) == -1)
667                                 fatal_cannot("change to original directory");
668  next:
669                         service++;
670                 }
671                 if (want_exit) break;
672                 usleep(420000);
673                 tnow = time(NULL) + 0x400000000000000aULL;
674         }
675         return rc > 99 ? 99 : rc;
676 }
677
678 #if ENABLE_SV
679 int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
680 int sv_main(int argc UNUSED_PARAM, char **argv)
681 {
682         return sv(argv);
683 }
684 #endif
685
686 //usage:#define svc_trivial_usage
687 //usage:       "[-udopchaitkx] SERVICE_DIR..."
688 //usage:#define svc_full_usage "\n\n"
689 //usage:       "Control services monitored by runsv supervisor"
690 //usage:   "\n"
691 //usage:   "\n""        -u      If service is not running, start it; restart if it stops"
692 //usage:   "\n""        -d      If service is running, send TERM+CONT signals; do not restart it"
693 //usage:   "\n""        -o      Once: if service is not running, start it; do not restart it"
694 //usage:   "\n""        -pchaitk Send STOP, CONT, HUP, ALRM, INT, TERM, KILL signal to service"
695 //usage:   "\n""        -x      Exit: runsv will exit as soon as the service is down"
696 #if ENABLE_SVC
697 int svc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
698 int svc_main(int argc UNUSED_PARAM, char **argv)
699 {
700         char command[2];
701         const char *optstring;
702         unsigned opts;
703
704         INIT_G();
705
706         optstring = "udopchaitkx";
707         opts = getopt32(argv, optstring);
708         argv += optind;
709         if (!argv[0] || !opts)
710                 bb_show_usage();
711
712         argv -= 2;
713         if (optind > 2) {
714                 argv--;
715                 argv[2] = (char*)"--";
716         }
717         argv[0] = (char*)"sv";
718         argv[1] = command;
719         command[1] = '\0';
720
721         /* getopt32() was already called:
722          * reset the libc getopt() function, which keeps internal state.
723          */
724         GETOPT_RESET();
725
726         do {
727                 if (opts & 1) {
728                         int r;
729                         command[0] = *optstring;
730                         r = sv(argv);
731                         if (r)
732                                 return 1;
733                 }
734                 optstring++;
735                 opts >>= 1;
736         } while (opts);
737
738         return 0;
739 }
740 #endif