join some common strings, -400 bytes
[oweals/busybox.git] / runit / svlogd.c
index 4e9644fd5e61a78f4e729460d6472f3aae270bc4..c080b9acc703a7053c3fae5ac162327e3935f37b 100644 (file)
@@ -25,40 +25,132 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
-/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
+/* 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 "busybox.h"
-#include "runit_lib.h"
-
-static unsigned verbose;
-static int linemax = 1000;
-////static int buflen = 1024;
-static int linelen;
-
-static char **fndir;
-static int fdwdir;
-static int wstat;
-static struct taia trotate;
+/*
+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 *
+*/
 
-static char *line;
-static smallint exitasap;
-static smallint rotateasap;
-static smallint reopenasap;
-static smallint linecomplete = 1;
+//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"
 
-static smallint tmaxflag;
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
 
-static char repl;
-static const char *replace = "";
+#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
 
-static sigset_t *blocked_sigset;
-static iopause_fd input;
-static int fl_flag_0;
+#define FMT_PTIME 30
 
-static struct logdir {
+struct logdir {
        ////char *btmp;
        /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
        char *inst;
@@ -68,26 +160,84 @@ static struct logdir {
        unsigned sizemax;
        unsigned nmax;
        unsigned nmin;
-       /* int (not long) because of taia_uint() usage: */
-       unsigned tmax;
+       unsigned rotate_period;
        int ppid;
        int fddir;
        int fdcur;
        FILE* filecur; ////
        int fdlock;
-       struct taia trotate;
+       unsigned next_rotate;
        char fnsave[FMT_PTIME];
        char match;
        char matcherr;
-} *dir;
-static unsigned dirn;
+};
+
+
+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: "
 
-#define usage() bb_show_usage()
 static void fatalx(const char *m0)
 {
        bb_error_msg_and_die(FATAL"%s", m0);
@@ -111,12 +261,12 @@ static void pause_nomem(void)
 }
 static void pause1cannot(const char *m0)
 {
-       bb_perror_msg(PAUSE"cannot %s", m0);
+       bb_perror_msg(PAUSE"can't %s", m0);
        sleep(3);
 }
 static void pause2cannot(const char *m0, const char *m1)
 {
-       bb_perror_msg(PAUSE"cannot %s %s", m0, m1);
+       bb_perror_msg(PAUSE"can't %s %s", m0, m1);
        sleep(3);
 }
 
@@ -128,25 +278,125 @@ static char* wstrdup(const char *str)
        return s;
 }
 
-static unsigned processorstart(struct logdir *ld)
+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 0;
+       if (!ld->processor) return;
        if (ld->ppid) {
                warnx("processor already running", ld->name);
-               return 0;
+               return;
        }
-       while ((pid = fork()) == -1)
-               pause2cannot("fork for processor", ld->name);
+
+       /* 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) {
-               char *prog[4];
                int fd;
 
                /* child */
-               signal(SIGTERM, SIG_DFL);
-               signal(SIGALRM, SIG_DFL);
-               signal(SIGHUP, SIG_DFL);
+               /* 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);
@@ -155,13 +405,13 @@ static unsigned processorstart(struct logdir *ld)
                        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';
+               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");
+               fd = open("state", O_RDONLY|O_NDELAY);
                if (fd == -1) {
                        if (errno != ENOENT)
-                               bb_perror_msg_and_die(FATAL"cannot %s processor %s", "open state for", ld->name);
+                               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);
                }
@@ -169,16 +419,11 @@ static unsigned processorstart(struct logdir *ld)
                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;
-               execve("/bin/sh", prog, environ);
-               bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name);
+               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;
-       return 1;
 }
 
 static unsigned processorstop(struct logdir *ld)
@@ -187,15 +432,16 @@ static unsigned processorstop(struct logdir *ld)
 
        if (ld->ppid) {
                sig_unblock(SIGHUP);
-               while (wait_pid(&wstat, ld->ppid) == -1)
+               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;
+       if (ld->fddir == -1)
+               return 1;
        while (fchdir(ld->fddir) == -1)
                pause2cannot("change directory, want processor", ld->name);
-       if (wait_exitcode(wstat) != 0) {
+       if (WEXITSTATUS(wstat) != 0) {
                warnx("processor failed, restart", ld->name);
                ld->fnsave[26] = 't';
                unlink(ld->fnsave);
@@ -215,7 +461,7 @@ static unsigned processorstop(struct logdir *ld)
                pause2cannot("set mode of processed", ld->name);
        ld->fnsave[26] = 'u';
        if (unlink(ld->fnsave) == -1)
-               bb_error_msg(WARNING"cannot unlink: %s/%s", ld->name, ld->fnsave);
+               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)
@@ -240,7 +486,7 @@ static void rmoldest(struct logdir *ld)
                if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
                        if (f->d_name[26] == 't') {
                                if (unlink(f->d_name) == -1)
-                                       warn2("cannot unlink processor leftover", f->d_name);
+                                       warn2("can't unlink processor leftover", f->d_name);
                        } else {
                                ++n;
                                if (strcmp(f->d_name, oldest) < 0)
@@ -250,29 +496,29 @@ static void rmoldest(struct logdir *ld)
                }
        }
        if (errno)
-               warn2("cannot read directory", ld->name);
+               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("cannot unlink oldest logfile", ld->name);
+                       warn2("can't unlink oldest logfile", ld->name);
        }
 }
 
 static unsigned rotate(struct logdir *ld)
 {
        struct stat st;
-       struct taia now;
+       unsigned now;
 
        if (ld->fddir == -1) {
-               ld->tmax = 0;
+               ld->rotate_period = 0;
                return 0;
        }
        if (ld->ppid)
                while (!processorstop(ld))
-                       /* wait */;
+                       continue;
 
        while (fchdir(ld->fddir) == -1)
                pause2cannot("change directory, want rotate", ld->name);
