runsvdir: shrink (by Vladimir)
[oweals/busybox.git] / runit / runsvdir.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 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
30
31 #include <sys/poll.h>
32 #include <sys/file.h>
33 #include "libbb.h"
34 #include "runit_lib.h"
35
36 #define MAXSERVICES 1000
37
38 /* Should be not needed - all dirs are on same FS, right? */
39 #define CHECK_DEVNO_TOO 0
40
41 struct service {
42 #if CHECK_DEVNO_TOO
43         dev_t dev;
44 #endif
45         ino_t ino;
46         pid_t pid;
47         smallint isgone;
48 };
49
50 struct globals {
51         struct service *sv;
52         char *svdir;
53         int svnum;
54 #if ENABLE_FEATURE_RUNSVDIR_LOG
55         char *rplog;
56         int rploglen;
57         struct fd_pair logpipe;
58         struct pollfd pfd[1];
59         unsigned stamplog;
60 #endif
61         smallint need_rescan; /* = 1; */
62         smallint set_pgrp;
63 };
64 #define G (*(struct globals*)&bb_common_bufsiz1)
65 #define sv          (G.sv          )
66 #define svdir       (G.svdir       )
67 #define svnum       (G.svnum       )
68 #define rplog       (G.rplog       )
69 #define rploglen    (G.rploglen    )
70 #define logpipe     (G.logpipe     )
71 #define pfd         (G.pfd         )
72 #define stamplog    (G.stamplog    )
73 #define need_rescan (G.need_rescan )
74 #define set_pgrp    (G.set_pgrp    )
75 #define INIT_G() do { \
76         need_rescan = 1; \
77 } while (0)
78
79 static void fatal2_cannot(const char *m1, const char *m2)
80 {
81         bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2);
82         /* was exiting 100 */
83 }
84 static void warn3x(const char *m1, const char *m2, const char *m3)
85 {
86         bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
87 }
88 static void warn2_cannot(const char *m1, const char *m2)
89 {
90         warn3x("cannot ", m1, m2);
91 }
92 #if ENABLE_FEATURE_RUNSVDIR_LOG
93 static void warnx(const char *m1)
94 {
95         warn3x(m1, "", "");
96 }
97 #endif
98
99 static void runsv(int no, const char *name)
100 {
101         pid_t pid = vfork();
102
103         if (pid == -1) {
104                 warn2_cannot("vfork", "");
105                 return;
106         }
107         if (pid == 0) {
108                 /* child */
109                 if (set_pgrp)
110                         setsid();
111 /* man execv:
112  * Signals set to be caught by the calling process image
113  * shall be set to the default action in the new process image.
114  * Therefore, we do not need this: */
115 #if 0
116                 bb_signals(0
117                         | (1 << SIGHUP)
118                         | (1 << SIGTERM)
119                         , SIG_DFL);
120 #endif
121                 execlp("runsv", "runsv", name, NULL);
122                 fatal2_cannot("start runsv ", name);
123         }
124         sv[no].pid = pid;
125 }
126
127 static void do_rescan(void)
128 {
129         DIR *dir;
130         direntry *d;
131         int i;
132         struct stat s;
133
134         dir = opendir(".");
135         if (!dir) {
136                 warn2_cannot("open directory ", svdir);
137                 return;
138         }
139         for (i = 0; i < svnum; i++)
140                 sv[i].isgone = 1;
141
142         while (1) {
143                 errno = 0;
144                 d = readdir(dir);
145                 if (!d)
146                         break;
147                 if (d->d_name[0] == '.')
148                         continue;
149                 if (stat(d->d_name, &s) == -1) {
150                         warn2_cannot("stat ", d->d_name);
151                         continue;
152                 }
153                 if (!S_ISDIR(s.st_mode))
154                         continue;
155                 for (i = 0; i < svnum; i++) {
156                         if ((sv[i].ino == s.st_ino)
157 #if CHECK_DEVNO_TOO
158                          && (sv[i].dev == s.st_dev)
159 #endif
160                         ) {
161                                 sv[i].isgone = 0;
162                                 if (!sv[i].pid)
163                                         runsv(i, d->d_name);
164                                 break;
165                         }
166                 }
167                 if (i == svnum) {
168                         /* new service */
169                         struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
170                         if (!svnew) {
171                                 warn2_cannot("start runsv ", d->d_name);
172                                 continue;
173                         }
174                         sv = svnew;
175                         svnum++;
176                         memset(&sv[i], 0, sizeof(sv[i]));
177                         sv[i].ino = s.st_ino;
178 #if CHECK_DEVNO_TOO
179                         sv[i].dev = s.st_dev;
180 #endif
181                         /*sv[i].pid = 0;*/
182                         /*sv[i].isgone = 0;*/
183                         runsv(i, d->d_name);
184                         need_rescan = 1;
185                 }
186         }
187         i = errno;
188         closedir(dir);
189         if (i) {
190                 warn2_cannot("read directory ", svdir);
191                 need_rescan = 1;
192                 return;
193         }
194
195         /* Send SIGTERM to runsv whose directories were not found (removed) */
196         for (i = 0; i < svnum; i++) {
197                 if (!sv[i].isgone)
198                         continue;
199                 if (sv[i].pid)
200                         kill(sv[i].pid, SIGTERM);
201                 svnum--;
202                 sv[i] = sv[svnum];
203                 i--; /* so that we don't skip new sv[i] (bug was here!) */
204                 need_rescan = 1;
205         }
206 }
207
208 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
209 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
210 {
211         struct stat s;
212         dev_t last_dev = last_dev; /* for gcc */
213         ino_t last_ino = last_ino; /* for gcc */
214         time_t last_mtime = 0;
215         int wstat;
216         int curdir;
217         int pid;
218         unsigned deadline;
219         unsigned now;
220         unsigned stampcheck;
221         int i;
222
223         INIT_G();
224
225         opt_complementary = "-1";
226         set_pgrp = getopt32(argv, "P");
227         argv += optind;
228
229         bb_signals(0
230                 | (1 << SIGTERM)
231                 | (1 << SIGHUP)
232                 /* For busybox's init, SIGTERM == reboot,
233                  * SIGUSR1 == halt
234                  * SIGUSR2 == poweroff
235                  * so we need to intercept SIGUSRn too.
236                  * Note that we do not implement actual reboot
237                  * (killall(TERM) + umount, etc), we just pause
238                  * respawing and avoid exiting (-> making kernel oops).
239                  * The user is responsible for the rest. */
240                 | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
241                 , record_signo);
242         svdir = *argv++;
243
244 #if ENABLE_FEATURE_RUNSVDIR_LOG
245         /* setup log */
246         if (*argv) {
247                 rplog = *argv;
248                 rploglen = strlen(rplog);
249                 if (rploglen < 7) {
250                         warnx("log must have at least seven characters");
251                 } else if (piped_pair(logpipe)) {
252                         warnx("cannot create pipe for log");
253                 } else {
254                         close_on_exec_on(logpipe.rd);
255                         close_on_exec_on(logpipe.wr);
256                         ndelay_on(logpipe.rd);
257                         ndelay_on(logpipe.wr);
258                         if (dup2(logpipe.wr, 2) == -1) {
259                                 warnx("cannot set filedescriptor for log");
260                         } else {
261                                 pfd[0].fd = logpipe.rd;
262                                 pfd[0].events = POLLIN;
263                                 stamplog = monotonic_sec();
264                                 goto run;
265                         }
266                 }
267                 rplog = NULL;
268                 warnx("log service disabled");
269         }
270  run:
271 #endif
272         curdir = open_read(".");
273         if (curdir == -1)
274                 fatal2_cannot("open current directory", "");
275         close_on_exec_on(curdir);
276
277         stampcheck = monotonic_sec();
278
279         for (;;) {
280                 /* collect children */
281                 for (;;) {
282                         pid = wait_any_nohang(&wstat);
283                         if (pid <= 0)
284                                 break;
285                         for (i = 0; i < svnum; i++) {
286                                 if (pid == sv[i].pid) {
287                                         /* runsv has gone */
288                                         sv[i].pid = 0;
289                                         need_rescan = 1;
290                                         break;
291                                 }
292                         }
293                 }
294
295                 now = monotonic_sec();
296                 if ((int)(now - stampcheck) >= 0) {
297                         /* wait at least a second */
298                         stampcheck = now + 1;
299
300                         if (stat(svdir, &s) != -1) {
301                                 if (need_rescan || s.st_mtime != last_mtime
302                                  || s.st_ino != last_ino || s.st_dev != last_dev
303                                 ) {
304                                         /* svdir modified */
305                                         if (chdir(svdir) != -1) {
306                                                 last_mtime = s.st_mtime;
307                                                 last_dev = s.st_dev;
308                                                 last_ino = s.st_ino;
309                                                 need_rescan = 0;
310                                                 //if (now <= mtime)
311                                                 //      sleep(1);
312                                                 do_rescan();
313                                                 while (fchdir(curdir) == -1) {
314                                                         warn2_cannot("change directory, pausing", "");
315                                                         sleep(5);
316                                                 }
317                                         } else
318                                                 warn2_cannot("change directory to ", svdir);
319                                 }
320                         } else
321                                 warn2_cannot("stat ", svdir);
322                 }
323
324 #if ENABLE_FEATURE_RUNSVDIR_LOG
325                 if (rplog) {
326                         if ((int)(now - stamplog) >= 0) {
327                                 write(logpipe.wr, ".", 1);
328                                 stamplog = now + 900;
329                         }
330                 }
331                 pfd[0].revents = 0;
332 #endif
333                 deadline = (need_rescan ? 1 : 5);
334  do_sleep:
335                 sig_block(SIGCHLD);
336 #if ENABLE_FEATURE_RUNSVDIR_LOG
337                 if (rplog)
338                         poll(pfd, 1, deadline*1000);
339                 else
340 #endif
341                         sleep(deadline);
342                 sig_unblock(SIGCHLD);
343
344 #if ENABLE_FEATURE_RUNSVDIR_LOG
345                 if (pfd[0].revents & POLLIN) {
346                         char ch;
347                         while (read(logpipe.rd, &ch, 1) > 0) {
348                                 if (ch < ' ')
349                                         ch = ' ';
350                                 for (i = 6; i < rploglen; i++)
351                                         rplog[i-1] = rplog[i];
352                                 rplog[rploglen-1] = ch;
353                         }
354                 }
355 #endif
356                 switch (bb_got_signal) {
357                 case SIGHUP:
358                         for (i = 0; i < svnum; i++)
359                                 if (sv[i].pid)
360                                         kill(sv[i].pid, SIGTERM);
361                         /* fall through */
362                 case SIGTERM:
363                         /* exit, unless we are init */
364                         if (getpid() != 1)
365                                 goto ret;
366                 default:
367                         /* so we are init. do not exit,
368                          * and pause respawning - we may be rebooting... */
369                         bb_got_signal = 0;
370                         deadline = 60;
371                         goto do_sleep;
372                 }
373         }
374  ret:
375         return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
376 }