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#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
41struct 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
50struct 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} FIX_ALIASING;
62#define G (*(struct globals*)&bb_common_bufsiz1)
63#define sv          (G.sv          )
64#define svdir       (G.svdir       )
65#define svnum       (G.svnum       )
66#define rplog       (G.rplog       )
67#define rploglen    (G.rploglen    )
68#define logpipe     (G.logpipe     )
69#define pfd         (G.pfd         )
70#define stamplog    (G.stamplog    )
71#define INIT_G() do { \
72} while (0)
73
74static void fatal2_cannot(const char *m1, const char *m2)
75{
76	bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
77	/* was exiting 100 */
78}
79static void warn3x(const char *m1, const char *m2, const char *m3)
80{
81	bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
82}
83static void warn2_cannot(const char *m1, const char *m2)
84{
85	warn3x("can't ", m1, m2);
86}
87#if ENABLE_FEATURE_RUNSVDIR_LOG
88static void warnx(const char *m1)
89{
90	warn3x(m1, "", "");
91}
92#endif
93
94/* inlining + vfork -> bigger code */
95static NOINLINE pid_t runsv(const char *name)
96{
97	pid_t pid;
98
99	/* If we got signaled, stop spawning children at once! */
100	if (bb_got_signal)
101		return 0;
102
103	pid = vfork();
104	if (pid == -1) {
105		warn2_cannot("vfork", "");
106		return 0;
107	}
108	if (pid == 0) {
109		/* child */
110		if (option_mask32 & 1) /* -P option? */
111			setsid();
112/* man execv:
113 * "Signals set to be caught by the calling process image
114 *  shall be set to the default action in the new process image."
115 * Therefore, we do not need this: */
116#if 0
117		bb_signals(0
118			| (1 << SIGHUP)
119			| (1 << SIGTERM)
120			, SIG_DFL);
121#endif
122		execlp("runsv", "runsv", name, (char *) NULL);
123		fatal2_cannot("start runsv ", name);
124	}
125	return pid;
126}
127
128/* gcc 4.3.0 does better with NOINLINE */
129static NOINLINE int do_rescan(void)
130{
131	DIR *dir;
132	struct dirent *d;
133	int i;
134	struct stat s;
135	int need_rescan = 0;
136
137	dir = opendir(".");
138	if (!dir) {
139		warn2_cannot("open directory ", svdir);
140		return 1; /* need to rescan again soon */
141	}
142	for (i = 0; i < svnum; i++)
143		sv[i].isgone = 1;
144
145	while (1) {
146		errno = 0;
147		d = readdir(dir);
148		if (!d)
149			break;
150		if (d->d_name[0] == '.')
151			continue;
152		if (stat(d->d_name, &s) == -1) {
153			warn2_cannot("stat ", d->d_name);
154			continue;
155		}
156		if (!S_ISDIR(s.st_mode))
157			continue;
158		/* Do we have this service listed already? */
159		for (i = 0; i < svnum; i++) {
160			if ((sv[i].ino == s.st_ino)
161#if CHECK_DEVNO_TOO
162			 && (sv[i].dev == s.st_dev)
163#endif
164			) {
165				if (sv[i].pid == 0) /* restart if it has died */
166					goto run_ith_sv;
167				sv[i].isgone = 0; /* "we still see you" */
168				goto next_dentry;
169			}
170		}
171		{ /* Not found, make new service */
172			struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
173			if (!svnew) {
174				warn2_cannot("start runsv ", d->d_name);
175				need_rescan = 1;
176				continue;
177			}
178			sv = svnew;
179			svnum++;
180#if CHECK_DEVNO_TOO
181			sv[i].dev = s.st_dev;
182#endif
183			sv[i].ino = s.st_ino;
184 run_ith_sv:
185			sv[i].pid = runsv(d->d_name);
186			sv[i].isgone = 0;
187		}
188 next_dentry: ;
189	}
190	i = errno;
191	closedir(dir);
192	if (i) { /* readdir failed */
193		warn2_cannot("read directory ", svdir);
194		return 1; /* need to rescan again soon */
195	}
196
197	/* Send SIGTERM to runsv whose directories
198	 * were no longer found (-> must have been removed) */
199	for (i = 0; i < svnum; i++) {
200		if (!sv[i].isgone)
201			continue;
202		if (sv[i].pid)
203			kill(sv[i].pid, SIGTERM);
204		svnum--;
205		sv[i] = sv[svnum];
206		i--; /* so that we don't skip new sv[i] (bug was here!) */
207	}
208	return need_rescan;
209}
210
211int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
212int runsvdir_main(int argc UNUSED_PARAM, char **argv)
213{
214	struct stat s;
215	dev_t last_dev = last_dev; /* for gcc */
216	ino_t last_ino = last_ino; /* for gcc */
217	time_t last_mtime = 0;
218	int wstat;
219	int curdir;
220	pid_t pid;
221	unsigned deadline;
222	unsigned now;
223	unsigned stampcheck;
224	int i;
225	int need_rescan = 1;
226	char *opt_s_argv[3];
227
228	INIT_G();
229
230	opt_complementary = "-1";
231	opt_s_argv[0] = NULL;
232	opt_s_argv[2] = NULL;
233	getopt32(argv, "Ps:", &opt_s_argv[0]);
234	argv += optind;
235
236	bb_signals(0
237		| (1 << SIGTERM)
238		| (1 << SIGHUP)
239		/* For busybox's init, SIGTERM == reboot,
240		 * SIGUSR1 == halt
241		 * SIGUSR2 == poweroff
242		 * so we need to intercept SIGUSRn too.
243		 * Note that we do not implement actual reboot
244		 * (killall(TERM) + umount, etc), we just pause
245		 * respawing and avoid exiting (-> making kernel oops).
246		 * The user is responsible for the rest. */
247		| (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
248		, record_signo);
249	svdir = *argv++;
250
251#if ENABLE_FEATURE_RUNSVDIR_LOG
252	/* setup log */
253	if (*argv) {
254		rplog = *argv;
255		rploglen = strlen(rplog);
256		if (rploglen < 7) {
257			warnx("log must have at least seven characters");
258		} else if (piped_pair(logpipe)) {
259			warnx("can't create pipe for log");
260		} else {
261			close_on_exec_on(logpipe.rd);
262			close_on_exec_on(logpipe.wr);
263			ndelay_on(logpipe.rd);
264			ndelay_on(logpipe.wr);
265			if (dup2(logpipe.wr, 2) == -1) {
266				warnx("can't set filedescriptor for log");
267			} else {
268				pfd[0].fd = logpipe.rd;
269				pfd[0].events = POLLIN;
270				stamplog = monotonic_sec();
271				goto run;
272			}
273		}
274		rplog = NULL;
275		warnx("log service disabled");
276	}
277 run:
278#endif
279	curdir = open_read(".");
280	if (curdir == -1)
281		fatal2_cannot("open current directory", "");
282	close_on_exec_on(curdir);
283
284	stampcheck = monotonic_sec();
285
286	for (;;) {
287		/* collect children */
288		for (;;) {
289			pid = wait_any_nohang(&wstat);
290			if (pid <= 0)
291				break;
292			for (i = 0; i < svnum; i++) {
293				if (pid == sv[i].pid) {
294					/* runsv has died */
295					sv[i].pid = 0;
296					need_rescan = 1;
297				}
298			}
299		}
300
301		now = monotonic_sec();
302		if ((int)(now - stampcheck) >= 0) {
303			/* wait at least a second */
304			stampcheck = now + 1;
305
306			if (stat(svdir, &s) != -1) {
307				if (need_rescan || s.st_mtime != last_mtime
308				 || s.st_ino != last_ino || s.st_dev != last_dev
309				) {
310					/* svdir modified */
311					if (chdir(svdir) != -1) {
312						last_mtime = s.st_mtime;
313						last_dev = s.st_dev;
314						last_ino = s.st_ino;
315						//if (now <= mtime)
316						//	sleep(1);
317						need_rescan = do_rescan();
318						while (fchdir(curdir) == -1) {
319							warn2_cannot("change directory, pausing", "");
320							sleep(5);
321						}
322					} else {
323						warn2_cannot("change directory to ", svdir);
324					}
325				}
326			} else {
327				warn2_cannot("stat ", svdir);
328			}
329		}
330
331#if ENABLE_FEATURE_RUNSVDIR_LOG
332		if (rplog) {
333			if ((int)(now - stamplog) >= 0) {
334				write(logpipe.wr, ".", 1);
335				stamplog = now + 900;
336			}
337		}
338		pfd[0].revents = 0;
339#endif
340		deadline = (need_rescan ? 1 : 5);
341		sig_block(SIGCHLD);
342#if ENABLE_FEATURE_RUNSVDIR_LOG
343		if (rplog)
344			poll(pfd, 1, deadline*1000);
345		else
346#endif
347			sleep(deadline);
348		sig_unblock(SIGCHLD);
349
350#if ENABLE_FEATURE_RUNSVDIR_LOG
351		if (pfd[0].revents & POLLIN) {
352			char ch;
353			while (read(logpipe.rd, &ch, 1) > 0) {
354				if (ch < ' ')
355					ch = ' ';
356				for (i = 6; i < rploglen; i++)
357					rplog[i-1] = rplog[i];
358				rplog[rploglen-1] = ch;
359			}
360		}
361#endif
362		if (!bb_got_signal)
363			continue;
364
365		/* -s SCRIPT: useful if we are init.
366		 * In this case typically script never returns,
367		 * it halts/powers off/reboots the system. */
368		if (opt_s_argv[0]) {
369			/* Single parameter: signal# */
370			opt_s_argv[1] = utoa(bb_got_signal);
371			pid = spawn(opt_s_argv);
372			if (pid > 0) {
373				/* Remembering to wait for _any_ children,
374				 * not just pid */
375				while (wait(NULL) != pid)
376					continue;
377			}
378		}
379
380		if (bb_got_signal == SIGHUP) {
381			for (i = 0; i < svnum; i++)
382				if (sv[i].pid)
383					kill(sv[i].pid, SIGTERM);
384		}
385		/* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
386		/* Exit unless we are init */
387		if (getpid() != 1)
388			return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
389
390		/* init continues to monitor services forever */
391		bb_got_signal = 0;
392	} /* for (;;) */
393}
394