aboutsummaryrefslogtreecommitdiff
path: root/runit
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
committerBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
commit73b16af8feec390afbabd9356d6e5e83c0390838 (patch)
tree3730020ba2f9caeb9d7815a975af51830b51ce11 /runit
busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2busybox-1.13.3
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'runit')
-rw-r--r--runit/Config.in83
-rw-r--r--runit/Kbuild17
-rw-r--r--runit/chpst.c388
-rw-r--r--runit/runit_lib.c273
-rw-r--r--runit/runit_lib.h105
-rw-r--r--runit/runsv.c655
-rw-r--r--runit/runsvdir.c395
-rw-r--r--runit/sv.c598
-rw-r--r--runit/svlogd.c1056
9 files changed, 3570 insertions, 0 deletions
diff --git a/runit/Config.in b/runit/Config.in
new file mode 100644
index 0000000..422ca75
--- /dev/null
+++ b/runit/Config.in
@@ -0,0 +1,83 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Runit Utilities"
+
+config RUNSV
+ bool "runsv"
+ default n
+ help
+ runsv starts and monitors a service and optionally an appendant log
+ service.
+
+config RUNSVDIR
+ bool "runsvdir"
+ default n
+ 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 n
+ 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 n
+ 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 n
+ help
+ chpst changes the process state according to the given options, and
+ execs specified program.
+
+config SETUIDGID
+ bool "setuidgid"
+ help
+ Sets soft resource limits as specified by options
+
+config ENVUIDGID
+ bool "envuidgid"
+ help
+ Sets $UID to account's uid and $GID to account's gid
+
+config ENVDIR
+ bool "envdir"
+ help
+ Sets various environment variables as specified by files
+ in the given directory
+
+config SOFTLIMIT
+ bool "softlimit"
+ help
+ Sets soft resource limits as specified by options
+
+endmenu
diff --git a/runit/Kbuild b/runit/Kbuild
new file mode 100644
index 0000000..ab9eef6
--- /dev/null
+++ b/runit/Kbuild
@@ -0,0 +1,17 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_RUNSV) += runsv.o runit_lib.o
+lib-$(CONFIG_RUNSVDIR) += runsvdir.o runit_lib.o
+lib-$(CONFIG_SV) += sv.o runit_lib.o
+lib-$(CONFIG_SVLOGD) += svlogd.o runit_lib.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/runit/chpst.c b/runit/chpst.c
new file mode 100644
index 0000000..82a81f5
--- /dev/null
+++ b/runit/chpst.c
@@ -0,0 +1,388 @@
+/*
+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 */
+
+#include "libbb.h"
+#include <dirent.h>
+
+/*
+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,
+};
+
+static 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 = opendir(".");
+ if (!dir)
+ bb_perror_msg_and_die("opendir %s", directory_name);
+ 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;
+ } else
+ 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:"
+ USE_CHPST("/:n:vP012"),
+ &limita, &limitc, &limitd, &limitf, &limitl,
+ &limitm, &limito, &limitp, &limitr, &limits, &limitt,
+ &set_user, &env_user, &env_dir
+ USE_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);
+
+ // FIXME: 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) {
+ xget_uidgid(&ugid, env_user);
+ xsetenv("GID", utoa(ugid.gid));
+ xsetenv("UID", utoa(ugid.uid));
+ }
+
+ if (opt & OPT_u) {
+ xget_uidgid(&ugid, set_user);
+ }
+
+ if (opt & OPT_root) {
+ xchdir(root);
+ xchroot(".");
+ }
+
+ 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(argv[0], argv);
+ bb_perror_msg_and_die("exec %s", argv[0]);
+}
diff --git a/runit/runit_lib.c b/runit/runit_lib.c
new file mode 100644
index 0000000..f33619d
--- /dev/null
+++ b/runit/runit_lib.c
@@ -0,0 +1,273 @@
+/*
+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> */
+/* Collected into one file from runit's many tiny files */
+/* TODO: review, eliminate unneeded stuff, move good stuff to libbb */
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+unsigned byte_chr(char *s,unsigned n,int c)
+{
+ char ch;
+ char *t;
+
+ ch = c;
+ t = s;
+ for (;;) {
+ if (!n) break;
+ if (*t == ch) break;
+ ++t;
+ --n;
+ }
+ return t - s;
+}
+
+#ifdef UNUSED
+static /* as it isn't used anywhere else */
+void tai_pack(char *s, const struct tai *t)
+{
+ uint64_t x;
+
+ x = t->x;
+ s[7] = x & 255; x >>= 8;
+ s[6] = x & 255; x >>= 8;
+ s[5] = x & 255; x >>= 8;
+ s[4] = x & 255; x >>= 8;
+ s[3] = x & 255; x >>= 8;
+ s[2] = x & 255; x >>= 8;
+ s[1] = x & 255; x >>= 8;
+ s[0] = x;
+}
+
+void tai_unpack(const char *s,struct tai *t)
+{
+ uint64_t x;
+
+ x = (unsigned char) s[0];
+ x <<= 8; x += (unsigned char) s[1];
+ x <<= 8; x += (unsigned char) s[2];
+ x <<= 8; x += (unsigned char) s[3];
+ x <<= 8; x += (unsigned char) s[4];
+ x <<= 8; x += (unsigned char) s[5];
+ x <<= 8; x += (unsigned char) s[6];
+ x <<= 8; x += (unsigned char) s[7];
+ t->x = x;
+}
+
+
+void taia_add(struct taia *t,const struct taia *u,const struct taia *v)
+{
+ t->sec.x = u->sec.x + v->sec.x;
+ t->nano = u->nano + v->nano;
+ t->atto = u->atto + v->atto;
+ if (t->atto > 999999999UL) {
+ t->atto -= 1000000000UL;
+ ++t->nano;
+ }
+ if (t->nano > 999999999UL) {
+ t->nano -= 1000000000UL;
+ ++t->sec.x;
+ }
+}
+
+int taia_less(const struct taia *t, const struct taia *u)
+{
+ if (t->sec.x < u->sec.x) return 1;
+ if (t->sec.x > u->sec.x) return 0;
+ if (t->nano < u->nano) return 1;
+ if (t->nano > u->nano) return 0;
+ return t->atto < u->atto;
+}
+
+void taia_now(struct taia *t)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ tai_unix(&t->sec, now.tv_sec);
+ t->nano = 1000 * now.tv_usec + 500;
+ t->atto = 0;
+}
+
+/* UNUSED
+void taia_pack(char *s, const struct taia *t)
+{
+ unsigned long x;
+
+ tai_pack(s, &t->sec);
+ s += 8;
+
+ x = t->atto;
+ s[7] = x & 255; x >>= 8;
+ s[6] = x & 255; x >>= 8;
+ s[5] = x & 255; x >>= 8;
+ s[4] = x;
+ x = t->nano;
+ s[3] = x & 255; x >>= 8;
+ s[2] = x & 255; x >>= 8;
+ s[1] = x & 255; x >>= 8;
+ s[0] = x;
+}
+*/
+
+void taia_sub(struct taia *t, const struct taia *u, const struct taia *v)
+{
+ unsigned long unano = u->nano;
+ unsigned long uatto = u->atto;
+
+ t->sec.x = u->sec.x - v->sec.x;
+ t->nano = unano - v->nano;
+ t->atto = uatto - v->atto;
+ if (t->atto > uatto) {
+ t->atto += 1000000000UL;
+ --t->nano;
+ }
+ if (t->nano > unano) {
+ t->nano += 1000000000UL;
+ --t->sec.x;
+ }
+}
+
+/* XXX: breaks tai encapsulation */
+void taia_uint(struct taia *t, unsigned s)
+{
+ t->sec.x = s;
+ t->nano = 0;
+ t->atto = 0;
+}
+
+static
+uint64_t taia2millisec(const struct taia *t)
+{
+ return (t->sec.x * 1000) + (t->nano / 1000000);
+}
+
+void iopause(iopause_fd *x, unsigned len, struct taia *deadline, struct taia *stamp)
+{
+ int millisecs;
+ int i;
+
+ if (taia_less(deadline, stamp))
+ millisecs = 0;
+ else {
+ uint64_t m;
+ struct taia t;
+ t = *stamp;
+ taia_sub(&t, deadline, &t);
+ millisecs = m = taia2millisec(&t);
+ if (m > 1000) millisecs = 1000;
+ millisecs += 20;
+ }
+
+ for (i = 0; i < len; ++i)
+ x[i].revents = 0;
+
+ poll(x, len, millisecs);
+ /* XXX: some kernels apparently need x[0] even if len is 0 */
+ /* XXX: how to handle EAGAIN? are kernels really this dumb? */
+ /* XXX: how to handle EINVAL? when exactly can this happen? */
+}
+#endif
+
+int lock_ex(int fd)
+{
+ return flock(fd,LOCK_EX);
+}
+
+int lock_exnb(int fd)
+{
+ return flock(fd,LOCK_EX | LOCK_NB);
+}
+
+int open_append(const char *fn)
+{
+ return open(fn, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+}
+
+int open_read(const char *fn)
+{
+ return open(fn, O_RDONLY|O_NDELAY);
+}
+
+int open_trunc(const char *fn)
+{
+ return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644);
+}
+
+int open_write(const char *fn)
+{
+ return open(fn, O_WRONLY|O_NDELAY);
+}
+
+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;
+}
diff --git a/runit/runit_lib.h b/runit/runit_lib.h
new file mode 100644
index 0000000..b0b6dc2
--- /dev/null
+++ b/runit/runit_lib.h
@@ -0,0 +1,105 @@
+/*
+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.
+*/
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+extern unsigned byte_chr(char *s,unsigned n,int c);
+
+#define direntry struct dirent
+
+//struct tai {
+// uint64_t x;
+//};
+//
+//#define tai_unix(t,u) ((void) ((t)->x = 0x400000000000000aULL + (uint64_t) (u)))
+//
+//#define TAI_PACK 8
+//extern void tai_unpack(const char *,struct tai *);
+//
+//extern void tai_uint(struct tai *,unsigned);
+//
+//struct taia {
+// struct tai sec;
+// unsigned long nano; /* 0...999999999 */
+// unsigned long atto; /* 0...999999999 */
+//};
+//
+//extern void taia_now(struct taia *);
+//
+//extern void taia_add(struct taia *,const struct taia *,const struct taia *);
+//extern void taia_addsec(struct taia *,const struct taia *,int);
+//extern void taia_sub(struct taia *,const struct taia *,const struct taia *);
+//extern void taia_half(struct taia *,const struct taia *);
+//extern int taia_less(const struct taia *,const struct taia *);
+//
+//#define TAIA_PACK 16
+//extern void taia_pack(char *,const struct taia *);
+//
+//extern void taia_uint(struct taia *,unsigned);
+//
+//typedef struct pollfd iopause_fd;
+//#define IOPAUSE_READ POLLIN
+//#define IOPAUSE_WRITE POLLOUT
+//
+//extern void iopause(iopause_fd *,unsigned,struct taia *,struct taia *);
+
+extern int lock_ex(int);
+extern int lock_un(int);
+extern int lock_exnb(int);
+
+extern int open_read(const char *);
+extern int open_excl(const char *);
+extern int open_append(const char *);
+extern int open_trunc(const char *);
+extern int open_write(const char *);
+
+extern unsigned pmatch(const char *, const char *, unsigned);
+
+#define str_diff(s,t) strcmp((s), (t))
+#define str_equal(s,t) (!strcmp((s), (t)))
+
+/*
+ * 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;
+ 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];
+};
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
diff --git a/runit/runsv.c b/runit/runsv.c
new file mode 100644
index 0000000..1237208
--- /dev/null
+++ b/runit/runsv.c
@@ -0,0 +1,655 @@
+/*
+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 */
+
+#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 want;
+ smallint islog;
+ struct timespec start;
+ int fdlock;
+ int fdcontrol;
+ int fdcontrolwrite;
+};
+
+struct globals {
+ smallint haslog;
+ smallint sigterm;
+ smallint pidchanged;
+ struct fd_pair selfpipe;
+ struct fd_pair logpipe;
+ char *dir;
+ struct svdir svd[2];
+};
+#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 char *add_str(char *p, const char *to_add)
+{
+ while ((*p = *to_add) != '\0') {
+ p++;
+ to_add++;
+ }
+ return p;
+}
+
+static int open_trunc_or_warn(const char *name)
+{
+ int fd = open_trunc(name);
+ 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 = add_str(p, "down");
+ break;
+ case S_RUN:
+ p = add_str(p, "run");
+ break;
+ case S_FINISH:
+ p = add_str(p, "finish");
+ break;
+ }
+ if (s->ctrl & C_PAUSE) p = add_str(p, ", paused");
+ if (s->ctrl & C_TERM) p = add_str(p, ", got TERM");
+ if (s->state != S_DOWN)
+ switch (s->want) {
+ case W_DOWN:
+ p = add_str(p, ", want down");
+ break;
+ case W_EXIT:
+ p = add_str(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->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;
+ char *prog[2];
+
+ 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) {
+ /* child */
+ if (haslog && dup2(logpipe.wr, 1) == -1)
+ warn_cannot("setup stdout for control/?");
+ prog[0] = a;
+ prog[1] = NULL;
+ execv(a, prog);
+ fatal_cannot("run control/?");
+ }
+ /* parent */
+ while (safe_waitpid(pid, &w, 0) == -1) {
+ warn_cannot("wait for child control/?");
+ return 0;
+ }
+ return !wait_exitcode(w);
+ }
+ } 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->want == W_DOWN) {
+ kill(s->pid, SIGCONT);
+ custom(s, 'd');
+ return;
+ }
+ if (s->want == W_EXIT) {
+ kill(s->pid, SIGCONT);
+ custom(s, 'x');
+ }
+}
+
+static void startservice(struct svdir *s)
+{
+ int p;
+ char *run[2];
+
+ if (s->state == S_FINISH)
+ run[0] = (char*)"./finish";
+ else {
+ run[0] = (char*)"./run";
+ custom(s, 'u');
+ }
+ run[1] = NULL;
+
+ 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);
+ execvp(*run, run);
+ fatal2_cannot(s->islog ? "start log/" : "start ", *run);
+ }
+ /* 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->want = W_DOWN;
+ update_status(s);
+ if (s->pid && s->state != S_FINISH)
+ stopservice(s);
+ break;
+ case 'u': /* up */
+ s->want = W_UP;
+ update_status(s);
+ if (s->pid == 0)
+ startservice(s);
+ break;
+ case 'x': /* exit */
+ if (s->islog)
+ break;
+ s->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);
+ if (s->ctrl & C_PAUSE)
+ s->ctrl &= ~C_PAUSE;
+ update_status(s);
+ break;
+ case 'o': /* once */
+ s->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();
+
+ if (!argv[1] || argv[2])
+ bb_show_usage();
+ dir = argv[1];
+
+ 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(1 << SIGCHLD, s_child);
+ sig_block(SIGTERM);
+ bb_signals_recursive(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].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].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].want = W_UP;
+ svd[1].islog = 1;
+ gettimeofday_ns(&svd[1].start);
+ if (stat("log/down", &s) != -1)
+ svd[1].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 (lock_exnb(svd[0].fdlock) == -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 (lock_ex(svd[1].fdlock) == -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].want == W_UP)
+ startservice(&svd[1]);
+ if (!svd[0].pid)
+ if (svd[0].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].pid = 0;
+ pidchanged = 1;
+ svd[0].ctrl &=~ C_TERM;
+ if (svd[0].state != S_FINISH) {
+ fd = open_read("finish");
+ 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[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].want == W_EXIT && svd[0].state == S_DOWN) {
+ if (svd[1].pid == 0)
+ _exit(EXIT_SUCCESS);
+ if (svd[1].want != W_EXIT) {
+ svd[1].want = W_EXIT;
+ /* stopservice(&svd[1]); */
+ update_status(&svd[1]);
+ close(logpipe.wr);
+ close(logpipe.rd);
+ }
+ }
+ } /* for (;;) */
+ /* not reached */
+ return 0;
+}
diff --git a/runit/runsvdir.c b/runit/runsvdir.c
new file mode 100644
index 0000000..9d560e0
--- /dev/null
+++ b/runit/runsvdir.c
@@ -0,0 +1,395 @@
+/*
+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 */
+
+#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
+};
+#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: cannot %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("cannot ", 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, NULL);
+ fatal2_cannot("start runsv ", name);
+ }
+ return pid;
+}
+
+/* gcc 4.3.0 does better with NOINLINE */
+static NOINLINE int do_rescan(void)
+{
+ DIR *dir;
+ direntry *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("cannot 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("cannot 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_read(".");
+ 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 (now <= mtime)
+ // sleep(1);
+ 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) {
+ /* Remebering to wait for _any_ children,
+ * not just pid */
+ while (wait(NULL) != pid)
+ continue;
+ }
+ }
+
+ switch (bb_got_signal) {
+ case SIGHUP:
+ for (i = 0; i < svnum; i++)
+ if (sv[i].pid)
+ kill(sv[i].pid, SIGTERM);
+ /* Fall through */
+ default: /* SIGTERM (or SIGUSRn if we are init) */
+ /* Exit unless we are init */
+ if (getpid() == 1)
+ break;
+ return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
+ }
+
+ bb_got_signal = 0;
+ } /* for (;;) */
+}
diff --git a/runit/sv.c b/runit/sv.c
new file mode 100644
index 0000000..7e5efde
--- /dev/null
+++ b/runit/sv.c
@@ -0,0 +1,598 @@
+/*
+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 */
+
+#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;
+};
+#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)
+
+
+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_write("supervise/ok");
+ if (fd == -1) {
+ if (errno == ENODEV) {
+ *acts == 'x' ? ok("runsv not running")
+ : failx("runsv not running");
+ return 0;
+ }
+ warn("cannot open supervise/ok");
+ return -1;
+ }
+ close(fd);
+ fd = open_read("supervise/status");
+ if (fd == -1) {
+ warn("cannot open supervise/status");
+ return -1;
+ }
+ r = read(fd, &svstatus, 20);
+ close(fd);
+ switch (r) {
+ case 20:
+ break;
+ case -1:
+ warn("cannot read supervise/status");
+ return -1;
+ default:
+ errno = 0;
+ warn("cannot 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"cannot 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;
+
+ r = svstatus_get();
+ switch (r) { case -1: case 0: return 0; }
+
+ r = svstatus_print(*service);
+ if (chdir("log") == -1) {
+ if (errno != ENOENT) {
+ printf("; log: "WARN"cannot 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"cannot 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"cannot %s child %s/check", "run", *service);
+ return 0;
+ }
+ while (safe_waitpid(pid, &w, 0) == -1) {
+ bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
+ return 0;
+ }
+ return !wait_exitcode(w);
+}
+
+static int check(const char *a)
+{
+ int r;
+ unsigned pid;
+ uint64_t timestamp;
+
+ r = svstatus_get();
+ if (r == -1)
+ return -1;
+ if (r == 0) {
+ if (*a == 'x')
+ return 1;
+ return -1;
+ }
+ pid = SWAP_LE32(svstatus.pid_le32);
+ switch (*a) {
+ case 'x':
+ return 0;
+ case 'u':
+ if (!pid || svstatus.run_or_finish != 1) return 0;
+ if (!checkscript()) return 0;
+ break;
+ case 'd':
+ if (pid) return 0;
+ break;
+ case 'c':
+ if (pid && !checkscript()) return 0;
+ break;
+ case 't':
+ if (!pid && svstatus.want == 'd') break;
+ timestamp = SWAP_BE64(svstatus.time_be64);
+ if ((tstart > timestamp) || !pid || svstatus.got_term || !checkscript())
+ return 0;
+ break;
+ case 'o':
+ timestamp = SWAP_BE64(svstatus.time_be64);
+ if ((!pid && tstart > timestamp) || (pid && 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;
+
+ if (svstatus_get() <= 0)
+ return -1;
+ if (svstatus.want == *a)
+ return 0;
+ fd = open_write("supervise/control");
+ if (fd == -1) {
+ if (errno != ENODEV)
+ warn("cannot open supervise/control");
+ else
+ *a == 'x' ? ok("runsv not running") : failx("runsv not running");
+ return -1;
+ }
+ r = write(fd, a, strlen(a));
+ close(fd);
+ if (r != strlen(a)) {
+ warn("cannot write to supervise/control");
+ return -1;
+ }
+ return 1;
+}
+
+int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sv_main(int argc, char **argv)
+{
+ unsigned opt;
+ unsigned i, want_exit;
+ char *x;
+ char *action;
+ const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
+ unsigned services;
+ char **servicex;
+ 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 */
+ opt = getopt32(argv, "w:v", &waitsec, &verbose);
+ argc -= optind;
+ argv += optind;
+ action = *argv++;
+ if (!action || !*argv) bb_show_usage();
+ service = argv;
+ services = argc - 1;
+
+ tnow = time(0) + 0x400000000000000aULL;
+ tstart = tnow;
+ curdir = open_read(".");
+ 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();
+ }
+
+ servicex = service;
+ for (i = 0; i < services; ++i) {
+ if ((**service != '/') && (**service != '.')) {
+ if (chdir(varservice) == -1)
+ goto chdir_failed_0;
+ }
+ if (chdir(*service) == -1) {
+ chdir_failed_0:
+ fail("cannot change to service directory");
+ goto nullify_service_0;
+ }
+ if (act && (act(acts) == -1)) {
+ nullify_service_0:
+ *service = NULL;
+ }
+ if (fchdir(curdir) == -1)
+ fatal_cannot("change to original directory");
+ service++;
+ }
+
+ if (cbk) while (1) {
+ int diff;
+
+ diff = tnow - tstart;
+ service = servicex;
+ want_exit = 1;
+ for (i = 0; i < services; ++i, ++service) {
+ if (!*service)
+ continue;
+ if ((**service != '/') && (**service != '.')) {
+ if (chdir(varservice) == -1)
+ goto chdir_failed;
+ }
+ if (chdir(*service) == -1) {
+ chdir_failed:
+ fail("cannot 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(*service);
+ ++rc;
+ }
+ bb_putchar('\n'); /* will also flush the output */
+ if (kll)
+ control("k");
+ nullify_service:
+ *service = NULL;
+ }
+ if (fchdir(curdir) == -1)
+ fatal_cannot("change to original directory");
+ }
+ if (want_exit) break;
+ usleep(420000);
+ tnow = time(0) + 0x400000000000000aULL;
+ }
+ return rc > 99 ? 99 : rc;
+}
diff --git a/runit/svlogd.c b/runit/svlogd.c
new file mode 100644
index 0000000..9beb9f5
--- /dev/null
+++ b/runit/svlogd.c
@@ -0,0 +1,1056 @@
+/*
+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 */
+
+#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;
+
+ 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 (*(struct globals*)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 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: "
+
+#define usage() bb_show_usage()
+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;
+}
+
+/*** ex fmt_ptime.[ch] ***/
+
+/* NUL terminated */
+static void fmt_time_human_30nul(char *s)
+{
+ struct tm *t;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ t = gmtime(&(tv.tv_sec));
+ sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
+ (unsigned)(1900 + t->tm_year),
+ (unsigned)(t->tm_mon + 1),
+ (unsigned)(t->tm_mday),
+ (unsigned)(t->tm_hour),
+ (unsigned)(t->tm_min),
+ (unsigned)(t->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];
+
+ while ((pid = vfork()) == -1)
+ pause2cannot("vfork for processor", ld->name);
+ if (!pid) {
+ char *prog[4];
+ 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_read("state");
+ 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);
+
+// getenv("SHELL")?
+ prog[0] = (char*)"sh";
+ prog[1] = (char*)"-c";
+ prog[2] = ld->processor;
+ prog[3] = NULL;
+ execv("/bin/sh", prog);
+ 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 (wait_exitcode(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);
+ /* we presume this cannot fail */
+ ld->filecur = fdopen(ld->fdcur, "a"); ////
+ 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 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)
+ || (lock_exnb(ld->fdlock) == -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));
+ if (i < 0 && errno != ENOENT)
+ bb_perror_msg(WARNING"%s/config", ld->name);
+ if (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':
+ /* Add '\n'-terminated line to ld->inst */
+ while (1) {
+ int l = asprintf(&new, "%s%s\n", 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 },
+ { }
+ };
+ ld->sizemax = xatou_sfx(&s[1], km_suffixes);
+ break;
+ }
+ case 'n':
+ ld->nmax = xatoi_u(&s[1]);
+ break;
+ case 'N':
+ ld->nmin = xatoi_u(&s[1]);
+ break;
+ case 't': {
+ static const struct suffix_mult mh_suffixes[] = {
+ { "m", 60 },
+ { "h", 60*60 },
+ /*{ "d", 24*60*60 },*/
+ { }
+ };
+ 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);
+ /* we presume this cannot fail */
+ ld->filecur = fdopen(ld->fdcur, "a"); ////
+ 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 = 0;
+ 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;
+ void* (*memRchr)(const void *, int, size_t) = memchr;
+
+ 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]) 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_u(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) usage();
+ ////if (buflen <= linemax) usage();
+ fdwdir = xopen(".", O_RDONLY|O_NDELAY);
+ close_on_exec_on(fdwdir);
+ dir = xzalloc(dirn * sizeof(struct logdir));
+ 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(1 << SIGTERM, sig_term_handler);
+ bb_signals_recursive(1 << SIGCHLD, sig_child_handler);
+ bb_signals_recursive(1 << SIGALRM, sig_alarm_handler);
+ bb_signals_recursive(1 << SIGHUP, sig_hangup_handler);
+
+ logdirs_reopen();
+
+ /* Without timestamps, we don't have to print each line
+ * separately, so we can look for _last_ newline, not first,
+ * thus batching writes */
+ if (!timestamp)
+ memRchr = memrchr;
+
+ 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(NULL);////
+ }
+
+ for (i = 0; i < dirn; ++i) {
+ if (dir[i].ppid)
+ while (!processorstop(&dir[i]))
+ /* repeat */;
+ logdir_close(&dir[i]);
+ }
+ return 0;
+}