suppress warnings about easch <applet>_main() having
[oweals/busybox.git] / runit / sv.c
1 /* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
2 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
3
4 #include <sys/poll.h>
5 #include <sys/file.h>
6 #include "busybox.h"
7 #include "runit_lib.h"
8
9 static const char *acts;
10 static char **service;
11 static unsigned rc;
12 static struct taia tstart, tnow;
13 static char svstatus[20];
14
15 #define usage() bb_show_usage()
16
17 static void fatal_cannot(const char *m1) ATTRIBUTE_NORETURN;
18 static void fatal_cannot(const char *m1)
19 {
20         bb_perror_msg("fatal: cannot %s", m1);
21         _exit(151);
22 }
23
24 static void out(const char *p, const char *m1)
25 {
26         printf("%s%s: %s", p, *service, m1);
27         if (errno) {
28                 printf(": %s", strerror(errno));
29         }
30         puts(""); /* will also flush the output */
31 }
32
33 #define WARN    "warning: "
34 #define OK      "ok: "
35
36 static void fail(const char *m1) {
37         ++rc;
38         out("fail: ", m1);
39 }
40 static void failx(const char *m1) {
41         errno = 0;
42         fail(m1);
43 }
44 static void warn_cannot(const char *m1) {
45         ++rc;
46         out("warning: cannot ", m1);
47 }
48 static void warnx_cannot(const char *m1) {
49         errno = 0;
50         warn_cannot(m1);
51 }
52 static void ok(const char *m1) {
53         errno = 0;
54         out(OK, m1);
55 }
56
57 static int svstatus_get(void)
58 {
59         int fd, r;
60
61         fd = open_write("supervise/ok");
62         if (fd == -1) {
63                 if (errno == ENODEV) {
64                         *acts == 'x' ? ok("runsv not running")
65                                      : failx("runsv not running");
66                         return 0;
67                 }
68                 warn_cannot("open supervise/ok");
69                 return -1;
70         }
71         close(fd);
72         fd = open_read("supervise/status");
73         if (fd == -1) {
74                 warn_cannot("open supervise/status");
75                 return -1;
76         }
77         r = read(fd, svstatus, 20);
78         close(fd);
79         switch (r) {
80         case 20: break;
81         case -1: warn_cannot("read supervise/status"); return -1;
82         default: warnx_cannot("read supervise/status: bad format"); return -1;
83         }
84         return 1;
85 }
86
87 static unsigned svstatus_print(const char *m)
88 {
89         long diff;
90         int pid;
91         int normallyup = 0;
92         struct stat s;
93         struct tai tstatus;
94
95         if (stat("down", &s) == -1) {
96                 if (errno != ENOENT) {
97                         bb_perror_msg(WARN"cannot stat %s/down", *service);
98                         return 0;
99                 }
100                 normallyup = 1;
101         }
102         pid = (unsigned char) svstatus[15];
103         pid <<= 8; pid += (unsigned char)svstatus[14];
104         pid <<= 8; pid += (unsigned char)svstatus[13];
105         pid <<= 8; pid += (unsigned char)svstatus[12];
106         tai_unpack(svstatus, &tstatus);
107         if (pid) {
108                 switch (svstatus[19]) {
109                 case 1: printf("run: "); break;
110                 case 2: printf("finish: "); break;
111                 }
112                 printf("%s: (pid %d) ", m, pid);
113         } else {
114                 printf("down: %s: ", m);
115         }
116         diff = tnow.sec.x - tstatus.x;
117         printf("%lds", (diff < 0 ? 0L : diff));
118         if (pid) {
119                 if (!normallyup) printf(", normally down");
120         } else {
121                 if (normallyup) printf(", normally up");
122         }
123         if (pid && svstatus[16]) printf(", paused");
124         if (!pid && (svstatus[17] == 'u')) printf(", want up");
125         if (pid && (svstatus[17] == 'd')) printf(", want down");
126         if (pid && svstatus[18]) printf(", got TERM");
127         return pid ? 1 : 2;
128 }
129
130 static int status(const char *unused)
131 {
132         int r;
133
134         r = svstatus_get();
135         switch (r) { case -1: case 0: return 0; }
136
137         r = svstatus_print(*service);
138         if (chdir("log") == -1) {
139                 if (errno != ENOENT) {
140                         printf("; log: "WARN"cannot change to log service directory: %s",
141                                         strerror(errno));
142                 }
143         } else if (svstatus_get()) {
144                 printf("; ");
145                 svstatus_print("log");
146         }
147         puts(""); /* will also flush the output */
148         return r;
149 }
150
151 static int checkscript(void)
152 {
153         char *prog[2];
154         struct stat s;
155         int pid, w;
156
157         if (stat("check", &s) == -1) {
158                 if (errno == ENOENT) return 1;
159                 bb_perror_msg(WARN"cannot stat %s/check", *service);
160                 return 0;
161         }
162         /* if (!(s.st_mode & S_IXUSR)) return 1; */
163         if ((pid = fork()) == -1) {
164                 bb_perror_msg(WARN"cannot fork for %s/check", *service);
165                 return 0;
166         }
167         if (!pid) {
168                 prog[0] = (char*)"./check";
169                 prog[1] = NULL;
170                 close(1);
171                 execve("check", prog, environ);
172                 bb_perror_msg(WARN"cannot run %s/check", *service);
173                 _exit(0);
174         }
175         while (wait_pid(&w, pid) == -1) {
176                 if (errno == EINTR) continue;
177                 bb_perror_msg(WARN"cannot wait for child %s/check", *service);
178                 return 0;
179         }
180         return !wait_exitcode(w);
181 }
182
183 static int check(const char *a)
184 {
185         int r;
186         unsigned pid;
187         struct tai tstatus;
188
189         r = svstatus_get();
190         if (r == -1)
191                 return -1;
192         if (r == 0) {
193                 if (*a == 'x')
194                         return 1;
195                 return -1;
196         }
197         pid = (unsigned char)svstatus[15];
198         pid <<= 8; pid += (unsigned char)svstatus[14];
199         pid <<= 8; pid += (unsigned char)svstatus[13];
200         pid <<= 8; pid += (unsigned char)svstatus[12];
201         switch (*a) {
202         case 'x':
203                 return 0;
204         case 'u':
205                 if (!pid || svstatus[19] != 1) return 0;
206                 if (!checkscript()) return 0;
207                 break;
208         case 'd':
209                 if (pid) return 0;
210                 break;
211         case 'c':
212                 if (pid && !checkscript()) return 0;
213                 break;
214         case 't':
215                 if (!pid && svstatus[17] == 'd') break;
216                 tai_unpack(svstatus, &tstatus);
217                 if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript())
218                         return 0;
219                 break;
220         case 'o':
221                 tai_unpack(svstatus, &tstatus);
222                 if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd'))
223                         return 0;
224         }
225         printf(OK);
226         svstatus_print(*service);
227         puts(""); /* will also flush the output */
228         return 1;
229 }
230
231 static int control(const char *a)
232 {
233         int fd, r;
234
235         if (svstatus_get() <= 0) return -1;
236         if (svstatus[17] == *a) return 0;
237         fd = open_write("supervise/control");
238         if (fd == -1) {
239                 if (errno != ENODEV)
240                         warn_cannot("open supervise/control");
241                 else
242                         *a == 'x' ? ok("runsv not running") : failx("runsv not running");
243                 return -1;
244         }
245         r = write(fd, a, strlen(a));
246         close(fd);
247         if (r != strlen(a)) {
248                 warn_cannot("write to supervise/control");
249                 return -1;
250         }
251         return 1;
252 }
253
254 int sv_main(int argc, char **argv);
255 int sv_main(int argc, char **argv)
256 {
257         unsigned opt;
258         unsigned i, want_exit;
259         char *x;
260         char *action;
261         const char *varservice = "/var/service/";
262         unsigned services;
263         char **servicex;
264         unsigned long waitsec = 7;
265         smallint kll = 0;
266         smallint verbose = 0;
267         int (*act)(const char*);
268         int (*cbk)(const char*);
269         int curdir;
270
271         xfunc_error_retval = 100;
272
273         x = getenv("SVDIR");
274         if (x) varservice = x;
275         x = getenv("SVWAIT");
276         if (x) waitsec = xatoul(x);
277
278         opt = getopt32(argc, argv, "w:v", &x);
279         if (opt & 1) waitsec = xatoul(x); // -w
280         if (opt & 2) verbose = 1; // -v
281         argc -= optind;
282         argv += optind;
283         action = *argv++;
284         if (!action || !*argv) usage();
285         service = argv;
286         services = argc - 1;
287
288         taia_now(&tnow);
289         tstart = tnow;
290         curdir = open_read(".");
291         if (curdir == -1)
292                 fatal_cannot("open current directory");
293
294         act = &control;
295         acts = "s";
296         cbk = &check;
297
298         switch (*action) {
299         case 'x':
300         case 'e':
301                 acts = "x";
302                 if (!verbose) cbk = NULL;
303                 break;
304         case 'X':
305         case 'E':
306                 acts = "x";
307                 kll = 1;
308                 break;
309         case 'D':
310                 acts = "d";
311                 kll = 1;
312                 break;
313         case 'T':
314                 acts = "tc";
315                 kll = 1;
316                 break;
317         case 'c':
318                 if (!str_diff(action, "check")) {
319                         act = NULL;
320                         acts = "c";
321                         break;
322                 }
323         case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
324         case 'a': case 'i': case 'k': case 'q': case '1': case '2':
325                 action[1] = '\0';
326                 acts = action;
327                 if (!verbose) cbk = NULL;
328                 break;
329         case 's':
330                 if (!str_diff(action, "shutdown")) {
331                         acts = "x";
332                         break;
333                 }
334                 if (!str_diff(action, "start")) {
335                         acts = "u";
336                         break;
337                 }
338                 if (!str_diff(action, "stop")) {
339                         acts = "d";
340                         break;
341                 }
342                 /* "status" */
343                 act = &status;
344                 cbk = NULL;
345                 break;
346         case 'r':
347                 if (!str_diff(action, "restart")) {
348                         acts = "tcu";
349                         break;
350                 }
351                 usage();
352         case 'f':
353                 if (!str_diff(action, "force-reload")) {
354                         acts = "tc";
355                         kll = 1;
356                         break;
357                 }
358                 if (!str_diff(action, "force-restart")) {
359                         acts = "tcu";
360                         kll = 1;
361                         break;
362                 }
363                 if (!str_diff(action, "force-shutdown")) {
364                         acts = "x";
365                         kll = 1;
366                         break;
367                 }
368                 if (!str_diff(action, "force-stop")) {
369                         acts = "d";
370                         kll = 1;
371                         break;
372                 }
373         default:
374                 usage();
375         }
376
377         servicex = service;
378         for (i = 0; i < services; ++i) {
379                 if ((**service != '/') && (**service != '.')) {
380                         if (chdir(varservice) == -1)
381                                 goto chdir_failed_0;
382                 }
383                 if (chdir(*service) == -1) {
384  chdir_failed_0:
385                         fail("cannot change to service directory");
386                         goto nullify_service_0;
387                 }
388                 if (act && (act(acts) == -1)) {
389  nullify_service_0:
390                         *service = NULL;
391                 }
392                 if (fchdir(curdir) == -1)
393                         fatal_cannot("change to original directory");
394                 service++;
395         }
396
397         if (cbk) while (1) {
398                 //struct taia tdiff;
399                 long diff;
400
401                 //taia_sub(&tdiff, &tnow, &tstart);
402                 diff = tnow.sec.x - tstart.sec.x;
403                 service = servicex;
404                 want_exit = 1;
405                 for (i = 0; i < services; ++i, ++service) {
406                         if (!*service)
407                                 continue;
408                         if ((**service != '/') && (**service != '.')) {
409                                 if (chdir(varservice) == -1)
410                                         goto chdir_failed;
411                         }
412                         if (chdir(*service) == -1) {
413  chdir_failed:
414                                 fail("cannot change to service directory");
415                                 goto nullify_service;
416                         }
417                         if (cbk(acts) != 0)
418                                 goto nullify_service;
419                         want_exit = 0;
420                         if (diff >= waitsec) {
421                                 printf(kll ? "kill: " : "timeout: ");
422                                 if (svstatus_get() > 0) {
423                                         svstatus_print(*service);
424                                         ++rc;
425                                 }
426                                 puts(""); /* will also flush the output */
427                                 if (kll)
428                                         control("k");
429  nullify_service:
430                                 *service = NULL;
431                         }
432                         if (fchdir(curdir) == -1)
433                                 fatal_cannot("change to original directory");
434                 }
435                 if (want_exit) break;
436                 usleep(420000);
437                 taia_now(&tnow);
438         }
439         return rc > 99 ? 99 : rc;
440 }