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#if ENABLE_MONOTONIC_SYSCALL
37#include <sys/syscall.h>
38
39/* libc has incredibly messy way of doing this,
40 * typically requiring -lrt. We just skip all this mess */
41static void gettimeofday_ns(struct timespec *ts)
42{
43	syscall(__NR_clock_gettime, CLOCK_REALTIME, ts);
44}
45#else
46static void gettimeofday_ns(struct timespec *ts)
47{
48	if (sizeof(struct timeval) == sizeof(struct timespec)
49	 && sizeof(((struct timeval*)ts)->tv_usec) == sizeof(ts->tv_nsec)
50	) {
51		/* Cheat */
52		gettimeofday((void*)ts, NULL);
53		ts->tv_nsec *= 1000;
54	} else {
55		extern void BUG_need_to_implement_gettimeofday_ns(void);
56		BUG_need_to_implement_gettimeofday_ns();
57	}
58}
59#endif
60
61/* Compare possibly overflowing unsigned counters */
62#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
63
64static int selfpipe[2];
65
66/* state */
67#define S_DOWN 0
68#define S_RUN 1
69#define S_FINISH 2
70/* ctrl */
71#define C_NOOP 0
72#define C_TERM 1
73#define C_PAUSE 2
74/* want */
75#define W_UP 0
76#define W_DOWN 1
77#define W_EXIT 2
78
79struct svdir {
80	int pid;
81	smallint state;
82	smallint ctrl;
83	smallint want;
84	smallint islog;
85	struct timespec start;
86	int fdlock;
87	int fdcontrol;
88	int fdcontrolwrite;
89};
90
91static struct svdir svd[2];
92static smallint sigterm;
93static smallint haslog;
94static smallint pidchanged = 1;
95static int logpipe[2];
96static char *dir;
97
98static void fatal2_cannot(const char *m1, const char *m2)
99{
100	bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
101	/* was exiting 111 */
102}
103static void fatal_cannot(const char *m)
104{
105	fatal2_cannot(m, "");
106	/* was exiting 111 */
107}
108static void fatal2x_cannot(const char *m1, const char *m2)
109{
110	bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
111	/* was exiting 111 */
112}
113static void warn_cannot(const char *m)
114{
115	bb_perror_msg("%s: warning: cannot %s", dir, m);
116}
117
118static void s_child(int sig_no)
119{
120	write(selfpipe[1], "", 1);
121}
122
123static void s_term(int sig_no)
124{
125	sigterm = 1;
126	write(selfpipe[1], "", 1);
127}
128
129static char *add_str(char *p, const char *to_add)
130{
131	while ((*p = *to_add) != '\0') {
132		p++;
133		to_add++;
134	}
135	return p;
136}
137
138static int open_trunc_or_warn(const char *name)
139{
140	int fd = open_trunc(name);
141	if (fd < 0)
142		bb_perror_msg("%s: warning: cannot open %s",
143				dir, name);
144	return fd;
145}
146
147static int rename_or_warn(const char *old, const char *new)
148{
149	if (rename(old, new) == -1) {
150		bb_perror_msg("%s: warning: cannot rename %s to %s",
151				dir, old, new);
152		return -1;
153	}
154	return 0;
155}
156
157static void update_status(struct svdir *s)
158{
159	ssize_t sz;
160	int fd;
161	svstatus_t status;
162
163	/* pid */
164	if (pidchanged) {
165		fd = open_trunc_or_warn("supervise/pid.new");
166		if (fd < 0)
167			return;
168		if (s->pid) {
169			char spid[sizeof(int)*3 + 2];
170			int size = sprintf(spid, "%u\n", (unsigned)s->pid);
171			write(fd, spid, size);
172		}
173		close(fd);
174		if (rename_or_warn("supervise/pid.new",
175		    s->islog ? "log/supervise/pid" : "log/supervise/pid"+4))
176			return;
177		pidchanged = 0;
178	}
179
180	/* stat */
181	fd = open_trunc_or_warn("supervise/stat.new");
182	if (fd < -1)
183		return;
184
185	{
186		char stat_buf[sizeof("finish, paused, got TERM, want down\n")];
187		char *p = stat_buf;
188		switch (s->state) {
189		case S_DOWN:
190			p = add_str(p, "down");
191			break;
192		case S_RUN:
193			p = add_str(p, "run");
194			break;
195		case S_FINISH:
196			p = add_str(p, "finish");
197			break;
198		}
199		if (s->ctrl & C_PAUSE) p = add_str(p, ", paused");
200		if (s->ctrl & C_TERM) p = add_str(p, ", got TERM");
201		if (s->state != S_DOWN)
202			switch (s->want) {
203			case W_DOWN:
204				p = add_str(p, ", want down");
205				break;
206			case W_EXIT:
207				p = add_str(p, ", want exit");
208				break;
209			}
210		*p++ = '\n';
211		write(fd, stat_buf, p - stat_buf);
212		close(fd);
213	}
214
215	rename_or_warn("supervise/stat.new",
216		s->islog ? "log/supervise/stat" : "log/supervise/stat"+4);
217
218	/* supervise compatibility */
219	memset(&status, 0, sizeof(status));
220	status.time_be64 = SWAP_BE64(s->start.tv_sec + 0x400000000000000aULL);
221	status.time_nsec_be32 = SWAP_BE32(s->start.tv_nsec);
222	status.pid_le32 = SWAP_LE32(s->pid);
223	if (s->ctrl & C_PAUSE)
224		status.paused = 1;
225	if (s->want == W_UP)
226		status.want = 'u';
227	else
228		status.want = 'd';
229	if (s->ctrl & C_TERM)
230		status.got_term = 1;
231	status.run_or_finish = s->state;
232	fd = open_trunc_or_warn("supervise/status.new");
233	if (fd < 0)
234		return;
235	sz = write(fd, &status, sizeof(status));
236	close(fd);
237	if (sz != sizeof(status)) {
238		warn_cannot("write supervise/status.new");
239		unlink("supervise/status.new");
240		return;
241	}
242	rename_or_warn("supervise/status.new",
243		s->islog ? "log/supervise/status" : "log/supervise/status"+4);
244}
245
246static unsigned custom(struct svdir *s, char c)
247{
248	int pid;
249	int w;
250	char a[10];
251	struct stat st;
252	char *prog[2];
253
254	if (s->islog) return 0;
255	strcpy(a, "control/?");
256	a[8] = c;
257	if (stat(a, &st) == 0) {
258		if (st.st_mode & S_IXUSR) {
259			pid = fork();
260			if (pid == -1) {
261				warn_cannot("fork for control/?");
262				return 0;
263			}
264			if (!pid) {
265				if (haslog && dup2(logpipe[1], 1) == -1)
266					warn_cannot("setup stdout for control/?");
267				prog[0] = a;
268				prog[1] = NULL;
269				execve(a, prog, environ);
270				fatal_cannot("run control/?");
271			}
272			while (wait_pid(&w, pid) == -1) {
273				if (errno == EINTR) continue;
274				warn_cannot("wait for child control/?");
275				return 0;
276			}
277			return !wait_exitcode(w);
278		}
279	} else {
280		if (errno != ENOENT)
281			warn_cannot("stat control/?");
282	}
283	return 0;
284}
285
286static void stopservice(struct svdir *s)
287{
288	if (s->pid && !custom(s, 't')) {
289		kill(s->pid, SIGTERM);
290		s->ctrl |= C_TERM;
291		update_status(s);
292	}
293	if (s->want == W_DOWN) {
294		kill(s->pid, SIGCONT);
295		custom(s, 'd');
296		return;
297	}
298	if (s->want == W_EXIT) {
299		kill(s->pid, SIGCONT);
300		custom(s, 'x');
301	}
302}
303
304static void startservice(struct svdir *s)
305{
306	int p;
307	char *run[2];
308
309	if (s->state == S_FINISH)
310		run[0] = (char*)"./finish";
311	else {
312		run[0] = (char*)"./run";
313		custom(s, 'u');
314	}
315	run[1] = NULL;
316
317	if (s->pid != 0)
318		stopservice(s); /* should never happen */
319	while ((p = fork()) == -1) {
320		warn_cannot("fork, sleeping");
321		sleep(5);
322	}
323	if (p == 0) {
324		/* child */
325		if (haslog) {
326			if (s->islog) {
327				xdup2(logpipe[0], 0);
328				close(logpipe[1]);
329				xchdir("./log");
330			} else {
331				xdup2(logpipe[1], 1);
332				close(logpipe[0]);
333			}
334		}
335		signal(SIGCHLD, SIG_DFL);
336		signal(SIGTERM, SIG_DFL);
337		sig_unblock(SIGCHLD);
338		sig_unblock(SIGTERM);
339		execvp(*run, run);
340		fatal2_cannot(s->islog ? "start log/" : "start ", *run);
341	}
342	if (s->state != S_FINISH) {
343		gettimeofday_ns(&s->start);
344		s->state = S_RUN;
345	}
346	s->pid = p;
347	pidchanged = 1;
348	s->ctrl = C_NOOP;
349	update_status(s);
350}
351
352static int ctrl(struct svdir *s, char c)
353{
354	int sig;
355
356	switch (c) {
357	case 'd': /* down */
358		s->want = W_DOWN;
359		update_status(s);
360		if (s->pid && s->state != S_FINISH)
361			stopservice(s);
362		break;
363	case 'u': /* up */
364		s->want = W_UP;
365		update_status(s);
366		if (s->pid == 0)
367			startservice(s);
368		break;
369	case 'x': /* exit */
370		if (s->islog)
371			break;
372		s->want = W_EXIT;
373		update_status(s);
374		/* FALLTHROUGH */
375	case 't': /* sig term */
376		if (s->pid && s->state != S_FINISH)
377			stopservice(s);
378		break;
379	case 'k': /* sig kill */
380		if (s->pid && !custom(s, c))
381			kill(s->pid, SIGKILL);
382		s->state = S_DOWN;
383		break;
384	case 'p': /* sig pause */
385		if (s->pid && !custom(s, c))
386			kill(s->pid, SIGSTOP);
387		s->ctrl |= C_PAUSE;
388		update_status(s);
389		break;
390	case 'c': /* sig cont */
391		if (s->pid && !custom(s, c))
392			kill(s->pid, SIGCONT);
393		if (s->ctrl & C_PAUSE)
394			s->ctrl &= ~C_PAUSE;
395		update_status(s);
396		break;
397	case 'o': /* once */
398		s->want = W_DOWN;
399		update_status(s);
400		if (!s->pid)
401			startservice(s);
402		break;
403	case 'a': /* sig alarm */
404		sig = SIGALRM;
405		goto sendsig;
406	case 'h': /* sig hup */
407		sig = SIGHUP;
408		goto sendsig;
409	case 'i': /* sig int */
410		sig = SIGINT;
411		goto sendsig;
412	case 'q': /* sig quit */
413		sig = SIGQUIT;
414		goto sendsig;
415	case '1': /* sig usr1 */
416		sig = SIGUSR1;
417		goto sendsig;
418	case '2': /* sig usr2 */
419		sig = SIGUSR2;
420		goto sendsig;
421	}
422	return 1;
423 sendsig:
424	if (s->pid && !custom(s, c))
425		kill(s->pid, sig);
426	return 1;
427}
428
429int runsv_main(int argc, char **argv);
430int runsv_main(int argc, char **argv)
431{
432	struct stat s;
433	int fd;
434	int r;
435	char buf[256];
436
437	if (!argv[1] || argv[2])
438		bb_show_usage();
439	dir = argv[1];
440
441	xpipe(selfpipe);
442	coe(selfpipe[0]);
443	coe(selfpipe[1]);
444	ndelay_on(selfpipe[0]);
445	ndelay_on(selfpipe[1]);
446
447	sig_block(SIGCHLD);
448	sig_catch(SIGCHLD, s_child);
449	sig_block(SIGTERM);
450	sig_catch(SIGTERM, s_term);
451
452	xchdir(dir);
453	/* bss: svd[0].pid = 0; */
454	if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
455	if (C_NOOP) svd[0].ctrl = C_NOOP;
456	if (W_UP) svd[0].want = W_UP;
457	/* bss: svd[0].islog = 0; */
458	/* bss: svd[1].pid = 0; */
459	gettimeofday_ns(&svd[0].start);
460	if (stat("down", &s) != -1) svd[0].want = W_DOWN;
461
462	if (stat("log", &s) == -1) {
463		if (errno != ENOENT)
464			warn_cannot("stat ./log");
465	} else {
466		if (!S_ISDIR(s.st_mode)) {
467			errno = 0;
468			warn_cannot("stat log/down: log is not a directory");
469		} else {
470			haslog = 1;
471			svd[1].state = S_DOWN;
472			svd[1].ctrl = C_NOOP;
473			svd[1].want = W_UP;
474			svd[1].islog = 1;
475			gettimeofday_ns(&svd[1].start);
476			if (stat("log/down", &s) != -1)
477				svd[1].want = W_DOWN;
478			xpipe(logpipe);
479			coe(logpipe[0]);
480			coe(logpipe[1]);
481		}
482	}
483
484	if (mkdir("supervise", 0700) == -1) {
485		r = readlink("supervise", buf, sizeof(buf));
486		if (r != -1) {
487			if (r == sizeof(buf))
488				fatal2x_cannot("readlink ./supervise", ": name too long");
489			buf[r] = 0;
490			mkdir(buf, 0700);
491		} else {
492			if ((errno != ENOENT) && (errno != EINVAL))
493				fatal_cannot("readlink ./supervise");
494		}
495	}
496	svd[0].fdlock = xopen3("log/supervise/lock"+4,
497			O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
498	if (lock_exnb(svd[0].fdlock) == -1)
499		fatal_cannot("lock supervise/lock");
500	coe(svd[0].fdlock);
501	if (haslog) {
502		if (mkdir("log/supervise", 0700) == -1) {
503			r = readlink("log/supervise", buf, 256);
504			if (r != -1) {
505				if (r == 256)
506					fatal2x_cannot("readlink ./log/supervise", ": name too long");
507				buf[r] = 0;
508				fd = xopen(".", O_RDONLY|O_NDELAY);
509				xchdir("./log");
510				mkdir(buf, 0700);
511				if (fchdir(fd) == -1)
512					fatal_cannot("change back to service directory");
513				close(fd);
514			}
515			else {
516				if ((errno != ENOENT) && (errno != EINVAL))
517					fatal_cannot("readlink ./log/supervise");
518			}
519		}
520		svd[1].fdlock = xopen3("log/supervise/lock",
521				O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
522		if (lock_ex(svd[1].fdlock) == -1)
523			fatal_cannot("lock log/supervise/lock");
524		coe(svd[1].fdlock);
525	}
526
527	mkfifo("log/supervise/control"+4, 0600);
528	svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
529	coe(svd[0].fdcontrol);
530	svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
531	coe(svd[0].fdcontrolwrite);
532	update_status(&svd[0]);
533	if (haslog) {
534		mkfifo("log/supervise/control", 0600);
535		svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
536		coe(svd[1].fdcontrol);
537		svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
538		coe(svd[1].fdcontrolwrite);
539		update_status(&svd[1]);
540	}
541	mkfifo("log/supervise/ok"+4, 0600);
542	fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
543	coe(fd);
544	if (haslog) {
545		mkfifo("log/supervise/ok", 0600);
546		fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
547		coe(fd);
548	}
549	for (;;) {
550		struct pollfd x[3];
551		unsigned deadline;
552		char ch;
553
554		if (haslog)
555			if (!svd[1].pid && svd[1].want == W_UP)
556				startservice(&svd[1]);
557		if (!svd[0].pid)
558			if (svd[0].want == W_UP || svd[0].state == S_FINISH)
559				startservice(&svd[0]);
560
561		x[0].fd = selfpipe[0];
562		x[0].events = POLLIN;
563		x[1].fd = svd[0].fdcontrol;
564		x[1].events = POLLIN;
565		/* x[2] is used only if haslog == 1 */
566		x[2].fd = svd[1].fdcontrol;
567		x[2].events = POLLIN;
568		sig_unblock(SIGTERM);
569		sig_unblock(SIGCHLD);
570		poll(x, 2 + haslog, 3600*1000);
571		sig_block(SIGTERM);
572		sig_block(SIGCHLD);
573
574		while (read(selfpipe[0], &ch, 1) == 1)
575			continue;
576
577		for (;;) {
578			int child;
579			int wstat;
580
581			child = wait_nohang(&wstat);
582			if (!child)
583				break;
584			if ((child == -1) && (errno != EINTR))
585				break;
586			if (child == svd[0].pid) {
587				svd[0].pid = 0;
588				pidchanged = 1;
589				svd[0].ctrl &=~ C_TERM;
590				if (svd[0].state != S_FINISH) {
591					fd = open_read("finish");
592					if (fd != -1) {
593						close(fd);
594						svd[0].state = S_FINISH;
595						update_status(&svd[0]);
596						continue;
597					}
598				}
599				svd[0].state = S_DOWN;
600				deadline = svd[0].start.tv_sec + 1;
601				gettimeofday_ns(&svd[0].start);
602				update_status(&svd[0]);
603				if (LESS(svd[0].start.tv_sec, deadline))
604					sleep(1);
605			}
606			if (haslog) {
607				if (child == svd[1].pid) {
608					svd[1].pid = 0;
609					pidchanged = 1;
610					svd[1].state = S_DOWN;
611					svd[1].ctrl &= ~C_TERM;
612					deadline = svd[1].start.tv_sec + 1;
613					gettimeofday_ns(&svd[1].start);
614					update_status(&svd[1]);
615					if (LESS(svd[1].start.tv_sec, deadline))
616						sleep(1);
617				}
618			}
619		}
620		if (read(svd[0].fdcontrol, &ch, 1) == 1)
621			ctrl(&svd[0], ch);
622		if (haslog)
623			if (read(svd[1].fdcontrol, &ch, 1) == 1)
624				ctrl(&svd[1], ch);
625
626		if (sigterm) {
627			ctrl(&svd[0], 'x');
628			sigterm = 0;
629		}
630
631		if (svd[0].want == W_EXIT && svd[0].state == S_DOWN) {
632			if (svd[1].pid == 0)
633				_exit(0);
634			if (svd[1].want != W_EXIT) {
635				svd[1].want = W_EXIT;
636				/* stopservice(&svd[1]); */
637				update_status(&svd[1]);
638				close(logpipe[1]);
639				close(logpipe[0]);
640			}
641		}
642	}
643	/* not reached */
644	return 0;
645}
646