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