@@ -284,17 +530,16 @@ static unsigned rotate(struct logdir *ld)
                ld->fnsave[26] = 'u';
        ld->fnsave[27] = '\0';
        do {
-               taia_now(&now);
-               fmt_taia25(ld->fnsave, &now);
+               fmt_time_bernstein_25(ld->fnsave);
                errno = 0;
                stat(ld->fnsave, &st);
        } while (errno != ENOENT);
 
-       if (ld->tmax && taia_less(&ld->trotate, &now)) {
-               taia_uint(&ld->trotate, ld->tmax);
-               taia_add(&ld->trotate, &now, &ld->trotate);
-               if (taia_less(&ld->trotate, &trotate))
-                       trotate = ld->trotate;
+       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) {
@@ -313,13 +558,14 @@ static unsigned rotate(struct logdir *ld)
                        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"); ////
+               while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
+                       pause2cannot("create new current", ld->name); /* very unlikely */
                setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
-               coe(ld->fdcur);
+               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);
        }
@@ -354,12 +600,12 @@ static int buffer_pwrite(int n, char *s, unsigned len)
 
                        while (fchdir(ld->fddir) == -1)
                                pause2cannot("change directory, want remove old logfile",
-                                                        ld->name);
+                                                       ld->name);
                        oldest[0] = 'A';
                        oldest[1] = oldest[27] = '\0';
                        while (!(d = opendir(".")))
                                pause2cannot("open directory, want remove old logfile",
-                                                        ld->name);
+                                                       ld->name);
                        errno = 0;
                        while ((f = readdir(d)))
                                if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
@@ -367,7 +613,7 @@ static int buffer_pwrite(int n, char *s, unsigned len)
                                        if (strcmp(f->d_name, oldest) < 0)
                                                memcpy(oldest, f->d_name, 27);
                                }
-                       if (errno) warn2("cannot read directory, want remove old logfile",
+                       if (errno) warn2("can't read directory, want remove old logfile",
                                        ld->name);
                        closedir(d);
                        errno = ENOSPC;
@@ -377,7 +623,7 @@ static int buffer_pwrite(int n, char *s, unsigned len)
                                                        ld->name, oldest);
                                        errno = 0;
                                        if (unlink(oldest) == -1) {
-                                               warn2("cannot unlink oldest logfile", ld->name);
+                                               warn2("can't unlink oldest logfile", ld->name);
                                                errno = ENOSPC;
                                        }
                                        while (fchdir(fdwdir) == -1)
@@ -422,41 +668,43 @@ static void logdir_close(struct logdir *ld)
        ld->processor = NULL;
 }
 
-static unsigned logdir_open(struct logdir *ld, const char *fn)
+static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
 {
        char buf[128];
-       struct taia now;
+       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("cannot open log directory", (char*)fn);
+               warn2("can't open log directory", (char*)fn);
                return 0;
        }
