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