Bump to version 1.22.1
[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 //usage:#define svlogd_trivial_usage
129 //usage:       "[-ttv] [-r C] [-R CHARS] [-l MATCHLEN] [-b BUFLEN] DIR..."
130 //usage:#define svlogd_full_usage "\n\n"
131 //usage:       "Continuously read log data from stdin and write to rotated log files in DIRs"
132 //usage:   "\n"
133 //usage:   "\n""DIR/config file modifies behavior:"
134 //usage:   "\n""sSIZE - when to rotate logs"
135 //usage:   "\n""nNUM - number of files to retain"
136 /*usage:   "\n""NNUM - min number files to retain" - confusing */
137 /*usage:   "\n""tSEC - rotate file if it get SEC seconds old" - confusing */
138 //usage:   "\n""!PROG - process rotated log with PROG"
139 /*usage:   "\n""uIPADDR - send log over UDP" - unsupported */
140 /*usage:   "\n""UIPADDR - send log over UDP and DONT log" - unsupported */
141 /*usage:   "\n""pPFX - prefix each line with PFX" - unsupported */
142 //usage:   "\n""+,-PATTERN - (de)select line for logging"
143 //usage:   "\n""E,ePATTERN - (de)select line for stderr"
144
145 #include <sys/file.h>
146 #include "libbb.h"
147 #include "runit_lib.h"
148
149 #define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
150
151 #define FMT_PTIME 30
152
153 struct logdir {
154         ////char *btmp;
155         /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
156         char *inst;
157         char *processor;
158         char *name;
159         unsigned size;
160         unsigned sizemax;
161         unsigned nmax;
162         unsigned nmin;
163         unsigned rotate_period;
164         int ppid;
165         int fddir;
166         int fdcur;
167         FILE* filecur; ////
168         int fdlock;
169         unsigned next_rotate;
170         char fnsave[FMT_PTIME];
171         char match;
172         char matcherr;
173 };
174
175
176 struct globals {
177         struct logdir *dir;
178         unsigned verbose;
179         int linemax;
180         ////int buflen;
181         int linelen;
182
183         int fdwdir;
184         char **fndir;
185         int wstat;
186         unsigned nearest_rotate;
187
188         void* (*memRchr)(const void *, int, size_t);
189         char *shell;
190
191         smallint exitasap;
192         smallint rotateasap;
193         smallint reopenasap;
194         smallint linecomplete;
195         smallint tmaxflag;
196
197         char repl;
198         const char *replace;
199         int fl_flag_0;
200         unsigned dirn;
201
202         sigset_t blocked_sigset;
203 };
204 #define G (*ptr_to_globals)
205 #define dir            (G.dir           )
206 #define verbose        (G.verbose       )
207 #define linemax        (G.linemax       )
208 #define buflen         (G.buflen        )
209 #define linelen        (G.linelen       )
210 #define fndir          (G.fndir         )
211 #define fdwdir         (G.fdwdir        )
212 #define wstat          (G.wstat         )
213 #define memRchr        (G.memRchr       )
214 #define nearest_rotate (G.nearest_rotate)
215 #define exitasap       (G.exitasap      )
216 #define rotateasap     (G.rotateasap    )
217 #define reopenasap     (G.reopenasap    )
218 #define linecomplete   (G.linecomplete  )
219 #define tmaxflag       (G.tmaxflag      )
220 #define repl           (G.repl          )
221 #define replace        (G.replace       )
222 #define blocked_sigset (G.blocked_sigset)
223 #define fl_flag_0      (G.fl_flag_0     )
224 #define dirn           (G.dirn          )
225 #define INIT_G() do { \
226         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
227         linemax = 1000; \
228         /*buflen = 1024;*/ \
229         linecomplete = 1; \
230         replace = ""; \
231 } while (0)
232
233 #define line bb_common_bufsiz1
234
235
236 #define FATAL "fatal: "
237 #define WARNING "warning: "
238 #define PAUSE "pausing: "
239 #define INFO "info: "
240
241 static void fatalx(const char *m0)
242 {
243         bb_error_msg_and_die(FATAL"%s", m0);
244 }
245 static void warn(const char *m0)
246 {
247         bb_perror_msg(WARNING"%s", m0);
248 }
249 static void warn2(const char *m0, const char *m1)
250 {
251         bb_perror_msg(WARNING"%s: %s", m0, m1);
252 }
253 static void warnx(const char *m0, const char *m1)
254 {
255         bb_error_msg(WARNING"%s: %s", m0, m1);
256 }
257 static void pause_nomem(void)
258 {
259         bb_error_msg(PAUSE"out of memory");
260         sleep(3);
261 }
262 static void pause1cannot(const char *m0)
263 {
264         bb_perror_msg(PAUSE"can't %s", m0);
265         sleep(3);
266 }
267 static void pause2cannot(const char *m0, const char *m1)
268 {
269         bb_perror_msg(PAUSE"can't %s %s", m0, m1);
270         sleep(3);
271 }
272
273 static char* wstrdup(const char *str)
274 {
275         char *s;
276         while (!(s = strdup(str)))
277                 pause_nomem();
278         return s;
279 }
280
281 static unsigned pmatch(const char *p, const char *s, unsigned len)
282 {
283         for (;;) {
284                 char c = *p++;
285                 if (!c) return !len;
286                 switch (c) {
287                 case '*':
288                         c = *p;
289                         if (!c) return 1;
290                         for (;;) {
291                                 if (!len) return 0;
292                                 if (*s == c) break;
293                                 ++s;
294                                 --len;
295                         }
296                         continue;
297                 case '+':
298                         c = *p++;
299                         if (c != *s) return 0;
300                         for (;;) {
301                                 if (!len) return 1;
302                                 if (*s != c) break;
303                                 ++s;
304                                 --len;
305                         }
306                         continue;
307                         /*
308                 case '?':
309                         if (*p == '?') {
310                                 if (*s != '?') return 0;
311                                 ++p;
312                         }
313                         ++s; --len;
314                         continue;
315                         */
316                 default:
317                         if (!len) return 0;
318                         if (*s != c) return 0;
319                         ++s;
320                         --len;
321                         continue;
322                 }
323         }
324         return 0;
325 }
326
327 /*** ex fmt_ptime.[ch] ***/
328
329 /* NUL terminated */
330 static void fmt_time_human_30nul(char *s)
331 {
332         struct tm *ptm;
333         struct timeval tv;
334
335         gettimeofday(&tv, NULL);
336         ptm = gmtime(&tv.tv_sec);
337         sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
338                 (unsigned)(1900 + ptm->tm_year),
339                 (unsigned)(ptm->tm_mon + 1),
340                 (unsigned)(ptm->tm_mday),
341                 (unsigned)(ptm->tm_hour),
342                 (unsigned)(ptm->tm_min),
343                 (unsigned)(ptm->tm_sec),
344                 (unsigned)(tv.tv_usec)
345         );
346         /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
347         /* 5   + 3   + 3   + 3   + 3   + 3   + 9 = */
348         /* 20 (up to '.' inclusive) + 9 (not including '\0') */
349 }
350
351 /* NOT terminated! */
352 static void fmt_time_bernstein_25(char *s)
353 {
354         uint32_t pack[3];
355         struct timeval tv;
356         unsigned sec_hi;
357
358         gettimeofday(&tv, NULL);
359         sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
360         tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
361         tv.tv_usec *= 1000;
362         /* Network order is big-endian: most significant byte first.
363          * This is exactly what we want here */
364         pack[0] = htonl(sec_hi);
365         pack[1] = htonl(tv.tv_sec);
366         pack[2] = htonl(tv.tv_usec);
367         *s++ = '@';
368         bin2hex(s, (char*)pack, 12);
369 }
370
371 static void processorstart(struct logdir *ld)
372 {
373         char sv_ch;
374         int pid;
375
376         if (!ld->processor) return;
377         if (ld->ppid) {
378                 warnx("processor already running", ld->name);
379                 return;
380         }
381
382         /* vfork'ed child trashes this byte, save... */
383         sv_ch = ld->fnsave[26];
384
385         if (!G.shell)
386                 G.shell = xstrdup(get_shell_name());
387
388         while ((pid = vfork()) == -1)
389                 pause2cannot("vfork for processor", ld->name);
390         if (!pid) {
391                 int fd;
392
393                 /* child */
394                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
395                 /*bb_signals(0
396                         + (1 << SIGTERM)
397                         + (1 << SIGALRM)
398                         + (1 << SIGHUP)
399                         , SIG_DFL);*/
400                 sig_unblock(SIGTERM);
401                 sig_unblock(SIGALRM);
402                 sig_unblock(SIGHUP);
403
404                 if (verbose)
405                         bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
406                 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
407                 xmove_fd(fd, 0);
408                 ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
409                 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
410                 xmove_fd(fd, 1);
411                 fd = open("state", O_RDONLY|O_NDELAY);
412                 if (fd == -1) {
413                         if (errno != ENOENT)
414                                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
415                         close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
416                         fd = xopen("state", O_RDONLY|O_NDELAY);
417                 }
418                 xmove_fd(fd, 4);
419                 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
420                 xmove_fd(fd, 5);
421
422                 execl(G.shell, G.shell, "-c", ld->processor, (char*) NULL);
423                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
424         }
425         ld->fnsave[26] = sv_ch; /* ...restore */
426         ld->ppid = pid;
427 }
428
429 static unsigned processorstop(struct logdir *ld)
430 {
431         char f[28];
432
433         if (ld->ppid) {
434                 sig_unblock(SIGHUP);
435                 while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
436                         pause2cannot("wait for processor", ld->name);
437                 sig_block(SIGHUP);
438                 ld->ppid = 0;
439         }
440         if (ld->fddir == -1)
441                 return 1;
442         while (fchdir(ld->fddir) == -1)
443                 pause2cannot("change directory, want processor", ld->name);
444         if (WEXITSTATUS(wstat) != 0) {
445                 warnx("processor failed, restart", ld->name);
446                 ld->fnsave[26] = 't';
447                 unlink(ld->fnsave);
448                 ld->fnsave[26] = 'u';
449                 processorstart(ld);
450                 while (fchdir(fdwdir) == -1)
451                         pause1cannot("change to initial working directory");
452                 return ld->processor ? 0 : 1;
453         }
454         ld->fnsave[26] = 't';
455         memcpy(f, ld->fnsave, 26);
456         f[26] = 's';
457         f[27] = '\0';
458         while (rename(ld->fnsave, f) == -1)
459                 pause2cannot("rename processed", ld->name);
460         while (chmod(f, 0744) == -1)
461                 pause2cannot("set mode of processed", ld->name);
462         ld->fnsave[26] = 'u';
463         if (unlink(ld->fnsave) == -1)
464                 bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
465         while (rename("newstate", "state") == -1)
466                 pause2cannot("rename state", ld->name);
467         if (verbose)
468                 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
469         while (fchdir(fdwdir) == -1)
470                 pause1cannot("change to initial working directory");
471         return 1;
472 }
473
474 static void rmoldest(struct logdir *ld)
475 {
476         DIR *d;
477         struct dirent *f;
478         char oldest[FMT_PTIME];
479         int n = 0;
480
481         oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
482         while (!(d = opendir(".")))
483                 pause2cannot("open directory, want rotate", ld->name);
484         errno = 0;
485         while ((f = readdir(d))) {
486                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
487                         if (f->d_name[26] == 't') {
488                                 if (unlink(f->d_name) == -1)
489                                         warn2("can't unlink processor leftover", f->d_name);
490                         } else {
491                                 ++n;
492                                 if (strcmp(f->d_name, oldest) < 0)
493                                         memcpy(oldest, f->d_name, 27);
494                         }
495                         errno = 0;
496                 }
497         }
498         if (errno)
499                 warn2("can't read directory", ld->name);
500         closedir(d);
501
502         if (ld->nmax && (n > ld->nmax)) {
503                 if (verbose)
504                         bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
505                 if ((*oldest == '@') && (unlink(oldest) == -1))
506                         warn2("can't unlink oldest logfile", ld->name);
507         }
508 }
509
510 static unsigned rotate(struct logdir *ld)
511 {
512         struct stat st;
513         unsigned now;
514
515         if (ld->fddir == -1) {
516                 ld->rotate_period = 0;
517                 return 0;
518         }
519         if (ld->ppid)
520                 while (!processorstop(ld))
521                         continue;
522
523         while (fchdir(ld->fddir) == -1)
524                 pause2cannot("change directory, want rotate", ld->name);
525
526         /* create new filename */
527         ld->fnsave[25] = '.';
528         ld->fnsave[26] = 's';
529         if (ld->processor)
530                 ld->fnsave[26] = 'u';
531         ld->fnsave[27] = '\0';
532         do {
533                 fmt_time_bernstein_25(ld->fnsave);
534                 errno = 0;
535                 stat(ld->fnsave, &st);
536         } while (errno != ENOENT);
537
538         now = monotonic_sec();
539         if (ld->rotate_period && LESS(ld->next_rotate, now)) {
540                 ld->next_rotate = now + ld->rotate_period;
541                 if (LESS(ld->next_rotate, nearest_rotate))
542                         nearest_rotate = ld->next_rotate;
543         }
544
545         if (ld->size > 0) {
546                 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
547                         pause2cannot("fsync current logfile", ld->name);
548                 while (fchmod(ld->fdcur, 0744) == -1)
549                         pause2cannot("set mode of current", ld->name);
550                 ////close(ld->fdcur);
551                 fclose(ld->filecur);
552
553                 if (verbose) {
554                         bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
555                                         ld->fnsave, ld->size);
556                 }
557                 while (rename("current", ld->fnsave) == -1)
558                         pause2cannot("rename current", ld->name);
559                 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
560                         pause2cannot("create new current", ld->name);
561                 while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
562                         pause2cannot("create new current", ld->name); /* very unlikely */
563                 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
564                 close_on_exec_on(ld->fdcur);
565                 ld->size = 0;
566                 while (fchmod(ld->fdcur, 0644) == -1)
567                         pause2cannot("set mode of current", ld->name);
568
569                 rmoldest(ld);
570                 processorstart(ld);
571         }
572
573         while (fchdir(fdwdir) == -1)
574                 pause1cannot("change to initial working directory");
575         return 1;
576 }
577
578 static int buffer_pwrite(int n, char *s, unsigned len)
579 {
580         int i;
581         struct logdir *ld = &dir[n];
582
583         if (ld->sizemax) {
584                 if (ld->size >= ld->sizemax)
585                         rotate(ld);
586                 if (len > (ld->sizemax - ld->size))
587                         len = ld->sizemax - ld->size;
588         }
589         while (1) {
590                 ////i = full_write(ld->fdcur, s, len);
591                 ////if (i != -1) break;
592                 i = fwrite(s, 1, len, ld->filecur);
593                 if (i == len) break;
594
595                 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
596                         DIR *d;
597                         struct dirent *f;
598                         char oldest[FMT_PTIME];
599                         int j = 0;
600
601                         while (fchdir(ld->fddir) == -1)
602                                 pause2cannot("change directory, want remove old logfile",
603                                                         ld->name);
604                         oldest[0] = 'A';
605                         oldest[1] = oldest[27] = '\0';
606                         while (!(d = opendir(".")))
607                                 pause2cannot("open directory, want remove old logfile",
608                                                         ld->name);
609                         errno = 0;
610                         while ((f = readdir(d)))
611                                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
612                                         ++j;
613                                         if (strcmp(f->d_name, oldest) < 0)
614                                                 memcpy(oldest, f->d_name, 27);
615                                 }
616                         if (errno) warn2("can't read directory, want remove old logfile",
617                                         ld->name);
618                         closedir(d);
619                         errno = ENOSPC;
620                         if (j > ld->nmin) {
621                                 if (*oldest == '@') {
622                                         bb_error_msg(WARNING"out of disk space, delete: %s/%s",
623                                                         ld->name, oldest);
624                                         errno = 0;
625                                         if (unlink(oldest) == -1) {
626                                                 warn2("can't unlink oldest logfile", ld->name);
627                                                 errno = ENOSPC;
628                                         }
629                                         while (fchdir(fdwdir) == -1)
630                                                 pause1cannot("change to initial working directory");
631                                 }
632                         }
633                 }
634                 if (errno)
635                         pause2cannot("write to current", ld->name);
636         }
637
638         ld->size += i;
639         if (ld->sizemax)
640                 if (s[i-1] == '\n')
641                         if (ld->size >= (ld->sizemax - linemax))
642                                 rotate(ld);
643         return i;
644 }
645
646 static void logdir_close(struct logdir *ld)
647 {
648         if (ld->fddir == -1)
649                 return;
650         if (verbose)
651                 bb_error_msg(INFO"close: %s", ld->name);
652         close(ld->fddir);
653         ld->fddir = -1;
654         if (ld->fdcur == -1)
655                 return; /* impossible */
656         while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
657                 pause2cannot("fsync current logfile", ld->name);
658         while (fchmod(ld->fdcur, 0744) == -1)
659                 pause2cannot("set mode of current", ld->name);
660         ////close(ld->fdcur);
661         fclose(ld->filecur);
662         ld->fdcur = -1;
663         if (ld->fdlock == -1)
664                 return; /* impossible */
665         close(ld->fdlock);
666         ld->fdlock = -1;
667         free(ld->processor);
668         ld->processor = NULL;
669 }
670
671 static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
672 {
673         char buf[128];
674         unsigned now;
675         char *new, *s, *np;
676         int i;
677         struct stat st;
678
679         now = monotonic_sec();
680
681         ld->fddir = open(fn, O_RDONLY|O_NDELAY);
682         if (ld->fddir == -1) {
683                 warn2("can't open log directory", (char*)fn);
684                 return 0;
685         }
686         close_on_exec_on(ld->fddir);
687         if (fchdir(ld->fddir) == -1) {
688                 logdir_close(ld);
689                 warn2("can't change directory", (char*)fn);
690                 return 0;
691         }
692         ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
693         if ((ld->fdlock == -1)
694          || (flock(ld->fdlock, LOCK_EX | LOCK_NB) == -1)
695         ) {
696                 logdir_close(ld);
697                 warn2("can't lock directory", (char*)fn);
698                 while (fchdir(fdwdir) == -1)
699                         pause1cannot("change to initial working directory");
700                 return 0;
701         }
702         close_on_exec_on(ld->fdlock);
703
704         ld->size = 0;
705         ld->sizemax = 1000000;
706         ld->nmax = ld->nmin = 10;
707         ld->rotate_period = 0;
708         ld->name = (char*)fn;
709         ld->ppid = 0;
710         ld->match = '+';
711         free(ld->inst); ld->inst = NULL;
712         free(ld->processor); ld->processor = NULL;
713
714         /* read config */
715         i = open_read_close("config", buf, sizeof(buf) - 1);
716         if (i < 0 && errno != ENOENT)
717                 bb_perror_msg(WARNING"%s/config", ld->name);
718         if (i > 0) {
719                 buf[i] = '\0';
720                 if (verbose)
721                         bb_error_msg(INFO"read: %s/config", ld->name);
722                 s = buf;
723                 while (s) {
724                         np = strchr(s, '\n');
725                         if (np)
726                                 *np++ = '\0';
727                         switch (s[0]) {
728                         case '+':
729                         case '-':
730                         case 'e':
731                         case 'E':
732                                 /* Filtering requires one-line buffering,
733                                  * resetting the "find newline" function
734                                  * accordingly */
735                                 memRchr = memchr;
736                                 /* Add '\n'-terminated line to ld->inst */
737                                 while (1) {
738                                         int l = asprintf(&new, "%s%s\n", ld->inst ? ld->inst : "", s);
739                                         if (l >= 0 && new)
740                                                 break;
741                                         pause_nomem();
742                                 }
743                                 free(ld->inst);
744                                 ld->inst = new;
745                                 break;
746                         case 's': {
747                                 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
748                                 break;
749                         }
750                         case 'n':
751                                 ld->nmax = xatoi_positive(&s[1]);
752                                 break;
753                         case 'N':
754                                 ld->nmin = xatoi_positive(&s[1]);
755                                 break;
756                         case 't': {
757                                 static const struct suffix_mult mh_suffixes[] = {
758                                         { "m", 60 },
759                                         { "h", 60*60 },
760                                         /*{ "d", 24*60*60 },*/
761                                         { "", 0 }
762                                 };
763                                 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
764                                 if (ld->rotate_period) {
765                                         ld->next_rotate = now + ld->rotate_period;
766                                         if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
767                                                 nearest_rotate = ld->next_rotate;
768                                         tmaxflag = 1;
769                                 }
770                                 break;
771                         }
772                         case '!':
773                                 if (s[1]) {
774                                         free(ld->processor);
775                                         ld->processor = wstrdup(s);
776                                 }
777                                 break;
778                         }
779                         s = np;
780                 }
781                 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
782                 s = ld->inst;
783                 while (s) {
784                         np = strchr(s, '\n');
785                         if (np)
786                                 *np++ = '\0';
787                         s = np;
788                 }
789         }
790
791         /* open current */
792         i = stat("current", &st);
793         if (i != -1) {
794                 if (st.st_size && !(st.st_mode & S_IXUSR)) {
795                         ld->fnsave[25] = '.';
796                         ld->fnsave[26] = 'u';
797                         ld->fnsave[27] = '\0';
798                         do {
799                                 fmt_time_bernstein_25(ld->fnsave);
800                                 errno = 0;
801                                 stat(ld->fnsave, &st);
802                         } while (errno != ENOENT);
803                         while (rename("current", ld->fnsave) == -1)
804                                 pause2cannot("rename current", ld->name);
805                         rmoldest(ld);
806                         i = -1;
807                 } else {
808                         /* st.st_size can be not just bigger, but WIDER!
809                          * This code is safe: if st.st_size > 4GB, we select
810                          * ld->sizemax (because it's "unsigned") */
811                         ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
812                 }
813         } else {
814                 if (errno != ENOENT) {
815                         logdir_close(ld);
816                         warn2("can't stat current", ld->name);
817                         while (fchdir(fdwdir) == -1)
818                                 pause1cannot("change to initial working directory");
819                         return 0;
820                 }
821         }
822         while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
823                 pause2cannot("open current", ld->name);
824         while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
825                 pause2cannot("open current", ld->name); ////
826         setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
827
828         close_on_exec_on(ld->fdcur);
829         while (fchmod(ld->fdcur, 0644) == -1)
830                 pause2cannot("set mode of current", ld->name);
831
832         if (verbose) {
833                 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
834                 else bb_error_msg(INFO"new: %s/current", ld->name);
835         }
836
837         while (fchdir(fdwdir) == -1)
838                 pause1cannot("change to initial working directory");
839         return 1;
840 }
841
842 static void logdirs_reopen(void)
843 {
844         int l;
845         int ok = 0;
846
847         tmaxflag = 0;
848         for (l = 0; l < dirn; ++l) {
849                 logdir_close(&dir[l]);
850                 if (logdir_open(&dir[l], fndir[l]))
851                         ok = 1;
852         }
853         if (!ok)
854                 fatalx("no functional log directories");
855 }
856
857 /* Will look good in libbb one day */
858 static ssize_t ndelay_read(int fd, void *buf, size_t count)
859 {
860         if (!(fl_flag_0 & O_NONBLOCK))
861                 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
862         count = safe_read(fd, buf, count);
863         if (!(fl_flag_0 & O_NONBLOCK))
864                 fcntl(fd, F_SETFL, fl_flag_0);
865         return count;
866 }
867
868 /* Used for reading stdin */
869 static int buffer_pread(/*int fd, */char *s, unsigned len)
870 {
871         unsigned now;
872         struct pollfd input;
873         int i;
874
875         input.fd = STDIN_FILENO;
876         input.events = POLLIN;
877
878         do {
879                 if (rotateasap) {
880                         for (i = 0; i < dirn; ++i)
881                                 rotate(dir + i);
882                         rotateasap = 0;
883                 }
884                 if (exitasap) {
885                         if (linecomplete)
886                                 return 0;
887                         len = 1;
888                 }
889                 if (reopenasap) {
890                         logdirs_reopen();
891                         reopenasap = 0;
892                 }
893                 now = monotonic_sec();
894                 nearest_rotate = now + (45 * 60 + 45);
895                 for (i = 0; i < dirn; ++i) {
896                         if (dir[i].rotate_period) {
897                                 if (LESS(dir[i].next_rotate, now))
898                                         rotate(dir + i);
899                                 if (LESS(dir[i].next_rotate, nearest_rotate))
900                                         nearest_rotate = dir[i].next_rotate;
901                         }
902                 }
903
904                 sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
905                 i = nearest_rotate - now;
906                 if (i > 1000000)
907                         i = 1000000;
908                 if (i <= 0)
909                         i = 1;
910                 poll(&input, 1, i * 1000);
911                 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
912
913                 i = ndelay_read(STDIN_FILENO, s, len);
914                 if (i >= 0)
915                         break;
916                 if (errno == EINTR)
917                         continue;
918                 if (errno != EAGAIN) {
919                         warn("can't read standard input");
920                         break;
921                 }
922                 /* else: EAGAIN - normal, repeat silently */
923         } while (!exitasap);
924
925         if (i > 0) {
926                 int cnt;
927                 linecomplete = (s[i-1] == '\n');
928                 if (!repl)
929                         return i;
930
931                 cnt = i;
932                 while (--cnt >= 0) {
933                         char ch = *s;
934                         if (ch != '\n') {
935                                 if (ch < 32 || ch > 126)
936                                         *s = repl;
937                                 else {
938                                         int j;
939                                         for (j = 0; replace[j]; ++j) {
940                                                 if (ch == replace[j]) {
941                                                         *s = repl;
942                                                         break;
943                                                 }
944                                         }
945                                 }
946                         }
947                         s++;
948                 }
949         }
950         return i;
951 }
952
953 static void sig_term_handler(int sig_no UNUSED_PARAM)
954 {
955         if (verbose)
956                 bb_error_msg(INFO"sig%s received", "term");
957         exitasap = 1;
958 }
959
960 static void sig_child_handler(int sig_no UNUSED_PARAM)
961 {
962         pid_t pid;
963         int l;
964
965         if (verbose)
966                 bb_error_msg(INFO"sig%s received", "child");
967         while ((pid = wait_any_nohang(&wstat)) > 0) {
968                 for (l = 0; l < dirn; ++l) {
969                         if (dir[l].ppid == pid) {
970                                 dir[l].ppid = 0;
971                                 processorstop(&dir[l]);
972                                 break;
973                         }
974                 }
975         }
976 }
977
978 static void sig_alarm_handler(int sig_no UNUSED_PARAM)
979 {
980         if (verbose)
981                 bb_error_msg(INFO"sig%s received", "alarm");
982         rotateasap = 1;
983 }
984
985 static void sig_hangup_handler(int sig_no UNUSED_PARAM)
986 {
987         if (verbose)
988                 bb_error_msg(INFO"sig%s received", "hangup");
989         reopenasap = 1;
990 }
991
992 static void logmatch(struct logdir *ld)
993 {
994         char *s;
995
996         ld->match = '+';
997         ld->matcherr = 'E';
998         s = ld->inst;
999         while (s && s[0]) {
1000                 switch (s[0]) {
1001                 case '+':
1002                 case '-':
1003                         if (pmatch(s+1, line, linelen))
1004                                 ld->match = s[0];
1005                         break;
1006                 case 'e':
1007                 case 'E':
1008                         if (pmatch(s+1, line, linelen))
1009                                 ld->matcherr = s[0];
1010                         break;
1011                 }
1012                 s += strlen(s) + 1;
1013         }
1014 }
1015
1016 int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1017 int svlogd_main(int argc, char **argv)
1018 {
1019         char *r, *l, *b;
1020         ssize_t stdin_cnt = 0;
1021         int i;
1022         unsigned opt;
1023         unsigned timestamp = 0;
1024
1025         INIT_G();
1026
1027         opt_complementary = "tt:vv";
1028         opt = getopt32(argv, "r:R:l:b:tv",
1029                         &r, &replace, &l, &b, &timestamp, &verbose);
1030         if (opt & 1) { // -r
1031                 repl = r[0];
1032                 if (!repl || r[1])
1033                         bb_show_usage();
1034         }
1035         if (opt & 2) if (!repl) repl = '_'; // -R
1036         if (opt & 4) { // -l
1037                 linemax = xatou_range(l, 0, BUFSIZ-26);
1038                 if (linemax == 0)
1039                         linemax = BUFSIZ-26;
1040                 if (linemax < 256)
1041                         linemax = 256;
1042         }
1043         ////if (opt & 8) { // -b
1044         ////    buflen = xatoi_positive(b);
1045         ////    if (buflen == 0) buflen = 1024;
1046         ////}
1047         //if (opt & 0x10) timestamp++; // -t
1048         //if (opt & 0x20) verbose++; // -v
1049         //if (timestamp > 2) timestamp = 2;
1050         argv += optind;
1051         argc -= optind;
1052
1053         dirn = argc;
1054         if (dirn <= 0)
1055                 bb_show_usage();
1056         ////if (buflen <= linemax) bb_show_usage();
1057         fdwdir = xopen(".", O_RDONLY|O_NDELAY);
1058         close_on_exec_on(fdwdir);
1059         dir = xzalloc(dirn * sizeof(dir[0]));
1060         for (i = 0; i < dirn; ++i) {
1061                 dir[i].fddir = -1;
1062                 dir[i].fdcur = -1;
1063                 ////dir[i].btmp = xmalloc(buflen);
1064                 /*dir[i].ppid = 0;*/
1065         }
1066         /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
1067         fndir = argv;
1068         /* We cannot set NONBLOCK on fd #0 permanently - this setting
1069          * _isn't_ per-process! It is shared among all other processes
1070          * with the same stdin */
1071         fl_flag_0 = fcntl(0, F_GETFL);
1072
1073         sigemptyset(&blocked_sigset);
1074         sigaddset(&blocked_sigset, SIGTERM);
1075         sigaddset(&blocked_sigset, SIGCHLD);
1076         sigaddset(&blocked_sigset, SIGALRM);
1077         sigaddset(&blocked_sigset, SIGHUP);
1078         sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
1079         bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
1080         bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
1081         bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
1082         bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
1083
1084         /* Without timestamps, we don't have to print each line
1085          * separately, so we can look for _last_ newline, not first,
1086          * thus batching writes. If filtering is enabled in config,
1087          * logdirs_reopen resets it to memchr.
1088          */
1089         memRchr = (timestamp ? memchr : memrchr);
1090
1091         logdirs_reopen();
1092
1093         setvbuf(stderr, NULL, _IOFBF, linelen);
1094
1095         /* Each iteration processes one or more lines */
1096         while (1) {
1097                 char stamp[FMT_PTIME];
1098                 char *lineptr;
1099                 char *printptr;
1100                 char *np;
1101                 int printlen;
1102                 char ch;
1103
1104                 lineptr = line;
1105                 if (timestamp)
1106                         lineptr += 26;
1107
1108                 /* lineptr[0..linemax-1] - buffer for stdin */
1109                 /* (possibly has some unprocessed data from prev loop) */
1110
1111                 /* Refill the buffer if needed */
1112                 np = memRchr(lineptr, '\n', stdin_cnt);
1113                 if (!np && !exitasap) {
1114                         i = linemax - stdin_cnt; /* avail. bytes at tail */
1115                         if (i >= 128) {
1116                                 i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
1117                                 if (i <= 0) /* EOF or error on stdin */
1118                                         exitasap = 1;
1119                                 else {
1120                                         np = memRchr(lineptr + stdin_cnt, '\n', i);
1121                                         stdin_cnt += i;
1122                                 }
1123                         }
1124                 }
1125                 if (stdin_cnt <= 0 && exitasap)
1126                         break;
1127
1128                 /* Search for '\n' (in fact, np already holds the result) */
1129                 linelen = stdin_cnt;
1130                 if (np) {
1131  print_to_nl:
1132                         /* NB: starting from here lineptr may point
1133                          * farther out into line[] */
1134                         linelen = np - lineptr + 1;
1135                 }
1136                 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1137                 ch = lineptr[linelen-1];
1138
1139                 /* Biggest performance hit was coming from the fact
1140                  * that we did not buffer writes. We were reading many lines
1141                  * in one read() above, but wrote one line per write().
1142                  * We are using stdio to fix that */
1143
1144                 /* write out lineptr[0..linelen-1] to each log destination
1145                  * (or lineptr[-26..linelen-1] if timestamping) */
1146                 printlen = linelen;
1147                 printptr = lineptr;
1148                 if (timestamp) {
1149                         if (timestamp == 1)
1150                                 fmt_time_bernstein_25(stamp);
1151                         else /* 2: */
1152                                 fmt_time_human_30nul(stamp);
1153                         printlen += 26;
1154                         printptr -= 26;
1155                         memcpy(printptr, stamp, 25);
1156                         printptr[25] = ' ';
1157                 }
1158                 for (i = 0; i < dirn; ++i) {
1159                         struct logdir *ld = &dir[i];
1160                         if (ld->fddir == -1)
1161                                 continue;
1162                         if (ld->inst)
1163                                 logmatch(ld);
1164                         if (ld->matcherr == 'e') {
1165                                 /* runit-1.8.0 compat: if timestamping, do it on stderr too */
1166                                 ////full_write(STDERR_FILENO, printptr, printlen);
1167                                 fwrite(printptr, 1, printlen, stderr);
1168                         }
1169                         if (ld->match != '+')
1170                                 continue;
1171                         buffer_pwrite(i, printptr, printlen);
1172                 }
1173
1174                 /* If we didn't see '\n' (long input line), */
1175                 /* read/write repeatedly until we see it */
1176                 while (ch != '\n') {
1177                         /* lineptr is emptied now, safe to use as buffer */
1178                         stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1179                         if (stdin_cnt <= 0) { /* EOF or error on stdin */
1180                                 exitasap = 1;
1181                                 lineptr[0] = ch = '\n';
1182                                 linelen = 1;
1183                                 stdin_cnt = 1;
1184                         } else {
1185                                 linelen = stdin_cnt;
1186                                 np = memRchr(lineptr, '\n', stdin_cnt);
1187                                 if (np)
1188                                         linelen = np - lineptr + 1;
1189                                 ch = lineptr[linelen-1];
1190                         }
1191                         /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1192                         for (i = 0; i < dirn; ++i) {
1193                                 if (dir[i].fddir == -1)
1194                                         continue;
1195                                 if (dir[i].matcherr == 'e') {
1196                                         ////full_write(STDERR_FILENO, lineptr, linelen);
1197                                         fwrite(lineptr, 1, linelen, stderr);
1198                                 }
1199                                 if (dir[i].match != '+')
1200                                         continue;
1201                                 buffer_pwrite(i, lineptr, linelen);
1202                         }
1203                 }
1204
1205                 stdin_cnt -= linelen;
1206                 if (stdin_cnt > 0) {
1207                         lineptr += linelen;
1208                         /* If we see another '\n', we don't need to read
1209                          * next piece of input: can print what we have */
1210                         np = memRchr(lineptr, '\n', stdin_cnt);
1211                         if (np)
1212                                 goto print_to_nl;
1213                         /* Move unprocessed data to the front of line */
1214                         memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1215                 }
1216                 fflush_all();////
1217         }
1218
1219         for (i = 0; i < dirn; ++i) {
1220                 if (dir[i].ppid)
1221                         while (!processorstop(&dir[i]))
1222                                 continue;
1223                 logdir_close(&dir[i]);
1224         }
1225         return 0;
1226 }