Bump to version 1.22.1
[platform/upstream/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/file.h>
173 #include "libbb.h"
174 #include "runit_lib.h"
175
176 struct 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
197 static void fatal_cannot(const char *m1) NORETURN;
198 static void fatal_cannot(const char *m1)
199 {
200         bb_perror_msg("fatal: can't %s", m1);
201         _exit(151);
202 }
203
204 static 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
216 static void fail(const char *m1)
217 {
218         ++rc;
219         out("fail: ", m1);
220 }
221 static void failx(const char *m1)
222 {
223         errno = 0;
224         fail(m1);
225 }
226 static void warn(const char *m1)
227 {
228         ++rc;
229         /* "warning: <service>: <m1>\n" */
230         out("warning: ", m1);
231 }
232 static void ok(const char *m1)
233 {
234         errno = 0;
235         out(OK, m1);
236 }
237
238 static 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
274 static 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
314 static 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
335 static 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
361 static 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
406 static 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
436 int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
437 int 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 }