usage.c: remove reference to busybox.h
[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 "libbb.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 {
188         ++rc;
189         out("fail: ", m1);
190 }
191 static void failx(const char *m1)
192 {
193         errno = 0;
194         fail(m1);
195 }
196 static void warn_cannot(const char *m1)
197 {
198         ++rc;
199         out("warning: cannot ", m1);
200 }
201 static void warnx_cannot(const char *m1)
202 {
203         errno = 0;
204         warn_cannot(m1);
205 }
206 static void ok(const char *m1)
207 {
208         errno = 0;
209         out(OK, m1);
210 }
211
212 static int svstatus_get(void)
213 {
214         int fd, r;
215
216         fd = open_write("supervise/ok");
217         if (fd == -1) {
218                 if (errno == ENODEV) {
219                         *acts == 'x' ? ok("runsv not running")
220                                      : failx("runsv not running");
221                         return 0;
222                 }
223                 warn_cannot("open supervise/ok");
224                 return -1;
225         }
226         close(fd);
227         fd = open_read("supervise/status");
228         if (fd == -1) {
229                 warn_cannot("open supervise/status");
230                 return -1;
231         }
232         r = read(fd, svstatus, 20);
233         close(fd);
234         switch (r) {
235         case 20: break;
236         case -1: warn_cannot("read supervise/status"); return -1;
237         default: warnx_cannot("read supervise/status: bad format"); return -1;
238         }
239         return 1;
240 }
241
242 static unsigned svstatus_print(const char *m)
243 {
244         long diff;
245         int pid;
246         int normallyup = 0;
247         struct stat s;
248         struct tai tstatus;
249
250         if (stat("down", &s) == -1) {
251                 if (errno != ENOENT) {
252                         bb_perror_msg(WARN"cannot stat %s/down", *service);
253                         return 0;
254                 }
255                 normallyup = 1;
256         }
257         pid = (unsigned char) svstatus[15];
258         pid <<= 8; pid += (unsigned char)svstatus[14];
259         pid <<= 8; pid += (unsigned char)svstatus[13];
260         pid <<= 8; pid += (unsigned char)svstatus[12];
261         tai_unpack(svstatus, &tstatus);
262         if (pid) {
263                 switch (svstatus[19]) {
264                 case 1: printf("run: "); break;
265                 case 2: printf("finish: "); break;
266                 }
267                 printf("%s: (pid %d) ", m, pid);
268         } else {
269                 printf("down: %s: ", m);
270         }
271         diff = tnow.sec.x - tstatus.x;
272         printf("%lds", (diff < 0 ? 0L : diff));
273         if (pid) {
274                 if (!normallyup) printf(", normally down");
275                 if (svstatus[16]) printf(", paused");
276                 if (svstatus[17] == 'd') printf(", want down");
277                 if (svstatus[18]) printf(", got TERM");
278         } else {
279                 if (normallyup) printf(", normally up");
280                 if (svstatus[17] == 'u') printf(", want up");
281         }
282         return pid ? 1 : 2;
283 }
284
285 static int status(const char *unused)
286 {
287         int r;
288
289         r = svstatus_get();
290         switch (r) { case -1: case 0: return 0; }
291
292         r = svstatus_print(*service);
293         if (chdir("log") == -1) {
294                 if (errno != ENOENT) {
295                         printf("; log: "WARN"cannot change to log service directory: %s",
296                                         strerror(errno));
297                 }
298         } else if (svstatus_get()) {
299                 printf("; ");
300                 svstatus_print("log");
301         }
302         puts(""); /* will also flush the output */
303         return r;
304 }
305
306 static int checkscript(void)
307 {
308         char *prog[2];
309         struct stat s;
310         int pid, w;
311
312         if (stat("check", &s) == -1) {
313                 if (errno == ENOENT) return 1;
314                 bb_perror_msg(WARN"cannot stat %s/check", *service);
315                 return 0;
316         }
317         /* if (!(s.st_mode & S_IXUSR)) return 1; */
318         prog[0] = (char*)"./check";
319         prog[1] = NULL;
320         pid = spawn(prog);
321         if (pid <= 0) {
322                 bb_perror_msg(WARN"cannot %s child %s/check", "run", *service);
323                 return 0;
324         }
325         while (wait_pid(&w, pid) == -1) {
326                 if (errno == EINTR) continue;
327                 bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
328                 return 0;
329         }
330         return !wait_exitcode(w);
331 }
332
333 static int check(const char *a)
334 {
335         int r;
336         unsigned pid;
337         struct tai tstatus;
338
339         r = svstatus_get();
340         if (r == -1)
341                 return -1;
342         if (r == 0) {
343                 if (*a == 'x')
344                         return 1;
345                 return -1;
346         }
347         pid = (unsigned char)svstatus[15];
348         pid <<= 8; pid += (unsigned char)svstatus[14];
349         pid <<= 8; pid += (unsigned char)svstatus[13];
350         pid <<= 8; pid += (unsigned char)svstatus[12];
351         switch (*a) {
352         case 'x':
353                 return 0;
354         case 'u':
355                 if (!pid || svstatus[19] != 1) return 0;
356                 if (!checkscript()) return 0;
357                 break;
358         case 'd':
359                 if (pid) return 0;
360                 break;
361         case 'c':
362                 if (pid && !checkscript()) return 0;
363                 break;
364         case 't':
365                 if (!pid && svstatus[17] == 'd') break;
366                 tai_unpack(svstatus, &tstatus);
367                 if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript())
368                         return 0;
369                 break;
370         case 'o':
371                 tai_unpack(svstatus, &tstatus);
372                 if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd'))
373                         return 0;
374         }
375         printf(OK);
376         svstatus_print(*service);
377         puts(""); /* will also flush the output */
378         return 1;
379 }
380
381 static int control(const char *a)
382 {
383         int fd, r;
384
385         if (svstatus_get() <= 0) return -1;
386         if (svstatus[17] == *a) 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 long 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 = xatoul(x);
427
428         opt = getopt32(argc, argv, "w:v", &x);
429         if (opt & 1) waitsec = xatoul(x); // -w
430         if (opt & 2) verbose = 1; // -v
431         argc -= optind;
432         argv += optind;
433         action = *argv++;
434         if (!action || !*argv) usage();
435         service = argv;
436         services = argc - 1;
437
438         taia_now(&tnow);
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_diff(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_diff(action, "shutdown")) {
481                         acts = "x";
482                         break;
483                 }
484                 if (!str_diff(action, "start")) {
485                         acts = "u";
486                         break;
487                 }
488                 if (!str_diff(action, "stop")) {
489                         acts = "d";
490                         break;
491                 }
492                 /* "status" */
493                 act = &status;
494                 cbk = NULL;
495                 break;
496         case 'r':
497                 if (!str_diff(action, "restart")) {
498                         acts = "tcu";
499                         break;
500                 }
501                 usage();
502         case 'f':
503                 if (!str_diff(action, "force-reload")) {
504                         acts = "tc";
505                         kll = 1;
506                         break;
507                 }
508                 if (!str_diff(action, "force-restart")) {
509                         acts = "tcu";
510                         kll = 1;
511                         break;
512                 }
513                 if (!str_diff(action, "force-shutdown")) {
514                         acts = "x";
515                         kll = 1;
516                         break;
517                 }
518                 if (!str_diff(action, "force-stop")) {
519                         acts = "d";
520                         kll = 1;
521                         break;
522                 }
523         default:
524                 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                 //struct taia tdiff;
549                 long diff;
550
551                 //taia_sub(&tdiff, &tnow, &tstart);
552                 diff = tnow.sec.x - tstart.sec.x;
553                 service = servicex;
554                 want_exit = 1;
555                 for (i = 0; i < services; ++i, ++service) {
556                         if (!*service)
557                                 continue;
558                         if ((**service != '/') && (**service != '.')) {
559                                 if (chdir(varservice) == -1)
560                                         goto chdir_failed;
561                         }
562                         if (chdir(*service) == -1) {
563  chdir_failed:
564                                 fail("cannot change to service directory");
565                                 goto nullify_service;
566                         }
567                         if (cbk(acts) != 0)
568                                 goto nullify_service;
569                         want_exit = 0;
570                         if (diff >= waitsec) {
571                                 printf(kll ? "kill: " : "timeout: ");
572                                 if (svstatus_get() > 0) {
573                                         svstatus_print(*service);
574                                         ++rc;
575                                 }
576                                 puts(""); /* will also flush the output */
577                                 if (kll)
578                                         control("k");
579  nullify_service:
580                                 *service = NULL;
581                         }
582                         if (fchdir(curdir) == -1)
583                                 fatal_cannot("change to original directory");
584                 }
585                 if (want_exit) break;
586                 usleep(420000);
587                 taia_now(&tnow);
588         }
589         return rc > 99 ? 99 : rc;
590 }