[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit

Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/busybox/src/runit/Config.src b/ap/app/busybox/src/runit/Config.src
new file mode 100644
index 0000000..9db9740
--- /dev/null
+++ b/ap/app/busybox/src/runit/Config.src
@@ -0,0 +1,89 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Runit Utilities"
+
+INSERT
+
+config RUNSV
+	bool "runsv"
+	default y
+	help
+	  runsv starts and monitors a service and optionally an appendant log
+	  service.
+
+config RUNSVDIR
+	bool "runsvdir"
+	default y
+	help
+	  runsvdir starts a runsv process for each subdirectory, or symlink to
+	  a directory, in the services directory dir, up to a limit of 1000
+	  subdirectories, and restarts a runsv process if it terminates.
+
+config FEATURE_RUNSVDIR_LOG
+	bool "Enable scrolling argument log"
+	depends on RUNSVDIR
+	default n
+	help
+	  Enable feature where second parameter of runsvdir holds last error
+	  message (viewable via top/ps). Otherwise (feature is off
+	  or no parameter), error messages go to stderr only.
+
+config SV
+	bool "sv"
+	default y
+	help
+	  sv reports the current status and controls the state of services
+	  monitored by the runsv supervisor.
+
+config SV_DEFAULT_SERVICE_DIR
+	string "Default directory for services"
+	default "/var/service"
+	depends on SV
+	help
+	  Default directory for services.
+	  Defaults to "/var/service"
+
+config SVLOGD
+	bool "svlogd"
+	default y
+	help
+	  svlogd continuously reads log data from its standard input, optionally
+	  filters log messages, and writes the data to one or more automatically
+	  rotated logs.
+
+config CHPST
+	bool "chpst"
+	default y
+	help
+	  chpst changes the process state according to the given options, and
+	  execs specified program.
+
+config SETUIDGID
+	bool "setuidgid"
+	default y
+	help
+	  Sets soft resource limits as specified by options
+
+config ENVUIDGID
+	bool "envuidgid"
+	default y
+	help
+	  Sets $UID to account's uid and $GID to account's gid
+
+config ENVDIR
+	bool "envdir"
+	default y
+	help
+	  Sets various environment variables as specified by files
+	  in the given directory
+
+config SOFTLIMIT
+	bool "softlimit"
+	default y
+	help
+	  Sets soft resource limits as specified by options
+
+endmenu
diff --git a/ap/app/busybox/src/runit/Kbuild.src b/ap/app/busybox/src/runit/Kbuild.src
new file mode 100644
index 0000000..0fce955
--- /dev/null
+++ b/ap/app/busybox/src/runit/Kbuild.src
@@ -0,0 +1,20 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under GPLv2, see file LICENSE in this source tree.
+
+lib-y:=
+
+INSERT
+
+lib-$(CONFIG_RUNSV) += runsv.o
+lib-$(CONFIG_RUNSVDIR) += runsvdir.o
+lib-$(CONFIG_SV) += sv.o
+lib-$(CONFIG_SVLOGD) += svlogd.o
+lib-$(CONFIG_CHPST) += chpst.o
+
+lib-$(CONFIG_ENVDIR) += chpst.o
+lib-$(CONFIG_ENVUIDGID) += chpst.o
+lib-$(CONFIG_SETUIDGID) += chpst.o
+lib-$(CONFIG_SOFTLIMIT) += chpst.o
diff --git a/ap/app/busybox/src/runit/chpst.c b/ap/app/busybox/src/runit/chpst.c
new file mode 100644
index 0000000..ed72c8b
--- /dev/null
+++ b/ap/app/busybox/src/runit/chpst.c
@@ -0,0 +1,445 @@
+/*
+Copyright (c) 2001-2006, Gerrit Pape
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+/* Dependencies on runit_lib.c removed */
+
+//usage:#define chpst_trivial_usage
+//usage:       "[-vP012] [-u USER[:GRP]] [-U USER[:GRP]] [-e DIR]\n"
+//usage:       "	[-/ DIR] [-n NICE] [-m BYTES] [-d BYTES] [-o N]\n"
+//usage:       "	[-p N] [-f BYTES] [-c BYTES] PROG ARGS"
+//usage:#define chpst_full_usage "\n\n"
+//usage:       "Change the process state, run PROG\n"
+//usage:     "\n	-u USER[:GRP]	Set uid and gid"
+//usage:     "\n	-U USER[:GRP]	Set $UID and $GID in environment"
+//usage:     "\n	-e DIR		Set environment variables as specified by files"
+//usage:     "\n			in DIR: file=1st_line_of_file"
+//usage:     "\n	-/ DIR		Chroot to DIR"
+//usage:     "\n	-n NICE		Add NICE to nice value"
+//usage:     "\n	-m BYTES	Same as -d BYTES -s BYTES -l BYTES"
+//usage:     "\n	-d BYTES	Limit data segment"
+//usage:     "\n	-o N		Limit number of open files per process"
+//usage:     "\n	-p N		Limit number of processes per uid"
+//usage:     "\n	-f BYTES	Limit output file sizes"
+//usage:     "\n	-c BYTES	Limit core file size"
+//usage:     "\n	-v		Verbose"
+//usage:     "\n	-P		Create new process group"
+//usage:     "\n	-0		Close stdin"
+//usage:     "\n	-1		Close stdout"
+//usage:     "\n	-2		Close stderr"
+//usage:
+//usage:#define envdir_trivial_usage
+//usage:       "DIR PROG ARGS"
+//usage:#define envdir_full_usage "\n\n"
+//usage:       "Set various environment variables as specified by files\n"
+//usage:       "in the directory DIR, run PROG"
+//usage:
+//usage:#define envuidgid_trivial_usage
+//usage:       "USER PROG ARGS"
+//usage:#define envuidgid_full_usage "\n\n"
+//usage:       "Set $UID to USER's uid and $GID to USER's gid, run PROG"
+//usage:
+//usage:#define setuidgid_trivial_usage
+//usage:       "USER PROG ARGS"
+//usage:#define setuidgid_full_usage "\n\n"
+//usage:       "Set uid and gid to USER's uid and gid, drop supplementary group ids,\n"
+//usage:       "run PROG"
+//usage:
+//usage:#define softlimit_trivial_usage
+//usage:       "[-a BYTES] [-m BYTES] [-d BYTES] [-s BYTES] [-l BYTES]\n"
+//usage:       "	[-f BYTES] [-c BYTES] [-r BYTES] [-o N] [-p N] [-t N]\n"
+//usage:       "	PROG ARGS"
+//usage:#define softlimit_full_usage "\n\n"
+//usage:       "Set soft resource limits, then run PROG\n"
+//usage:     "\n	-a BYTES	Limit total size of all segments"
+//usage:     "\n	-m BYTES	Same as -d BYTES -s BYTES -l BYTES -a BYTES"
+//usage:     "\n	-d BYTES	Limit data segment"
+//usage:     "\n	-s BYTES	Limit stack segment"
+//usage:     "\n	-l BYTES	Limit locked memory size"
+//usage:     "\n	-o N		Limit number of open files per process"
+//usage:     "\n	-p N		Limit number of processes per uid"
+//usage:     "\nOptions controlling file sizes:"
+//usage:     "\n	-f BYTES	Limit output file sizes"
+//usage:     "\n	-c BYTES	Limit core file size"
+//usage:     "\nEfficiency opts:"
+//usage:     "\n	-r BYTES	Limit resident set size"
+//usage:     "\n	-t N		Limit CPU time, process receives"
+//usage:     "\n			a SIGXCPU after N seconds"
+
+#include "libbb.h"
+#include <sys/resource.h> /* getrlimit */
+
+/*
+Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
+
+Only softlimit and chpst are taking options:
+
+# common
+-o N            Limit number of open files per process
+-p N            Limit number of processes per uid
+-m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
+-d BYTES        Limit data segment
+-f BYTES        Limit output file sizes
+-c BYTES        Limit core file size
+# softlimit
+-a BYTES        Limit total size of all segments
+-s BYTES        Limit stack segment
+-l BYTES        Limit locked memory size
+-r BYTES        Limit resident set size
+-t N            Limit CPU time
+# chpst
+-u USER[:GRP]   Set uid and gid
+-U USER[:GRP]   Set $UID and $GID in environment
+-e DIR          Set environment variables as specified by files in DIR
+-/ DIR          Chroot to DIR
+-n NICE         Add NICE to nice value
+-v              Verbose
+-P              Create new process group
+-0 -1 -2        Close fd 0,1,2
+
+Even though we accept all these options for both softlimit and chpst,
+they are not to be advertised on their help texts.
+We have enough problems with feature creep in other people's
+software, don't want to add our own.
+
+envdir, envuidgid, setuidgid take no options, but they reuse code which
+handles -e, -U and -u.
+*/
+
+enum {
+	OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
+	OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+	OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+	OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+	OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
+	OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+	OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+	OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+	OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
+	OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
+	OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
+	OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
+	OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
+	OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
+	OPT_root = (1 << 14) * ENABLE_CHPST,
+	OPT_n = (1 << 15) * ENABLE_CHPST,
+	OPT_v = (1 << 16) * ENABLE_CHPST,
+	OPT_P = (1 << 17) * ENABLE_CHPST,
+	OPT_0 = (1 << 18) * ENABLE_CHPST,
+	OPT_1 = (1 << 19) * ENABLE_CHPST,
+	OPT_2 = (1 << 20) * ENABLE_CHPST,
+};
+
+/* TODO: use recursive_action? */
+static NOINLINE void edir(const char *directory_name)
+{
+	int wdir;
+	DIR *dir;
+	struct dirent *d;
+	int fd;
+
+	wdir = xopen(".", O_RDONLY | O_NDELAY);
+	xchdir(directory_name);
+	dir = xopendir(".");
+	for (;;) {
+		char buf[256];
+		char *tail;
+		int size;
+
+		errno = 0;
+		d = readdir(dir);
+		if (!d) {
+			if (errno)
+				bb_perror_msg_and_die("readdir %s",
+						directory_name);
+			break;
+		}
+		if (d->d_name[0] == '.')
+			continue;
+		fd = open(d->d_name, O_RDONLY | O_NDELAY);
+		if (fd < 0) {
+			if ((errno == EISDIR) && directory_name) {
+				if (option_mask32 & OPT_v)
+					bb_perror_msg("warning: %s/%s is a directory",
+						directory_name, d->d_name);
+				continue;
+			}
+			bb_perror_msg_and_die("open %s/%s",
+						directory_name, d->d_name);
+		}
+		size = full_read(fd, buf, sizeof(buf)-1);
+		close(fd);
+		if (size < 0)
+			bb_perror_msg_and_die("read %s/%s",
+					directory_name, d->d_name);
+		if (size == 0) {
+			unsetenv(d->d_name);
+			continue;
+		}
+		buf[size] = '\n';
+		tail = strchr(buf, '\n');
+		/* skip trailing whitespace */
+		while (1) {
+			*tail = '\0';
+			tail--;
+			if (tail < buf || !isspace(*tail))
+				break;
+		}
+		xsetenv(d->d_name, buf);
+	}
+	closedir(dir);
+	if (fchdir(wdir) == -1)
+		bb_perror_msg_and_die("fchdir");
+	close(wdir);
+}
+
+static void limit(int what, long l)
+{
+	struct rlimit r;
+
+	/* Never fails under Linux (except if you pass it bad arguments) */
+	getrlimit(what, &r);
+	if ((l < 0) || (l > r.rlim_max))
+		r.rlim_cur = r.rlim_max;
+	else
+		r.rlim_cur = l;
+	if (setrlimit(what, &r) == -1)
+		bb_perror_msg_and_die("setrlimit");
+}
+
+int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chpst_main(int argc UNUSED_PARAM, char **argv)
+{
+	struct bb_uidgid_t ugid;
+	char *set_user = set_user; /* for compiler */
+	char *env_user = env_user;
+	char *env_dir = env_dir;
+	char *root;
+	char *nicestr;
+	unsigned limita;
+	unsigned limitc;
+	unsigned limitd;
+	unsigned limitf;
+	unsigned limitl;
+	unsigned limitm;
+	unsigned limito;
+	unsigned limitp;
+	unsigned limitr;
+	unsigned limits;
+	unsigned limitt;
+	unsigned opt;
+
+	if ((ENABLE_CHPST && applet_name[0] == 'c')
+	 || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
+	) {
+		// FIXME: can we live with int-sized limits?
+		// can we live with 40000 days?
+		// if yes -> getopt converts strings to numbers for us
+		opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+";
+		opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:"
+			IF_CHPST("/:n:vP012"),
+			&limita, &limitc, &limitd, &limitf, &limitl,
+			&limitm, &limito, &limitp, &limitr, &limits, &limitt,
+			&set_user, &env_user, &env_dir
+			IF_CHPST(, &root, &nicestr));
+		argv += optind;
+		if (opt & OPT_m) { // -m means -asld
+			limita = limits = limitl = limitd = limitm;
+			opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
+		}
+	} else {
+		option_mask32 = opt = 0;
+		argv++;
+		if (!*argv)
+			bb_show_usage();
+	}
+
+	// envdir?
+	if (ENABLE_ENVDIR && applet_name[3] == 'd') {
+		env_dir = *argv++;
+		opt |= OPT_e;
+	}
+
+	// setuidgid?
+	if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
+		set_user = *argv++;
+		opt |= OPT_u;
+	}
+
+	// envuidgid?
+	if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
+		env_user = *argv++;
+		opt |= OPT_U;
+	}
+
+	// we must have PROG [ARGS]
+	if (!*argv)
+		bb_show_usage();
+
+	// set limits
+	if (opt & OPT_d) {
+#ifdef RLIMIT_DATA
+		limit(RLIMIT_DATA, limitd);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"DATA");
+#endif
+	}
+	if (opt & OPT_s) {
+#ifdef RLIMIT_STACK
+		limit(RLIMIT_STACK, limits);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"STACK");
+#endif
+	}
+	if (opt & OPT_l) {
+#ifdef RLIMIT_MEMLOCK
+		limit(RLIMIT_MEMLOCK, limitl);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"MEMLOCK");
+#endif
+	}
+	if (opt & OPT_a) {
+#ifdef RLIMIT_VMEM
+		limit(RLIMIT_VMEM, limita);
+#else
+#ifdef RLIMIT_AS
+		limit(RLIMIT_AS, limita);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"VMEM");
+#endif
+#endif
+	}
+	if (opt & OPT_o) {
+#ifdef RLIMIT_NOFILE
+		limit(RLIMIT_NOFILE, limito);
+#else
+#ifdef RLIMIT_OFILE
+		limit(RLIMIT_OFILE, limito);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"NOFILE");
+#endif
+#endif
+	}
+	if (opt & OPT_p) {
+#ifdef RLIMIT_NPROC
+		limit(RLIMIT_NPROC, limitp);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"NPROC");
+#endif
+	}
+	if (opt & OPT_f) {
+#ifdef RLIMIT_FSIZE
+		limit(RLIMIT_FSIZE, limitf);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"FSIZE");
+#endif
+	}
+	if (opt & OPT_c) {
+#ifdef RLIMIT_CORE
+		limit(RLIMIT_CORE, limitc);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"CORE");
+#endif
+	}
+	if (opt & OPT_r) {
+#ifdef RLIMIT_RSS
+		limit(RLIMIT_RSS, limitr);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"RSS");
+#endif
+	}
+	if (opt & OPT_t) {
+#ifdef RLIMIT_CPU
+		limit(RLIMIT_CPU, limitt);
+#else
+		if (opt & OPT_v)
+			bb_error_msg("system does not support RLIMIT_%s",
+				"CPU");
+#endif
+	}
+
+	if (opt & OPT_P)
+		setsid();
+
+	if (opt & OPT_e)
+		edir(env_dir);
+
+	if (opt & (OPT_u|OPT_U))
+		xget_uidgid(&ugid, set_user);
+
+	// chrooted jail must have /etc/passwd if we move this after chroot.
+	// OTOH chroot fails for non-roots.
+	// Solution: cache uid/gid before chroot, apply uid/gid after.
+	if (opt & OPT_U) {
+		xsetenv("GID", utoa(ugid.gid));
+		xsetenv("UID", utoa(ugid.uid));
+	}
+
+	if (opt & OPT_root) {
+		xchroot(root);
+	}
+
+	if (opt & OPT_u) {
+		if (setgroups(1, &ugid.gid) == -1)
+			bb_perror_msg_and_die("setgroups");
+		xsetgid(ugid.gid);
+		xsetuid(ugid.uid);
+	}
+
+	if (opt & OPT_n) {
+		errno = 0;
+		if (nice(xatoi(nicestr)) == -1)
+			bb_perror_msg_and_die("nice");
+	}
+
+	if (opt & OPT_0)
+		close(STDIN_FILENO);
+	if (opt & OPT_1)
+		close(STDOUT_FILENO);
+	if (opt & OPT_2)
+		close(STDERR_FILENO);
+
+	BB_EXECVP_or_die(argv);
+}
diff --git a/ap/app/busybox/src/runit/runit_lib.h b/ap/app/busybox/src/runit/runit_lib.h
new file mode 100644
index 0000000..c36ea4c
--- /dev/null
+++ b/ap/app/busybox/src/runit/runit_lib.h
@@ -0,0 +1,46 @@
+/*
+Copyright (c) 2001-2006, Gerrit Pape
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/*
+ * runsv / supervise / sv stuff
+ */
+typedef struct svstatus_t {
+	uint64_t time_be64 PACKED;
+	uint32_t time_nsec_be32 PACKED;
+	uint32_t pid_le32 PACKED;
+	uint8_t  paused;
+	uint8_t  want; /* 'u' or 'd' */
+	uint8_t  got_term;
+	uint8_t  run_or_finish;
+} svstatus_t;
+struct ERR_svstatus_must_be_20_bytes {
+	char ERR_svstatus_must_be_20_bytes[sizeof(svstatus_t) == 20 ? 1 : -1];
+};
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/ap/app/busybox/src/runit/runsv.c b/ap/app/busybox/src/runit/runsv.c
new file mode 100644
index 0000000..3e1a3c8
--- /dev/null
+++ b/ap/app/busybox/src/runit/runsv.c
@@ -0,0 +1,671 @@
+/*
+Copyright (c) 2001-2006, Gerrit Pape
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+//usage:#define runsv_trivial_usage
+//usage:       "DIR"
+//usage:#define runsv_full_usage "\n\n"
+//usage:       "Start and monitor a service and optionally an appendant log service"
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+#if ENABLE_MONOTONIC_SYSCALL
+#include <sys/syscall.h>
+
+/* libc has incredibly messy way of doing this,
+ * typically requiring -lrt. We just skip all this mess */
+static void gettimeofday_ns(struct timespec *ts)
+{
+	syscall(__NR_clock_gettime, CLOCK_REALTIME, ts);
+}
+#else
+static void gettimeofday_ns(struct timespec *ts)
+{
+	if (sizeof(struct timeval) == sizeof(struct timespec)
+	 && sizeof(((struct timeval*)ts)->tv_usec) == sizeof(ts->tv_nsec)
+	) {
+		/* Cheat */
+		gettimeofday((void*)ts, NULL);
+		ts->tv_nsec *= 1000;
+	} else {
+		extern void BUG_need_to_implement_gettimeofday_ns(void);
+		BUG_need_to_implement_gettimeofday_ns();
+	}
+}
+#endif
+
+/* Compare possibly overflowing unsigned counters */
+#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
+
+/* state */
+#define S_DOWN 0
+#define S_RUN 1
+#define S_FINISH 2
+/* ctrl */
+#define C_NOOP 0
+#define C_TERM 1
+#define C_PAUSE 2
+/* want */
+#define W_UP 0
+#define W_DOWN 1
+#define W_EXIT 2
+
+struct svdir {
+	int pid;
+	smallint state;
+	smallint ctrl;
+	smallint sd_want;
+	smallint islog;
+	struct timespec start;
+	int fdlock;
+	int fdcontrol;
+	int fdcontrolwrite;
+	int wstat;
+};
+
+struct globals {
+	smallint haslog;
+	smallint sigterm;
+	smallint pidchanged;
+	struct fd_pair selfpipe;
+	struct fd_pair logpipe;
+	char *dir;
+	struct svdir svd[2];
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define haslog       (G.haslog      )
+#define sigterm      (G.sigterm     )
+#define pidchanged   (G.pidchanged  )
+#define selfpipe     (G.selfpipe    )
+#define logpipe      (G.logpipe     )
+#define dir          (G.dir         )
+#define svd          (G.svd         )
+#define INIT_G() do { \
+	pidchanged = 1; \
+} while (0)
+
+static void fatal2_cannot(const char *m1, const char *m2)
+{
+	bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
+	/* was exiting 111 */
+}
+static void fatal_cannot(const char *m)
+{
+	fatal2_cannot(m, "");
+	/* was exiting 111 */
+}
+static void fatal2x_cannot(const char *m1, const char *m2)
+{
+	bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
+	/* was exiting 111 */
+}
+static void warn_cannot(const char *m)
+{
+	bb_perror_msg("%s: warning: cannot %s", dir, m);
+}
+
+static void s_child(int sig_no UNUSED_PARAM)
+{
+	write(selfpipe.wr, "", 1);
+}
+
+static void s_term(int sig_no UNUSED_PARAM)
+{
+	sigterm = 1;
+	write(selfpipe.wr, "", 1); /* XXX */
+}
+
+static int open_trunc_or_warn(const char *name)
+{
+	/* Why O_NDELAY? */
+	int fd = open(name, O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT, 0644);
+	if (fd < 0)
+		bb_perror_msg("%s: warning: cannot open %s",
+				dir, name);
+	return fd;
+}
+
+static void update_status(struct svdir *s)
+{
+	ssize_t sz;
+	int fd;
+	svstatus_t status;
+
+	/* pid */
+	if (pidchanged) {
+		fd = open_trunc_or_warn("supervise/pid.new");
+		if (fd < 0)
+			return;
+		if (s->pid) {
+			char spid[sizeof(int)*3 + 2];
+			int size = sprintf(spid, "%u\n", (unsigned)s->pid);
+			write(fd, spid, size);
+		}
+		close(fd);
+		if (rename_or_warn("supervise/pid.new",
+				s->islog ? "log/supervise/pid" : "log/supervise/pid"+4))
+			return;
+		pidchanged = 0;
+	}
+
+	/* stat */
+	fd = open_trunc_or_warn("supervise/stat.new");
+	if (fd < -1)
+		return;
+
+	{
+		char stat_buf[sizeof("finish, paused, got TERM, want down\n")];
+		char *p = stat_buf;
+		switch (s->state) {
+		case S_DOWN:
+			p = stpcpy(p, "down");
+			break;
+		case S_RUN:
+			p = stpcpy(p, "run");
+			break;
+		case S_FINISH:
+			p = stpcpy(p, "finish");
+			break;
+		}
+		if (s->ctrl & C_PAUSE)
+			p = stpcpy(p, ", paused");
+		if (s->ctrl & C_TERM)
+			p = stpcpy(p, ", got TERM");
+		if (s->state != S_DOWN)
+			switch (s->sd_want) {
+			case W_DOWN:
+				p = stpcpy(p, ", want down");
+				break;
+			case W_EXIT:
+				p = stpcpy(p, ", want exit");
+				break;
+			}
+		*p++ = '\n';
+		write(fd, stat_buf, p - stat_buf);
+		close(fd);
+	}
+
+	rename_or_warn("supervise/stat.new",
+		s->islog ? "log/supervise/stat" : "log/supervise/stat"+4);
+
+	/* supervise compatibility */
+	memset(&status, 0, sizeof(status));
+	status.time_be64 = SWAP_BE64(s->start.tv_sec + 0x400000000000000aULL);
+	status.time_nsec_be32 = SWAP_BE32(s->start.tv_nsec);
+	status.pid_le32 = SWAP_LE32(s->pid);
+	if (s->ctrl & C_PAUSE)
+		status.paused = 1;
+	if (s->sd_want == W_UP)
+		status.want = 'u';
+	else
+		status.want = 'd';
+	if (s->ctrl & C_TERM)
+		status.got_term = 1;
+	status.run_or_finish = s->state;
+	fd = open_trunc_or_warn("supervise/status.new");
+	if (fd < 0)
+		return;
+	sz = write(fd, &status, sizeof(status));
+	close(fd);
+	if (sz != sizeof(status)) {
+		warn_cannot("write supervise/status.new");
+		unlink("supervise/status.new");
+		return;
+	}
+	rename_or_warn("supervise/status.new",
+		s->islog ? "log/supervise/status" : "log/supervise/status"+4);
+}
+
+static unsigned custom(struct svdir *s, char c)
+{
+	pid_t pid;
+	int w;
+	char a[10];
+	struct stat st;
+
+	if (s->islog)
+		return 0;
+	strcpy(a, "control/?");
+	a[8] = c; /* replace '?' */
+	if (stat(a, &st) == 0) {
+		if (st.st_mode & S_IXUSR) {
+			pid = vfork();
+			if (pid == -1) {
+				warn_cannot("vfork for control/?");
+				return 0;
+			}
+			if (pid == 0) {
+				/* child */
+				if (haslog && dup2(logpipe.wr, 1) == -1)
+					warn_cannot("setup stdout for control/?");
+				execl(a, a, (char *) NULL);
+				fatal_cannot("run control/?");
+			}
+			/* parent */
+			if (safe_waitpid(pid, &w, 0) == -1) {
+				warn_cannot("wait for child control/?");
+				return 0;
+			}
+			return WEXITSTATUS(w) == 0;
+		}
+	} else {
+		if (errno != ENOENT)
+			warn_cannot("stat control/?");
+	}
+	return 0;
+}
+
+static void stopservice(struct svdir *s)
+{
+	if (s->pid && !custom(s, 't')) {
+		kill(s->pid, SIGTERM);
+		s->ctrl |= C_TERM;
+		update_status(s);
+	}
+	if (s->sd_want == W_DOWN) {
+		kill(s->pid, SIGCONT);
+		custom(s, 'd');
+		return;
+	}
+	if (s->sd_want == W_EXIT) {
+		kill(s->pid, SIGCONT);
+		custom(s, 'x');
+	}
+}
+
+static void startservice(struct svdir *s)
+{
+	int p;
+	const char *arg[4];
+	char exitcode[sizeof(int)*3 + 2];
+
+	if (s->state == S_FINISH) {
+/* Two arguments are given to ./finish. The first one is ./run exit code,
+ * or -1 if ./run didnt exit normally. The second one is
+ * the least significant byte of the exit status as determined by waitpid;
+ * for instance it is 0 if ./run exited normally, and the signal number
+ * if ./run was terminated by a signal. If runsv cannot start ./run
+ * for some reason, the exit code is 111 and the status is 0.
+ */
+		arg[0] = "./finish";
+		arg[1] = "-1";
+		if (WIFEXITED(s->wstat)) {
+			*utoa_to_buf(WEXITSTATUS(s->wstat), exitcode, sizeof(exitcode)) = '\0';
+			arg[1] = exitcode;
+		}
+		//arg[2] = "0";
+		//if (WIFSIGNALED(s->wstat)) {
+			arg[2] = utoa(WTERMSIG(s->wstat));
+		//}
+		arg[3] = NULL;
+	} else {
+		arg[0] = "./run";
+		arg[1] = NULL;
+		custom(s, 'u');
+	}
+
+	if (s->pid != 0)
+		stopservice(s); /* should never happen */
+	while ((p = vfork()) == -1) {
+		warn_cannot("vfork, sleeping");
+		sleep(5);
+	}
+	if (p == 0) {
+		/* child */
+		if (haslog) {
+			/* NB: bug alert! right order is close, then dup2 */
+			if (s->islog) {
+				xchdir("./log");
+				close(logpipe.wr);
+				xdup2(logpipe.rd, 0);
+			} else {
+				close(logpipe.rd);
+				xdup2(logpipe.wr, 1);
+			}
+		}
+		/* Non-ignored signals revert to SIG_DFL on exec anyway */
+		/*bb_signals(0
+			+ (1 << SIGCHLD)
+			+ (1 << SIGTERM)
+			, SIG_DFL);*/
+		sig_unblock(SIGCHLD);
+		sig_unblock(SIGTERM);
+		execv(arg[0], (char**) arg);
+		fatal2_cannot(s->islog ? "start log/" : "start ", arg[0]);
+	}
+	/* parent */
+	if (s->state != S_FINISH) {
+		gettimeofday_ns(&s->start);
+		s->state = S_RUN;
+	}
+	s->pid = p;
+	pidchanged = 1;
+	s->ctrl = C_NOOP;
+	update_status(s);
+}
+
+static int ctrl(struct svdir *s, char c)
+{
+	int sig;
+
+	switch (c) {
+	case 'd': /* down */
+		s->sd_want = W_DOWN;
+		update_status(s);
+		if (s->pid && s->state != S_FINISH)
+			stopservice(s);
+		break;
+	case 'u': /* up */
+		s->sd_want = W_UP;
+		update_status(s);
+		if (s->pid == 0)
+			startservice(s);
+		break;
+	case 'x': /* exit */
+		if (s->islog)
+			break;
+		s->sd_want = W_EXIT;
+		update_status(s);
+		/* FALLTHROUGH */
+	case 't': /* sig term */
+		if (s->pid && s->state != S_FINISH)
+			stopservice(s);
+		break;
+	case 'k': /* sig kill */
+		if (s->pid && !custom(s, c))
+			kill(s->pid, SIGKILL);
+		s->state = S_DOWN;
+		break;
+	case 'p': /* sig pause */
+		if (s->pid && !custom(s, c))
+			kill(s->pid, SIGSTOP);
+		s->ctrl |= C_PAUSE;
+		update_status(s);
+		break;
+	case 'c': /* sig cont */
+		if (s->pid && !custom(s, c))
+			kill(s->pid, SIGCONT);
+		s->ctrl &= ~C_PAUSE;
+		update_status(s);
+		break;
+	case 'o': /* once */
+		s->sd_want = W_DOWN;
+		update_status(s);
+		if (!s->pid)
+			startservice(s);
+		break;
+	case 'a': /* sig alarm */
+		sig = SIGALRM;
+		goto sendsig;
+	case 'h': /* sig hup */
+		sig = SIGHUP;
+		goto sendsig;
+	case 'i': /* sig int */
+		sig = SIGINT;
+		goto sendsig;
+	case 'q': /* sig quit */
+		sig = SIGQUIT;
+		goto sendsig;
+	case '1': /* sig usr1 */
+		sig = SIGUSR1;
+		goto sendsig;
+	case '2': /* sig usr2 */
+		sig = SIGUSR2;
+		goto sendsig;
+	}
+	return 1;
+ sendsig:
+	if (s->pid && !custom(s, c))
+		kill(s->pid, sig);
+	return 1;
+}
+
+int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runsv_main(int argc UNUSED_PARAM, char **argv)
+{
+	struct stat s;
+	int fd;
+	int r;
+	char buf[256];
+
+	INIT_G();
+
+	dir = single_argv(argv);
+
+	xpiped_pair(selfpipe);
+	close_on_exec_on(selfpipe.rd);
+	close_on_exec_on(selfpipe.wr);
+	ndelay_on(selfpipe.rd);
+	ndelay_on(selfpipe.wr);
+
+	sig_block(SIGCHLD);
+	bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
+	sig_block(SIGTERM);
+	bb_signals_recursive_norestart(1 << SIGTERM, s_term);
+
+	xchdir(dir);
+	/* bss: svd[0].pid = 0; */
+	if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
+	if (C_NOOP) svd[0].ctrl = C_NOOP;
+	if (W_UP) svd[0].sd_want = W_UP;
+	/* bss: svd[0].islog = 0; */
+	/* bss: svd[1].pid = 0; */
+	gettimeofday_ns(&svd[0].start);
+	if (stat("down", &s) != -1)
+		svd[0].sd_want = W_DOWN;
+
+	if (stat("log", &s) == -1) {
+		if (errno != ENOENT)
+			warn_cannot("stat ./log");
+	} else {
+		if (!S_ISDIR(s.st_mode)) {
+			errno = 0;
+			warn_cannot("stat log/down: log is not a directory");
+		} else {
+			haslog = 1;
+			svd[1].state = S_DOWN;
+			svd[1].ctrl = C_NOOP;
+			svd[1].sd_want = W_UP;
+			svd[1].islog = 1;
+			gettimeofday_ns(&svd[1].start);
+			if (stat("log/down", &s) != -1)
+				svd[1].sd_want = W_DOWN;
+			xpiped_pair(logpipe);
+			close_on_exec_on(logpipe.rd);
+			close_on_exec_on(logpipe.wr);
+		}
+	}
+
+	if (mkdir("supervise", 0700) == -1) {
+		r = readlink("supervise", buf, sizeof(buf));
+		if (r != -1) {
+			if (r == sizeof(buf))
+				fatal2x_cannot("readlink ./supervise", ": name too long");
+			buf[r] = 0;
+			mkdir(buf, 0700);
+		} else {
+			if ((errno != ENOENT) && (errno != EINVAL))
+				fatal_cannot("readlink ./supervise");
+		}
+	}
+	svd[0].fdlock = xopen3("log/supervise/lock"+4,
+			O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+	if (flock(svd[0].fdlock, LOCK_EX | LOCK_NB) == -1)
+		fatal_cannot("lock supervise/lock");
+	close_on_exec_on(svd[0].fdlock);
+	if (haslog) {
+		if (mkdir("log/supervise", 0700) == -1) {
+			r = readlink("log/supervise", buf, 256);
+			if (r != -1) {
+				if (r == 256)
+					fatal2x_cannot("readlink ./log/supervise", ": name too long");
+				buf[r] = 0;
+				fd = xopen(".", O_RDONLY|O_NDELAY);
+				xchdir("./log");
+				mkdir(buf, 0700);
+				if (fchdir(fd) == -1)
+					fatal_cannot("change back to service directory");
+				close(fd);
+			}
+			else {
+				if ((errno != ENOENT) && (errno != EINVAL))
+					fatal_cannot("readlink ./log/supervise");
+			}
+		}
+		svd[1].fdlock = xopen3("log/supervise/lock",
+				O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+		if (flock(svd[1].fdlock, LOCK_EX) == -1)
+			fatal_cannot("lock log/supervise/lock");
+		close_on_exec_on(svd[1].fdlock);
+	}
+
+	mkfifo("log/supervise/control"+4, 0600);
+	svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
+	close_on_exec_on(svd[0].fdcontrol);
+	svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
+	close_on_exec_on(svd[0].fdcontrolwrite);
+	update_status(&svd[0]);
+	if (haslog) {
+		mkfifo("log/supervise/control", 0600);
+		svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
+		close_on_exec_on(svd[1].fdcontrol);
+		svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
+		close_on_exec_on(svd[1].fdcontrolwrite);
+		update_status(&svd[1]);
+	}
+	mkfifo("log/supervise/ok"+4, 0600);
+	fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
+	close_on_exec_on(fd);
+	if (haslog) {
+		mkfifo("log/supervise/ok", 0600);
+		fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
+		close_on_exec_on(fd);
+	}
+	for (;;) {
+		struct pollfd x[3];
+		unsigned deadline;
+		char ch;
+
+		if (haslog)
+			if (!svd[1].pid && svd[1].sd_want == W_UP)
+				startservice(&svd[1]);
+		if (!svd[0].pid)
+			if (svd[0].sd_want == W_UP || svd[0].state == S_FINISH)
+				startservice(&svd[0]);
+
+		x[0].fd = selfpipe.rd;
+		x[0].events = POLLIN;
+		x[1].fd = svd[0].fdcontrol;
+		x[1].events = POLLIN;
+		/* x[2] is used only if haslog == 1 */
+		x[2].fd = svd[1].fdcontrol;
+		x[2].events = POLLIN;
+		sig_unblock(SIGTERM);
+		sig_unblock(SIGCHLD);
+		poll(x, 2 + haslog, 3600*1000);
+		sig_block(SIGTERM);
+		sig_block(SIGCHLD);
+
+		while (read(selfpipe.rd, &ch, 1) == 1)
+			continue;
+
+		for (;;) {
+			pid_t child;
+			int wstat;
+
+			child = wait_any_nohang(&wstat);
+			if (!child)
+				break;
+			if ((child == -1) && (errno != EINTR))
+				break;
+			if (child == svd[0].pid) {
+				svd[0].wstat = wstat;
+				svd[0].pid = 0;
+				pidchanged = 1;
+				svd[0].ctrl &= ~C_TERM;
+				if (svd[0].state != S_FINISH) {
+					fd = open("finish", O_RDONLY|O_NDELAY);
+					if (fd != -1) {
+						close(fd);
+						svd[0].state = S_FINISH;
+						update_status(&svd[0]);
+						continue;
+					}
+				}
+				svd[0].state = S_DOWN;
+				deadline = svd[0].start.tv_sec + 1;
+				gettimeofday_ns(&svd[0].start);
+				update_status(&svd[0]);
+				if (LESS(svd[0].start.tv_sec, deadline))
+					sleep(1);
+			}
+			if (haslog) {
+				if (child == svd[1].pid) {
+					svd[0].wstat = wstat;
+					svd[1].pid = 0;
+					pidchanged = 1;
+					svd[1].state = S_DOWN;
+					svd[1].ctrl &= ~C_TERM;
+					deadline = svd[1].start.tv_sec + 1;
+					gettimeofday_ns(&svd[1].start);
+					update_status(&svd[1]);
+					if (LESS(svd[1].start.tv_sec, deadline))
+						sleep(1);
+				}
+			}
+		} /* for (;;) */
+		if (read(svd[0].fdcontrol, &ch, 1) == 1)
+			ctrl(&svd[0], ch);
+		if (haslog)
+			if (read(svd[1].fdcontrol, &ch, 1) == 1)
+				ctrl(&svd[1], ch);
+
+		if (sigterm) {
+			ctrl(&svd[0], 'x');
+			sigterm = 0;
+		}
+
+		if (svd[0].sd_want == W_EXIT && svd[0].state == S_DOWN) {
+			if (svd[1].pid == 0)
+				_exit(EXIT_SUCCESS);
+			if (svd[1].sd_want != W_EXIT) {
+				svd[1].sd_want = W_EXIT;
+				/* stopservice(&svd[1]); */
+				update_status(&svd[1]);
+				close(logpipe.wr);
+				close(logpipe.rd);
+			}
+		}
+	} /* for (;;) */
+	/* not reached */
+	return 0;
+}
diff --git a/ap/app/busybox/src/runit/runsvdir.c b/ap/app/busybox/src/runit/runsvdir.c
new file mode 100644
index 0000000..32526cf
--- /dev/null
+++ b/ap/app/busybox/src/runit/runsvdir.c
@@ -0,0 +1,402 @@
+/*
+Copyright (c) 2001-2006, Gerrit Pape
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+//usage:#define runsvdir_trivial_usage
+//usage:       "[-P] [-s SCRIPT] DIR"
+//usage:#define runsvdir_full_usage "\n\n"
+//usage:       "Start a runsv process for each subdirectory. If it exits, restart it.\n"
+//usage:     "\n	-P		Put each runsv in a new session"
+//usage:     "\n	-s SCRIPT	Run SCRIPT <signo> after signal is processed"
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+#define MAXSERVICES 1000
+
+/* Should be not needed - all dirs are on same FS, right? */
+#define CHECK_DEVNO_TOO 0
+
+struct service {
+#if CHECK_DEVNO_TOO
+	dev_t dev;
+#endif
+	ino_t ino;
+	pid_t pid;
+	smallint isgone;
+};
+
+struct globals {
+	struct service *sv;
+	char *svdir;
+	int svnum;
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+	char *rplog;
+	int rploglen;
+	struct fd_pair logpipe;
+	struct pollfd pfd[1];
+	unsigned stamplog;
+#endif
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define sv          (G.sv          )
+#define svdir       (G.svdir       )
+#define svnum       (G.svnum       )
+#define rplog       (G.rplog       )
+#define rploglen    (G.rploglen    )
+#define logpipe     (G.logpipe     )
+#define pfd         (G.pfd         )
+#define stamplog    (G.stamplog    )
+#define INIT_G() do { } while (0)
+
+static void fatal2_cannot(const char *m1, const char *m2)
+{
+	bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
+	/* was exiting 100 */
+}
+static void warn3x(const char *m1, const char *m2, const char *m3)
+{
+	bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
+}
+static void warn2_cannot(const char *m1, const char *m2)
+{
+	warn3x("can't ", m1, m2);
+}
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+static void warnx(const char *m1)
+{
+	warn3x(m1, "", "");
+}
+#endif
+
+/* inlining + vfork -> bigger code */
+static NOINLINE pid_t runsv(const char *name)
+{
+	pid_t pid;
+
+	/* If we got signaled, stop spawning children at once! */
+	if (bb_got_signal)
+		return 0;
+
+	pid = vfork();
+	if (pid == -1) {
+		warn2_cannot("vfork", "");
+		return 0;
+	}
+	if (pid == 0) {
+		/* child */
+		if (option_mask32 & 1) /* -P option? */
+			setsid();
+/* man execv:
+ * "Signals set to be caught by the calling process image
+ *  shall be set to the default action in the new process image."
+ * Therefore, we do not need this: */
+#if 0
+		bb_signals(0
+			| (1 << SIGHUP)
+			| (1 << SIGTERM)
+			, SIG_DFL);
+#endif
+		execlp("runsv", "runsv", name, (char *) NULL);
+		fatal2_cannot("start runsv ", name);
+	}
+	return pid;
+}
+
+/* gcc 4.3.0 does better with NOINLINE */
+static NOINLINE int do_rescan(void)
+{
+	DIR *dir;
+	struct dirent *d;
+	int i;
+	struct stat s;
+	int need_rescan = 0;
+
+	dir = opendir(".");
+	if (!dir) {
+		warn2_cannot("open directory ", svdir);
+		return 1; /* need to rescan again soon */
+	}
+	for (i = 0; i < svnum; i++)
+		sv[i].isgone = 1;
+
+	while (1) {
+		errno = 0;
+		d = readdir(dir);
+		if (!d)
+			break;
+		if (d->d_name[0] == '.')
+			continue;
+		if (stat(d->d_name, &s) == -1) {
+			warn2_cannot("stat ", d->d_name);
+			continue;
+		}
+		if (!S_ISDIR(s.st_mode))
+			continue;
+		/* Do we have this service listed already? */
+		for (i = 0; i < svnum; i++) {
+			if ((sv[i].ino == s.st_ino)
+#if CHECK_DEVNO_TOO
+			 && (sv[i].dev == s.st_dev)
+#endif
+			) {
+				if (sv[i].pid == 0) /* restart if it has died */
+					goto run_ith_sv;
+				sv[i].isgone = 0; /* "we still see you" */
+				goto next_dentry;
+			}
+		}
+		{ /* Not found, make new service */
+			struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
+			if (!svnew) {
+				warn2_cannot("start runsv ", d->d_name);
+				need_rescan = 1;
+				continue;
+			}
+			sv = svnew;
+			svnum++;
+#if CHECK_DEVNO_TOO
+			sv[i].dev = s.st_dev;
+#endif
+			sv[i].ino = s.st_ino;
+ run_ith_sv:
+			sv[i].pid = runsv(d->d_name);
+			sv[i].isgone = 0;
+		}
+ next_dentry: ;
+	}
+	i = errno;
+	closedir(dir);
+	if (i) { /* readdir failed */
+		warn2_cannot("read directory ", svdir);
+		return 1; /* need to rescan again soon */
+	}
+
+	/* Send SIGTERM to runsv whose directories
+	 * were no longer found (-> must have been removed) */
+	for (i = 0; i < svnum; i++) {
+		if (!sv[i].isgone)
+			continue;
+		if (sv[i].pid)
+			kill(sv[i].pid, SIGTERM);
+		svnum--;
+		sv[i] = sv[svnum];
+		i--; /* so that we don't skip new sv[i] (bug was here!) */
+	}
+	return need_rescan;
+}
+
+int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runsvdir_main(int argc UNUSED_PARAM, char **argv)
+{
+	struct stat s;
+	dev_t last_dev = last_dev; /* for gcc */
+	ino_t last_ino = last_ino; /* for gcc */
+	time_t last_mtime = 0;
+	int wstat;
+	int curdir;
+	pid_t pid;
+	unsigned deadline;
+	unsigned now;
+	unsigned stampcheck;
+	int i;
+	int need_rescan = 1;
+	char *opt_s_argv[3];
+
+	INIT_G();
+
+	opt_complementary = "-1";
+	opt_s_argv[0] = NULL;
+	opt_s_argv[2] = NULL;
+	getopt32(argv, "Ps:", &opt_s_argv[0]);
+	argv += optind;
+
+	bb_signals(0
+		| (1 << SIGTERM)
+		| (1 << SIGHUP)
+		/* For busybox's init, SIGTERM == reboot,
+		 * SIGUSR1 == halt
+		 * SIGUSR2 == poweroff
+		 * so we need to intercept SIGUSRn too.
+		 * Note that we do not implement actual reboot
+		 * (killall(TERM) + umount, etc), we just pause
+		 * respawing and avoid exiting (-> making kernel oops).
+		 * The user is responsible for the rest. */
+		| (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
+		, record_signo);
+	svdir = *argv++;
+
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+	/* setup log */
+	if (*argv) {
+		rplog = *argv;
+		rploglen = strlen(rplog);
+		if (rploglen < 7) {
+			warnx("log must have at least seven characters");
+		} else if (piped_pair(logpipe)) {
+			warnx("can't create pipe for log");
+		} else {
+			close_on_exec_on(logpipe.rd);
+			close_on_exec_on(logpipe.wr);
+			ndelay_on(logpipe.rd);
+			ndelay_on(logpipe.wr);
+			if (dup2(logpipe.wr, 2) == -1) {
+				warnx("can't set filedescriptor for log");
+			} else {
+				pfd[0].fd = logpipe.rd;
+				pfd[0].events = POLLIN;
+				stamplog = monotonic_sec();
+				goto run;
+			}
+		}
+		rplog = NULL;
+		warnx("log service disabled");
+	}
+ run:
+#endif
+	curdir = open(".", O_RDONLY|O_NDELAY);
+	if (curdir == -1)
+		fatal2_cannot("open current directory", "");
+	close_on_exec_on(curdir);
+
+	stampcheck = monotonic_sec();
+
+	for (;;) {
+		/* collect children */
+		for (;;) {
+			pid = wait_any_nohang(&wstat);
+			if (pid <= 0)
+				break;
+			for (i = 0; i < svnum; i++) {
+				if (pid == sv[i].pid) {
+					/* runsv has died */
+					sv[i].pid = 0;
+					need_rescan = 1;
+				}
+			}
+		}
+
+		now = monotonic_sec();
+		if ((int)(now - stampcheck) >= 0) {
+			/* wait at least a second */
+			stampcheck = now + 1;
+
+			if (stat(svdir, &s) != -1) {
+				if (need_rescan || s.st_mtime != last_mtime
+				 || s.st_ino != last_ino || s.st_dev != last_dev
+				) {
+					/* svdir modified */
+					if (chdir(svdir) != -1) {
+						last_mtime = s.st_mtime;
+						last_dev = s.st_dev;
+						last_ino = s.st_ino;
+						/* if the svdir changed this very second, wait until the
+						 * next second, because we won't be able to detect more
+						 * changes within this second */
+						while (time(NULL) == last_mtime)
+							usleep(100000);
+						need_rescan = do_rescan();
+						while (fchdir(curdir) == -1) {
+							warn2_cannot("change directory, pausing", "");
+							sleep(5);
+						}
+					} else {
+						warn2_cannot("change directory to ", svdir);
+					}
+				}
+			} else {
+				warn2_cannot("stat ", svdir);
+			}
+		}
+
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+		if (rplog) {
+			if ((int)(now - stamplog) >= 0) {
+				write(logpipe.wr, ".", 1);
+				stamplog = now + 900;
+			}
+		}
+		pfd[0].revents = 0;
+#endif
+		deadline = (need_rescan ? 1 : 5);
+		sig_block(SIGCHLD);
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+		if (rplog)
+			poll(pfd, 1, deadline*1000);
+		else
+#endif
+			sleep(deadline);
+		sig_unblock(SIGCHLD);
+
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+		if (pfd[0].revents & POLLIN) {
+			char ch;
+			while (read(logpipe.rd, &ch, 1) > 0) {
+				if (ch < ' ')
+					ch = ' ';
+				for (i = 6; i < rploglen; i++)
+					rplog[i-1] = rplog[i];
+				rplog[rploglen-1] = ch;
+			}
+		}
+#endif
+		if (!bb_got_signal)
+			continue;
+
+		/* -s SCRIPT: useful if we are init.
+		 * In this case typically script never returns,
+		 * it halts/powers off/reboots the system. */
+		if (opt_s_argv[0]) {
+			/* Single parameter: signal# */
+			opt_s_argv[1] = utoa(bb_got_signal);
+			pid = spawn(opt_s_argv);
+			if (pid > 0) {
+				/* Remembering to wait for _any_ children,
+				 * not just pid */
+				while (wait(NULL) != pid)
+					continue;
+			}
+		}
+
+		if (bb_got_signal == SIGHUP) {
+			for (i = 0; i < svnum; i++)
+				if (sv[i].pid)
+					kill(sv[i].pid, SIGTERM);
+		}
+		/* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
+		/* Exit unless we are init */
+		if (getpid() != 1)
+			return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
+
+		/* init continues to monitor services forever */
+		bb_got_signal = 0;
+	} /* for (;;) */
+}
diff --git a/ap/app/busybox/src/runit/sv.c b/ap/app/busybox/src/runit/sv.c
new file mode 100644
index 0000000..5b01c87
--- /dev/null
+++ b/ap/app/busybox/src/runit/sv.c
@@ -0,0 +1,618 @@
+/*
+Copyright (c) 2001-2006, Gerrit Pape
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
+
+sv - control and manage services monitored by runsv
+
+sv [-v] [-w sec] command services
+/etc/init.d/service [-w sec] command
+
+The sv program reports the current status and controls the state of services
+monitored by the runsv(8) supervisor.
+
+services consists of one or more arguments, each argument naming a directory
+service used by runsv(8). If service doesn't start with a dot or slash,
+it is searched in the default services directory /var/service/, otherwise
+relative to the current directory.
+
+command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
+1, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
+force-reload, force-restart, force-shutdown.
+
+The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
+script interface. The service to be controlled then is specified by the
+base name of the "init script".
+
+status
+    Report the current status of the service, and the appendant log service
+    if available, to standard output.
+up
+    If the service is not running, start it. If the service stops, restart it.
+down
+    If the service is running, send it the TERM signal, and the CONT signal.
+    If ./run exits, start ./finish if it exists. After it stops, do not
+    restart service.
+once
+    If the service is not running, start it. Do not restart it if it stops.
+pause cont hup alarm interrupt quit 1 2 term kill
+    If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
+    USR1, USR2, TERM, or KILL signal respectively.
+exit
+    If the service is running, send it the TERM signal, and the CONT signal.
+    Do not restart the service. If the service is down, and no log service
+    exists, runsv(8) exits. If the service is down and a log service exists,
+    send the TERM signal to the log service. If the log service is down,
+    runsv(8) exits. This command is ignored if it is given to an appendant
+    log service.
+
+sv actually looks only at the first character of above commands.
+
+Commands compatible to LSB init script actions:
+
+status
+    Same as status.
+start
+    Same as up, but wait up to 7 seconds for the command to take effect.
+    Then report the status or timeout. If the script ./check exists in
+    the service directory, sv runs this script to check whether the service
+    is up and available; it's considered to be available if ./check exits
+    with 0.
+stop
+    Same as down, but wait up to 7 seconds for the service to become down.
+    Then report the status or timeout.
+restart
+    Send the commands term, cont, and up to the service, and wait up to
+    7 seconds for the service to restart. Then report the status or timeout.
+    If the script ./check exists in the service directory, sv runs this script
+    to check whether the service is up and available again; it's considered
+    to be available if ./check exits with 0.
+shutdown
+    Same as exit, but wait up to 7 seconds for the runsv(8) process
+    to terminate. Then report the status or timeout.
+force-stop
+    Same as down, but wait up to 7 seconds for the service to become down.
+    Then report the status, and on timeout send the service the kill command.
+force-reload
+    Send the service the term and cont commands, and wait up to
+    7 seconds for the service to restart. Then report the status,
+    and on timeout send the service the kill command.
+force-restart
+    Send the service the term, cont and up commands, and wait up to
+    7 seconds for the service to restart. Then report the status, and
+    on timeout send the service the kill command. If the script ./check
+    exists in the service directory, sv runs this script to check whether
+    the service is up and available again; it's considered to be available
+    if ./check exits with 0.
+force-shutdown
+    Same as exit, but wait up to 7 seconds for the runsv(8) process to
+    terminate. Then report the status, and on timeout send the service
+    the kill command.
+
+Additional Commands
+
+check
+    Check for the service to be in the state that's been requested. Wait up to
+    7 seconds for the service to reach the requested state, then report
+    the status or timeout. If the requested state of the service is up,
+    and the script ./check exists in the service directory, sv runs
+    this script to check whether the service is up and running;
+    it's considered to be up if ./check exits with 0.
+
+Options
+
+-v
+    wait up to 7 seconds for the command to take effect.
+    Then report the status or timeout.
+-w sec
+    Override the default timeout of 7 seconds with sec seconds. Implies -v.
+
+Environment
+
+SVDIR
+    The environment variable $SVDIR overrides the default services directory
+    /var/service.
+SVWAIT
+    The environment variable $SVWAIT overrides the default 7 seconds to wait
+    for a command to take effect. It is overridden by the -w option.
+
+Exit Codes
+    sv exits 0, if the command was successfully sent to all services, and,
+    if it was told to wait, the command has taken effect to all services.
+
+    For each service that caused an error (e.g. the directory is not
+    controlled by a runsv(8) process, or sv timed out while waiting),
+    sv increases the exit code by one and exits non zero. The maximum
+    is 99. sv exits 100 on error.
+*/
+
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+//usage:#define sv_trivial_usage
+//usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
+//usage:#define sv_full_usage "\n\n"
+//usage:       "Control services monitored by runsv supervisor.\n"
+//usage:       "Commands (only first character is enough):\n"
+//usage:       "\n"
+//usage:       "status: query service status\n"
+//usage:       "up: if service isn't running, start it. If service stops, restart it\n"
+//usage:       "once: like 'up', but if service stops, don't restart it\n"
+//usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
+//usage:       "	if it exists. After it stops, don't restart service\n"
+//usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
+//usage:       "	runsv exits too\n"
+//usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
+//usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+struct globals {
+	const char *acts;
+	char **service;
+	unsigned rc;
+/* "Bernstein" time format: unix + 0x400000000000000aULL */
+	uint64_t tstart, tnow;
+	svstatus_t svstatus;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define acts         (G.acts        )
+#define service      (G.service     )
+#define rc           (G.rc          )
+#define tstart       (G.tstart      )
+#define tnow         (G.tnow        )
+#define svstatus     (G.svstatus    )
+#define INIT_G() do { } while (0)
+
+
+#define str_equal(s,t) (!strcmp((s), (t)))
+
+
+static void fatal_cannot(const char *m1) NORETURN;
+static void fatal_cannot(const char *m1)
+{
+	bb_perror_msg("fatal: can't %s", m1);
+	_exit(151);
+}
+
+static void out(const char *p, const char *m1)
+{
+	printf("%s%s: %s", p, *service, m1);
+	if (errno) {
+		printf(": %s", strerror(errno));
+	}
+	bb_putchar('\n'); /* will also flush the output */
+}
+
+#define WARN    "warning: "
+#define OK      "ok: "
+
+static void fail(const char *m1)
+{
+	++rc;
+	out("fail: ", m1);
+}
+static void failx(const char *m1)
+{
+	errno = 0;
+	fail(m1);
+}
+static void warn(const char *m1)
+{
+	++rc;
+	/* "warning: <service>: <m1>\n" */
+	out("warning: ", m1);
+}
+static void ok(const char *m1)
+{
+	errno = 0;
+	out(OK, m1);
+}
+
+static int svstatus_get(void)
+{
+	int fd, r;
+
+	fd = open("supervise/ok", O_WRONLY|O_NDELAY);
+	if (fd == -1) {
+		if (errno == ENODEV) {
+			*acts == 'x' ? ok("runsv not running")
+			             : failx("runsv not running");
+			return 0;
+		}
+		warn("can't open supervise/ok");
+		return -1;
+	}
+	close(fd);
+	fd = open("supervise/status", O_RDONLY|O_NDELAY);
+	if (fd == -1) {
+		warn("can't open supervise/status");
+		return -1;
+	}
+	r = read(fd, &svstatus, 20);
+	close(fd);
+	switch (r) {
+	case 20:
+		break;
+	case -1:
+		warn("can't read supervise/status");
+		return -1;
+	default:
+		errno = 0;
+		warn("can't read supervise/status: bad format");
+		return -1;
+	}
+	return 1;
+}
+
+static unsigned svstatus_print(const char *m)
+{
+	int diff;
+	int pid;
+	int normallyup = 0;
+	struct stat s;
+	uint64_t timestamp;
+
+	if (stat("down", &s) == -1) {
+		if (errno != ENOENT) {
+			bb_perror_msg(WARN"can't stat %s/down", *service);
+			return 0;
+		}
+		normallyup = 1;
+	}
+	pid = SWAP_LE32(svstatus.pid_le32);
+	timestamp = SWAP_BE64(svstatus.time_be64);
+	if (pid) {
+		switch (svstatus.run_or_finish) {
+		case 1: printf("run: "); break;
+		case 2: printf("finish: "); break;
+		}
+		printf("%s: (pid %d) ", m, pid);
+	} else {
+		printf("down: %s: ", m);
+	}
+	diff = tnow - timestamp;
+	printf("%us", (diff < 0 ? 0 : diff));
+	if (pid) {
+		if (!normallyup) printf(", normally down");
+		if (svstatus.paused) printf(", paused");
+		if (svstatus.want == 'd') printf(", want down");
+		if (svstatus.got_term) printf(", got TERM");
+	} else {
+		if (normallyup) printf(", normally up");
+		if (svstatus.want == 'u') printf(", want up");
+	}
+	return pid ? 1 : 2;
+}
+
+static int status(const char *unused UNUSED_PARAM)
+{
+	int r;
+
+	if (svstatus_get() <= 0)
+		return 0;
+
+	r = svstatus_print(*service);
+	if (chdir("log") == -1) {
+		if (errno != ENOENT) {
+			printf("; log: "WARN"can't change to log service directory: %s",
+					strerror(errno));
+		}
+	} else if (svstatus_get()) {
+		printf("; ");
+		svstatus_print("log");
+	}
+	bb_putchar('\n'); /* will also flush the output */
+	return r;
+}
+
+static int checkscript(void)
+{
+	char *prog[2];
+	struct stat s;
+	int pid, w;
+
+	if (stat("check", &s) == -1) {
+		if (errno == ENOENT) return 1;
+		bb_perror_msg(WARN"can't stat %s/check", *service);
+		return 0;
+	}
+	/* if (!(s.st_mode & S_IXUSR)) return 1; */
+	prog[0] = (char*)"./check";
+	prog[1] = NULL;
+	pid = spawn(prog);
+	if (pid <= 0) {
+		bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
+		return 0;
+	}
+	while (safe_waitpid(pid, &w, 0) == -1) {
+		bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
+		return 0;
+	}
+	return WEXITSTATUS(w) == 0;
+}
+
+static int check(const char *a)
+{
+	int r;
+	unsigned pid_le32;
+	uint64_t timestamp;
+
+	r = svstatus_get();
+	if (r == -1)
+		return -1;
+	if (r == 0) {
+		if (*a == 'x')
+			return 1;
+		return -1;
+	}
+	pid_le32 = svstatus.pid_le32;
+	switch (*a) {
+	case 'x':
+		return 0;
+	case 'u':
+		if (!pid_le32 || svstatus.run_or_finish != 1) return 0;
+		if (!checkscript()) return 0;
+		break;
+	case 'd':
+		if (pid_le32) return 0;
+		break;
+	case 'c':
+		if (pid_le32 && !checkscript()) return 0;
+		break;
+	case 't':
+		if (!pid_le32 && svstatus.want == 'd') break;
+		timestamp = SWAP_BE64(svstatus.time_be64);
+		if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
+			return 0;
+		break;
+	case 'o':
+		timestamp = SWAP_BE64(svstatus.time_be64);
+		if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
+			return 0;
+	}
+	printf(OK);
+	svstatus_print(*service);
+	bb_putchar('\n'); /* will also flush the output */
+	return 1;
+}
+
+static int control(const char *a)
+{
+	int fd, r, l;
+
+/* Is it an optimization?
+   It causes problems with "sv o SRV; ...; sv d SRV"
+   ('d' is not passed to SRV because its .want == 'd'):
+	if (svstatus_get() <= 0)
+		return -1;
+	if (svstatus.want == *a)
+		return 0;
+*/
+	fd = open("supervise/control", O_WRONLY|O_NDELAY);
+	if (fd == -1) {
+		if (errno != ENODEV)
+			warn("can't open supervise/control");
+		else
+			*a == 'x' ? ok("runsv not running") : failx("runsv not running");
+		return -1;
+	}
+	l = strlen(a);
+	r = write(fd, a, l);
+	close(fd);
+	if (r != l) {
+		warn("can't write to supervise/control");
+		return -1;
+	}
+	return 1;
+}
+
+int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sv_main(int argc UNUSED_PARAM, char **argv)
+{
+	char *x;
+	char *action;
+	const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
+	unsigned waitsec = 7;
+	smallint kll = 0;
+	int verbose = 0;
+	int (*act)(const char*);
+	int (*cbk)(const char*);
+	int curdir;
+
+	INIT_G();
+
+	xfunc_error_retval = 100;
+
+	x = getenv("SVDIR");
+	if (x) varservice = x;
+	x = getenv("SVWAIT");
+	if (x) waitsec = xatou(x);
+
+	opt_complementary = "w+:vv"; /* -w N, -v is a counter */
+	getopt32(argv, "w:v", &waitsec, &verbose);
+	argv += optind;
+	action = *argv++;
+	if (!action || !*argv) bb_show_usage();
+
+	tnow = time(NULL) + 0x400000000000000aULL;
+	tstart = tnow;
+	curdir = open(".", O_RDONLY|O_NDELAY);
+	if (curdir == -1)
+		fatal_cannot("open current directory");
+
+	act = &control;
+	acts = "s";
+	cbk = &check;
+
+	switch (*action) {
+	case 'x':
+	case 'e':
+		acts = "x";
+		if (!verbose) cbk = NULL;
+		break;
+	case 'X':
+	case 'E':
+		acts = "x";
+		kll = 1;
+		break;
+	case 'D':
+		acts = "d";
+		kll = 1;
+		break;
+	case 'T':
+		acts = "tc";
+		kll = 1;
+		break;
+	case 'c':
+		if (str_equal(action, "check")) {
+			act = NULL;
+			acts = "c";
+			break;
+		}
+	case 'u': case 'd': case 'o': case 't': case 'p': case 'h':
+	case 'a': case 'i': case 'k': case 'q': case '1': case '2':
+		action[1] = '\0';
+		acts = action;
+		if (!verbose) cbk = NULL;
+		break;
+	case 's':
+		if (str_equal(action, "shutdown")) {
+			acts = "x";
+			break;
+		}
+		if (str_equal(action, "start")) {
+			acts = "u";
+			break;
+		}
+		if (str_equal(action, "stop")) {
+			acts = "d";
+			break;
+		}
+		/* "status" */
+		act = &status;
+		cbk = NULL;
+		break;
+	case 'r':
+		if (str_equal(action, "restart")) {
+			acts = "tcu";
+			break;
+		}
+		bb_show_usage();
+	case 'f':
+		if (str_equal(action, "force-reload")) {
+			acts = "tc";
+			kll = 1;
+			break;
+		}
+		if (str_equal(action, "force-restart")) {
+			acts = "tcu";
+			kll = 1;
+			break;
+		}
+		if (str_equal(action, "force-shutdown")) {
+			acts = "x";
+			kll = 1;
+			break;
+		}
+		if (str_equal(action, "force-stop")) {
+			acts = "d";
+			kll = 1;
+			break;
+		}
+	default:
+		bb_show_usage();
+	}
+
+	service = argv;
+	while ((x = *service) != NULL) {
+		if (x[0] != '/' && x[0] != '.') {
+			if (chdir(varservice) == -1)
+				goto chdir_failed_0;
+		}
+		if (chdir(x) == -1) {
+ chdir_failed_0:
+			fail("can't change to service directory");
+			goto nullify_service_0;
+		}
+		if (act && (act(acts) == -1)) {
+ nullify_service_0:
+			*service = (char*) -1L; /* "dead" */
+		}
+		if (fchdir(curdir) == -1)
+			fatal_cannot("change to original directory");
+		service++;
+	}
+
+	if (cbk) while (1) {
+		int want_exit;
+		int diff;
+
+		diff = tnow - tstart;
+		service = argv;
+		want_exit = 1;
+		while ((x = *service) != NULL) {
+			if (x == (char*) -1L) /* "dead" */
+				goto next;
+			if (x[0] != '/' && x[0] != '.') {
+				if (chdir(varservice) == -1)
+					goto chdir_failed;
+			}
+			if (chdir(x) == -1) {
+ chdir_failed:
+				fail("can't change to service directory");
+				goto nullify_service;
+			}
+			if (cbk(acts) != 0)
+				goto nullify_service;
+			want_exit = 0;
+			if (diff >= waitsec) {
+				printf(kll ? "kill: " : "timeout: ");
+				if (svstatus_get() > 0) {
+					svstatus_print(x);
+					++rc;
+				}
+				bb_putchar('\n'); /* will also flush the output */
+				if (kll)
+					control("k");
+ nullify_service:
+				*service = (char*) -1L; /* "dead" */
+			}
+			if (fchdir(curdir) == -1)
+				fatal_cannot("change to original directory");
+ next:
+			service++;
+		}
+		if (want_exit) break;
+		usleep(420000);
+		tnow = time(NULL) + 0x400000000000000aULL;
+	}
+	return rc > 99 ? 99 : rc;
+}
diff --git a/ap/app/busybox/src/runit/svlogd.c b/ap/app/busybox/src/runit/svlogd.c
new file mode 100644
index 0000000..b7a0a6e
--- /dev/null
+++ b/ap/app/busybox/src/runit/svlogd.c
@@ -0,0 +1,1232 @@
+/*
+Copyright (c) 2001-2006, Gerrit Pape
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+/*
+Config files
+
+On startup, and after receiving a HUP signal, svlogd checks for each
+log directory log if the configuration file log/config exists,
+and if so, reads the file line by line and adjusts configuration
+for log as follows:
+
+If the line is empty, or starts with a #, it is ignored. A line
+of the form
+
+ssize
+    sets the maximum file size of current when svlogd should rotate
+    the current log file to size bytes. Default is 1000000.
+    If size is zero, svlogd doesnt rotate log files
+    You should set size to at least (2 * len).
+nnum
+    sets the number of old log files svlogd should maintain to num.
+    If svlogd sees more that num old log files in log after log file
+    rotation, it deletes the oldest one. Default is 10.
+    If num is zero, svlogd doesnt remove old log files.
+Nmin
+    sets the minimum number of old log files svlogd should maintain
+    to min. min must be less than num. If min is set, and svlogd
+    cannot write to current because the filesystem is full,
+    and it sees more than min old log files, it deletes the oldest one.
+ttimeout
+    sets the maximum age of the current log file when svlogd should
+    rotate the current log file to timeout seconds. If current
+    is timeout seconds old, and is not empty, svlogd forces log file rotation.
+!processor
+    tells svlogd to feed each recent log file through processor
+    (see above) on log file rotation. By default log files are not processed.
+ua.b.c.d[:port]
+    tells svlogd to transmit the first len characters of selected
+    log messages to the IP address a.b.c.d, port number port.
+    If port isnt set, the default port for syslog is used (514).
+    len can be set through the -l option, see below. If svlogd
+    has trouble sending udp packets, it writes error messages
+    to the log directory. Attention: logging through udp is unreliable,
+    and should be used in private networks only.
+Ua.b.c.d[:port]
+    is the same as the u line above, but the log messages are no longer
+    written to the log directory, but transmitted through udp only.
+    Error messages from svlogd concerning sending udp packages still go
+    to the log directory.
+pprefix
+    tells svlogd to prefix each line to be written to the log directory,
+    to standard error, or through UDP, with prefix.
+
+If a line starts with a -, +, e, or E, svlogd matches the first len characters
+of each log message against pattern and acts accordingly:
+
+-pattern
+    the log message is deselected.
++pattern
+    the log message is selected.
+epattern
+    the log message is selected to be printed to standard error.
+Epattern
+    the log message is deselected to be printed to standard error.
+
+Initially each line is selected to be written to log/current. Deselected
+log messages are discarded from log. Initially each line is deselected
+to be written to standard err. Log messages selected for standard error
+are written to standard error.
+
+Pattern Matching
+
+svlogd matches a log message against the string pattern as follows:
+
+pattern is applied to the log message one character by one, starting
+with the first. A character not a star (*) and not a plus (+) matches itself.
+A plus matches the next character in pattern in the log message one
+or more times. A star before the end of pattern matches any string
+in the log message that does not include the next character in pattern.
+A star at the end of pattern matches any string.
+
+Timestamps optionally added by svlogd are not considered part
+of the log message.
+
+An svlogd pattern is not a regular expression. For example consider
+a log message like this
+
+2005-12-18_09:13:50.97618 tcpsvd: info: pid 1977 from 10.4.1.14
+
+The following pattern doesnt match
+
+-*pid*
+
+because the first star matches up to the first p in tcpsvd,
+and then the match fails because i is not s. To match this
+log message, you can use a pattern like this instead
+
+-*: *: pid *
+*/
+
+//usage:#define svlogd_trivial_usage
+//usage:       "[-ttv] [-r C] [-R CHARS] [-l MATCHLEN] [-b BUFLEN] DIR..."
+//usage:#define svlogd_full_usage "\n\n"
+//usage:       "Continuously read log data from stdin and write to rotated log files in DIRs"
+//usage:   "\n"
+//usage:   "\n""DIR/config file modifies behavior:"
+//usage:   "\n""sSIZE - when to rotate logs"
+//usage:   "\n""nNUM - number of files to retain"
+/*usage:   "\n""NNUM - min number files to retain" - confusing */
+/*usage:   "\n""tSEC - rotate file if it get SEC seconds old" - confusing */
+//usage:   "\n""!PROG - process rotated log with PROG"
+/*usage:   "\n""uIPADDR - send log over UDP" - unsupported */
+/*usage:   "\n""UIPADDR - send log over UDP and DONT log" - unsupported */
+/*usage:   "\n""pPFX - prefix each line with PFX" - unsupported */
+//usage:   "\n""+,-PATTERN - (de)select line for logging"
+//usage:   "\n""E,ePATTERN - (de)select line for stderr"
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
+
+#define FMT_PTIME 30
+
+struct logdir {
+	////char *btmp;
+	/* pattern list to match, in "aa\0bb\0\cc\0\0" form */
+	char *inst;
+	char *processor;
+	char *name;
+	unsigned size;
+	unsigned sizemax;
+	unsigned nmax;
+	unsigned nmin;
+	unsigned rotate_period;
+	int ppid;
+	int fddir;
+	int fdcur;
+	FILE* filecur; ////
+	int fdlock;
+	unsigned next_rotate;
+	char fnsave[FMT_PTIME];
+	char match;
+	char matcherr;
+};
+
+
+struct globals {
+	struct logdir *dir;
+	unsigned verbose;
+	int linemax;
+	////int buflen;
+	int linelen;
+
+	int fdwdir;
+	char **fndir;
+	int wstat;
+	unsigned nearest_rotate;
+
+	void* (*memRchr)(const void *, int, size_t);
+	char *shell;
+
+	smallint exitasap;
+	smallint rotateasap;
+	smallint reopenasap;
+	smallint linecomplete;
+	smallint tmaxflag;
+
+	char repl;
+	const char *replace;
+	int fl_flag_0;
+	unsigned dirn;
+
+	sigset_t blocked_sigset;
+};
+#define G (*ptr_to_globals)
+#define dir            (G.dir           )
+#define verbose        (G.verbose       )
+#define linemax        (G.linemax       )
+#define buflen         (G.buflen        )
+#define linelen        (G.linelen       )
+#define fndir          (G.fndir         )
+#define fdwdir         (G.fdwdir        )
+#define wstat          (G.wstat         )
+#define memRchr        (G.memRchr       )
+#define nearest_rotate (G.nearest_rotate)
+#define exitasap       (G.exitasap      )
+#define rotateasap     (G.rotateasap    )
+#define reopenasap     (G.reopenasap    )
+#define linecomplete   (G.linecomplete  )
+#define tmaxflag       (G.tmaxflag      )
+#define repl           (G.repl          )
+#define replace        (G.replace       )
+#define blocked_sigset (G.blocked_sigset)
+#define fl_flag_0      (G.fl_flag_0     )
+#define dirn           (G.dirn          )
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+	linemax = 1000; \
+	/*buflen = 1024;*/ \
+	linecomplete = 1; \
+	replace = ""; \
+} while (0)
+
+#define line bb_common_bufsiz1
+
+
+#define FATAL "fatal: "
+#define WARNING "warning: "
+#define PAUSE "pausing: "
+#define INFO "info: "
+
+static void fatalx(const char *m0)
+{
+	bb_error_msg_and_die(FATAL"%s", m0);
+}
+static void warn(const char *m0)
+{
+	bb_perror_msg(WARNING"%s", m0);
+}
+static void warn2(const char *m0, const char *m1)
+{
+	bb_perror_msg(WARNING"%s: %s", m0, m1);
+}
+static void warnx(const char *m0, const char *m1)
+{
+	bb_error_msg(WARNING"%s: %s", m0, m1);
+}
+static void pause_nomem(void)
+{
+	bb_error_msg(PAUSE"out of memory");
+	sleep(3);
+}
+static void pause1cannot(const char *m0)
+{
+	bb_perror_msg(PAUSE"can't %s", m0);
+	sleep(3);
+}
+static void pause2cannot(const char *m0, const char *m1)
+{
+	bb_perror_msg(PAUSE"can't %s %s", m0, m1);
+	sleep(3);
+}
+
+static char* wstrdup(const char *str)
+{
+	char *s;
+	while (!(s = strdup(str)))
+		pause_nomem();
+	return s;
+}
+
+static unsigned pmatch(const char *p, const char *s, unsigned len)
+{
+	for (;;) {
+		char c = *p++;
+		if (!c) return !len;
+		switch (c) {
+		case '*':
+			c = *p;
+			if (!c) return 1;
+			for (;;) {
+				if (!len) return 0;
+				if (*s == c) break;
+				++s;
+				--len;
+			}
+			continue;
+		case '+':
+			c = *p++;
+			if (c != *s) return 0;
+			for (;;) {
+				if (!len) return 1;
+				if (*s != c) break;
+				++s;
+				--len;
+			}
+			continue;
+			/*
+		case '?':
+			if (*p == '?') {
+				if (*s != '?') return 0;
+				++p;
+			}
+			++s; --len;
+			continue;
+			*/
+		default:
+			if (!len) return 0;
+			if (*s != c) return 0;
+			++s;
+			--len;
+			continue;
+		}
+	}
+	return 0;
+}
+
+/*** ex fmt_ptime.[ch] ***/
+
+/* NUL terminated */
+static void fmt_time_human_30nul(char *s)
+{
+	struct tm *ptm;
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
+	ptm = gmtime(&tv.tv_sec);
+	sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
+		(unsigned)(1900 + ptm->tm_year),
+		(unsigned)(ptm->tm_mon + 1),
+		(unsigned)(ptm->tm_mday),
+		(unsigned)(ptm->tm_hour),
+		(unsigned)(ptm->tm_min),
+		(unsigned)(ptm->tm_sec),
+		(unsigned)(tv.tv_usec)
+	);
+	/* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
+	/* 5   + 3   + 3   + 3   + 3   + 3   + 9 = */
+	/* 20 (up to '.' inclusive) + 9 (not including '\0') */
+}
+
+/* NOT terminated! */
+static void fmt_time_bernstein_25(char *s)
+{
+	uint32_t pack[3];
+	struct timeval tv;
+	unsigned sec_hi;
+
+	gettimeofday(&tv, NULL);
+	sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
+	tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
+	tv.tv_usec *= 1000;
+	/* Network order is big-endian: most significant byte first.
+	 * This is exactly what we want here */
+	pack[0] = htonl(sec_hi);
+	pack[1] = htonl(tv.tv_sec);
+	pack[2] = htonl(tv.tv_usec);
+	*s++ = '@';
+	bin2hex(s, (char*)pack, 12);
+}
+
+static void processorstart(struct logdir *ld)
+{
+	char sv_ch;
+	int pid;
+
+	if (!ld->processor) return;
+	if (ld->ppid) {
+		warnx("processor already running", ld->name);
+		return;
+	}
+
+	/* vfork'ed child trashes this byte, save... */
+	sv_ch = ld->fnsave[26];
+
+	if (!G.shell)
+		G.shell = xstrdup(get_shell_name());
+
+	while ((pid = vfork()) == -1)
+		pause2cannot("vfork for processor", ld->name);
+	if (!pid) {
+		int fd;
+
+		/* child */
+		/* Non-ignored signals revert to SIG_DFL on exec anyway */
+		/*bb_signals(0
+			+ (1 << SIGTERM)
+			+ (1 << SIGALRM)
+			+ (1 << SIGHUP)
+			, SIG_DFL);*/
+		sig_unblock(SIGTERM);
+		sig_unblock(SIGALRM);
+		sig_unblock(SIGHUP);
+
+		if (verbose)
+			bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
+		fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
+		xmove_fd(fd, 0);
+		ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
+		fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
+		xmove_fd(fd, 1);
+		fd = open("state", O_RDONLY|O_NDELAY);
+		if (fd == -1) {
+			if (errno != ENOENT)
+				bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
+			close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
+			fd = xopen("state", O_RDONLY|O_NDELAY);
+		}
+		xmove_fd(fd, 4);
+		fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
+		xmove_fd(fd, 5);
+
+		execl(G.shell, G.shell, "-c", ld->processor, (char*) NULL);
+		bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
+	}
+	ld->fnsave[26] = sv_ch; /* ...restore */
+	ld->ppid = pid;
+}
+
+static unsigned processorstop(struct logdir *ld)
+{
+	char f[28];
+
+	if (ld->ppid) {
+		sig_unblock(SIGHUP);
+		while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
+			pause2cannot("wait for processor", ld->name);
+		sig_block(SIGHUP);
+		ld->ppid = 0;
+	}
+	if (ld->fddir == -1)
+		return 1;
+	while (fchdir(ld->fddir) == -1)
+		pause2cannot("change directory, want processor", ld->name);
+	if (WEXITSTATUS(wstat) != 0) {
+		warnx("processor failed, restart", ld->name);
+		ld->fnsave[26] = 't';
+		unlink(ld->fnsave);
+		ld->fnsave[26] = 'u';
+		processorstart(ld);
+		while (fchdir(fdwdir) == -1)
+			pause1cannot("change to initial working directory");
+		return ld->processor ? 0 : 1;
+	}
+	ld->fnsave[26] = 't';
+	memcpy(f, ld->fnsave, 26);
+	f[26] = 's';
+	f[27] = '\0';
+	while (rename(ld->fnsave, f) == -1)
+		pause2cannot("rename processed", ld->name);
+	while (chmod(f, 0744) == -1)
+		pause2cannot("set mode of processed", ld->name);
+	ld->fnsave[26] = 'u';
+	if (unlink(ld->fnsave) == -1)
+		bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
+	while (rename("newstate", "state") == -1)
+		pause2cannot("rename state", ld->name);
+	if (verbose)
+		bb_error_msg(INFO"processed: %s/%s", ld->name, f);
+	while (fchdir(fdwdir) == -1)
+		pause1cannot("change to initial working directory");
+	return 1;
+}
+
+static void rmoldest(struct logdir *ld)
+{
+	DIR *d;
+	struct dirent *f;
+	char oldest[FMT_PTIME];
+	int n = 0;
+
+	oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
+	while (!(d = opendir(".")))
+		pause2cannot("open directory, want rotate", ld->name);
+	errno = 0;
+	while ((f = readdir(d))) {
+		if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
+			if (f->d_name[26] == 't') {
+				if (unlink(f->d_name) == -1)
+					warn2("can't unlink processor leftover", f->d_name);
+			} else {
+				++n;
+				if (strcmp(f->d_name, oldest) < 0)
+					memcpy(oldest, f->d_name, 27);
+			}
+			errno = 0;
+		}
+	}
+	if (errno)
+		warn2("can't read directory", ld->name);
+	closedir(d);
+
+	if (ld->nmax && (n > ld->nmax)) {
+		if (verbose)
+			bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
+		if ((*oldest == '@') && (unlink(oldest) == -1))
+			warn2("can't unlink oldest logfile", ld->name);
+	}
+}
+
+static unsigned rotate(struct logdir *ld)
+{
+	struct stat st;
+	unsigned now;
+
+	if (ld->fddir == -1) {
+		ld->rotate_period = 0;
+		return 0;
+	}
+	if (ld->ppid)
+		while (!processorstop(ld))
+			continue;
+
+	while (fchdir(ld->fddir) == -1)
+		pause2cannot("change directory, want rotate", ld->name);
+
+	/* create new filename */
+	ld->fnsave[25] = '.';
+	ld->fnsave[26] = 's';
+	if (ld->processor)
+		ld->fnsave[26] = 'u';
+	ld->fnsave[27] = '\0';
+	do {
+		fmt_time_bernstein_25(ld->fnsave);
+		errno = 0;
+		stat(ld->fnsave, &st);
+	} while (errno != ENOENT);
+
+	now = monotonic_sec();
+	if (ld->rotate_period && LESS(ld->next_rotate, now)) {
+		ld->next_rotate = now + ld->rotate_period;
+		if (LESS(ld->next_rotate, nearest_rotate))
+			nearest_rotate = ld->next_rotate;
+	}
+
+	if (ld->size > 0) {
+		while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
+			pause2cannot("fsync current logfile", ld->name);
+		while (fchmod(ld->fdcur, 0744) == -1)
+			pause2cannot("set mode of current", ld->name);
+		////close(ld->fdcur);
+		fclose(ld->filecur);
+
+		if (verbose) {
+			bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
+					ld->fnsave, ld->size);
+		}
+		while (rename("current", ld->fnsave) == -1)
+			pause2cannot("rename current", ld->name);
+		while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
+			pause2cannot("create new current", ld->name);
+		while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
+			pause2cannot("create new current", ld->name); /* very unlikely */
+		setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
+		close_on_exec_on(ld->fdcur);
+		ld->size = 0;
+		while (fchmod(ld->fdcur, 0644) == -1)
+			pause2cannot("set mode of current", ld->name);
+
+		rmoldest(ld);
+		processorstart(ld);
+	}
+
+	while (fchdir(fdwdir) == -1)
+		pause1cannot("change to initial working directory");
+	return 1;
+}
+
+static int buffer_pwrite(int n, char *s, unsigned len)
+{
+	int i;
+	struct logdir *ld = &dir[n];
+
+	if (ld->sizemax) {
+		if (ld->size >= ld->sizemax)
+			rotate(ld);
+		if (len > (ld->sizemax - ld->size))
+			len = ld->sizemax - ld->size;
+	}
+	while (1) {
+		////i = full_write(ld->fdcur, s, len);
+		////if (i != -1) break;
+		i = fwrite(s, 1, len, ld->filecur);
+		if (i == len) break;
+
+		if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
+			DIR *d;
+			struct dirent *f;
+			char oldest[FMT_PTIME];
+			int j = 0;
+
+			while (fchdir(ld->fddir) == -1)
+				pause2cannot("change directory, want remove old logfile",
+							ld->name);
+			oldest[0] = 'A';
+			oldest[1] = oldest[27] = '\0';
+			while (!(d = opendir(".")))
+				pause2cannot("open directory, want remove old logfile",
+							ld->name);
+			errno = 0;
+			while ((f = readdir(d)))
+				if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
+					++j;
+					if (strcmp(f->d_name, oldest) < 0)
+						memcpy(oldest, f->d_name, 27);
+				}
+			if (errno) warn2("can't read directory, want remove old logfile",
+					ld->name);
+			closedir(d);
+			errno = ENOSPC;
+			if (j > ld->nmin) {
+				if (*oldest == '@') {
+					bb_error_msg(WARNING"out of disk space, delete: %s/%s",
+							ld->name, oldest);
+					errno = 0;
+					if (unlink(oldest) == -1) {
+						warn2("can't unlink oldest logfile", ld->name);
+						errno = ENOSPC;
+					}
+					while (fchdir(fdwdir) == -1)
+						pause1cannot("change to initial working directory");
+				}
+			}
+		}
+		if (errno)
+			pause2cannot("write to current", ld->name);
+	}
+
+	ld->size += i;
+	if (ld->sizemax)
+		if (s[i-1] == '\n')
+			if (ld->size >= (ld->sizemax - linemax))
+				rotate(ld);
+	return i;
+}
+
+static void logdir_close(struct logdir *ld)
+{
+	if (ld->fddir == -1)
+		return;
+	if (verbose)
+		bb_error_msg(INFO"close: %s", ld->name);
+	close(ld->fddir);
+	ld->fddir = -1;
+	if (ld->fdcur == -1)
+		return; /* impossible */
+	while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
+		pause2cannot("fsync current logfile", ld->name);
+	while (fchmod(ld->fdcur, 0744) == -1)
+		pause2cannot("set mode of current", ld->name);
+	////close(ld->fdcur);
+	fclose(ld->filecur);
+	ld->fdcur = -1;
+	if (ld->fdlock == -1)
+		return; /* impossible */
+	close(ld->fdlock);
+	ld->fdlock = -1;
+	free(ld->processor);
+	ld->processor = NULL;
+}
+
+static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
+{
+	char buf[128];
+	unsigned now;
+	char *new, *s, *np;
+	int i;
+	struct stat st;
+
+	now = monotonic_sec();
+
+	ld->fddir = open(fn, O_RDONLY|O_NDELAY);
+	if (ld->fddir == -1) {
+		warn2("can't open log directory", (char*)fn);
+		return 0;
+	}
+	close_on_exec_on(ld->fddir);
+	if (fchdir(ld->fddir) == -1) {
+		logdir_close(ld);
+		warn2("can't change directory", (char*)fn);
+		return 0;
+	}
+	ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+	if ((ld->fdlock == -1)
+	 || (flock(ld->fdlock, LOCK_EX | LOCK_NB) == -1)
+	) {
+		logdir_close(ld);
+		warn2("can't lock directory", (char*)fn);
+		while (fchdir(fdwdir) == -1)
+			pause1cannot("change to initial working directory");
+		return 0;
+	}
+	close_on_exec_on(ld->fdlock);
+
+	ld->size = 0;
+	ld->sizemax = 1000000;
+	ld->nmax = ld->nmin = 10;
+	ld->rotate_period = 0;
+	ld->name = (char*)fn;
+	ld->ppid = 0;
+	ld->match = '+';
+	free(ld->inst); ld->inst = NULL;
+	free(ld->processor); ld->processor = NULL;
+
+	/* read config */
+	i = open_read_close("config", buf, sizeof(buf) - 1);
+	if (i < 0 && errno != ENOENT)
+		bb_perror_msg(WARNING"%s/config", ld->name);
+	if (i > 0) {
+		buf[i] = '\0';
+		if (verbose)
+			bb_error_msg(INFO"read: %s/config", ld->name);
+		s = buf;
+		while (s) {
+			np = strchr(s, '\n');
+			if (np)
+				*np++ = '\0';
+			switch (s[0]) {
+			case '+':
+			case '-':
+			case 'e':
+			case 'E':
+				/* Filtering requires one-line buffering,
+				 * resetting the "find newline" function
+				 * accordingly */
+				memRchr = memchr;
+				/* Add '\n'-terminated line to ld->inst */
+				while (1) {
+					int l = asprintf(&new, "%s%s\n", ld->inst ? ld->inst : "", s);
+					if (l >= 0 && new)
+						break;
+					pause_nomem();
+				}
+				free(ld->inst);
+				ld->inst = new;
+				break;
+			case 's': {
+				static const struct suffix_mult km_suffixes[] = {
+					{ "k", 1024 },
+					{ "m", 1024*1024 },
+					{ "", 0 }
+				};
+				ld->sizemax = xatou_sfx(&s[1], km_suffixes);
+				break;
+			}
+			case 'n':
+				ld->nmax = xatoi_positive(&s[1]);
+				break;
+			case 'N':
+				ld->nmin = xatoi_positive(&s[1]);
+				break;
+			case 't': {
+				static const struct suffix_mult mh_suffixes[] = {
+					{ "m", 60 },
+					{ "h", 60*60 },
+					/*{ "d", 24*60*60 },*/
+					{ "", 0 }
+				};
+				ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
+				if (ld->rotate_period) {
+					ld->next_rotate = now + ld->rotate_period;
+					if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
+						nearest_rotate = ld->next_rotate;
+					tmaxflag = 1;
+				}
+				break;
+			}
+			case '!':
+				if (s[1]) {
+					free(ld->processor);
+					ld->processor = wstrdup(s);
+				}
+				break;
+			}
+			s = np;
+		}
+		/* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
+		s = ld->inst;
+		while (s) {
+			np = strchr(s, '\n');
+			if (np)
+				*np++ = '\0';
+			s = np;
+		}
+	}
+
+	/* open current */
+	i = stat("current", &st);
+	if (i != -1) {
+		if (st.st_size && !(st.st_mode & S_IXUSR)) {
+			ld->fnsave[25] = '.';
+			ld->fnsave[26] = 'u';
+			ld->fnsave[27] = '\0';
+			do {
+				fmt_time_bernstein_25(ld->fnsave);
+				errno = 0;
+				stat(ld->fnsave, &st);
+			} while (errno != ENOENT);
+			while (rename("current", ld->fnsave) == -1)
+				pause2cannot("rename current", ld->name);
+			rmoldest(ld);
+			i = -1;
+		} else {
+			/* st.st_size can be not just bigger, but WIDER!
+			 * This code is safe: if st.st_size > 4GB, we select
+			 * ld->sizemax (because it's "unsigned") */
+			ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
+		}
+	} else {
+		if (errno != ENOENT) {
+			logdir_close(ld);
+			warn2("can't stat current", ld->name);
+			while (fchdir(fdwdir) == -1)
+				pause1cannot("change to initial working directory");
+			return 0;
+		}
+	}
+	while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
+		pause2cannot("open current", ld->name);
+	while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
+		pause2cannot("open current", ld->name); ////
+	setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
+
+	close_on_exec_on(ld->fdcur);
+	while (fchmod(ld->fdcur, 0644) == -1)
+		pause2cannot("set mode of current", ld->name);
+
+	if (verbose) {
+		if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
+		else bb_error_msg(INFO"new: %s/current", ld->name);
+	}
+
+	while (fchdir(fdwdir) == -1)
+		pause1cannot("change to initial working directory");
+	return 1;
+}
+
+static void logdirs_reopen(void)
+{
+	int l;
+	int ok = 0;
+
+	tmaxflag = 0;
+	for (l = 0; l < dirn; ++l) {
+		logdir_close(&dir[l]);
+		if (logdir_open(&dir[l], fndir[l]))
+			ok = 1;
+	}
+	if (!ok)
+		fatalx("no functional log directories");
+}
+
+/* Will look good in libbb one day */
+static ssize_t ndelay_read(int fd, void *buf, size_t count)
+{
+	if (!(fl_flag_0 & O_NONBLOCK))
+		fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
+	count = safe_read(fd, buf, count);
+	if (!(fl_flag_0 & O_NONBLOCK))
+		fcntl(fd, F_SETFL, fl_flag_0);
+	return count;
+}
+
+/* Used for reading stdin */
+static int buffer_pread(/*int fd, */char *s, unsigned len)
+{
+	unsigned now;
+	struct pollfd input;
+	int i;
+
+	input.fd = STDIN_FILENO;
+	input.events = POLLIN;
+
+	do {
+		if (rotateasap) {
+			for (i = 0; i < dirn; ++i)
+				rotate(dir + i);
+			rotateasap = 0;
+		}
+		if (exitasap) {
+			if (linecomplete)
+				return 0;
+			len = 1;
+		}
+		if (reopenasap) {
+			logdirs_reopen();
+			reopenasap = 0;
+		}
+		now = monotonic_sec();
+		nearest_rotate = now + (45 * 60 + 45);
+		for (i = 0; i < dirn; ++i) {
+			if (dir[i].rotate_period) {
+				if (LESS(dir[i].next_rotate, now))
+					rotate(dir + i);
+				if (LESS(dir[i].next_rotate, nearest_rotate))
+					nearest_rotate = dir[i].next_rotate;
+			}
+		}
+
+		sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
+		i = nearest_rotate - now;
+		if (i > 1000000)
+			i = 1000000;
+		if (i <= 0)
+			i = 1;
+		poll(&input, 1, i * 1000);
+		sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
+
+		i = ndelay_read(STDIN_FILENO, s, len);
+		if (i >= 0)
+			break;
+		if (errno == EINTR)
+			continue;
+		if (errno != EAGAIN) {
+			warn("can't read standard input");
+			break;
+		}
+		/* else: EAGAIN - normal, repeat silently */
+	} while (!exitasap);
+
+	if (i > 0) {
+		int cnt;
+		linecomplete = (s[i-1] == '\n');
+		if (!repl)
+			return i;
+
+		cnt = i;
+		while (--cnt >= 0) {
+			char ch = *s;
+			if (ch != '\n') {
+				if (ch < 32 || ch > 126)
+					*s = repl;
+				else {
+					int j;
+					for (j = 0; replace[j]; ++j) {
+						if (ch == replace[j]) {
+							*s = repl;
+							break;
+						}
+					}
+				}
+			}
+			s++;
+		}
+	}
+	return i;
+}
+
+static void sig_term_handler(int sig_no UNUSED_PARAM)
+{
+	if (verbose)
+		bb_error_msg(INFO"sig%s received", "term");
+	exitasap = 1;
+}
+
+static void sig_child_handler(int sig_no UNUSED_PARAM)
+{
+	pid_t pid;
+	int l;
+
+	if (verbose)
+		bb_error_msg(INFO"sig%s received", "child");
+	while ((pid = wait_any_nohang(&wstat)) > 0) {
+		for (l = 0; l < dirn; ++l) {
+			if (dir[l].ppid == pid) {
+				dir[l].ppid = 0;
+				processorstop(&dir[l]);
+				break;
+			}
+		}
+	}
+}
+
+static void sig_alarm_handler(int sig_no UNUSED_PARAM)
+{
+	if (verbose)
+		bb_error_msg(INFO"sig%s received", "alarm");
+	rotateasap = 1;
+}
+
+static void sig_hangup_handler(int sig_no UNUSED_PARAM)
+{
+	if (verbose)
+		bb_error_msg(INFO"sig%s received", "hangup");
+	reopenasap = 1;
+}
+
+static void logmatch(struct logdir *ld)
+{
+	char *s;
+
+	ld->match = '+';
+	ld->matcherr = 'E';
+	s = ld->inst;
+	while (s && s[0]) {
+		switch (s[0]) {
+		case '+':
+		case '-':
+			if (pmatch(s+1, line, linelen))
+				ld->match = s[0];
+			break;
+		case 'e':
+		case 'E':
+			if (pmatch(s+1, line, linelen))
+				ld->matcherr = s[0];
+			break;
+		}
+		s += strlen(s) + 1;
+	}
+}
+
+int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int svlogd_main(int argc, char **argv)
+{
+	char *r, *l, *b;
+	ssize_t stdin_cnt = 0;
+	int i;
+	unsigned opt;
+	unsigned timestamp = 0;
+
+	INIT_G();
+
+	opt_complementary = "tt:vv";
+	opt = getopt32(argv, "r:R:l:b:tv",
+			&r, &replace, &l, &b, &timestamp, &verbose);
+	if (opt & 1) { // -r
+		repl = r[0];
+		if (!repl || r[1])
+			bb_show_usage();
+	}
+	if (opt & 2) if (!repl) repl = '_'; // -R
+	if (opt & 4) { // -l
+		linemax = xatou_range(l, 0, BUFSIZ-26);
+		if (linemax == 0)
+			linemax = BUFSIZ-26;
+		if (linemax < 256)
+			linemax = 256;
+	}
+	////if (opt & 8) { // -b
+	////	buflen = xatoi_positive(b);
+	////	if (buflen == 0) buflen = 1024;
+	////}
+	//if (opt & 0x10) timestamp++; // -t
+	//if (opt & 0x20) verbose++; // -v
+	//if (timestamp > 2) timestamp = 2;
+	argv += optind;
+	argc -= optind;
+
+	dirn = argc;
+	if (dirn <= 0)
+		bb_show_usage();
+	////if (buflen <= linemax) bb_show_usage();
+	fdwdir = xopen(".", O_RDONLY|O_NDELAY);
+	close_on_exec_on(fdwdir);
+	dir = xzalloc(dirn * sizeof(dir[0]));
+	for (i = 0; i < dirn; ++i) {
+		dir[i].fddir = -1;
+		dir[i].fdcur = -1;
+		////dir[i].btmp = xmalloc(buflen);
+		/*dir[i].ppid = 0;*/
+	}
+	/* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
+	fndir = argv;
+	/* We cannot set NONBLOCK on fd #0 permanently - this setting
+	 * _isn't_ per-process! It is shared among all other processes
+	 * with the same stdin */
+	fl_flag_0 = fcntl(0, F_GETFL);
+
+	sigemptyset(&blocked_sigset);
+	sigaddset(&blocked_sigset, SIGTERM);
+	sigaddset(&blocked_sigset, SIGCHLD);
+	sigaddset(&blocked_sigset, SIGALRM);
+	sigaddset(&blocked_sigset, SIGHUP);
+	sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
+	bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
+	bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
+	bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
+	bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
+
+	/* Without timestamps, we don't have to print each line
+	 * separately, so we can look for _last_ newline, not first,
+	 * thus batching writes. If filtering is enabled in config,
+	 * logdirs_reopen resets it to memchr.
+	 */
+	memRchr = (timestamp ? memchr : memrchr);
+
+	logdirs_reopen();
+
+	setvbuf(stderr, NULL, _IOFBF, linelen);
+
+	/* Each iteration processes one or more lines */
+	while (1) {
+		char stamp[FMT_PTIME];
+		char *lineptr;
+		char *printptr;
+		char *np;
+		int printlen;
+		char ch;
+
+		lineptr = line;
+		if (timestamp)
+			lineptr += 26;
+
+		/* lineptr[0..linemax-1] - buffer for stdin */
+		/* (possibly has some unprocessed data from prev loop) */
+
+		/* Refill the buffer if needed */
+		np = memRchr(lineptr, '\n', stdin_cnt);
+		if (!np && !exitasap) {
+			i = linemax - stdin_cnt; /* avail. bytes at tail */
+			if (i >= 128) {
+				i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
+				if (i <= 0) /* EOF or error on stdin */
+					exitasap = 1;
+				else {
+					np = memRchr(lineptr + stdin_cnt, '\n', i);
+					stdin_cnt += i;
+				}
+			}
+		}
+		if (stdin_cnt <= 0 && exitasap)
+			break;
+
+		/* Search for '\n' (in fact, np already holds the result) */
+		linelen = stdin_cnt;
+		if (np) {
+ print_to_nl:
+			/* NB: starting from here lineptr may point
+			 * farther out into line[] */
+			linelen = np - lineptr + 1;
+		}
+		/* linelen == no of chars incl. '\n' (or == stdin_cnt) */
+		ch = lineptr[linelen-1];
+
+		/* Biggest performance hit was coming from the fact
+		 * that we did not buffer writes. We were reading many lines
+		 * in one read() above, but wrote one line per write().
+		 * We are using stdio to fix that */
+
+		/* write out lineptr[0..linelen-1] to each log destination
+		 * (or lineptr[-26..linelen-1] if timestamping) */
+		printlen = linelen;
+		printptr = lineptr;
+		if (timestamp) {
+			if (timestamp == 1)
+				fmt_time_bernstein_25(stamp);
+			else /* 2: */
+				fmt_time_human_30nul(stamp);
+			printlen += 26;
+			printptr -= 26;
+			memcpy(printptr, stamp, 25);
+			printptr[25] = ' ';
+		}
+		for (i = 0; i < dirn; ++i) {
+			struct logdir *ld = &dir[i];
+			if (ld->fddir == -1)
+				continue;
+			if (ld->inst)
+				logmatch(ld);
+			if (ld->matcherr == 'e') {
+				/* runit-1.8.0 compat: if timestamping, do it on stderr too */
+				////full_write(STDERR_FILENO, printptr, printlen);
+				fwrite(printptr, 1, printlen, stderr);
+			}
+			if (ld->match != '+')
+				continue;
+			buffer_pwrite(i, printptr, printlen);
+		}
+
+		/* If we didn't see '\n' (long input line), */
+		/* read/write repeatedly until we see it */
+		while (ch != '\n') {
+			/* lineptr is emptied now, safe to use as buffer */
+			stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
+			if (stdin_cnt <= 0) { /* EOF or error on stdin */
+				exitasap = 1;
+				lineptr[0] = ch = '\n';
+				linelen = 1;
+				stdin_cnt = 1;
+			} else {
+				linelen = stdin_cnt;
+				np = memRchr(lineptr, '\n', stdin_cnt);
+				if (np)
+					linelen = np - lineptr + 1;
+				ch = lineptr[linelen-1];
+			}
+			/* linelen == no of chars incl. '\n' (or == stdin_cnt) */
+			for (i = 0; i < dirn; ++i) {
+				if (dir[i].fddir == -1)
+					continue;
+				if (dir[i].matcherr == 'e') {
+					////full_write(STDERR_FILENO, lineptr, linelen);
+					fwrite(lineptr, 1, linelen, stderr);
+				}
+				if (dir[i].match != '+')
+					continue;
+				buffer_pwrite(i, lineptr, linelen);
+			}
+		}
+
+		stdin_cnt -= linelen;
+		if (stdin_cnt > 0) {
+			lineptr += linelen;
+			/* If we see another '\n', we don't need to read
+			 * next piece of input: can print what we have */
+			np = memRchr(lineptr, '\n', stdin_cnt);
+			if (np)
+				goto print_to_nl;
+			/* Move unprocessed data to the front of line */
+			memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
+		}
+		fflush_all();////
+	}
+
+	for (i = 0; i < dirn; ++i) {
+		if (dir[i].ppid)
+			while (!processorstop(&dir[i]))
+				continue;
+		logdir_close(&dir[i]);
+	}
+	return 0;
+}