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