2 Copyright (c) 2001-2006, Gerrit Pape
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
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.
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.
28 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
31 //usage:#define runsvdir_trivial_usage
32 //usage: "[-P] [-s SCRIPT] DIR"
33 //usage:#define runsvdir_full_usage "\n\n"
34 //usage: "Start a runsv process for each subdirectory. If it exits, restart it.\n"
35 //usage: "\n -P Put each runsv in a new session"
36 //usage: "\n -s SCRIPT Run SCRIPT <signo> after signal is processed"
40 #include "runit_lib.h"
42 #define MAXSERVICES 1000
44 /* Should be not needed - all dirs are on same FS, right? */
45 #define CHECK_DEVNO_TOO 0
60 #if ENABLE_FEATURE_RUNSVDIR_LOG
63 struct fd_pair logpipe;
68 #define G (*(struct globals*)&bb_common_bufsiz1)
70 #define svdir (G.svdir )
71 #define svnum (G.svnum )
72 #define rplog (G.rplog )
73 #define rploglen (G.rploglen )
74 #define logpipe (G.logpipe )
76 #define stamplog (G.stamplog )
77 #define INIT_G() do { } while (0)
79 static void fatal2_cannot(const char *m1, const char *m2)
81 bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
84 static void warn3x(const char *m1, const char *m2, const char *m3)
86 bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
88 static void warn2_cannot(const char *m1, const char *m2)
90 warn3x("can't ", m1, m2);
92 #if ENABLE_FEATURE_RUNSVDIR_LOG
93 static void warnx(const char *m1)
99 /* inlining + vfork -> bigger code */
100 static NOINLINE pid_t runsv(const char *name)
104 /* If we got signaled, stop spawning children at once! */
110 warn2_cannot("vfork", "");
115 if (option_mask32 & 1) /* -P option? */
118 * "Signals set to be caught by the calling process image
119 * shall be set to the default action in the new process image."
120 * Therefore, we do not need this: */
127 execlp("runsv", "runsv", name, (char *) NULL);
128 fatal2_cannot("start runsv ", name);
133 /* gcc 4.3.0 does better with NOINLINE */
134 static NOINLINE int do_rescan(void)
144 warn2_cannot("open directory ", svdir);
145 return 1; /* need to rescan again soon */
147 for (i = 0; i < svnum; i++)
155 if (d->d_name[0] == '.')
157 if (stat(d->d_name, &s) == -1) {
158 warn2_cannot("stat ", d->d_name);
161 if (!S_ISDIR(s.st_mode))
163 /* Do we have this service listed already? */
164 for (i = 0; i < svnum; i++) {
165 if ((sv[i].ino == s.st_ino)
167 && (sv[i].dev == s.st_dev)
170 if (sv[i].pid == 0) /* restart if it has died */
172 sv[i].isgone = 0; /* "we still see you" */
176 { /* Not found, make new service */
177 struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
179 warn2_cannot("start runsv ", d->d_name);
186 sv[i].dev = s.st_dev;
188 sv[i].ino = s.st_ino;
190 sv[i].pid = runsv(d->d_name);
197 if (i) { /* readdir failed */
198 warn2_cannot("read directory ", svdir);
199 return 1; /* need to rescan again soon */
202 /* Send SIGTERM to runsv whose directories
203 * were no longer found (-> must have been removed) */
204 for (i = 0; i < svnum; i++) {
208 kill(sv[i].pid, SIGTERM);
211 i--; /* so that we don't skip new sv[i] (bug was here!) */
216 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
217 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
220 dev_t last_dev = last_dev; /* for gcc */
221 ino_t last_ino = last_ino; /* for gcc */
222 time_t last_mtime = 0;
235 opt_complementary = "-1";
236 opt_s_argv[0] = NULL;
237 opt_s_argv[2] = NULL;
238 getopt32(argv, "Ps:", &opt_s_argv[0]);
244 /* For busybox's init, SIGTERM == reboot,
246 * SIGUSR2 == poweroff
247 * so we need to intercept SIGUSRn too.
248 * Note that we do not implement actual reboot
249 * (killall(TERM) + umount, etc), we just pause
250 * respawing and avoid exiting (-> making kernel oops).
251 * The user is responsible for the rest. */
252 | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
256 #if ENABLE_FEATURE_RUNSVDIR_LOG
260 rploglen = strlen(rplog);
262 warnx("log must have at least seven characters");
263 } else if (piped_pair(logpipe)) {
264 warnx("can't create pipe for log");
266 close_on_exec_on(logpipe.rd);
267 close_on_exec_on(logpipe.wr);
268 ndelay_on(logpipe.rd);
269 ndelay_on(logpipe.wr);
270 if (dup2(logpipe.wr, 2) == -1) {
271 warnx("can't set filedescriptor for log");
273 pfd[0].fd = logpipe.rd;
274 pfd[0].events = POLLIN;
275 stamplog = monotonic_sec();
280 warnx("log service disabled");
284 curdir = open(".", O_RDONLY|O_NDELAY);
286 fatal2_cannot("open current directory", "");
287 close_on_exec_on(curdir);
289 stampcheck = monotonic_sec();
292 /* collect children */
294 pid = wait_any_nohang(&wstat);
297 for (i = 0; i < svnum; i++) {
298 if (pid == sv[i].pid) {
306 now = monotonic_sec();
307 if ((int)(now - stampcheck) >= 0) {
308 /* wait at least a second */
309 stampcheck = now + 1;
311 if (stat(svdir, &s) != -1) {
312 if (need_rescan || s.st_mtime != last_mtime
313 || s.st_ino != last_ino || s.st_dev != last_dev
316 if (chdir(svdir) != -1) {
317 last_mtime = s.st_mtime;
320 /* if the svdir changed this very second, wait until the
321 * next second, because we won't be able to detect more
322 * changes within this second */
323 while (time(NULL) == last_mtime)
325 need_rescan = do_rescan();
326 while (fchdir(curdir) == -1) {
327 warn2_cannot("change directory, pausing", "");
331 warn2_cannot("change directory to ", svdir);
335 warn2_cannot("stat ", svdir);
339 #if ENABLE_FEATURE_RUNSVDIR_LOG
341 if ((int)(now - stamplog) >= 0) {
342 write(logpipe.wr, ".", 1);
343 stamplog = now + 900;
348 deadline = (need_rescan ? 1 : 5);
350 #if ENABLE_FEATURE_RUNSVDIR_LOG
352 poll(pfd, 1, deadline*1000);
356 sig_unblock(SIGCHLD);
358 #if ENABLE_FEATURE_RUNSVDIR_LOG
359 if (pfd[0].revents & POLLIN) {
361 while (read(logpipe.rd, &ch, 1) > 0) {
364 for (i = 6; i < rploglen; i++)
365 rplog[i-1] = rplog[i];
366 rplog[rploglen-1] = ch;
373 /* -s SCRIPT: useful if we are init.
374 * In this case typically script never returns,
375 * it halts/powers off/reboots the system. */
377 /* Single parameter: signal# */
378 opt_s_argv[1] = utoa(bb_got_signal);
379 pid = spawn(opt_s_argv);
381 /* Remembering to wait for _any_ children,
383 while (wait(NULL) != pid)
388 if (bb_got_signal == SIGHUP) {
389 for (i = 0; i < svnum; i++)
391 kill(sv[i].pid, SIGTERM);
393 /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
394 /* Exit unless we are init */
396 return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
398 /* init continues to monitor services forever */