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