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 Denis 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
38struct service {
39	dev_t dev;
40	ino_t ino;
41	pid_t pid;
42	smallint isgone;
43};
44
45struct service *sv;
46static char *svdir;
47static int svnum;
48static char *rplog;
49static int rploglen;
50static int logpipe[2];
51static struct pollfd pfd[1];
52static unsigned stamplog;
53static smallint check = 1;
54static smallint exitsoon;
55static smallint set_pgrp;
56
57static void fatal2_cannot(const char *m1, const char *m2)
58{
59	bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2);
60	/* was exiting 100 */
61}
62static void warn3x(const char *m1, const char *m2, const char *m3)
63{
64	bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
65}
66static void warn2_cannot(const char *m1, const char *m2)
67{
68	warn3x("cannot ", m1, m2);
69}
70static void warnx(const char *m1)
71{
72	warn3x(m1, "", "");
73}
74
75static void s_term(int sig_no)
76{
77	exitsoon = 1;
78}
79static void s_hangup(int sig_no)
80{
81	exitsoon = 2;
82}
83
84static void runsv(int no, const char *name)
85{
86	pid_t pid;
87	char *prog[3];
88
89	prog[0] = (char*)"runsv";
90	prog[1] = (char*)name;
91	prog[2] = NULL;
92
93	pid = vfork();
94
95	if (pid == -1) {
96		warn2_cannot("vfork", "");
97		return;
98	}
99	if (pid == 0) {
100		/* child */
101		if (set_pgrp)
102			setsid();
103		signal(SIGHUP, SIG_DFL);
104		signal(SIGTERM, SIG_DFL);
105		execvp(prog[0], prog);
106		fatal2_cannot("start runsv ", name);
107	}
108	sv[no].pid = pid;
109}
110
111static void runsvdir(void)
112{
113	DIR *dir;
114	direntry *d;
115	int i;
116	struct stat s;
117
118	dir = opendir(".");
119	if (!dir) {
120		warn2_cannot("open directory ", svdir);
121		return;
122	}
123	for (i = 0; i < svnum; i++)
124		sv[i].isgone = 1;
125	errno = 0;
126	while ((d = readdir(dir))) {
127		if (d->d_name[0] == '.')
128			continue;
129		if (stat(d->d_name, &s) == -1) {
130			warn2_cannot("stat ", d->d_name);
131			errno = 0;
132			continue;
133		}
134		if (!S_ISDIR(s.st_mode))
135			continue;
136		for (i = 0; i < svnum; i++) {
137			if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) {
138				sv[i].isgone = 0;
139				if (!sv[i].pid)
140					runsv(i, d->d_name);
141				break;
142			}
143		}
144		if (i == svnum) {
145			/* new service */
146			struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
147			if (!svnew) {
148				warn3x("cannot start runsv ", d->d_name,
149						" too many services");
150				continue;
151			}
152			sv = svnew;
153			svnum++;
154			memset(&sv[i], 0, sizeof(sv[i]));
155			sv[i].ino = s.st_ino;
156			sv[i].dev = s.st_dev;
157			/*sv[i].pid = 0;*/
158			/*sv[i].isgone = 0;*/
159			runsv(i, d->d_name);
160			check = 1;
161		}
162	}
163	if (errno) {
164		warn2_cannot("read directory ", svdir);
165		closedir(dir);
166		check = 1;
167		return;
168	}
169	closedir(dir);
170
171	/* SIGTERM removed runsv's */
172	for (i = 0; i < svnum; i++) {
173		if (!sv[i].isgone)
174			continue;
175		if (sv[i].pid)
176			kill(sv[i].pid, SIGTERM);
177		sv[i] = sv[--svnum];
178		check = 1;
179	}
180}
181
182static int setup_log(void)
183{
184	rploglen = strlen(rplog);
185	if (rploglen < 7) {
186		warnx("log must have at least seven characters");
187		return 0;
188	}
189	if (pipe(logpipe)) {
190		warnx("cannot create pipe for log");
191		return -1;
192	}
193	coe(logpipe[1]);
194	coe(logpipe[0]);
195	ndelay_on(logpipe[0]);
196	ndelay_on(logpipe[1]);
197	if (dup2(logpipe[1], 2) == -1) {
198		warnx("cannot set filedescriptor for log");
199		return -1;
200	}
201	pfd[0].fd = logpipe[0];
202	pfd[0].events = POLLIN;
203	stamplog = monotonic_sec();
204	return 1;
205}
206
207int runsvdir_main(int argc, char **argv);
208int runsvdir_main(int argc, char **argv)
209{
210	struct stat s;
211	dev_t last_dev = last_dev; /* for gcc */
212	ino_t last_ino = last_ino; /* for gcc */
213	time_t last_mtime = 0;
214	int wstat;
215	int curdir;
216	int pid;
217	unsigned deadline;
218	unsigned now;
219	unsigned stampcheck;
220	char ch;
221	int i;
222
223	argv++;
224	if (!*argv)
225		bb_show_usage();
226	if (argv[0][0] == '-') {
227		switch (argv[0][1]) {
228		case 'P': set_pgrp = 1;
229		case '-': ++argv;
230		}
231		if (!*argv)
232			bb_show_usage();
233	}
234
235	sig_catch(SIGTERM, s_term);
236	sig_catch(SIGHUP, s_hangup);
237	svdir = *argv++;
238	if (argv && *argv) {
239		rplog = *argv;
240		if (setup_log() != 1) {
241			rplog = 0;
242			warnx("log service disabled");
243		}
244	}
245	curdir = open_read(".");
246	if (curdir == -1)
247		fatal2_cannot("open current directory", "");
248	coe(curdir);
249
250	stampcheck = monotonic_sec();
251
252	for (;;) {
253		/* collect children */
254		for (;;) {
255			pid = wait_nohang(&wstat);
256			if (pid <= 0)
257				break;
258			for (i = 0; i < svnum; i++) {
259				if (pid == sv[i].pid) {
260					/* runsv has gone */
261					sv[i].pid = 0;
262					check = 1;
263					break;
264				}
265			}
266		}
267
268		now = monotonic_sec();
269		if ((int)(now - stampcheck) >= 0) {
270			/* wait at least a second */
271			stampcheck = now + 1;
272
273			if (stat(svdir, &s) != -1) {
274				if (check || s.st_mtime != last_mtime
275				 || s.st_ino != last_ino || s.st_dev != last_dev
276				) {
277					/* svdir modified */
278					if (chdir(svdir) != -1) {
279						last_mtime = s.st_mtime;
280						last_dev = s.st_dev;
281						last_ino = s.st_ino;
282						check = 0;
283						//if (now <= mtime)
284						//	sleep(1);
285						runsvdir();
286						while (fchdir(curdir) == -1) {
287							warn2_cannot("change directory, pausing", "");
288							sleep(5);
289						}
290					} else
291						warn2_cannot("change directory to ", svdir);
292				}
293			} else
294				warn2_cannot("stat ", svdir);
295		}
296
297		if (rplog) {
298			if ((int)(now - stamplog) >= 0) {
299				write(logpipe[1], ".", 1);
300				stamplog = now + 900;
301			}
302		}
303
304		pfd[0].revents = 0;
305		sig_block(SIGCHLD);
306		deadline = (check ? 1 : 5);
307		if (rplog)
308			poll(pfd, 1, deadline*1000);
309		else
310			sleep(deadline);
311		sig_unblock(SIGCHLD);
312
313		if (pfd[0].revents & POLLIN) {
314			while (read(logpipe[0], &ch, 1) > 0) {
315				if (ch) {
316					for (i = 6; i < rploglen; i++)
317						rplog[i-1] = rplog[i];
318					rplog[rploglen-1] = ch;
319				}
320			}
321		}
322
323		switch (exitsoon) {
324		case 1:
325			_exit(0);
326		case 2:
327			for (i = 0; i < svnum; i++)
328				if (sv[i].pid)
329					kill(sv[i].pid, SIGTERM);
330			_exit(111);
331		}
332	}
333	/* not reached */
334	return 0;
335}
336