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