9fe81b900d2cbd9de520ff812c1a69ea009e4be6
[platform/upstream/busybox.git] / runit / svlogd.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8    1. Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10    2. Redistributions in binary form must reproduce the above copyright
11       notice, this list of conditions and the following disclaimer in the
12       documentation and/or other materials provided with the distribution.
13    3. The name of the author may not be used to endorse or promote products
14       derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
30
31 /*
32 Config files
33
34 On startup, and after receiving a HUP signal, svlogd checks for each
35 log directory log if the configuration file log/config exists,
36 and if so, reads the file line by line and adjusts configuration
37 for log as follows:
38
39 If the line is empty, or starts with a #, it is ignored. A line
40 of the form
41
42 ssize
43     sets the maximum file size of current when svlogd should rotate
44     the current log file to size bytes. Default is 1000000.
45     If size is zero, svlogd doesnt rotate log files
46     You should set size to at least (2 * len).
47 nnum
48     sets the number of old log files svlogd should maintain to num.
49     If svlogd sees more that num old log files in log after log file
50     rotation, it deletes the oldest one. Default is 10.
51     If num is zero, svlogd doesnt remove old log files.
52 Nmin
53     sets the minimum number of old log files svlogd should maintain
54     to min. min must be less than num. If min is set, and svlogd
55     cannot write to current because the filesystem is full,
56     and it sees more than min old log files, it deletes the oldest one.
57 ttimeout
58     sets the maximum age of the current log file when svlogd should
59     rotate the current log file to timeout seconds. If current
60     is timeout seconds old, and is not empty, svlogd forces log file rotation.
61 !processor
62     tells svlogd to feed each recent log file through processor
63     (see above) on log file rotation. By default log files are not processed.
64 ua.b.c.d[:port]
65     tells svlogd to transmit the first len characters of selected
66     log messages to the IP address a.b.c.d, port number port.
67     If port isnt set, the default port for syslog is used (514).
68     len can be set through the -l option, see below. If svlogd
69     has trouble sending udp packets, it writes error messages
70     to the log directory. Attention: logging through udp is unreliable,
71     and should be used in private networks only.
72 Ua.b.c.d[:port]
73     is the same as the u line above, but the log messages are no longer
74     written to the log directory, but transmitted through udp only.
75     Error messages from svlogd concerning sending udp packages still go
76     to the log directory.
77 pprefix
78     tells svlogd to prefix each line to be written to the log directory,
79     to standard error, or through UDP, with prefix.
80
81 If a line starts with a -, +, e, or E, svlogd matches the first len characters
82 of each log message against pattern and acts accordingly:
83
84 -pattern
85     the log message is deselected.
86 +pattern
87     the log message is selected.
88 epattern
89     the log message is selected to be printed to standard error.
90 Epattern
91     the log message is deselected to be printed to standard error.
92
93 Initially each line is selected to be written to log/current. Deselected
94 log messages are discarded from log. Initially each line is deselected
95 to be written to standard err. Log messages selected for standard error
96 are written to standard error.
97
98 Pattern Matching
99
100 svlogd matches a log message against the string pattern as follows:
101
102 pattern is applied to the log message one character by one, starting
103 with the first. A character not a star (*) and not a plus (+) matches itself.
104 A plus matches the next character in pattern in the log message one
105 or more times. A star before the end of pattern matches any string
106 in the log message that does not include the next character in pattern.
107 A star at the end of pattern matches any string.
108
109 Timestamps optionally added by svlogd are not considered part
110 of the log message.
111
112 An svlogd pattern is not a regular expression. For example consider
113 a log message like this
114
115 2005-12-18_09:13:50.97618 tcpsvd: info: pid 1977 from 10.4.1.14
116
117 The following pattern doesnt match
118
119 -*pid*
120
121 because the first star matches up to the first p in tcpsvd,
122 and then the match fails because i is not s. To match this
123 log message, you can use a pattern like this instead
124
125 -*: *: pid *
126 */
127
128 #include <sys/poll.h>
129 #include <sys/file.h>
130 #include "libbb.h"
131 #include "runit_lib.h"
132
133 #define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
134
135 #define FMT_PTIME 30
136
137 struct logdir {
138         ////char *btmp;
139         /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
140         char *inst;
141         char *processor;
142         char *name;
143         unsigned size;
144         unsigned sizemax;
145         unsigned nmax;
146         unsigned nmin;
147         unsigned rotate_period;
148         int ppid;
149         int fddir;
150         int fdcur;
151         FILE* filecur; ////
152         int fdlock;
153         unsigned next_rotate;
154         char fnsave[FMT_PTIME];
155         char match;
156         char matcherr;
157 };
158
159
160 struct globals {
161         struct logdir *dir;
162         unsigned verbose;
163         int linemax;
164         ////int buflen;
165         int linelen;
166
167         int fdwdir;
168         char **fndir;
169         int wstat;
170         unsigned nearest_rotate;
171
172         void* (*memRchr)(const void *, int, size_t);
173
174         smallint exitasap;
175         smallint rotateasap;
176         smallint reopenasap;
177         smallint linecomplete;
178         smallint tmaxflag;
179
180         char repl;
181         const char *replace;
182         int fl_flag_0;
183         unsigned dirn;
184
185         sigset_t blocked_sigset;
186 };
187 #define G (*ptr_to_globals)
188 #define dir            (G.dir           )
189 #define verbose        (G.verbose       )
190 #define linemax        (G.linemax       )
191 #define buflen         (G.buflen        )
192 #define linelen        (G.linelen       )
193 #define fndir          (G.fndir         )
194 #define fdwdir         (G.fdwdir        )
195 #define wstat          (G.wstat         )
196 #define memRchr        (G.memRchr       )
197 #define nearest_rotate (G.nearest_rotate)
198 #define exitasap       (G.exitasap      )
199 #define rotateasap     (G.rotateasap    )
200 #define reopenasap     (G.reopenasap    )
201 #define linecomplete   (G.linecomplete  )
202 #define tmaxflag       (G.tmaxflag      )
203 #define repl           (G.repl          )
204 #define replace        (G.replace       )
205 #define blocked_sigset (G.blocked_sigset)
206 #define fl_flag_0      (G.fl_flag_0     )
207 #define dirn           (G.dirn          )
208 #define INIT_G() do { \
209         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
210         linemax = 1000; \
211         /*buflen = 1024;*/ \
212         linecomplete = 1; \
213         replace = ""; \
214 } while (0)
215
216 #define line bb_common_bufsiz1
217
218
219 #define FATAL "fatal: "
220 #define WARNING "warning: "
221 #define PAUSE "pausing: "
222 #define INFO "info: "
223
224 static void fatalx(const char *m0)
225 {
226         bb_error_msg_and_die(FATAL"%s", m0);
227 }
228 static void warn(const char *m0)
229 {
230         bb_perror_msg(WARNING"%s", m0);
231 }
232 static void warn2(const char *m0, const char *m1)
233 {
234         bb_perror_msg(WARNING"%s: %s", m0, m1);
235 }
236 static void warnx(const char *m0, const char *m1)
237 {
238         bb_error_msg(WARNING"%s: %s", m0, m1);
239 }
240 static void pause_nomem(void)
241 {
242         bb_error_msg(PAUSE"out of memory");
243         sleep(3);
244 }
245 static void pause1cannot(const char *m0)
246 {
247         bb_perror_msg(PAUSE"can't %s", m0);
248         sleep(3);
249 }
250 static void pause2cannot(const char *m0, const char *m1)
251 {
252         bb_perror_msg(PAUSE"can't %s %s", m0, m1);
253         sleep(3);
254 }
255
256 static char* wstrdup(const char *str)
257 {
258         char *s;
259         while (!(s = strdup(str)))
260                 pause_nomem();
261         return s;
262 }
263
264 /*** ex fmt_ptime.[ch] ***/
265
266 /* NUL terminated */
267 static void fmt_time_human_30nul(char *s)
268 {
269         struct tm *ptm;
270         struct timeval tv;
271
272         gettimeofday(&tv, NULL);
273         ptm = gmtime(&tv.tv_sec);
274         sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
275                 (unsigned)(1900 + ptm->tm_year),
276                 (unsigned)(ptm->tm_mon + 1),
277                 (unsigned)(ptm->tm_mday),
278                 (unsigned)(ptm->tm_hour),
279                 (unsigned)(ptm->tm_min),
280                 (unsigned)(ptm->tm_sec),
281                 (unsigned)(tv.tv_usec)
282         );
283         /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
284         /* 5   + 3   + 3   + 3   + 3   + 3   + 9 = */
285         /* 20 (up to '.' inclusive) + 9 (not including '\0') */
286 }
287
288 /* NOT terminated! */
289 static void fmt_time_bernstein_25(char *s)
290 {
291         uint32_t pack[3];
292         struct timeval tv;
293         unsigned sec_hi;
294
295         gettimeofday(&tv, NULL);
296         sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
297         tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
298         tv.tv_usec *= 1000;
299         /* Network order is big-endian: most significant byte first.
300          * This is exactly what we want here */
301         pack[0] = htonl(sec_hi);
302         pack[1] = htonl(tv.tv_sec);
303         pack[2] = htonl(tv.tv_usec);
304         *s++ = '@';
305         bin2hex(s, (char*)pack, 12);
306 }
307
308 static void processorstart(struct logdir *ld)
309 {
310         char sv_ch;
311         int pid;
312
313         if (!ld->processor) return;
314         if (ld->ppid) {
315                 warnx("processor already running", ld->name);
316                 return;
317         }
318
319         /* vfork'ed child trashes this byte, save... */
320         sv_ch = ld->fnsave[26];
321
322         while ((pid = vfork()) == -1)
323                 pause2cannot("vfork for processor", ld->name);
324         if (!pid) {
325                 int fd;
326
327                 /* child */
328                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
329                 /*bb_signals(0
330                         + (1 << SIGTERM)
331                         + (1 << SIGALRM)
332                         + (1 << SIGHUP)
333                         , SIG_DFL);*/
334                 sig_unblock(SIGTERM);
335                 sig_unblock(SIGALRM);
336                 sig_unblock(SIGHUP);
337
338                 if (verbose)
339                         bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
340                 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
341                 xmove_fd(fd, 0);
342                 ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
343                 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
344                 xmove_fd(fd, 1);
345                 fd = open_read("state");
346                 if (fd == -1) {
347                         if (errno != ENOENT)
348                                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
349                         close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
350                         fd = xopen("state", O_RDONLY|O_NDELAY);
351                 }
352                 xmove_fd(fd, 4);
353                 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
354                 xmove_fd(fd, 5);
355
356 // getenv("SHELL")?
357                 execl(DEFAULT_SHELL, DEFAULT_SHELL_SHORT_NAME, "-c", ld->processor, (char*) NULL);
358                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
359         }
360         ld->fnsave[26] = sv_ch; /* ...restore */
361         ld->ppid = pid;
362 }
363
364 static unsigned processorstop(struct logdir *ld)
365 {
366         char f[28];
367
368         if (ld->ppid) {
369                 sig_unblock(SIGHUP);
370                 while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
371                         pause2cannot("wait for processor", ld->name);
372                 sig_block(SIGHUP);
373                 ld->ppid = 0;
374         }
375         if (ld->fddir == -1)
376                 return 1;
377         while (fchdir(ld->fddir) == -1)
378                 pause2cannot("change directory, want processor", ld->name);
379         if (WEXITSTATUS(wstat) != 0) {
380                 warnx("processor failed, restart", ld->name);
381                 ld->fnsave[26] = 't';
382                 unlink(ld->fnsave);
383                 ld->fnsave[26] = 'u';
384                 processorstart(ld);
385                 while (fchdir(fdwdir) == -1)
386                         pause1cannot("change to initial working directory");
387                 return ld->processor ? 0 : 1;
388         }
389         ld->fnsave[26] = 't';
390         memcpy(f, ld->fnsave, 26);
391         f[26] = 's';
392         f[27] = '\0';
393         while (rename(ld->fnsave, f) == -1)
394                 pause2cannot("rename processed", ld->name);
395         while (chmod(f, 0744) == -1)
396                 pause2cannot("set mode of processed", ld->name);
397         ld->fnsave[26] = 'u';
398         if (unlink(ld->fnsave) == -1)
399                 bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
400         while (rename("newstate", "state") == -1)
401                 pause2cannot("rename state", ld->name);
402         if (verbose)
403                 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
404         while (fchdir(fdwdir) == -1)
405                 pause1cannot("change to initial working directory");
406         return 1;
407 }
408
409 static void rmoldest(struct logdir *ld)
410 {
411         DIR *d;
412         struct dirent *f;
413         char oldest[FMT_PTIME];
414         int n = 0;
415
416         oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
417         while (!(d = opendir(".")))
418                 pause2cannot("open directory, want rotate", ld->name);
419         errno = 0;
420         while ((f = readdir(d))) {
421                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
422                         if (f->d_name[26] == 't') {
423                                 if (unlink(f->d_name) == -1)
424                                         warn2("can't unlink processor leftover", f->d_name);
425                         } else {
426                                 ++n;
427                                 if (strcmp(f->d_name, oldest) < 0)
428                                         memcpy(oldest, f->d_name, 27);
429                         }
430                         errno = 0;
431                 }
432         }
433         if (errno)
434                 warn2("can't read directory", ld->name);
435         closedir(d);
436
437         if (ld->nmax && (n > ld->nmax)) {
438                 if (verbose)
439                         bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
440                 if ((*oldest == '@') && (unlink(oldest) == -1))
441                         warn2("can't unlink oldest logfile", ld->name);
442         }
443 }
444
445 static unsigned rotate(struct logdir *ld)
446 {
447         struct stat st;
448         unsigned now;
449
450         if (ld->fddir == -1) {
451                 ld->rotate_period = 0;
452                 return 0;
453         }
454         if (ld->ppid)
455                 while (!processorstop(ld))
456                         continue;
457
458         while (fchdir(ld->fddir) == -1)
459                 pause2cannot("change directory, want rotate", ld->name);
460
461         /* create new filename */
462         ld->fnsave[25] = '.';
463         ld->fnsave[26] = 's';
464         if (ld->processor)
465                 ld->fnsave[26] = 'u';
466         ld->fnsave[27] = '\0';
467         do {
468                 fmt_time_bernstein_25(ld->fnsave);
469                 errno = 0;
470                 stat(ld->fnsave, &st);
471         } while (errno != ENOENT);
472
473         now = monotonic_sec();
474         if (ld->rotate_period && LESS(ld->next_rotate, now)) {
475                 ld->next_rotate = now + ld->rotate_period;
476                 if (LESS(ld->next_rotate, nearest_rotate))
477                         nearest_rotate = ld->next_rotate;
478         }
479
480         if (ld->size > 0) {
481                 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
482                         pause2cannot("fsync current logfile", ld->name);
483                 while (fchmod(ld->fdcur, 0744) == -1)
484                         pause2cannot("set mode of current", ld->name);
485                 ////close(ld->fdcur);
486                 fclose(ld->filecur);
487
488                 if (verbose) {
489                         bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
490                                         ld->fnsave, ld->size);
491                 }
492                 while (rename("current", ld->fnsave) == -1)
493                         pause2cannot("rename current", ld->name);
494                 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
495                         pause2cannot("create new current", ld->name);
496                 while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
497                         pause2cannot("create new current", ld->name); /* very unlikely */
498                 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
499                 close_on_exec_on(ld->fdcur);
500                 ld->size = 0;
501                 while (fchmod(ld->fdcur, 0644) == -1)
502                         pause2cannot("set mode of current", ld->name);
503
504                 rmoldest(ld);
505                 processorstart(ld);
506         }
507
508         while (fchdir(fdwdir) == -1)
509                 pause1cannot("change to initial working directory");
510         return 1;
511 }
512
513 static int buffer_pwrite(int n, char *s, unsigned len)
514 {
515         int i;
516         struct logdir *ld = &dir[n];
517
518         if (ld->sizemax) {
519                 if (ld->size >= ld->sizemax)
520                         rotate(ld);
521                 if (len > (ld->sizemax - ld->size))
522                         len = ld->sizemax - ld->size;
523         }
524         while (1) {
525                 ////i = full_write(ld->fdcur, s, len);
526                 ////if (i != -1) break;
527                 i = fwrite(s, 1, len, ld->filecur);
528                 if (i == len) break;
529
530                 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
531                         DIR *d;
532                         struct dirent *f;
533                         char oldest[FMT_PTIME];
534                         int j = 0;
535
536                         while (fchdir(ld->fddir) == -1)
537                                 pause2cannot("change directory, want remove old logfile",
538                                                          ld->name);
539                         oldest[0] = 'A';
540                         oldest[1] = oldest[27] = '\0';
541                         while (!(d = opendir(".")))
542                                 pause2cannot("open directory, want remove old logfile",
543                                                          ld->name);
544                         errno = 0;
545                         while ((f = readdir(d)))
546                                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
547                                         ++j;
548                                         if (strcmp(f->d_name, oldest) < 0)
549                                                 memcpy(oldest, f->d_name, 27);
550                                 }
551                         if (errno) warn2("can't read directory, want remove old logfile",
552                                         ld->name);
553                         closedir(d);
554                         errno = ENOSPC;
555                         if (j > ld->nmin) {
556                                 if (*oldest == '@') {
557                                         bb_error_msg(WARNING"out of disk space, delete: %s/%s",
558                                                         ld->name, oldest);
559                                         errno = 0;
560                                         if (unlink(oldest) == -1) {
561                                                 warn2("can't unlink oldest logfile", ld->name);
562                                                 errno = ENOSPC;
563                                         }
564                                         while (fchdir(fdwdir) == -1)
565                                                 pause1cannot("change to initial working directory");
566                                 }
567                         }
568                 }
569                 if (errno)
570                         pause2cannot("write to current", ld->name);
571         }
572
573         ld->size += i;
574         if (ld->sizemax)
575                 if (s[i-1] == '\n')
576                         if (ld->size >= (ld->sizemax - linemax))
577                                 rotate(ld);
578         return i;
579 }
580
581 static void logdir_close(struct logdir *ld)
582 {
583         if (ld->fddir == -1)
584                 return;
585         if (verbose)
586                 bb_error_msg(INFO"close: %s", ld->name);
587         close(ld->fddir);
588         ld->fddir = -1;
589         if (ld->fdcur == -1)
590                 return; /* impossible */
591         while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
592                 pause2cannot("fsync current logfile", ld->name);
593         while (fchmod(ld->fdcur, 0744) == -1)
594                 pause2cannot("set mode of current", ld->name);
595         ////close(ld->fdcur);
596         fclose(ld->filecur);
597         ld->fdcur = -1;
598         if (ld->fdlock == -1)
599                 return; /* impossible */
600         close(ld->fdlock);
601         ld->fdlock = -1;
602         free(ld->processor);
603         ld->processor = NULL;
604 }
605
606 static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
607 {
608         char buf[128];
609         unsigned now;
610         char *new, *s, *np;
611         int i;
612         struct stat st;
613
614         now = monotonic_sec();
615
616         ld->fddir = open(fn, O_RDONLY|O_NDELAY);
617         if (ld->fddir == -1) {
618                 warn2("can't open log directory", (char*)fn);
619                 return 0;
620         }
621         close_on_exec_on(ld->fddir);
622         if (fchdir(ld->fddir) == -1) {
623                 logdir_close(ld);
624                 warn2("can't change directory", (char*)fn);
625                 return 0;
626         }
627         ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
628         if ((ld->fdlock == -1)
629          || (lock_exnb(ld->fdlock) == -1)
630         ) {
631                 logdir_close(ld);
632                 warn2("can't lock directory", (char*)fn);
633                 while (fchdir(fdwdir) == -1)
634                         pause1cannot("change to initial working directory");
635                 return 0;
636         }
637         close_on_exec_on(ld->fdlock);
638
639         ld->size = 0;
640         ld->sizemax = 1000000;
641         ld->nmax = ld->nmin = 10;
642         ld->rotate_period = 0;
643         ld->name = (char*)fn;
644         ld->ppid = 0;
645         ld->match = '+';
646         free(ld->inst); ld->inst = NULL;
647         free(ld->processor); ld->processor = NULL;
648
649         /* read config */
650         i = open_read_close("config", buf, sizeof(buf) - 1);
651         if (i < 0 && errno != ENOENT)
652                 bb_perror_msg(WARNING"%s/config", ld->name);
653         if (i > 0) {
654                 buf[i] = '\0';
655                 if (verbose)
656                         bb_error_msg(INFO"read: %s/config", ld->name);
657                 s = buf;
658                 while (s) {
659                         np = strchr(s, '\n');
660                         if (np)
661                                 *np++ = '\0';
662                         switch (s[0]) {
663                         case '+':
664                         case '-':
665                         case 'e':
666                         case 'E':
667                                 /* Filtering requires one-line buffering,
668                                  * resetting the "find newline" function
669                                  * accordingly */
670                                 memRchr = memchr;
671                                 /* Add '\n'-terminated line to ld->inst */
672                                 while (1) {
673                                         int l = asprintf(&new, "%s%s\n", ld->inst ? ld->inst : "", s);
674                                         if (l >= 0 && new)
675                                                 break;
676                                         pause_nomem();
677                                 }
678                                 free(ld->inst);
679                                 ld->inst = new;
680                                 break;
681                         case 's': {
682                                 static const struct suffix_mult km_suffixes[] = {
683                                         { "k", 1024 },
684                                         { "m", 1024*1024 },
685                                         { "", 0 }
686                                 };
687                                 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
688                                 break;
689                         }
690                         case 'n':
691                                 ld->nmax = xatoi_u(&s[1]);
692                                 break;
693                         case 'N':
694                                 ld->nmin = xatoi_u(&s[1]);
695                                 break;
696                         case 't': {
697                                 static const struct suffix_mult mh_suffixes[] = {
698                                         { "m", 60 },
699                                         { "h", 60*60 },
700                                         /*{ "d", 24*60*60 },*/
701                                         { "", 0 }
702                                 };
703                                 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
704                                 if (ld->rotate_period) {
705                                         ld->next_rotate = now + ld->rotate_period;
706                                         if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
707                                                 nearest_rotate = ld->next_rotate;
708                                         tmaxflag = 1;
709                                 }
710                                 break;
711                         }
712                         case '!':
713                                 if (s[1]) {
714                                         free(ld->processor);
715                                         ld->processor = wstrdup(s);
716                                 }
717                                 break;
718                         }
719                         s = np;
720                 }
721                 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
722                 s = ld->inst;
723                 while (s) {
724                         np = strchr(s, '\n');
725                         if (np)
726                                 *np++ = '\0';
727                         s = np;
728                 }
729         }
730
731         /* open current */
732         i = stat("current", &st);
733         if (i != -1) {
734                 if (st.st_size && !(st.st_mode & S_IXUSR)) {
735                         ld->fnsave[25] = '.';
736                         ld->fnsave[26] = 'u';
737                         ld->fnsave[27] = '\0';
738                         do {
739                                 fmt_time_bernstein_25(ld->fnsave);
740                                 errno = 0;
741                                 stat(ld->fnsave, &st);
742                         } while (errno != ENOENT);
743                         while (rename("current", ld->fnsave) == -1)
744                                 pause2cannot("rename current", ld->name);
745                         rmoldest(ld);
746                         i = -1;
747                 } else {
748                         /* st.st_size can be not just bigger, but WIDER!
749                          * This code is safe: if st.st_size > 4GB, we select
750                          * ld->sizemax (because it's "unsigned") */
751                         ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
752                 }
753         } else {
754                 if (errno != ENOENT) {
755                         logdir_close(ld);
756                         warn2("can't stat current", ld->name);
757                         while (fchdir(fdwdir) == -1)
758                                 pause1cannot("change to initial working directory");
759                         return 0;
760                 }
761         }
762         while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
763                 pause2cannot("open current", ld->name);
764         while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
765                 pause2cannot("open current", ld->name); ////
766         setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
767
768         close_on_exec_on(ld->fdcur);
769         while (fchmod(ld->fdcur, 0644) == -1)
770                 pause2cannot("set mode of current", ld->name);
771
772         if (verbose) {
773                 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
774                 else bb_error_msg(INFO"new: %s/current", ld->name);
775         }
776
777         while (fchdir(fdwdir) == -1)
778                 pause1cannot("change to initial working directory");
779         return 1;
780 }
781
782 static void logdirs_reopen(void)
783 {
784         int l;
785         int ok = 0;
786
787         tmaxflag = 0;
788         for (l = 0; l < dirn; ++l) {
789                 logdir_close(&dir[l]);
790                 if (logdir_open(&dir[l], fndir[l]))
791                         ok = 1;
792         }
793         if (!ok)
794                 fatalx("no functional log directories");
795 }
796
797 /* Will look good in libbb one day */
798 static ssize_t ndelay_read(int fd, void *buf, size_t count)
799 {
800         if (!(fl_flag_0 & O_NONBLOCK))
801                 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
802         count = safe_read(fd, buf, count);
803         if (!(fl_flag_0 & O_NONBLOCK))
804                 fcntl(fd, F_SETFL, fl_flag_0);
805         return count;
806 }
807
808 /* Used for reading stdin */
809 static int buffer_pread(/*int fd, */char *s, unsigned len)
810 {
811         unsigned now;
812         struct pollfd input;
813         int i;
814
815         input.fd = STDIN_FILENO;
816         input.events = POLLIN;
817
818         do {
819                 if (rotateasap) {
820                         for (i = 0; i < dirn; ++i)
821                                 rotate(dir + i);
822                         rotateasap = 0;
823                 }
824                 if (exitasap) {
825                         if (linecomplete)
826                                 return 0;
827                         len = 1;
828                 }
829                 if (reopenasap) {
830                         logdirs_reopen();
831                         reopenasap = 0;
832                 }
833                 now = monotonic_sec();
834                 nearest_rotate = now + (45 * 60 + 45);
835                 for (i = 0; i < dirn; ++i) {
836                         if (dir[i].rotate_period) {
837                                 if (LESS(dir[i].next_rotate, now))
838                                         rotate(dir + i);
839                                 if (LESS(dir[i].next_rotate, nearest_rotate))
840                                         nearest_rotate = dir[i].next_rotate;
841                         }
842                 }
843
844                 sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
845                 i = nearest_rotate - now;
846                 if (i > 1000000)
847                         i = 1000000;
848                 if (i <= 0)
849                         i = 1;
850                 poll(&input, 1, i * 1000);
851                 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
852
853                 i = ndelay_read(STDIN_FILENO, s, len);
854                 if (i >= 0)
855                         break;
856                 if (errno == EINTR)
857                         continue;
858                 if (errno != EAGAIN) {
859                         warn("can't read standard input");
860                         break;
861                 }
862                 /* else: EAGAIN - normal, repeat silently */
863         } while (!exitasap);
864
865         if (i > 0) {
866                 int cnt;
867                 linecomplete = (s[i-1] == '\n');
868                 if (!repl)
869                         return i;
870
871                 cnt = i;
872                 while (--cnt >= 0) {
873                         char ch = *s;
874                         if (ch != '\n') {
875                                 if (ch < 32 || ch > 126)
876                                         *s = repl;
877                                 else {
878                                         int j;
879                                         for (j = 0; replace[j]; ++j) {
880                                                 if (ch == replace[j]) {
881                                                         *s = repl;
882                                                         break;
883                                                 }
884                                         }
885                                 }
886                         }
887                         s++;
888                 }
889         }
890         return i;
891 }
892
893 static void sig_term_handler(int sig_no UNUSED_PARAM)
894 {
895         if (verbose)
896                 bb_error_msg(INFO"sig%s received", "term");
897         exitasap = 1;
898 }
899
900 static void sig_child_handler(int sig_no UNUSED_PARAM)
901 {
902         pid_t pid;
903         int l;
904
905         if (verbose)
906                 bb_error_msg(INFO"sig%s received", "child");
907         while ((pid = wait_any_nohang(&wstat)) > 0) {
908                 for (l = 0; l < dirn; ++l) {
909                         if (dir[l].ppid == pid) {
910                                 dir[l].ppid = 0;
911                                 processorstop(&dir[l]);
912                                 break;
913                         }
914                 }
915         }
916 }
917
918 static void sig_alarm_handler(int sig_no UNUSED_PARAM)
919 {
920         if (verbose)
921                 bb_error_msg(INFO"sig%s received", "alarm");
922         rotateasap = 1;
923 }
924
925 static void sig_hangup_handler(int sig_no UNUSED_PARAM)
926 {
927         if (verbose)
928                 bb_error_msg(INFO"sig%s received", "hangup");
929         reopenasap = 1;
930 }
931
932 static void logmatch(struct logdir *ld)
933 {
934         char *s;
935
936         ld->match = '+';
937         ld->matcherr = 'E';
938         s = ld->inst;
939         while (s && s[0]) {
940                 switch (s[0]) {
941                 case '+':
942                 case '-':
943                         if (pmatch(s+1, line, linelen))
944                                 ld->match = s[0];
945                         break;
946                 case 'e':
947                 case 'E':
948                         if (pmatch(s+1, line, linelen))
949                                 ld->matcherr = s[0];
950                         break;
951                 }
952                 s += strlen(s) + 1;
953         }
954 }
955
956 int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
957 int svlogd_main(int argc, char **argv)
958 {
959         char *r, *l, *b;
960         ssize_t stdin_cnt = 0;
961         int i;
962         unsigned opt;
963         unsigned timestamp = 0;
964
965         INIT_G();
966
967         opt_complementary = "tt:vv";
968         opt = getopt32(argv, "r:R:l:b:tv",
969                         &r, &replace, &l, &b, &timestamp, &verbose);
970         if (opt & 1) { // -r
971                 repl = r[0];
972                 if (!repl || r[1])
973                         bb_show_usage();
974         }
975         if (opt & 2) if (!repl) repl = '_'; // -R
976         if (opt & 4) { // -l
977                 linemax = xatou_range(l, 0, BUFSIZ-26);
978                 if (linemax == 0)
979                         linemax = BUFSIZ-26;
980                 if (linemax < 256)
981                         linemax = 256;
982         }
983         ////if (opt & 8) { // -b
984         ////    buflen = xatoi_u(b);
985         ////    if (buflen == 0) buflen = 1024;
986         ////}
987         //if (opt & 0x10) timestamp++; // -t
988         //if (opt & 0x20) verbose++; // -v
989         //if (timestamp > 2) timestamp = 2;
990         argv += optind;
991         argc -= optind;
992
993         dirn = argc;
994         if (dirn <= 0)
995                 bb_show_usage();
996         ////if (buflen <= linemax) bb_show_usage();
997         fdwdir = xopen(".", O_RDONLY|O_NDELAY);
998         close_on_exec_on(fdwdir);
999         dir = xzalloc(dirn * sizeof(dir[0]));
1000         for (i = 0; i < dirn; ++i) {
1001                 dir[i].fddir = -1;
1002                 dir[i].fdcur = -1;
1003                 ////dir[i].btmp = xmalloc(buflen);
1004                 /*dir[i].ppid = 0;*/
1005         }
1006         /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
1007         fndir = argv;
1008         /* We cannot set NONBLOCK on fd #0 permanently - this setting
1009          * _isn't_ per-process! It is shared among all other processes
1010          * with the same stdin */
1011         fl_flag_0 = fcntl(0, F_GETFL);
1012
1013         sigemptyset(&blocked_sigset);
1014         sigaddset(&blocked_sigset, SIGTERM);
1015         sigaddset(&blocked_sigset, SIGCHLD);
1016         sigaddset(&blocked_sigset, SIGALRM);
1017         sigaddset(&blocked_sigset, SIGHUP);
1018         sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
1019         bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
1020         bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
1021         bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
1022         bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
1023
1024         /* Without timestamps, we don't have to print each line
1025          * separately, so we can look for _last_ newline, not first,
1026          * thus batching writes. If filtering is enabled in config,
1027          * logdirs_reopen resets it to memchr.
1028          */
1029         memRchr = (timestamp ? memchr : memrchr);
1030
1031         logdirs_reopen();
1032
1033         setvbuf(stderr, NULL, _IOFBF, linelen);
1034
1035         /* Each iteration processes one or more lines */
1036         while (1) {
1037                 char stamp[FMT_PTIME];
1038                 char *lineptr;
1039                 char *printptr;
1040                 char *np;
1041                 int printlen;
1042                 char ch;
1043
1044                 lineptr = line;
1045                 if (timestamp)
1046                         lineptr += 26;
1047
1048                 /* lineptr[0..linemax-1] - buffer for stdin */
1049                 /* (possibly has some unprocessed data from prev loop) */
1050
1051                 /* Refill the buffer if needed */
1052                 np = memRchr(lineptr, '\n', stdin_cnt);
1053                 if (!np && !exitasap) {
1054                         i = linemax - stdin_cnt; /* avail. bytes at tail */
1055                         if (i >= 128) {
1056                                 i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
1057                                 if (i <= 0) /* EOF or error on stdin */
1058                                         exitasap = 1;
1059                                 else {
1060                                         np = memRchr(lineptr + stdin_cnt, '\n', i);
1061                                         stdin_cnt += i;
1062                                 }
1063                         }
1064                 }
1065                 if (stdin_cnt <= 0 && exitasap)
1066                         break;
1067
1068                 /* Search for '\n' (in fact, np already holds the result) */
1069                 linelen = stdin_cnt;
1070                 if (np) {
1071  print_to_nl:           /* NB: starting from here lineptr may point
1072                          * farther out into line[] */
1073                         linelen = np - lineptr + 1;
1074                 }
1075                 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1076                 ch = lineptr[linelen-1];
1077
1078                 /* Biggest performance hit was coming from the fact
1079                  * that we did not buffer writes. We were reading many lines
1080                  * in one read() above, but wrote one line per write().
1081                  * We are using stdio to fix that */
1082
1083                 /* write out lineptr[0..linelen-1] to each log destination
1084                  * (or lineptr[-26..linelen-1] if timestamping) */
1085                 printlen = linelen;
1086                 printptr = lineptr;
1087                 if (timestamp) {
1088                         if (timestamp == 1)
1089                                 fmt_time_bernstein_25(stamp);
1090                         else /* 2: */
1091                                 fmt_time_human_30nul(stamp);
1092                         printlen += 26;
1093                         printptr -= 26;
1094                         memcpy(printptr, stamp, 25);
1095                         printptr[25] = ' ';
1096                 }
1097                 for (i = 0; i < dirn; ++i) {
1098                         struct logdir *ld = &dir[i];
1099                         if (ld->fddir == -1)
1100                                 continue;
1101                         if (ld->inst)
1102                                 logmatch(ld);
1103                         if (ld->matcherr == 'e') {
1104                                 /* runit-1.8.0 compat: if timestamping, do it on stderr too */
1105                                 ////full_write(STDERR_FILENO, printptr, printlen);
1106                                 fwrite(printptr, 1, printlen, stderr);
1107                         }
1108                         if (ld->match != '+')
1109                                 continue;
1110                         buffer_pwrite(i, printptr, printlen);
1111                 }
1112
1113                 /* If we didn't see '\n' (long input line), */
1114                 /* read/write repeatedly until we see it */
1115                 while (ch != '\n') {
1116                         /* lineptr is emptied now, safe to use as buffer */
1117                         stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1118                         if (stdin_cnt <= 0) { /* EOF or error on stdin */
1119                                 exitasap = 1;
1120                                 lineptr[0] = ch = '\n';
1121                                 linelen = 1;
1122                                 stdin_cnt = 1;
1123                         } else {
1124                                 linelen = stdin_cnt;
1125                                 np = memRchr(lineptr, '\n', stdin_cnt);
1126                                 if (np)
1127                                         linelen = np - lineptr + 1;
1128                                 ch = lineptr[linelen-1];
1129                         }
1130                         /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1131                         for (i = 0; i < dirn; ++i) {
1132                                 if (dir[i].fddir == -1)
1133                                         continue;
1134                                 if (dir[i].matcherr == 'e') {
1135                                         ////full_write(STDERR_FILENO, lineptr, linelen);
1136                                         fwrite(lineptr, 1, linelen, stderr);
1137                                 }
1138                                 if (dir[i].match != '+')
1139                                         continue;
1140                                 buffer_pwrite(i, lineptr, linelen);
1141                         }
1142                 }
1143
1144                 stdin_cnt -= linelen;
1145                 if (stdin_cnt > 0) {
1146                         lineptr += linelen;
1147                         /* If we see another '\n', we don't need to read
1148                          * next piece of input: can print what we have */
1149                         np = memRchr(lineptr, '\n', stdin_cnt);
1150                         if (np)
1151                                 goto print_to_nl;
1152                         /* Move unprocessed data to the front of line */
1153                         memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1154                 }
1155                 fflush_all();////
1156         }
1157
1158         for (i = 0; i < dirn; ++i) {
1159                 if (dir[i].ppid)
1160                         while (!processorstop(&dir[i]))
1161                                 continue;
1162                 logdir_close(&dir[i]);
1163         }
1164         return 0;
1165 }