-       coe(ld->fddir);
+       close_on_exec_on(ld->fddir);
        if (fchdir(ld->fddir) == -1) {
                logdir_close(ld);
-               warn2("cannot change directory", (char*)fn);
+               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)
+        || (flock(ld->fdlock, LOCK_EX | LOCK_NB) == -1)
        ) {
                logdir_close(ld);
-               warn2("cannot lock directory", (char*)fn);
+               warn2("can't lock directory", (char*)fn);
                while (fchdir(fdwdir) == -1)
                        pause1cannot("change to initial working directory");
                return 0;
        }
-       coe(ld->fdlock);
+       close_on_exec_on(ld->fdlock);
 
        ld->size = 0;
        ld->sizemax = 1000000;
        ld->nmax = ld->nmin = 10;
-       ld->tmax = 0;
+       ld->rotate_period = 0;
        ld->name = (char*)fn;
        ld->ppid = 0;
        ld->match = '+';
@@ -464,56 +712,59 @@ static unsigned logdir_open(struct logdir *ld, const char *fn)
        free(ld->processor); ld->processor = NULL;
 
        /* read config */
-       i = open_read_close("config", buf, sizeof(buf));
-       if (i < 0)
-               warn2("cannot read config", ld->name);
+       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) {
-               if (verbose) bb_error_msg(INFO"read: %s/config", ld->name);
+               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';
+                       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?:"", s);
-                                       if (l >= 0 && new) break;
+                                       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 },
-                                               { NULL, 0 }
-                               };
                                ld->sizemax = xatou_sfx(&s[1], km_suffixes);
                                break;
                        }
                        case 'n':
-                               ld->nmax = xatoi_u(&s[1]);
+                               ld->nmax = xatoi_positive(&s[1]);
                                break;
                        case 'N':
-                               ld->nmin = xatoi_u(&s[1]);
+                               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 },*/
-                                               { NULL, 0 }
+                                       { "m", 60 },
+                                       { "h", 60*60 },
+                                       /*{ "d", 24*60*60 },*/
+                                       { "", 0 }
                                };
-                               ld->tmax = xatou_sfx(&s[1], mh_suffixes);
-                               if (ld->tmax) {
-                                       taia_uint(&ld->trotate, ld->tmax);
-                                       taia_add(&ld->trotate, &now, &ld->trotate);
-                                       if (!tmaxflag || taia_less(&ld->trotate, &trotate))
-                                               trotate = ld->trotate;
+                               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;
@@ -531,7 +782,8 @@ static unsigned logdir_open(struct logdir *ld, const char *fn)
                s = ld->inst;
                while (s) {
                        np = strchr(s, '\n');
-                       if (np) *np++ = '\0';
+                       if (np)
+                               *np++ = '\0';
                        s = np;
                }
        }
