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