d52eb6d228f8fe51f442ded82728e1722847ce01
[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 status
76     Same as status.
77 start
78     Same as up, but wait up to 7 seconds for the command to take effect.
79     Then report the status or timeout. If the script ./check exists in
80     the service directory, sv runs this script to check whether the service
81     is up and available; it's considered to be available if ./check exits
82     with 0.
83 stop
84     Same as down, but wait up to 7 seconds for the service to become down.
85     Then report the status or timeout.
86 restart
87     Send the commands term, cont, and up to the service, and wait up to
88     7 seconds for the service to restart. Then report the status or timeout.
89     If the script ./check exists in the service directory, sv runs this script
90     to check whether the service is up and available again; it's considered
91     to be available if ./check exits with 0.
92 shutdown
93     Same as exit, but wait up to 7 seconds for the runsv(8) process
94     to terminate. Then report the status or timeout.
95 force-stop
96     Same as down, but wait up to 7 seconds for the service to become down.
97     Then report the status, and on timeout send the service the kill command.
98 force-reload
99     Send the service the term and cont commands, and wait up to
100     7 seconds for the service to restart. Then report the status,
101     and on timeout send the service the kill command.
102 force-restart
103     Send the service the term, cont and up commands, and wait up to
104     7 seconds for the service to restart. Then report the status, and
105     on timeout send the service the kill command. If the script ./check
106     exists in the service directory, sv runs this script to check whether
107     the service is up and available again; it?s considered to be available
108     if ./check exits with 0.
109 force-shutdown
110     Same as exit, but wait up to 7 seconds for the runsv(8) process to
111     terminate. Then report the status, and on timeout send the service
112     the kill command.
113
114 Additional Commands
115
116 check
117     Check for the service to be in the state that's been requested. Wait up to
118     7 seconds for the service to reach the requested state, then report
119     the status or timeout. If the requested state of the service is up,
120     and the script ./check exists in the service directory, sv runs
121     this script to check whether the service is up and running;
122     it's considered to be up if ./check exits with 0.
123
124 Options
125
126 -v
127     wait up to 7 seconds for the command to take effect.
128     Then report the status or timeout.
129 -w sec
130     Override the default timeout of 7 seconds with sec seconds. Implies -v.
131
132 Environment
133
134 SVDIR
135     The environment variable $SVDIR overrides the default services directory
136     /var/service.
137 SVWAIT
138     The environment variable $SVWAIT overrides the default 7 seconds to wait
139     for a command to take effect. It is overridden by the -w option.
140
141 Exit Codes
142     sv exits 0, if the command was successfully sent to all services, and,
143     if it was told to wait, the command has taken effect to all services.
144
145     For each service that caused an error (e.g. the directory is not
146     controlled by a runsv(8) process, or sv timed out while waiting),
147     sv increases the exit code by one and exits non zero. The maximum
148     is 99. sv exits 100 on error.
149 */
150
151 /* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
152 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
153
154 #include <sys/poll.h>
155 #include <sys/file.h>
156 #include "busybox.h"
157 #include "runit_lib.h"
158
159 static const char *acts;
160 static char **service;
161 static unsigned rc;
162 static struct taia tstart, tnow;
163 static char svstatus[20];
164
165 #define usage() bb_show_usage()
166
167 static void fatal_cannot(const char *m1) ATTRIBUTE_NORETURN;
168 static void fatal_cannot(const char *m1)
169 {
170         bb_perror_msg("fatal: cannot %s", m1);
171         _exit(151);
172 }
173
174 static void out(const char *p, const char *m1)
175 {
176         printf("%s%s: %s", p, *service, m1);
177         if (errno) {
178                 printf(": %s", strerror(errno));
179         }
180         puts(""); /* will also flush the output */
181 }
182
183 #define WARN    "warning: "
184 #define OK      "ok: "
185
186 static void fail(const char *m1) {
187         ++rc;
188         out("fail: ", m1);
189 }
190 static void failx(const char *m1) {
191         errno = 0;
192         fail(m1);
193 }
194 static void warn_cannot(const char *m1) {
195         ++rc;
196         out("warning: cannot ", m1);
197 }
198 static void warnx_cannot(const char *m1) {
199         errno = 0;
200         warn_cannot(m1);
201 }
202 static void ok(const char *m1) {
203         errno = 0;
204         out(OK, m1);
205 }
206
207 static int svstatus_get(void)
208 {
209         int fd, r;
210
211         fd = open_write("supervise/ok");
212         if (fd == -1) {
213                 if (errno == ENODEV) {
214                         *acts == 'x' ? ok("runsv not running")
215                                      : failx("runsv not running");
216                         return 0;
217                 }
218                 warn_cannot("open supervise/ok");
219                 return -1;
220         }
221         close(fd);
222         fd = open_read("supervise/status");
223         if (fd == -1) {
224                 warn_cannot("open supervise/status");
225                 return -1;
226         }
227         r = read(fd, svstatus, 20);
228         close(fd);
229         switch (r) {
230         case 20: break;
231         case -1: warn_cannot("read supervise/status"); return -1;
232         default: warnx_cannot("read supervise/status: bad format"); return -1;
233         }
234         return 1;
235 }
236
237 static unsigned svstatus_print(const char *m)
238 {
239         long diff;
240         int pid;
241         int normallyup = 0;
242         struct stat s;
243         struct tai tstatus;
244
245         if (stat("down", &s) == -1) {
246                 if (errno != ENOENT) {
247                         bb_perror_msg(WARN"cannot stat %s/down", *service);
248                         return 0;
249                 }
250                 normallyup = 1;
251         }
252         pid = (unsigned char) svstatus[15];
253         pid <<= 8; pid += (unsigned char)svstatus[14];
254         pid <<= 8; pid += (unsigned char)svstatus[13];
255         pid <<= 8; pid += (unsigned char)svstatus[12];
256         tai_unpack(svstatus, &tstatus);
257         if (pid) {
258                 switch (svstatus[19]) {
259                 case 1: printf("run: "); break;
260                 case 2: printf("finish: "); break;
261                 }
262                 printf("%s: (pid %d) ", m, pid);
263         } else {
264                 printf("down: %s: ", m);
265         }
266         diff = tnow.sec.x - tstatus.x;
267         printf("%lds", (diff < 0 ? 0L : diff));
268         if (pid) {
269                 if (!normallyup) printf(", normally down");
270                 if (svstatus[16]) printf(", paused");
271                 if (svstatus[17] == 'd') printf(", want down");
272                 if (svstatus[18]) printf(", got TERM");
273         } else {
274                 if (normallyup) printf(", normally up");
275                 if (svstatus[17] == 'u') printf(", want up");
276         }
277         return pid ? 1 : 2;
278 }
279
280 static int status(const char *unused)
281 {
282         int r;
283
284         r = svstatus_get();
285         switch (r) { case -1: case 0: return 0; }
286
287         r = svstatus_print(*service);
288         if (chdir("log") == -1) {
289                 if (errno != ENOENT) {
290                         printf("; log: "WARN"cannot change to log service directory: %s",
291                                         strerror(errno));
292                 }
293         } else if (svstatus_get()) {
294                 printf("; ");
295                 svstatus_print("log");
296         }
297         puts(""); /* will also flush the output */
298         return r;
299 }
300
301 static int checkscript(void)
302 {
303         char *prog[2];
304         struct stat s;
305         int pid, w;
306
307         if (stat("check", &s) == -1) {
308                 if (errno == ENOENT) return 1;
309                 bb_perror_msg(WARN"cannot stat %s/check", *service);
310                 return 0;
311         }
312         /* if (!(s.st_mode & S_IXUSR)) return 1; */
313         prog[0] = (char*)"./check";
314         prog[1] = NULL;
315         pid = spawn(prog);
316         if (pid <= 0) {
317                 bb_perror_msg(WARN"cannot %s child %s/check", "run", *service);
318                 return 0;
319         }
320         while (wait_pid(&w, pid) == -1) {
321                 if (errno == EINTR) continue;
322                 bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
323                 return 0;
324         }
325         return !wait_exitcode(w);
326 }
327
328 static int check(const char *a)
329 {
330         int r;
331         unsigned pid;
332         struct tai tstatus;
333
334         r = svstatus_get();
335         if (r == -1)
336                 return -1;
337         if (r == 0) {
338                 if (*a == 'x')
339                         return 1;
340                 return -1;
341         }
342         pid = (unsigned char)svstatus[15];
343         pid <<= 8; pid += (unsigned char)svstatus[14];
344         pid <<= 8; pid += (unsigned char)svstatus[13];
345         pid <<= 8; pid += (unsigned char)svstatus[12];
346         switch (*a) {
347         case 'x':
348                 return 0;
349         case 'u':
350                 if (!pid || svstatus[19] != 1) return 0;
351                 if (!checkscript()) return 0;
352                 break;
353         case 'd':
354                 if (pid) return 0;
355                 break;
356         case 'c':
357                 if (pid && !checkscript()) return 0;
358                 break;
359         case 't':
360                 if (!pid && svstatus[17] == 'd') break;
361                 tai_unpack(svstatus, &tstatus);
362                 if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript())
363                         return 0;
364                 break;
365         case 'o':
366                 tai_unpack(svstatus, &tstatus);
367                 if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd'))
368                         return 0;
369         }
370         printf(OK);
371         svstatus_print(*service);
372         puts(""); /* will also flush the output */
373         return 1;
374 }
375
376 static int control(const char *a)
377 {
378         int fd, r;
379
380         if (svstatus_get() <= 0) return -1;
381         if (svstatus[17] == *a) return 0;
382         fd = open_write("supervise/control");
383         if (fd == -1) {
384                 if (errno != ENODEV)
385                         warn_cannot("open supervise/control");
386                 else
387                         *a == 'x' ? ok("runsv not running") : failx("runsv not running");
388                 return -1;
389         }
390         r = write(fd, a, strlen(a));
391         close(fd);
392         if (r != strlen(a)) {
393                 warn_cannot("write to supervise/control");
394                 return -1;
395         }
396         return 1;
397 }
398
399 int sv_main(int argc, char **argv);
400 int sv_main(int argc, char **argv)
401 {
402         unsigned opt;
403         unsigned i, want_exit;
404         char *x;
405         char *action;
406         const char *varservice = "/var/service/";
407         unsigned services;
408         char **servicex;
409         unsigned long waitsec = 7;
410         smallint kll = 0;
411         smallint verbose = 0;
412         int (*act)(const char*);
413         int (*cbk)(const char*);
414         int curdir;
415
416         xfunc_error_retval = 100;
417
418         x = getenv("SVDIR");
419         if (x) varservice = x;
420         x = getenv("SVWAIT");
421         if (x) waitsec = xatoul(x);
422
423         opt = getopt32(argc, argv, "w:v", &x);
424         if (opt & 1) waitsec = xatoul(x); // -w
425         if (opt & 2) verbose = 1; // -v
426         argc -= optind;
427         argv += optind;
428         action = *argv++;
429         if (!action || !*argv) usage();
430         service = argv;
431         services = argc - 1;
432
433         taia_now(&tnow);
434         tstart = tnow;
435         curdir = open_read(".");
436         if (curdir == -1)
437                 fatal_cannot("open current directory");
438
439         act = &control;
440         acts = "s";
441         cbk = &check;
442
443         switch (*action) {
444         case 'x':
445         case 'e':
446                 acts = "x";
447                 if (!verbose) cbk = NULL;
448                 break;
449         case 'X':
450         case 'E':
451                 acts = "x";
452                 kll = 1;
453                 break;
454         case 'D':
455                 acts = "d";
456                 kll = 1;
457                 break;
458         case 'T':
459                 acts = "tc";
460                 kll = 1;
461                 break;
462         case 'c':
463                 if (!str_diff(action, "check")) {
464                         act = NULL;
465                         acts = "c";
466                         break;
467                 }
468         case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
469         case 'a': case 'i': case 'k': case 'q': case '1': case '2':
470                 action[1] = '\0';
471                 acts = action;
472                 if (!verbose) cbk = NULL;
473                 break;
474         case 's':
475                 if (!str_diff(action, "shutdown")) {
476                         acts = "x";
477                         break;
478                 }
479                 if (!str_diff(action, "start")) {
480                         acts = "u";
481                         break;
482                 }
483                 if (!str_diff(action, "stop")) {
484                         acts = "d";
485                         break;
486                 }
487                 /* "status" */
488                 act = &status;
489                 cbk = NULL;
490                 break;
491         case 'r':
492                 if (!str_diff(action, "restart")) {
493                         acts = "tcu";
494                         break;
495                 }
496                 usage();
497         case 'f':
498                 if (!str_diff(action, "force-reload")) {
499                         acts = "tc";
500                         kll = 1;
501                         break;
502                 }
503                 if (!str_diff(action, "force-restart")) {
504                         acts = "tcu";
505                         kll = 1;
506                         break;
507                 }
508                 if (!str_diff(action, "force-shutdown")) {
509                         acts = "x";
510                         kll = 1;
511                         break;
512                 }
513                 if (!str_diff(action, "force-stop")) {
514                         acts = "d";
515                         kll = 1;
516                         break;
517                 }
518         default:
519                 usage();
520         }
521
522         servicex = service;
523         for (i = 0; i < services; ++i) {
524                 if ((**service != '/') && (**service != '.')) {
525                         if (chdir(varservice) == -1)
526                                 goto chdir_failed_0;
527                 }
528                 if (chdir(*service) == -1) {
529  chdir_failed_0:
530                         fail("cannot change to service directory");
531                         goto nullify_service_0;
532                 }
533                 if (act && (act(acts) == -1)) {
534  nullify_service_0:
535                         *service = NULL;
536                 }
537                 if (fchdir(curdir) == -1)
538                         fatal_cannot("change to original directory");
539                 service++;
540         }
541
542         if (cbk) while (1) {
543                 //struct taia tdiff;
544                 long diff;
545
546                 //taia_sub(&tdiff, &tnow, &tstart);
547                 diff = tnow.sec.x - tstart.sec.x;
548                 service = servicex;
549                 want_exit = 1;
550                 for (i = 0; i < services; ++i, ++service) {
551                         if (!*service)
552                                 continue;
553                         if ((**service != '/') && (**service != '.')) {
554                                 if (chdir(varservice) == -1)
555                                         goto chdir_failed;
556                         }
557                         if (chdir(*service) == -1) {
558  chdir_failed:
559                                 fail("cannot change to service directory");
560                                 goto nullify_service;
561                         }
562                         if (cbk(acts) != 0)
563                                 goto nullify_service;
564                         want_exit = 0;
565                         if (diff >= waitsec) {
566                                 printf(kll ? "kill: " : "timeout: ");
567                                 if (svstatus_get() > 0) {
568                                         svstatus_print(*service);
569                                         ++rc;
570                                 }
571                                 puts(""); /* will also flush the output */
572                                 if (kll)
573                                         control("k");
574  nullify_service:
575                                 *service = NULL;
576                         }
577                         if (fchdir(curdir) == -1)
578                                 fatal_cannot("change to original directory");
579                 }
580                 if (want_exit) break;
581                 usleep(420000);
582                 taia_now(&tnow);
583         }
584         return rc > 99 ? 99 : rc;
585 }