*: add most of the required setup_common_bufsiz() calls
[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
170 //applet:IF_SV(APPLET(sv, BB_DIR_USR_BIN, BB_SUID_DROP))
171
172 //kbuild:lib-$(CONFIG_SV) += sv.o
173
174 //usage:#define sv_trivial_usage
175 //usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
176 //usage:#define sv_full_usage "\n\n"
177 //usage:       "Control services monitored by runsv supervisor.\n"
178 //usage:       "Commands (only first character is enough):\n"
179 //usage:       "\n"
180 //usage:       "status: query service status\n"
181 //usage:       "up: if service isn't running, start it. If service stops, restart it\n"
182 //usage:       "once: like 'up', but if service stops, don't restart it\n"
183 //usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
184 //usage:       "        if it exists. After it stops, don't restart service\n"
185 //usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
186 //usage:       "        runsv exits too\n"
187 //usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
188 //usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
189
190 #include <sys/file.h>
191 #include "libbb.h"
192 #include "common_bufsiz.h"
193 #include "runit_lib.h"
194
195 struct globals {
196         const char *acts;
197         char **service;
198         unsigned rc;
199 /* "Bernstein" time format: unix + 0x400000000000000aULL */
200         uint64_t tstart, tnow;
201         svstatus_t svstatus;
202 } FIX_ALIASING;
203 #define G (*(struct globals*)bb_common_bufsiz1)
204 #define acts         (G.acts        )
205 #define service      (G.service     )
206 #define rc           (G.rc          )
207 #define tstart       (G.tstart      )
208 #define tnow         (G.tnow        )
209 #define svstatus     (G.svstatus    )
210 #define INIT_G() do { setup_common_bufsiz(); } while (0)
211
212
213 #define str_equal(s,t) (!strcmp((s), (t)))
214
215
216 static void fatal_cannot(const char *m1) NORETURN;
217 static void fatal_cannot(const char *m1)
218 {
219         bb_perror_msg("fatal: can't %s", m1);
220         _exit(151);
221 }
222
223 static void out(const char *p, const char *m1)
224 {
225         printf("%s%s: %s", p, *service, m1);
226         if (errno) {
227                 printf(": %s", strerror(errno));
228         }
229         bb_putchar('\n'); /* will also flush the output */
230 }
231
232 #define WARN    "warning: "
233 #define OK      "ok: "
234
235 static void fail(const char *m1)
236 {
237         ++rc;
238         out("fail: ", m1);
239 }
240 static void failx(const char *m1)
241 {
242         errno = 0;
243         fail(m1);
244 }
245 static void warn(const char *m1)
246 {
247         ++rc;
248         /* "warning: <service>: <m1>\n" */
249         out("warning: ", m1);
250 }
251 static void ok(const char *m1)
252 {
253         errno = 0;
254         out(OK, m1);
255 }
256
257 static int svstatus_get(void)
258 {
259         int fd, r;
260
261         fd = open("supervise/ok", O_WRONLY|O_NDELAY);
262         if (fd == -1) {
263                 if (errno == ENODEV) {
264                         *acts == 'x' ? ok("runsv not running")
265                                      : failx("runsv not running");
266                         return 0;
267                 }
268                 warn("can't open supervise/ok");
269                 return -1;
270         }
271         close(fd);
272         fd = open("supervise/status", O_RDONLY|O_NDELAY);
273         if (fd == -1) {
274                 warn("can't open supervise/status");
275                 return -1;
276         }
277         r = read(fd, &svstatus, 20);
278         close(fd);
279         switch (r) {
280         case 20:
281                 break;
282         case -1:
283                 warn("can't read supervise/status");
284                 return -1;
285         default:
286                 errno = 0;
287                 warn("can't read supervise/status: bad format");
288                 return -1;
289         }
290         return 1;
291 }
292
293 static unsigned svstatus_print(const char *m)
294 {
295         int diff;
296         int pid;
297         int normallyup = 0;
298         struct stat s;
299         uint64_t timestamp;
300
301         if (stat("down", &s) == -1) {
302                 if (errno != ENOENT) {
303                         bb_perror_msg(WARN"can't stat %s/down", *service);
304                         return 0;
305                 }
306                 normallyup = 1;
307         }
308         pid = SWAP_LE32(svstatus.pid_le32);
309         timestamp = SWAP_BE64(svstatus.time_be64);
310         if (pid) {
311                 switch (svstatus.run_or_finish) {
312                 case 1: printf("run: "); break;
313                 case 2: printf("finish: "); break;
314                 }
315                 printf("%s: (pid %d) ", m, pid);
316         } else {
317                 printf("down: %s: ", m);
318         }
319         diff = tnow - timestamp;
320         printf("%us", (diff < 0 ? 0 : diff));
321         if (pid) {
322                 if (!normallyup) printf(", normally down");
323                 if (svstatus.paused) printf(", paused");
324                 if (svstatus.want == 'd') printf(", want down");
325                 if (svstatus.got_term) printf(", got TERM");
326         } else {
327                 if (normallyup) printf(", normally up");
328                 if (svstatus.want == 'u') printf(", want up");
329         }
330         return pid ? 1 : 2;
331 }
332
333 static int status(const char *unused UNUSED_PARAM)
334 {
335         int r;
336
337         if (svstatus_get() <= 0)
338                 return 0;
339
340         r = svstatus_print(*service);
341         if (chdir("log") == -1) {
342                 if (errno != ENOENT) {
343                         printf("; log: "WARN"can't change to log service directory: %s",
344                                         strerror(errno));
345                 }
346         } else if (svstatus_get()) {
347                 printf("; ");
348                 svstatus_print("log");
349         }
350         bb_putchar('\n'); /* will also flush the output */
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         if (r == 0) {
390                 if (*a == 'x')
391                         return 1;
392                 return -1;
393         }
394         pid_le32 = svstatus.pid_le32;
395         switch (*a) {
396         case 'x':
397                 return 0;
398         case 'u':
399                 if (!pid_le32 || svstatus.run_or_finish != 1) return 0;
400                 if (!checkscript()) return 0;
401                 break;
402         case 'd':
403                 if (pid_le32) return 0;
404                 break;
405         case 'c':
406                 if (pid_le32 && !checkscript()) return 0;
407                 break;
408         case 't':
409                 if (!pid_le32 && svstatus.want == 'd') break;
410                 timestamp = SWAP_BE64(svstatus.time_be64);
411                 if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
412                         return 0;
413                 break;
414         case 'o':
415                 timestamp = SWAP_BE64(svstatus.time_be64);
416                 if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
417                         return 0;
418         }
419         printf(OK);
420         svstatus_print(*service);
421         bb_putchar('\n'); /* will also flush the output */
422         return 1;
423 }
424
425 static int control(const char *a)
426 {
427         int fd, r, l;
428
429 /* Is it an optimization?
430    It causes problems with "sv o SRV; ...; sv d SRV"
431    ('d' is not passed to SRV because its .want == 'd'):
432         if (svstatus_get() <= 0)
433                 return -1;
434         if (svstatus.want == *a)
435                 return 0;
436 */
437         fd = open("supervise/control", O_WRONLY|O_NDELAY);
438         if (fd == -1) {
439                 if (errno != ENODEV)
440                         warn("can't open supervise/control");
441                 else
442                         *a == 'x' ? ok("runsv not running") : failx("runsv not running");
443                 return -1;
444         }
445         l = strlen(a);
446         r = write(fd, a, l);
447         close(fd);
448         if (r != l) {
449                 warn("can't write to supervise/control");
450                 return -1;
451         }
452         return 1;
453 }
454
455 int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
456 int sv_main(int argc UNUSED_PARAM, char **argv)
457 {
458         char *x;
459         char *action;
460         const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
461         unsigned waitsec = 7;
462         smallint kll = 0;
463         int verbose = 0;
464         int (*act)(const char*);
465         int (*cbk)(const char*);
466         int curdir;
467
468         INIT_G();
469
470         xfunc_error_retval = 100;
471
472         x = getenv("SVDIR");
473         if (x) varservice = x;
474         x = getenv("SVWAIT");
475         if (x) waitsec = xatou(x);
476
477         opt_complementary = "w+:vv"; /* -w N, -v is a counter */
478         getopt32(argv, "w:v", &waitsec, &verbose);
479         argv += optind;
480         action = *argv++;
481         if (!action || !*argv) bb_show_usage();
482
483         tnow = time(NULL) + 0x400000000000000aULL;
484         tstart = tnow;
485         curdir = open(".", O_RDONLY|O_NDELAY);
486         if (curdir == -1)
487                 fatal_cannot("open current directory");
488
489         act = &control;
490         acts = "s";
491         cbk = &check;
492
493         switch (*action) {
494         case 'x':
495         case 'e':
496                 acts = "x";
497                 if (!verbose) cbk = NULL;
498                 break;
499         case 'X':
500         case 'E':
501                 acts = "x";
502                 kll = 1;
503                 break;
504         case 'D':
505                 acts = "d";
506                 kll = 1;
507                 break;
508         case 'T':
509                 acts = "tc";
510                 kll = 1;
511                 break;
512         case 'c':
513                 if (str_equal(action, "check")) {
514                         act = NULL;
515                         acts = "c";
516                         break;
517                 }
518         case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
519         case 'a': case 'i': case 'k': case 'q': case '1': case '2':
520                 action[1] = '\0';
521                 acts = action;
522                 if (!verbose) cbk = NULL;
523                 break;
524         case 's':
525                 if (str_equal(action, "shutdown")) {
526                         acts = "x";
527                         break;
528                 }
529                 if (str_equal(action, "start")) {
530                         acts = "u";
531                         break;
532                 }
533                 if (str_equal(action, "stop")) {
534                         acts = "d";
535                         break;
536                 }
537                 /* "status" */
538                 act = &status;
539                 cbk = NULL;
540                 break;
541         case 'r':
542                 if (str_equal(action, "restart")) {
543                         acts = "tcu";
544                         break;
545                 }
546                 bb_show_usage();
547         case 'f':
548                 if (str_equal(action, "force-reload")) {
549                         acts = "tc";
550                         kll = 1;
551                         break;
552                 }
553                 if (str_equal(action, "force-restart")) {
554                         acts = "tcu";
555                         kll = 1;
556                         break;
557                 }
558                 if (str_equal(action, "force-shutdown")) {
559                         acts = "x";
560                         kll = 1;
561                         break;
562                 }
563                 if (str_equal(action, "force-stop")) {
564                         acts = "d";
565                         kll = 1;
566                         break;
567                 }
568         default:
569                 bb_show_usage();
570         }
571
572         service = argv;
573         while ((x = *service) != NULL) {
574                 if (x[0] != '/' && x[0] != '.') {
575                         if (chdir(varservice) == -1)
576                                 goto chdir_failed_0;
577                 }
578                 if (chdir(x) == -1) {
579  chdir_failed_0:
580                         fail("can't change to service directory");
581                         goto nullify_service_0;
582                 }
583                 if (act && (act(acts) == -1)) {
584  nullify_service_0:
585                         *service = (char*) -1L; /* "dead" */
586                 }
587                 if (fchdir(curdir) == -1)
588                         fatal_cannot("change to original directory");
589                 service++;
590         }
591
592         if (cbk) while (1) {
593                 int want_exit;
594                 int diff;
595
596                 diff = tnow - tstart;
597                 service = argv;
598                 want_exit = 1;
599                 while ((x = *service) != NULL) {
600                         if (x == (char*) -1L) /* "dead" */
601                                 goto next;
602                         if (x[0] != '/' && x[0] != '.') {
603                                 if (chdir(varservice) == -1)
604                                         goto chdir_failed;
605                         }
606                         if (chdir(x) == -1) {
607  chdir_failed:
608                                 fail("can't change to service directory");
609                                 goto nullify_service;
610                         }
611                         if (cbk(acts) != 0)
612                                 goto nullify_service;
613                         want_exit = 0;
614                         if (diff >= waitsec) {
615                                 printf(kll ? "kill: " : "timeout: ");
616                                 if (svstatus_get() > 0) {
617                                         svstatus_print(x);
618                                         ++rc;
619                                 }
620                                 bb_putchar('\n'); /* will also flush the output */
621                                 if (kll)
622                                         control("k");
623  nullify_service:
624                                 *service = (char*) -1L; /* "dead" */
625                         }
626                         if (fchdir(curdir) == -1)
627                                 fatal_cannot("change to original directory");
628  next:
629                         service++;
630                 }
631                 if (want_exit) break;
632                 usleep(420000);
633                 tnow = time(NULL) + 0x400000000000000aULL;
634         }
635         return rc > 99 ? 99 : rc;
636 }