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"
41 #include "runit_lib.h"
43 #define MAXSERVICES 1000
45 /* Should be not needed - all dirs are on same FS, right? */
46 #define CHECK_DEVNO_TOO 0
61 #if ENABLE_FEATURE_RUNSVDIR_LOG
64 struct fd_pair logpipe;
69 #define G (*(struct globals*)&bb_common_bufsiz1)
71 #define svdir (G.svdir )
72 #define svnum (G.svnum )
73 #define rplog (G.rplog )
74 #define rploglen (G.rploglen )
75 #define logpipe (G.logpipe )
77 #define stamplog (G.stamplog )
78 #define INIT_G() do { } while (0)
80 static void fatal2_cannot(const char *m1, const char *m2)
82 bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
85 static void warn3x(const char *m1, const char *m2, const char *m3)
87 bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
89 static void warn2_cannot(const char *m1, const char *m2)
91 warn3x("can't ", m1, m2);
93 #if ENABLE_FEATURE_RUNSVDIR_LOG
94 static void warnx(const char *m1)
100 /* inlining + vfork -> bigger code */
101 static NOINLINE pid_t runsv(const char *name)
105 /* If we got signaled, stop spawning children at once! */
111 warn2_cannot("vfork", "");
116 if (option_mask32 & 1) /* -P option? */
119 * "Signals set to be caught by the calling process image
120 * shall be set to the default action in the new process image."
121 * Therefore, we do not need this: */
128 execlp("runsv", "runsv", name, (char *) NULL);
129 fatal2_cannot("start runsv ", name);
134 /* gcc 4.3.0 does better with NOINLINE */
135 static NOINLINE int do_rescan(void)
145 warn2_cannot("open directory ", svdir);
146 return 1; /* need to rescan again soon */
148 for (i = 0; i < svnum; i++)
156 if (d->d_name[0] == '.')
158 if (stat(d->d_name, &s) == -1) {
159 warn2_cannot("stat ", d->d_name);
162 if (!S_ISDIR(s.st_mode))
164 /* Do we have this service listed already? */
165 for (i = 0; i < svnum; i++) {
166 if ((sv[i].ino == s.st_ino)
168 && (sv[i].dev == s.st_dev)
171 if (sv[i].pid == 0) /* restart if it has died */
173 sv[i].isgone = 0; /* "we still see you" */
177 { /* Not found, make new service */
178 struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
180 warn2_cannot("start runsv ", d->d_name);
187 sv[i].dev = s.st_dev;
189 sv[i].ino = s.st_ino;
191 sv[i].pid = runsv(d->d_name);
198 if (i) { /* readdir failed */
199 warn2_cannot("read directory ", svdir);
200 return 1; /* need to rescan again soon */
203 /* Send SIGTERM to runsv whose directories
204 * were no longer found (-> must have been removed) */
205 for (i = 0; i < svnum; i++) {
209 kill(sv[i].pid, SIGTERM);
212 i--; /* so that we don't skip new sv[i] (bug was here!) */
217 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
218 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
221 dev_t last_dev = last_dev; /* for gcc */
222 ino_t last_ino = last_ino; /* for gcc */
223 time_t last_mtime = 0;
236 opt_complementary = "-1";
237 opt_s_argv[0] = NULL;
238 opt_s_argv[2] = NULL;
239 getopt32(argv, "Ps:", &opt_s_argv[0]);
245 /* For busybox's init, SIGTERM == reboot,
247 * SIGUSR2 == poweroff
248 * so we need to intercept SIGUSRn too.
249 * Note that we do not implement actual reboot
250 * (killall(TERM) + umount, etc), we just pause
251 * respawing and avoid exiting (-> making kernel oops).
252 * The user is responsible for the rest. */
253 | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
257 #if ENABLE_FEATURE_RUNSVDIR_LOG
261 rploglen = strlen(rplog);
263 warnx("log must have at least seven characters");
264 } else if (piped_pair(logpipe)) {
265 warnx("can't create pipe for log");
267 close_on_exec_on(logpipe.rd);
268 close_on_exec_on(logpipe.wr);
269 ndelay_on(logpipe.rd);
270 ndelay_on(logpipe.wr);
271 if (dup2(logpipe.wr, 2) == -1) {
272 warnx("can't set filedescriptor for log");
274 pfd[0].fd = logpipe.rd;
275 pfd[0].events = POLLIN;
276 stamplog = monotonic_sec();
281 warnx("log service disabled");
285 curdir = open(".", O_RDONLY|O_NDELAY);
287 fatal2_cannot("open current directory", "");
288 close_on_exec_on(curdir);
290 stampcheck = monotonic_sec();
293 /* collect children */
295 pid = wait_any_nohang(&wstat);
298 for (i = 0; i < svnum; i++) {
299 if (pid == sv[i].pid) {
307 now = monotonic_sec();
308 if ((int)(now - stampcheck) >= 0) {
309 /* wait at least a second */
310 stampcheck = now + 1;
312 if (stat(svdir, &s) != -1) {
313 if (need_rescan || s.st_mtime != last_mtime
314 || s.st_ino != last_ino || s.st_dev != last_dev
317 if (chdir(svdir) != -1) {
318 last_mtime = s.st_mtime;
321 /* if the svdir changed this very second, wait until the
322 * next second, because we won't be able to detect more
323 * changes within this second */
324 while (time(NULL) == last_mtime)
326 need_rescan = do_rescan();
327 while (fchdir(curdir) == -1) {
328 warn2_cannot("change directory, pausing", "");
332 warn2_cannot("change directory to ", svdir);
336 warn2_cannot("stat ", svdir);
340 #if ENABLE_FEATURE_RUNSVDIR_LOG
342 if ((int)(now - stamplog) >= 0) {
343 write(logpipe.wr, ".", 1);
344 stamplog = now + 900;
349 deadline = (need_rescan ? 1 : 5);
351 #if ENABLE_FEATURE_RUNSVDIR_LOG
353 poll(pfd, 1, deadline*1000);
357 sig_unblock(SIGCHLD);
359 #if ENABLE_FEATURE_RUNSVDIR_LOG
360 if (pfd[0].revents & POLLIN) {
362 while (read(logpipe.rd, &ch, 1) > 0) {
365 for (i = 6; i < rploglen; i++)
366 rplog[i-1] = rplog[i];
367 rplog[rploglen-1] = ch;
374 /* -s SCRIPT: useful if we are init.
375 * In this case typically script never returns,
376 * it halts/powers off/reboots the system. */
378 /* Single parameter: signal# */
379 opt_s_argv[1] = utoa(bb_got_signal);
380 pid = spawn(opt_s_argv);
382 /* Remembering to wait for _any_ children,
384 while (wait(NULL) != pid)
389 if (bb_got_signal == SIGHUP) {
390 for (i = 0; i < svnum; i++)
392 kill(sv[i].pid, SIGTERM);
394 /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
395 /* Exit unless we are init */
397 return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
399 /* init continues to monitor services forever */