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/* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
29
30sv - control and manage services monitored by runsv
31
32sv [-v] [-w sec] command services
33/etc/init.d/service [-w sec] command
34
35The sv program reports the current status and controls the state of services
36monitored by the runsv(8) supervisor.
37
38services consists of one or more arguments, each argument naming a directory
39service used by runsv(8). If service doesn't start with a dot or slash,
40it is searched in the default services directory /var/service/, otherwise
41relative to the current directory.
42
43command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
441, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
45force-reload, force-restart, force-shutdown.
46
47The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
48script interface. The service to be controlled then is specified by the
49base name of the "init script".
50
51status
52    Report the current status of the service, and the appendant log service
53    if available, to standard output.
54up
55    If the service is not running, start it. If the service stops, restart it.
56down
57    If the service is running, send it the TERM signal, and the CONT signal.
58    If ./run exits, start ./finish if it exists. After it stops, do not
59    restart service.
60once
61    If the service is not running, start it. Do not restart it if it stops.
62pause cont hup alarm interrupt quit 1 2 term kill
63    If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
64    USR1, USR2, TERM, or KILL signal respectively.
65exit
66    If the service is running, send it the TERM signal, and the CONT signal.
67    Do not restart the service. If the service is down, and no log service
68    exists, runsv(8) exits. If the service is down and a log service exists,
69    send the TERM signal to the log service. If the log service is down,
70    runsv(8) exits. This command is ignored if it is given to an appendant
71    log service.
72
73sv actually looks only at the first character of above commands.
74
75Commands compatible to LSB init script actions:
76
77status
78    Same as status.
79start
80    Same as up, but wait up to 7 seconds for the command to take effect.
81    Then report the status or timeout. If the script ./check exists in
82    the service directory, sv runs this script to check whether the service
83    is up and available; it's considered to be available if ./check exits
84    with 0.
85stop
86    Same as down, but wait up to 7 seconds for the service to become down.
87    Then report the status or timeout.
88restart
89    Send the commands term, cont, and up to the service, and wait up to
90    7 seconds for the service to restart. Then report the status or timeout.
91    If the script ./check exists in the service directory, sv runs this script
92    to check whether the service is up and available again; it's considered
93    to be available if ./check exits with 0.
94shutdown
95    Same as exit, but wait up to 7 seconds for the runsv(8) process
96    to terminate. Then report the status or timeout.
97force-stop
98    Same as down, but wait up to 7 seconds for the service to become down.
99    Then report the status, and on timeout send the service the kill command.
100force-reload
101    Send the service the term and cont commands, and wait up to
102    7 seconds for the service to restart. Then report the status,
103    and on timeout send the service the kill command.
104force-restart
105    Send the service the term, cont and up commands, and wait up to
106    7 seconds for the service to restart. Then report the status, and
107    on timeout send the service the kill command. If the script ./check
108    exists in the service directory, sv runs this script to check whether
109    the service is up and available again; it's considered to be available
110    if ./check exits with 0.
111force-shutdown
112    Same as exit, but wait up to 7 seconds for the runsv(8) process to
113    terminate. Then report the status, and on timeout send the service
114    the kill command.
115
116Additional Commands
117
118check
119    Check for the service to be in the state that's been requested. Wait up to
120    7 seconds for the service to reach the requested state, then report
121    the status or timeout. If the requested state of the service is up,
122    and the script ./check exists in the service directory, sv runs
123    this script to check whether the service is up and running;
124    it's considered to be up if ./check exits with 0.
125
126Options
127
128-v
129    wait up to 7 seconds for the command to take effect.
130    Then report the status or timeout.
131-w sec
132    Override the default timeout of 7 seconds with sec seconds. Implies -v.
133
134Environment
135
136SVDIR
137    The environment variable $SVDIR overrides the default services directory
138    /var/service.
139SVWAIT
140    The environment variable $SVWAIT overrides the default 7 seconds to wait
141    for a command to take effect. It is overridden by the -w option.
142
143Exit Codes
144    sv exits 0, if the command was successfully sent to all services, and,
145    if it was told to wait, the command has taken effect to all services.
146
147    For each service that caused an error (e.g. the directory is not
148    controlled by a runsv(8) process, or sv timed out while waiting),
149    sv increases the exit code by one and exits non zero. The maximum
150    is 99. sv exits 100 on error.
151*/
152
153/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
154/* TODO: depends on runit_lib.c - review and reduce/eliminate */
155
156#include <sys/poll.h>
157#include <sys/file.h>
158#include "libbb.h"
159#include "runit_lib.h"
160
161static const char *acts;
162static char **service;
163static unsigned rc;
164/* "Bernstein" time format: unix + 0x400000000000000aULL */
165static uint64_t tstart, tnow;
166svstatus_t svstatus;
167
168
169static void fatal_cannot(const char *m1) ATTRIBUTE_NORETURN;
170static void fatal_cannot(const char *m1)
171{
172	bb_perror_msg("fatal: cannot %s", m1);
173	_exit(151);
174}
175
176static void out(const char *p, const char *m1)
177{
178	printf("%s%s: %s", p, *service, m1);
179	if (errno) {
180		printf(": %s", strerror(errno));
181	}
182	puts(""); /* will also flush the output */
183}
184
185#define WARN    "warning: "
186#define OK      "ok: "
187
188static void fail(const char *m1)
189{
190	++rc;
191	out("fail: ", m1);
192}
193static void failx(const char *m1)
194{
195	errno = 0;
196	fail(m1);
197}
198static void warn(const char *m1)
199{
200	++rc;
201	/* "warning: <service>: <m1>\n" */
202	out("warning: ", m1);
203}
204static void ok(const char *m1)
205{
206	errno = 0;
207	out(OK, m1);
208}
209
210static int svstatus_get(void)
211{
212	int fd, r;
213
214	fd = open_write("supervise/ok");
215	if (fd == -1) {
216		if (errno == ENODEV) {
217			*acts == 'x' ? ok("runsv not running")
218			             : failx("runsv not running");
219			return 0;
220		}
221		warn("cannot open supervise/ok");
222		return -1;
223	}
224	close(fd);
225	fd = open_read("supervise/status");
226	if (fd == -1) {
227		warn("cannot open supervise/status");
228		return -1;
229	}
230	r = read(fd, &svstatus, 20);
231	close(fd);
232	switch (r) {
233	case 20:
234		break;
235	case -1:
236		warn("cannot read supervise/status");
237		return -1;
238	default:
239		errno = 0;
240		warn("cannot read supervise/status: bad format");
241		return -1;
242	}
243	return 1;
244}
245
246static unsigned svstatus_print(const char *m)
247{
248	int diff;
249	int pid;
250	int normallyup = 0;
251	struct stat s;
252	uint64_t timestamp;
253
254	if (stat("down", &s) == -1) {
255		if (errno != ENOENT) {
256			bb_perror_msg(WARN"cannot stat %s/down", *service);
257			return 0;
258		}
259		normallyup = 1;
260	}
261	pid = SWAP_LE32(svstatus.pid_le32);
262	timestamp = SWAP_BE64(svstatus.time_be64);
263	if (pid) {
264		switch (svstatus.run_or_finish) {
265		case 1: printf("run: "); break;
266		case 2: printf("finish: "); break;
267		}
268		printf("%s: (pid %d) ", m, pid);
269	} else {
270		printf("down: %s: ", m);
271	}
272	diff = tnow - timestamp;
273	printf("%us", (diff < 0 ? 0 : diff));
274	if (pid) {
275		if (!normallyup) printf(", normally down");
276		if (svstatus.paused) printf(", paused");
277		if (svstatus.want == 'd') printf(", want down");
278		if (svstatus.got_term) printf(", got TERM");
279	} else {
280		if (normallyup) printf(", normally up");
281		if (svstatus.want == 'u') printf(", want up");
282	}
283	return pid ? 1 : 2;
284}
285
286static int status(const char *unused)
287{
288	int r;
289
290	r = svstatus_get();
291	switch (r) { case -1: case 0: return 0; }
292
293	r = svstatus_print(*service);
294	if (chdir("log") == -1) {
295		if (errno != ENOENT) {
296			printf("; log: "WARN"cannot change to log service directory: %s",
297					strerror(errno));
298		}
299	} else if (svstatus_get()) {
300		printf("; ");
301		svstatus_print("log");
302	}
303	puts(""); /* will also flush the output */
304	return r;
305}
306
307static int checkscript(void)
308{
309	char *prog[2];
310	struct stat s;
311	int pid, w;
312
313	if (stat("check", &s) == -1) {
314		if (errno == ENOENT) return 1;
315		bb_perror_msg(WARN"cannot stat %s/check", *service);
316		return 0;
317	}
318	/* if (!(s.st_mode & S_IXUSR)) return 1; */
319	prog[0] = (char*)"./check";
320	prog[1] = NULL;
321	pid = spawn(prog);
322	if (pid <= 0) {
323		bb_perror_msg(WARN"cannot %s child %s/check", "run", *service);
324		return 0;
325	}
326	while (wait_pid(&w, pid) == -1) {
327		if (errno == EINTR) continue;
328		bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
329		return 0;
330	}
331	return !wait_exitcode(w);
332}
333
334static int check(const char *a)
335{
336	int r;
337	unsigned pid;
338	uint64_t timestamp;
339
340	r = svstatus_get();
341	if (r == -1)
342		return -1;
343	if (r == 0) {
344		if (*a == 'x')
345			return 1;
346		return -1;
347	}
348	pid = SWAP_LE32(svstatus.pid_le32);
349	switch (*a) {
350	case 'x':
351		return 0;
352	case 'u':
353		if (!pid || svstatus.run_or_finish != 1) return 0;
354		if (!checkscript()) return 0;
355		break;
356	case 'd':
357		if (pid) return 0;
358		break;
359	case 'c':
360		if (pid && !checkscript()) return 0;
361		break;
362	case 't':
363		if (!pid && svstatus.want == 'd') break;
364		timestamp = SWAP_BE64(svstatus.time_be64);
365		if ((tstart > timestamp) || !pid || svstatus.got_term || !checkscript())
366			return 0;
367		break;
368	case 'o':
369		timestamp = SWAP_BE64(svstatus.time_be64);
370		if ((!pid && tstart > timestamp) || (pid && svstatus.want != 'd'))
371			return 0;
372	}
373	printf(OK);
374	svstatus_print(*service);
375	puts(""); /* will also flush the output */
376	return 1;
377}
378
379static int control(const char *a)
380{
381	int fd, r;
382
383	if (svstatus_get() <= 0)
384		return -1;
385	if (svstatus.want == *a)
386		return 0;
387	fd = open_write("supervise/control");
388	if (fd == -1) {
389		if (errno != ENODEV)
390			warn("cannot open supervise/control");
391		else
392			*a == 'x' ? ok("runsv not running") : failx("runsv not running");
393		return -1;
394	}
395	r = write(fd, a, strlen(a));
396	close(fd);
397	if (r != strlen(a)) {
398		warn("cannot write to supervise/control");
399		return -1;
400	}
401	return 1;
402}
403
404int sv_main(int argc, char **argv);
405int sv_main(int argc, char **argv)
406{
407	unsigned opt;
408	unsigned i, want_exit;
409	char *x;
410	char *action;
411	const char *varservice = "/var/service/";
412	unsigned services;
413	char **servicex;
414	unsigned waitsec = 7;
415	smallint kll = 0;
416	smallint verbose = 0;
417	int (*act)(const char*);
418	int (*cbk)(const char*);
419	int curdir;
420
421	xfunc_error_retval = 100;
422
423	x = getenv("SVDIR");
424	if (x) varservice = x;
425	x = getenv("SVWAIT");
426	if (x) waitsec = xatou(x);
427
428	opt = getopt32(argv, "w:v", &x);
429	if (opt & 1) waitsec = xatou(x); // -w
430	if (opt & 2) verbose = 1; // -v
431	argc -= optind;
432	argv += optind;
433	action = *argv++;
434	if (!action || !*argv) bb_show_usage();
435	service = argv;
436	services = argc - 1;
437
438	tnow = time(0) + 0x400000000000000aULL;
439	tstart = tnow;
440	curdir = open_read(".");
441	if (curdir == -1)
442		fatal_cannot("open current directory");
443
444	act = &control;
445	acts = "s";
446	cbk = &check;
447
448	switch (*action) {
449	case 'x':
450	case 'e':
451		acts = "x";
452		if (!verbose) cbk = NULL;
453		break;
454	case 'X':
455	case 'E':
456		acts = "x";
457		kll = 1;
458		break;
459	case 'D':
460		acts = "d";
461		kll = 1;
462		break;
463	case 'T':
464		acts = "tc";
465		kll = 1;
466		break;
467	case 'c':
468		if (str_equal(action, "check")) {
469			act = NULL;
470			acts = "c";
471			break;
472		}
473	case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
474	case 'a': case 'i': case 'k': case 'q': case '1': case '2':
475		action[1] = '\0';
476		acts = action;
477		if (!verbose) cbk = NULL;
478		break;
479	case 's':
480		if (str_equal(action, "shutdown")) {
481			acts = "x";
482			break;
483		}
484		if (str_equal(action, "start")) {
485			acts = "u";
486			break;
487		}
488		if (str_equal(action, "stop")) {
489			acts = "d";
490			break;
491		}
492		/* "status" */
493		act = &status;
494		cbk = NULL;
495		break;
496	case 'r':
497		if (str_equal(action, "restart")) {
498			acts = "tcu";
499			break;
500		}
501		bb_show_usage();
502	case 'f':
503		if (str_equal(action, "force-reload")) {
504			acts = "tc";
505			kll = 1;
506			break;
507		}
508		if (str_equal(action, "force-restart")) {
509			acts = "tcu";
510			kll = 1;
511			break;
512		}
513		if (str_equal(action, "force-shutdown")) {
514			acts = "x";
515			kll = 1;
516			break;
517		}
518		if (str_equal(action, "force-stop")) {
519			acts = "d";
520			kll = 1;
521			break;
522		}
523	default:
524		bb_show_usage();
525	}
526
527	servicex = service;
528	for (i = 0; i < services; ++i) {
529		if ((**service != '/') && (**service != '.')) {
530			if (chdir(varservice) == -1)
531				goto chdir_failed_0;
532		}
533		if (chdir(*service) == -1) {
534 chdir_failed_0:
535			fail("cannot change to service directory");
536			goto nullify_service_0;
537		}
538		if (act && (act(acts) == -1)) {
539 nullify_service_0:
540			*service = NULL;
541		}
542		if (fchdir(curdir) == -1)
543			fatal_cannot("change to original directory");
544		service++;
545	}
546
547	if (cbk) while (1) {
548		int diff;
549
550		diff = tnow - tstart;
551		service = servicex;
552		want_exit = 1;
553		for (i = 0; i < services; ++i, ++service) {
554			if (!*service)
555				continue;
556			if ((**service != '/') && (**service != '.')) {
557				if (chdir(varservice) == -1)
558					goto chdir_failed;
559			}
560			if (chdir(*service) == -1) {
561 chdir_failed:
562				fail("cannot change to service directory");
563				goto nullify_service;
564			}
565			if (cbk(acts) != 0)
566				goto nullify_service;
567			want_exit = 0;
568			if (diff >= waitsec) {
569				printf(kll ? "kill: " : "timeout: ");
570				if (svstatus_get() > 0) {
571					svstatus_print(*service);
572					++rc;
573				}
574				puts(""); /* will also flush the output */
575				if (kll)
576					control("k");
577 nullify_service:
578				*service = NULL;
579			}
580			if (fchdir(curdir) == -1)
581				fatal_cannot("change to original directory");
582		}
583		if (want_exit) break;
584		usleep(420000);
585		tnow = time(0) + 0x400000000000000aULL;
586	}
587	return rc > 99 ? 99 : rc;
588}
589