summaryrefslogtreecommitdiff
path: root/runit/sv.c (plain)
blob: 825e9d45bbf8e4cec1c696cd17edc71eb5863db5
1/*
2Copyright (c) 2001-2006, Gerrit Pape
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*/
27
28/* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
29
30sv - control and manage services monitored by runsv
31
32sv [-v] [-w sec] command services
33/etc/init.d/service [-w sec] command
34
35The sv program reports the current status and controls the state of services
36monitored by the runsv(8) supervisor.
37
38services consists of one or more arguments, each argument naming a directory
39service used by runsv(8). If service doesn't start with a dot or slash,
40it is searched in the default services directory /var/service/, otherwise
41relative to the current directory.
42
43command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
441, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
45force-reload, force-restart, force-shutdown.
46
47The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
48script interface. The service to be controlled then is specified by the
49base name of the "init script".
50
51status
52 Report the current status of the service, and the appendant log service
53 if available, to standard output.
54up
55 If the service is not running, start it. If the service stops, restart it.
56down
57 If the service is running, send it the TERM signal, and the CONT signal.
58 If ./run exits, start ./finish if it exists. After it stops, do not
59 restart service.
60once
61 If the service is not running, start it. Do not restart it if it stops.
62pause cont hup alarm interrupt quit 1 2 term kill
63 If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
64 USR1, USR2, TERM, or KILL signal respectively.
65exit
66 If the service is running, send it the TERM signal, and the CONT signal.
67 Do not restart the service. If the service is down, and no log service
68 exists, runsv(8) exits. If the service is down and a log service exists,
69 send the TERM signal to the log service. If the log service is down,
70 runsv(8) exits. This command is ignored if it is given to an appendant
71 log service.
72
73sv actually looks only at the first character of above commands.
74
75Commands compatible to LSB init script actions:
76
77status
78 Same as status.
79start
80 Same as up, but wait up to 7 seconds for the command to take effect.
81 Then report the status or timeout. If the script ./check exists in
82 the service directory, sv runs this script to check whether the service
83 is up and available; it's considered to be available if ./check exits
84 with 0.
85stop
86 Same as down, but wait up to 7 seconds for the service to become down.
87 Then report the status or timeout.
88restart
89 Send the commands term, cont, and up to the service, and wait up to
90 7 seconds for the service to restart. Then report the status or timeout.
91 If the script ./check exists in the service directory, sv runs this script
92 to check whether the service is up and available again; it's considered
93 to be available if ./check exits with 0.
94shutdown
95 Same as exit, but wait up to 7 seconds for the runsv(8) process
96 to terminate. Then report the status or timeout.
97force-stop
98 Same as down, but wait up to 7 seconds for the service to become down.
99 Then report the status, and on timeout send the service the kill command.
100force-reload
101 Send the service the term and cont commands, and wait up to
102 7 seconds for the service to restart. Then report the status,
103 and on timeout send the service the kill command.
104force-restart
105 Send the service the term, cont and up commands, and wait up to
106 7 seconds for the service to restart. Then report the status, and
107 on timeout send the service the kill command. If the script ./check
108 exists in the service directory, sv runs this script to check whether
109 the service is up and available again; it's considered to be available
110 if ./check exits with 0.
111force-shutdown
112 Same as exit, but wait up to 7 seconds for the runsv(8) process to
113 terminate. Then report the status, and on timeout send the service
114 the kill command.
115
116Additional Commands
117
118check
119 Check for the service to be in the state that's been requested. Wait up to
120 7 seconds for the service to reach the requested state, then report
121 the status or timeout. If the requested state of the service is up,
122 and the script ./check exists in the service directory, sv runs
123 this script to check whether the service is up and running;
124 it's considered to be up if ./check exits with 0.
125
126Options
127
128-v
129 wait up to 7 seconds for the command to take effect.
130 Then report the status or timeout.
131-w sec
132 Override the default timeout of 7 seconds with sec seconds. Implies -v.
133
134Environment
135
136SVDIR
137 The environment variable $SVDIR overrides the default services directory
138 /var/service.
139SVWAIT
140 The environment variable $SVWAIT overrides the default 7 seconds to wait
141 for a command to take effect. It is overridden by the -w option.
142
143Exit Codes
144 sv exits 0, if the command was successfully sent to all services, and,
145 if it was told to wait, the command has taken effect to all services.
146
147 For each service that caused an error (e.g. the directory is not
148 controlled by a runsv(8) process, or sv timed out while waiting),
149 sv increases the exit code by one and exits non zero. The maximum
150 is 99. sv exits 100 on error.
151*/
152
153/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
154/* TODO: depends on runit_lib.c - review and reduce/eliminate */
155
156//usage:#define sv_trivial_usage
157//usage: "[-v] [-w SEC] CMD SERVICE_DIR..."
158//usage:#define sv_full_usage "\n\n"
159//usage: "Control services monitored by runsv supervisor.\n"
160//usage: "Commands (only first character is enough):\n"
161//usage: "\n"
162//usage: "status: query service status\n"
163//usage: "up: if service isn't running, start it. If service stops, restart it\n"
164//usage: "once: like 'up', but if service stops, don't restart it\n"
165//usage: "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
166//usage: " if it exists. After it stops, don't restart service\n"
167//usage: "exit: send TERM and CONT signals to service and log service. If they exit,\n"
168//usage: " runsv exits too\n"
169//usage: "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
170//usage: "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
171
172#include <sys/file.h>
173#include "libbb.h"
174#include "runit_lib.h"
175
176struct globals {
177 const char *acts;
178 char **service;
179 unsigned rc;
180/* "Bernstein" time format: unix + 0x400000000000000aULL */
181 uint64_t tstart, tnow;
182 svstatus_t svstatus;
183} FIX_ALIASING;
184#define G (*(struct globals*)&bb_common_bufsiz1)
185#define acts (G.acts )
186#define service (G.service )
187#define rc (G.rc )
188#define tstart (G.tstart )
189#define tnow (G.tnow )
190#define svstatus (G.svstatus )
191#define INIT_G() do { } while (0)
192
193
194#define str_equal(s,t) (!strcmp((s), (t)))
195
196
197static void fatal_cannot(const char *m1) NORETURN;
198static void fatal_cannot(const char *m1)
199{
200 bb_perror_msg("fatal: can't %s", m1);
201 _exit(151);
202}
203
204static void out(const char *p, const char *m1)
205{
206 printf("%s%s: %s", p, *service, m1);
207 if (errno) {
208 printf(": %s", strerror(errno));
209 }
210 bb_putchar('\n'); /* will also flush the output */
211}
212
213#define WARN "warning: "
214#define OK "ok: "
215
216static void fail(const char *m1)
217{
218 ++rc;
219 out("fail: ", m1);
220}
221static void failx(const char *m1)
222{
223 errno = 0;
224 fail(m1);
225}
226static void warn(const char *m1)
227{
228 ++rc;
229 /* "warning: <service>: <m1>\n" */
230 out("warning: ", m1);
231}
232static void ok(const char *m1)
233{
234 errno = 0;
235 out(OK, m1);
236}
237
238static int svstatus_get(void)
239{
240 int fd, r;
241
242 fd = open("supervise/ok", O_WRONLY|O_NDELAY);
243 if (fd == -1) {
244 if (errno == ENODEV) {
245 *acts == 'x' ? ok("runsv not running")
246 : failx("runsv not running");
247 return 0;
248 }
249 warn("can't open supervise/ok");
250 return -1;
251 }
252 close(fd);
253 fd = open("supervise/status", O_RDONLY|O_NDELAY);
254 if (fd == -1) {
255 warn("can't open supervise/status");
256 return -1;
257 }
258 r = read(fd, &svstatus, 20);
259 close(fd);
260 switch (r) {
261 case 20:
262 break;
263 case -1:
264 warn("can't read supervise/status");
265 return -1;
266 default:
267 errno = 0;
268 warn("can't read supervise/status: bad format");
269 return -1;
270 }
271 return 1;
272}
273
274static unsigned svstatus_print(const char *m)
275{
276 int diff;
277 int pid;
278 int normallyup = 0;
279 struct stat s;
280 uint64_t timestamp;
281
282 if (stat("down", &s) == -1) {
283 if (errno != ENOENT) {
284 bb_perror_msg(WARN"can't stat %s/down", *service);
285 return 0;
286 }
287 normallyup = 1;
288 }
289 pid = SWAP_LE32(svstatus.pid_le32);
290 timestamp = SWAP_BE64(svstatus.time_be64);
291 if (pid) {
292 switch (svstatus.run_or_finish) {
293 case 1: printf("run: "); break;
294 case 2: printf("finish: "); break;
295 }
296 printf("%s: (pid %d) ", m, pid);
297 } else {
298 printf("down: %s: ", m);
299 }
300 diff = tnow - timestamp;
301 printf("%us", (diff < 0 ? 0 : diff));
302 if (pid) {
303 if (!normallyup) printf(", normally down");
304 if (svstatus.paused) printf(", paused");
305 if (svstatus.want == 'd') printf(", want down");
306 if (svstatus.got_term) printf(", got TERM");
307 } else {
308 if (normallyup) printf(", normally up");
309 if (svstatus.want == 'u') printf(", want up");
310 }
311 return pid ? 1 : 2;
312}
313
314static int status(const char *unused UNUSED_PARAM)
315{
316 int r;
317
318 if (svstatus_get() <= 0)
319 return 0;
320
321 r = svstatus_print(*service);
322 if (chdir("log") == -1) {
323 if (errno != ENOENT) {
324 printf("; log: "WARN"can't change to log service directory: %s",
325 strerror(errno));
326 }
327 } else if (svstatus_get()) {
328 printf("; ");
329 svstatus_print("log");
330 }
331 bb_putchar('\n'); /* will also flush the output */
332 return r;
333}
334
335static int checkscript(void)
336{
337 char *prog[2];
338 struct stat s;
339 int pid, w;
340
341 if (stat("check", &s) == -1) {
342 if (errno == ENOENT) return 1;
343 bb_perror_msg(WARN"can't stat %s/check", *service);
344 return 0;
345 }
346 /* if (!(s.st_mode & S_IXUSR)) return 1; */
347 prog[0] = (char*)"./check";
348 prog[1] = NULL;
349 pid = spawn(prog);
350 if (pid <= 0) {
351 bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
352 return 0;
353 }
354 while (safe_waitpid(pid, &w, 0) == -1) {
355 bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
356 return 0;
357 }
358 return WEXITSTATUS(w) == 0;
359}
360
361static int check(const char *a)
362{
363 int r;
364 unsigned pid_le32;
365 uint64_t timestamp;
366
367 r = svstatus_get();
368 if (r == -1)
369 return -1;
370 if (r == 0) {
371 if (*a == 'x')
372 return 1;
373 return -1;
374 }
375 pid_le32 = svstatus.pid_le32;
376 switch (*a) {
377 case 'x':
378 return 0;
379 case 'u':
380 if (!pid_le32 || svstatus.run_or_finish != 1) return 0;
381 if (!checkscript()) return 0;
382 break;
383 case 'd':
384 if (pid_le32) return 0;
385 break;
386 case 'c':
387 if (pid_le32 && !checkscript()) return 0;
388 break;
389 case 't':
390 if (!pid_le32 && svstatus.want == 'd') break;
391 timestamp = SWAP_BE64(svstatus.time_be64);
392 if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
393 return 0;
394 break;
395 case 'o':
396 timestamp = SWAP_BE64(svstatus.time_be64);
397 if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
398 return 0;
399 }
400 printf(OK);
401 svstatus_print(*service);
402 bb_putchar('\n'); /* will also flush the output */
403 return 1;
404}
405
406static int control(const char *a)
407{
408 int fd, r, l;
409
410/* Is it an optimization?
411 It causes problems with "sv o SRV; ...; sv d SRV"
412 ('d' is not passed to SRV because its .want == 'd'):
413 if (svstatus_get() <= 0)
414 return -1;
415 if (svstatus.want == *a)
416 return 0;
417*/
418 fd = open("supervise/control", O_WRONLY|O_NDELAY);
419 if (fd == -1) {
420 if (errno != ENODEV)
421 warn("can't open supervise/control");
422 else
423 *a == 'x' ? ok("runsv not running") : failx("runsv not running");
424 return -1;
425 }
426 l = strlen(a);
427 r = write(fd, a, l);
428 close(fd);
429 if (r != l) {
430 warn("can't write to supervise/control");
431 return -1;
432 }
433 return 1;
434}
435
436int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
437int sv_main(int argc UNUSED_PARAM, char **argv)
438{
439 char *x;
440 char *action;
441 const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
442 unsigned waitsec = 7;
443 smallint kll = 0;
444 int verbose = 0;
445 int (*act)(const char*);
446 int (*cbk)(const char*);
447 int curdir;
448
449 INIT_G();
450
451 xfunc_error_retval = 100;
452
453 x = getenv("SVDIR");
454 if (x) varservice = x;
455 x = getenv("SVWAIT");
456 if (x) waitsec = xatou(x);
457
458 opt_complementary = "w+:vv"; /* -w N, -v is a counter */
459 getopt32(argv, "w:v", &waitsec, &verbose);
460 argv += optind;
461 action = *argv++;
462 if (!action || !*argv) bb_show_usage();
463
464 tnow = time(NULL) + 0x400000000000000aULL;
465 tstart = tnow;
466 curdir = open(".", O_RDONLY|O_NDELAY);
467 if (curdir == -1)
468 fatal_cannot("open current directory");
469
470 act = &control;
471 acts = "s";
472 cbk = &check;
473
474 switch (*action) {
475 case 'x':
476 case 'e':
477 acts = "x";
478 if (!verbose) cbk = NULL;
479 break;
480 case 'X':
481 case 'E':
482 acts = "x";
483 kll = 1;
484 break;
485 case 'D':
486 acts = "d";
487 kll = 1;
488 break;
489 case 'T':
490 acts = "tc";
491 kll = 1;
492 break;
493 case 'c':
494 if (str_equal(action, "check")) {
495 act = NULL;
496 acts = "c";
497 break;
498 }
499 case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
500 case 'a': case 'i': case 'k': case 'q': case '1': case '2':
501 action[1] = '\0';
502 acts = action;
503 if (!verbose) cbk = NULL;
504 break;
505 case 's':
506 if (str_equal(action, "shutdown")) {
507 acts = "x";
508 break;
509 }
510 if (str_equal(action, "start")) {
511 acts = "u";
512 break;
513 }
514 if (str_equal(action, "stop")) {
515 acts = "d";
516 break;
517 }
518 /* "status" */
519 act = &status;
520 cbk = NULL;
521 break;
522 case 'r':
523 if (str_equal(action, "restart")) {
524 acts = "tcu";
525 break;
526 }
527 bb_show_usage();
528 case 'f':
529 if (str_equal(action, "force-reload")) {
530 acts = "tc";
531 kll = 1;
532 break;
533 }
534 if (str_equal(action, "force-restart")) {
535 acts = "tcu";
536 kll = 1;
537 break;
538 }
539 if (str_equal(action, "force-shutdown")) {
540 acts = "x";
541 kll = 1;
542 break;
543 }
544 if (str_equal(action, "force-stop")) {
545 acts = "d";
546 kll = 1;
547 break;
548 }
549 default:
550 bb_show_usage();
551 }
552
553 service = argv;
554 while ((x = *service) != NULL) {
555 if (x[0] != '/' && x[0] != '.') {
556 if (chdir(varservice) == -1)
557 goto chdir_failed_0;
558 }
559 if (chdir(x) == -1) {
560 chdir_failed_0:
561 fail("can't change to service directory");
562 goto nullify_service_0;
563 }
564 if (act && (act(acts) == -1)) {
565 nullify_service_0:
566 *service = (char*) -1L; /* "dead" */
567 }
568 if (fchdir(curdir) == -1)
569 fatal_cannot("change to original directory");
570 service++;
571 }
572
573 if (cbk) while (1) {
574 int want_exit;
575 int diff;
576
577 diff = tnow - tstart;
578 service = argv;
579 want_exit = 1;
580 while ((x = *service) != NULL) {
581 if (x == (char*) -1L) /* "dead" */
582 goto next;
583 if (x[0] != '/' && x[0] != '.') {
584 if (chdir(varservice) == -1)
585 goto chdir_failed;
586 }
587 if (chdir(x) == -1) {
588 chdir_failed:
589 fail("can't change to service directory");
590 goto nullify_service;
591 }
592 if (cbk(acts) != 0)
593 goto nullify_service;
594 want_exit = 0;
595 if (diff >= waitsec) {
596 printf(kll ? "kill: " : "timeout: ");
597 if (svstatus_get() > 0) {
598 svstatus_print(x);
599 ++rc;
600 }
601 bb_putchar('\n'); /* will also flush the output */
602 if (kll)
603 control("k");
604 nullify_service:
605 *service = (char*) -1L; /* "dead" */
606 }
607 if (fchdir(curdir) == -1)
608 fatal_cannot("change to original directory");
609 next:
610 service++;
611 }
612 if (want_exit) break;
613 usleep(420000);
614 tnow = time(NULL) + 0x400000000000000aULL;
615 }
616 return rc > 99 ? 99 : rc;
617}
618