@@ -539,13 +791,12 @@ static unsigned logdir_open(struct logdir *ld, const char *fn)
        /* open current */
        i = stat("current", &st);
        if (i != -1) {
-               if (st.st_size && ! (st.st_mode & S_IXUSR)) {
+               if (st.st_size && !(st.st_mode & S_IXUSR)) {
                        ld->fnsave[25] = '.';
                        ld->fnsave[26] = 'u';
                        ld->fnsave[27] = '\0';
                        do {
-                               taia_now(&now);
-                               fmt_taia25(ld->fnsave, &now);
+                               fmt_time_bernstein_25(ld->fnsave);
                                errno = 0;
                                stat(ld->fnsave, &st);
                        } while (errno != ENOENT);
@@ -554,14 +805,15 @@ static unsigned logdir_open(struct logdir *ld, const char *fn)
                        rmoldest(ld);
                        i = -1;
                } else {
-                       /* Be paranoid: st.st_size can be not just bigger, but WIDER! */
-                       /* (bug in original svlogd. remove this comment when fixed there) */
+                       /* 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("cannot stat current", ld->name);
+                       warn2("can't stat current", ld->name);
                        while (fchdir(fdwdir) == -1)
                                pause1cannot("change to initial working directory");
                        return 0;
@@ -569,11 +821,11 @@ static unsigned logdir_open(struct logdir *ld, const char *fn)
        }
        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"); ////
+       while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
+               pause2cannot("open current", ld->name); ////
        setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
 
-       coe(ld->fdcur);
+       close_on_exec_on(ld->fdcur);
        while (fchmod(ld->fdcur, 0644) == -1)
                pause2cannot("set mode of current", ld->name);
 
@@ -589,17 +841,17 @@ static unsigned logdir_open(struct logdir *ld, const char *fn)
 
 static void logdirs_reopen(void)
 {
-       struct taia now;
        int l;
        int ok = 0;
 
        tmaxflag = 0;
-       taia_now(&now);
        for (l = 0; l < dirn; ++l) {
                logdir_close(&dir[l]);
-               if (logdir_open(&dir[l], fndir[l])) ok = 1;
+               if (logdir_open(&dir[l], fndir[l]))
+                       ok = 1;
        }
-       if (!ok) fatalx("no functional log directories");
+       if (!ok)
+               fatalx("no functional log directories");
 }
 
 /* Will look good in libbb one day */
@@ -614,44 +866,57 @@ static ssize_t ndelay_read(int fd, void *buf, size_t count)
 }
 
 /* Used for reading stdin */
-static int buffer_pread(int fd, char *s, unsigned len, struct taia *now)
+static int buffer_pread(/*int fd, */char *s, unsigned len)
 {
+       unsigned now;
+       struct pollfd input;
        int i;
 
-       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;
-       }
-       taia_uint(&trotate, 2744);
-       taia_add(&trotate, now, &trotate);
-       for (i = 0; i < dirn; ++i)
-               if (dir[i].tmax) {
-                       if (taia_less(&dir[i].trotate, now))
-                               rotate(dir+i);
-                       if (taia_less(&dir[i].trotate, &trotate))
-                               trotate = dir[i].trotate;
-               }
+       input.fd = STDIN_FILENO;
+       input.events = POLLIN;
 
        do {
-               sigprocmask(SIG_UNBLOCK, blocked_sigset, NULL);
-               iopause(&input, 1, &trotate, now);
-// TODO: do not unblock/block, but use sigpending after iopause
-// to see whether there was any sig? (one syscall less...)
-               sigprocmask(SIG_BLOCK, blocked_sigset, NULL);
-               i = ndelay_read(fd, s, len);
-               if (i >= 0) break;
+               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("cannot read standard input");
+                       warn("can't read standard input");
                        break;
                }
                /* else: EAGAIN - normal, repeat silently */
@@ -660,7 +925,8 @@ static int buffer_pread(int fd, char *s, unsigned len, struct taia *now)
        if (i > 0) {
                int cnt;
                linecomplete = (s[i-1] == '\n');
-               if (!repl) return i;
+               if (!repl)
+                       return i;
 
                cnt = i;
                while (--cnt >= 0) {
@@ -684,37 +950,39 @@ static int buffer_pread(int fd, char *s, unsigned len, struct taia *now)
        return i;
 }
 
-
-static void sig_term_handler(int sig_no)
+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)
+static void sig_child_handler(int sig_no UNUSED_PARAM)
 {
-       int pid, l;
+       pid_t pid;
+       int l;
 
        if (verbose)
                bb_error_msg(INFO"sig%s received", "child");
-       while ((pid = wait_nohang(&wstat)) > 0)
-               for (l = 0; l < dirn; ++l)
+       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)
+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)
+static void sig_hangup_handler(int sig_no UNUSED_PARAM)
 {
        if (verbose)
                bb_error_msg(INFO"sig%s received", "hangup");
@@ -745,34 +1013,35 @@ static void logmatch(struct logdir *ld)
        }
 }
 
