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