summaryrefslogtreecommitdiff
path: root/runit/runsvdir.c (plain)
blob: af7e75ba761fb813a386485e6522d7b6b53e7c41
1/*
2Copyright (c) 2001-2006, Gerrit Pape
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, 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
16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25ADVISED 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//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"
37
38#include <sys/file.h>
39#include "libbb.h"
40#include "runit_lib.h"
41
42#define MAXSERVICES 1000
43
44/* Should be not needed - all dirs are on same FS, right? */
45#define CHECK_DEVNO_TOO 0
46
47struct service {
48#if CHECK_DEVNO_TOO
49 dev_t dev;
50#endif
51 ino_t ino;
52 pid_t pid;
53 smallint isgone;
54};
55
56struct globals {
57 struct service *sv;
58 char *svdir;
59 int svnum;
60#if ENABLE_FEATURE_RUNSVDIR_LOG
61 char *rplog;
62 int rploglen;
63 struct fd_pair logpipe;
64 struct pollfd pfd[1];
65 unsigned stamplog;
66#endif
67} FIX_ALIASING;
68#define G (*(struct globals*)&bb_common_bufsiz1)
69#define sv (G.sv )
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 )
75#define pfd (G.pfd )
76#define stamplog (G.stamplog )
77#define INIT_G() do { } while (0)
78
79static void fatal2_cannot(const char *m1, const char *m2)
80{
81 bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
82 /* was exiting 100 */
83}
84static 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}
88static void warn2_cannot(const char *m1, const char *m2)
89{
90 warn3x("can't ", m1, m2);
91}
92#if ENABLE_FEATURE_RUNSVDIR_LOG
93static void warnx(const char *m1)
94{
95 warn3x(m1, "", "");
96}
97#endif
98
99/* inlining + vfork -> bigger code */
100static NOINLINE pid_t runsv(const char *name)
101{
102 pid_t pid;
103
104 /* If we got signaled, stop spawning children at once! */
105 if (bb_got_signal)
106 return 0;
107
108 pid = vfork();
109 if (pid == -1) {
110 warn2_cannot("vfork", "");
111 return 0;
112 }
113 if (pid == 0) {
114 /* child */
115 if (option_mask32 & 1) /* -P option? */
116 setsid();
117/* man execv:
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: */
121#if 0
122 bb_signals(0
123 | (1 << SIGHUP)
124 | (1 << SIGTERM)
125 , SIG_DFL);
126#endif
127 execlp("runsv", "runsv", name, (char *) NULL);
128 fatal2_cannot("start runsv ", name);
129 }
130 return pid;
131}
132
133/* gcc 4.3.0 does better with NOINLINE */
134static NOINLINE int do_rescan(void)
135{
136 DIR *dir;
137 struct dirent *d;
138 int i;
139 struct stat s;
140 int need_rescan = 0;
141
142 dir = opendir(".");
143 if (!dir) {
144 warn2_cannot("open directory ", svdir);
145 return 1; /* need to rescan again soon */
146 }
147 for (i = 0; i < svnum; i++)
148 sv[i].isgone = 1;
149
150 while (1) {
151 errno = 0;
152 d = readdir(dir);
153 if (!d)
154 break;
155 if (d->d_name[0] == '.')
156 continue;
157 if (stat(d->d_name, &s) == -1) {
158 warn2_cannot("stat ", d->d_name);
159 continue;
160 }
161 if (!S_ISDIR(s.st_mode))
162 continue;
163 /* Do we have this service listed already? */
164 for (i = 0; i < svnum; i++) {
165 if ((sv[i].ino == s.st_ino)
166#if CHECK_DEVNO_TOO
167 && (sv[i].dev == s.st_dev)
168#endif
169 ) {
170 if (sv[i].pid == 0) /* restart if it has died */
171 goto run_ith_sv;
172 sv[i].isgone = 0; /* "we still see you" */
173 goto next_dentry;
174 }
175 }
176 { /* Not found, make new service */
177 struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
178 if (!svnew) {
179 warn2_cannot("start runsv ", d->d_name);
180 need_rescan = 1;
181 continue;
182 }
183 sv = svnew;
184 svnum++;
185#if CHECK_DEVNO_TOO
186 sv[i].dev = s.st_dev;
187#endif
188 sv[i].ino = s.st_ino;
189 run_ith_sv:
190 sv[i].pid = runsv(d->d_name);
191 sv[i].isgone = 0;
192 }
193 next_dentry: ;
194 }
195 i = errno;
196 closedir(dir);
197 if (i) { /* readdir failed */
198 warn2_cannot("read directory ", svdir);
199 return 1; /* need to rescan again soon */
200 }
201
202 /* Send SIGTERM to runsv whose directories
203 * were no longer found (-> must have been removed) */
204 for (i = 0; i < svnum; i++) {
205 if (!sv[i].isgone)
206 continue;
207 if (sv[i].pid)
208 kill(sv[i].pid, SIGTERM);
209 svnum--;
210 sv[i] = sv[svnum];
211 i--; /* so that we don't skip new sv[i] (bug was here!) */
212 }
213 return need_rescan;
214}
215
216int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
217int runsvdir_main(int argc UNUSED_PARAM, char **argv)
218{
219 struct stat s;
220 dev_t last_dev = last_dev; /* for gcc */
221 ino_t last_ino = last_ino; /* for gcc */
222 time_t last_mtime = 0;
223 int wstat;
224 int curdir;
225 pid_t pid;
226 unsigned deadline;
227 unsigned now;
228 unsigned stampcheck;
229 int i;
230 int need_rescan = 1;
231 char *opt_s_argv[3];
232
233 INIT_G();
234
235 opt_complementary = "-1";
236 opt_s_argv[0] = NULL;
237 opt_s_argv[2] = NULL;
238 getopt32(argv, "Ps:", &opt_s_argv[0]);
239 argv += optind;
240
241 bb_signals(0
242 | (1 << SIGTERM)
243 | (1 << SIGHUP)
244 /* For busybox's init, SIGTERM == reboot,
245 * SIGUSR1 == halt
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)
253 , record_signo);
254 svdir = *argv++;
255
256#if ENABLE_FEATURE_RUNSVDIR_LOG
257 /* setup log */
258 if (*argv) {
259 rplog = *argv;
260 rploglen = strlen(rplog);
261 if (rploglen < 7) {
262 warnx("log must have at least seven characters");
263 } else if (piped_pair(logpipe)) {
264 warnx("can't create pipe for log");
265 } else {
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");
272 } else {
273 pfd[0].fd = logpipe.rd;
274 pfd[0].events = POLLIN;
275 stamplog = monotonic_sec();
276 goto run;
277 }
278 }
279 rplog = NULL;
280 warnx("log service disabled");
281 }
282 run:
283#endif
284 curdir = open(".", O_RDONLY|O_NDELAY);
285 if (curdir == -1)
286 fatal2_cannot("open current directory", "");
287 close_on_exec_on(curdir);
288
289 stampcheck = monotonic_sec();
290
291 for (;;) {
292 /* collect children */
293 for (;;) {
294 pid = wait_any_nohang(&wstat);
295 if (pid <= 0)
296 break;
297 for (i = 0; i < svnum; i++) {
298 if (pid == sv[i].pid) {
299 /* runsv has died */
300 sv[i].pid = 0;
301 need_rescan = 1;
302 }
303 }
304 }
305
306 now = monotonic_sec();
307 if ((int)(now - stampcheck) >= 0) {
308 /* wait at least a second */
309 stampcheck = now + 1;
310
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
314 ) {
315 /* svdir modified */
316 if (chdir(svdir) != -1) {
317 last_mtime = s.st_mtime;
318 last_dev = s.st_dev;
319 last_ino = s.st_ino;
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)
324 usleep(100000);
325 need_rescan = do_rescan();
326 while (fchdir(curdir) == -1) {
327 warn2_cannot("change directory, pausing", "");
328 sleep(5);
329 }
330 } else {
331 warn2_cannot("change directory to ", svdir);
332 }
333 }
334 } else {
335 warn2_cannot("stat ", svdir);
336 }
337 }
338
339#if ENABLE_FEATURE_RUNSVDIR_LOG
340 if (rplog) {
341 if ((int)(now - stamplog) >= 0) {
342 write(logpipe.wr, ".", 1);
343 stamplog = now + 900;
344 }
345 }
346 pfd[0].revents = 0;
347#endif
348 deadline = (need_rescan ? 1 : 5);
349 sig_block(SIGCHLD);
350#if ENABLE_FEATURE_RUNSVDIR_LOG
351 if (rplog)
352 poll(pfd, 1, deadline*1000);
353 else
354#endif
355 sleep(deadline);
356 sig_unblock(SIGCHLD);
357
358#if ENABLE_FEATURE_RUNSVDIR_LOG
359 if (pfd[0].revents & POLLIN) {
360 char ch;
361 while (read(logpipe.rd, &ch, 1) > 0) {
362 if (ch < ' ')
363 ch = ' ';
364 for (i = 6; i < rploglen; i++)
365 rplog[i-1] = rplog[i];
366 rplog[rploglen-1] = ch;
367 }
368 }
369#endif
370 if (!bb_got_signal)
371 continue;
372
373 /* -s SCRIPT: useful if we are init.
374 * In this case typically script never returns,
375 * it halts/powers off/reboots the system. */
376 if (opt_s_argv[0]) {
377 /* Single parameter: signal# */
378 opt_s_argv[1] = utoa(bb_got_signal);
379 pid = spawn(opt_s_argv);
380 if (pid > 0) {
381 /* Remembering to wait for _any_ children,
382 * not just pid */
383 while (wait(NULL) != pid)
384 continue;
385 }
386 }
387
388 if (bb_got_signal == SIGHUP) {
389 for (i = 0; i < svnum; i++)
390 if (sv[i].pid)
391 kill(sv[i].pid, SIGTERM);
392 }
393 /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
394 /* Exit unless we are init */
395 if (getpid() != 1)
396 return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
397
398 /* init continues to monitor services forever */
399 bb_got_signal = 0;
400 } /* for (;;) */
401}
402