-int svlogd_main(int argc, char **argv);
+int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int svlogd_main(int argc, char **argv)
 {
-       sigset_t ss;
-       char *r,*l,*b;
+       char *r, *l, *b;
        ssize_t stdin_cnt = 0;
        int i;
        unsigned opt;
        unsigned timestamp = 0;
-       void* (*memRchr)(const void *, int, size_t) = memchr;
 
-#define line bb_common_bufsiz1
+       INIT_G();
 
        opt_complementary = "tt:vv";
-       opt = getopt32(argc, argv, "r:R:l:b:tv",
+       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 (!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 (linemax == 0)
+                       linemax = BUFSIZ-26;
+               if (linemax < 256)
+                       linemax = 256;
        }
        ////if (opt & 8) { // -b
-       ////    buflen = xatoi_u(b);
+       ////    buflen = xatoi_positive(b);
        ////    if (buflen == 0) buflen = 1024;
        ////}
        //if (opt & 0x10) timestamp++; // -t
@@ -782,11 +1051,12 @@ int svlogd_main(int argc, char **argv)
        argc -= optind;
 
        dirn = argc;
-       if (dirn <= 0) usage();
-       ////if (buflen <= linemax) usage();
+       if (dirn <= 0)
+               bb_show_usage();
+       ////if (buflen <= linemax) bb_show_usage();
        fdwdir = xopen(".", O_RDONLY|O_NDELAY);
-       coe(fdwdir);
-       dir = xzalloc(dirn * sizeof(struct logdir));
+       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;
@@ -795,38 +1065,35 @@ int svlogd_main(int argc, char **argv)
        }
        /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
        fndir = argv;
-       input.fd = 0;
-       input.events = IOPAUSE_READ;
        /* 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, 0);
-
-       blocked_sigset = &ss;
-       sigemptyset(&ss);
-       sigaddset(&ss, SIGTERM);
-       sigaddset(&ss, SIGCHLD);
-       sigaddset(&ss, SIGALRM);
-       sigaddset(&ss, SIGHUP);
-       sigprocmask(SIG_BLOCK, &ss, NULL);
-       sig_catch(SIGTERM, sig_term_handler);
-       sig_catch(SIGCHLD, sig_child_handler);
-       sig_catch(SIGALRM, sig_alarm_handler);
-       sig_catch(SIGHUP, sig_hangup_handler);
-
-       logdirs_reopen();
+       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 (!timestamp)
-               memRchr = memrchr;
+        * 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) {
-               struct taia now;
                char stamp[FMT_PTIME];
                char *lineptr;
                char *printptr;
@@ -835,19 +1102,8 @@ int svlogd_main(int argc, char **argv)
                char ch;
 
                lineptr = line;
-               taia_now(&now);
-               /* Prepare timestamp if needed */
-               if (timestamp) {
-                       switch (timestamp) {
-                       case 1:
-                               fmt_taia25(stamp, &now);
-                               break;
-                       default: /* case 2: */
-                               fmt_ptime30nul(stamp, &now);
-                               break;
-                       }
+               if (timestamp)
                        lineptr += 26;
-               }
 
                /* lineptr[0..linemax-1] - buffer for stdin */
                /* (possibly has some unprocessed data from prev loop) */
@@ -857,7 +1113,7 @@ int svlogd_main(int argc, char **argv)
                if (!np && !exitasap) {
                        i = linemax - stdin_cnt; /* avail. bytes at tail */
                        if (i >= 128) {
-                               i = buffer_pread(0, lineptr + stdin_cnt, i, &now);
+                               i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
                                if (i <= 0) /* EOF or error on stdin */
                                        exitasap = 1;
                                else {
@@ -872,7 +1128,8 @@ int svlogd_main(int argc, char **argv)
                /* 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
+ print_to_nl:
+                       /* NB: starting from here lineptr may point
                         * farther out into line[] */
                        linelen = np - lineptr + 1;
                }
@@ -889,6 +1146,10 @@ int svlogd_main(int argc, char **argv)
                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);
@@ -896,13 +1157,17 @@ int svlogd_main(int argc, char **argv)
                }
                for (i = 0; i < dirn; ++i) {
                        struct logdir *ld = &dir[i];
-                       if (ld->fddir == -1) continue;
+                       if (ld->fddir == -1)
+                               continue;
                        if (ld->inst)
                                logmatch(ld);
-                       if (ld->matcherr == 'e')
-                               ////full_write(2, printptr, printlen);
-                               fwrite(lineptr, 1, linelen, stderr);
-                       if (ld->match != '+') continue;
+                       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);
                }
 
@@ -910,8 +1175,7 @@ int svlogd_main(int argc, char **argv)
                /* read/write repeatedly until we see it */
                while (ch != '\n') {
                        /* lineptr is emptied now, safe to use as buffer */
-                       taia_now(&now);
-                       stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax, &now);
+                       stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
                        if (stdin_cnt <= 0) { /* EOF or error on stdin */
                                exitasap = 1;
                                lineptr[0] = ch = '\n';
@@ -926,11 +1190,14 @@ int svlogd_main(int argc, char **argv)
                        }
                        /* 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(2, lineptr, linelen);
+                               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;
+                               }
+                               if (dir[i].match != '+')
+                                       continue;
                                buffer_pwrite(i, lineptr, linelen);
                        }
                }
@@ -946,13 +1213,13 @@ int svlogd_main(int argc, char **argv)
                        /* Move unprocessed data to the front of line */
                        memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
                }
-               fflush(NULL);////
+               fflush_all();////
        }
 
        for (i = 0; i < dirn; ++i) {
                if (dir[i].ppid)
                        while (!processorstop(&dir[i]))
-                               /* repeat */;
+                               continue;
                logdir_close(&dir[i]);
        }
        return 0;