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