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