mesg: operate on stdin, not on stderr (compat)
[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                                 static const struct suffix_mult km_suffixes[] = {
749                                         { "k", 1024 },
750                                         { "m", 1024*1024 },
751                                         { "", 0 }
752                                 };
753                                 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
754                                 break;
755                         }
756                         case 'n':
757                                 ld->nmax = xatoi_positive(&s[1]);
758                                 break;
759                         case 'N':
760                                 ld->nmin = xatoi_positive(&s[1]);
761                                 break;
762                         case 't': {
763                                 static const struct suffix_mult mh_suffixes[] = {
764                                         { "m", 60 },
765                                         { "h", 60*60 },
766                                         /*{ "d", 24*60*60 },*/
767                                         { "", 0 }
768                                 };
769                                 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
770                                 if (ld->rotate_period) {
771                                         ld->next_rotate = now + ld->rotate_period;
772                                         if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
773                                                 nearest_rotate = ld->next_rotate;
774                                         tmaxflag = 1;
775                                 }
776                                 break;
777                         }
778                         case '!':
779                                 if (s[1]) {
780                                         free(ld->processor);
781                                         ld->processor = wstrdup(s);
782                                 }
783                                 break;
784                         }
785                         s = np;
786                 }
787                 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
788                 s = ld->inst;
789                 while (s) {
790                         np = strchr(s, '\n');
791                         if (np)
792                                 *np++ = '\0';
793                         s = np;
794                 }
795         }
796
797         /* open current */
798         i = stat("current", &st);
799         if (i != -1) {
800                 if (st.st_size && !(st.st_mode & S_IXUSR)) {
801                         ld->fnsave[25] = '.';
802                         ld->fnsave[26] = 'u';
803                         ld->fnsave[27] = '\0';
804                         do {
805                                 fmt_time_bernstein_25(ld->fnsave);
806                                 errno = 0;
807                                 stat(ld->fnsave, &st);
808                         } while (errno != ENOENT);
809                         while (rename("current", ld->fnsave) == -1)
810                                 pause2cannot("rename current", ld->name);
811                         rmoldest(ld);
812                         i = -1;
813                 } else {
814                         /* st.st_size can be not just bigger, but WIDER!
815                          * This code is safe: if st.st_size > 4GB, we select
816                          * ld->sizemax (because it's "unsigned") */
817                         ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
818                 }
819         } else {
820                 if (errno != ENOENT) {
821                         logdir_close(ld);
822                         warn2("can't stat current", ld->name);
823                         while (fchdir(fdwdir) == -1)
824                                 pause1cannot("change to initial working directory");
825                         return 0;
826                 }
827         }
828         while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
829                 pause2cannot("open current", ld->name);
830         while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
831                 pause2cannot("open current", ld->name); ////
832         setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
833
834         close_on_exec_on(ld->fdcur);
835         while (fchmod(ld->fdcur, 0644) == -1)
836                 pause2cannot("set mode of current", ld->name);
837
838         if (verbose) {
839                 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
840                 else bb_error_msg(INFO"new: %s/current", ld->name);
841         }
842
843         while (fchdir(fdwdir) == -1)
844                 pause1cannot("change to initial working directory");
845         return 1;
846 }
847
848 static void logdirs_reopen(void)
849 {
850         int l;
851         int ok = 0;
852
853         tmaxflag = 0;
854         for (l = 0; l < dirn; ++l) {
855                 logdir_close(&dir[l]);
856                 if (logdir_open(&dir[l], fndir[l]))
857                         ok = 1;
858         }
859         if (!ok)
860                 fatalx("no functional log directories");
861 }
862
863 /* Will look good in libbb one day */
864 static ssize_t ndelay_read(int fd, void *buf, size_t count)
865 {
866         if (!(fl_flag_0 & O_NONBLOCK))
867                 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
868         count = safe_read(fd, buf, count);
869         if (!(fl_flag_0 & O_NONBLOCK))
870                 fcntl(fd, F_SETFL, fl_flag_0);
871         return count;
872 }
873
874 /* Used for reading stdin */
875 static int buffer_pread(/*int fd, */char *s, unsigned len)
876 {
877         unsigned now;
878         struct pollfd input;
879         int i;
880
881         input.fd = STDIN_FILENO;
882         input.events = POLLIN;
883
884         do {
885                 if (rotateasap) {
886                         for (i = 0; i < dirn; ++i)
887                                 rotate(dir + i);
888                         rotateasap = 0;
889                 }
890                 if (exitasap) {
891                         if (linecomplete)
892                                 return 0;
893                         len = 1;
894                 }
895                 if (reopenasap) {
896                         logdirs_reopen();
897                         reopenasap = 0;
898                 }
899                 now = monotonic_sec();
900                 nearest_rotate = now + (45 * 60 + 45);
901                 for (i = 0; i < dirn; ++i) {
902                         if (dir[i].rotate_period) {
903                                 if (LESS(dir[i].next_rotate, now))
904                                         rotate(dir + i);
905                                 if (LESS(dir[i].next_rotate, nearest_rotate))
906                                         nearest_rotate = dir[i].next_rotate;
907                         }
908                 }
909
910                 sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
911                 i = nearest_rotate - now;
912                 if (i > 1000000)
913                         i = 1000000;
914                 if (i <= 0)
915                         i = 1;
916                 poll(&input, 1, i * 1000);
917                 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
918
919                 i = ndelay_read(STDIN_FILENO, s, len);
920                 if (i >= 0)
921                         break;
922                 if (errno == EINTR)
923                         continue;
924                 if (errno != EAGAIN) {
925                         warn("can't read standard input");
926                         break;
927                 }
928                 /* else: EAGAIN - normal, repeat silently */
929         } while (!exitasap);
930
931         if (i > 0) {
932                 int cnt;
933                 linecomplete = (s[i-1] == '\n');
934                 if (!repl)
935                         return i;
936
937                 cnt = i;
938                 while (--cnt >= 0) {
939                         char ch = *s;
940                         if (ch != '\n') {
941                                 if (ch < 32 || ch > 126)
942                                         *s = repl;
943                                 else {
944                                         int j;
945                                         for (j = 0; replace[j]; ++j) {
946                                                 if (ch == replace[j]) {
947                                                         *s = repl;
948                                                         break;
949                                                 }
950                                         }
951                                 }
952                         }
953                         s++;
954                 }
955         }
956         return i;
957 }
958
959 static void sig_term_handler(int sig_no UNUSED_PARAM)
960 {
961         if (verbose)
962                 bb_error_msg(INFO"sig%s received", "term");
963         exitasap = 1;
964 }
965
966 static void sig_child_handler(int sig_no UNUSED_PARAM)
967 {
968         pid_t pid;
969         int l;
970
971         if (verbose)
972                 bb_error_msg(INFO"sig%s received", "child");
973         while ((pid = wait_any_nohang(&wstat)) > 0) {
974                 for (l = 0; l < dirn; ++l) {
975                         if (dir[l].ppid == pid) {
976                                 dir[l].ppid = 0;
977                                 processorstop(&dir[l]);
978                                 break;
979                         }
980                 }
981         }
982 }
983
984 static void sig_alarm_handler(int sig_no UNUSED_PARAM)
985 {
986         if (verbose)
987                 bb_error_msg(INFO"sig%s received", "alarm");
988         rotateasap = 1;
989 }
990
991 static void sig_hangup_handler(int sig_no UNUSED_PARAM)
992 {
993         if (verbose)
994                 bb_error_msg(INFO"sig%s received", "hangup");
995         reopenasap = 1;
996 }
997
998 static void logmatch(struct logdir *ld)
999 {
1000         char *s;
1001
1002         ld->match = '+';
1003         ld->matcherr = 'E';
1004         s = ld->inst;
1005         while (s && s[0]) {
1006                 switch (s[0]) {
1007                 case '+':
1008                 case '-':
1009                         if (pmatch(s+1, line, linelen))
1010                                 ld->match = s[0];
1011                         break;
1012                 case 'e':
1013                 case 'E':
1014                         if (pmatch(s+1, line, linelen))
1015                                 ld->matcherr = s[0];
1016                         break;
1017                 }
1018                 s += strlen(s) + 1;
1019         }
1020 }
1021
1022 int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1023 int svlogd_main(int argc, char **argv)
1024 {
1025         char *r, *l, *b;
1026         ssize_t stdin_cnt = 0;
1027         int i;
1028         unsigned opt;
1029         unsigned timestamp = 0;
1030
1031         INIT_G();
1032
1033         opt_complementary = "tt:vv";
1034         opt = getopt32(argv, "r:R:l:b:tv",
1035                         &r, &replace, &l, &b, &timestamp, &verbose);
1036         if (opt & 1) { // -r
1037                 repl = r[0];
1038                 if (!repl || r[1])
1039                         bb_show_usage();
1040         }
1041         if (opt & 2) if (!repl) repl = '_'; // -R
1042         if (opt & 4) { // -l
1043                 linemax = xatou_range(l, 0, BUFSIZ-26);
1044                 if (linemax == 0)
1045                         linemax = BUFSIZ-26;
1046                 if (linemax < 256)
1047                         linemax = 256;
1048         }
1049         ////if (opt & 8) { // -b
1050         ////    buflen = xatoi_positive(b);
1051         ////    if (buflen == 0) buflen = 1024;
1052         ////}
1053         //if (opt & 0x10) timestamp++; // -t
1054         //if (opt & 0x20) verbose++; // -v
1055         //if (timestamp > 2) timestamp = 2;
1056         argv += optind;
1057         argc -= optind;
1058
1059         dirn = argc;
1060         if (dirn <= 0)
1061                 bb_show_usage();
1062         ////if (buflen <= linemax) bb_show_usage();
1063         fdwdir = xopen(".", O_RDONLY|O_NDELAY);
1064         close_on_exec_on(fdwdir);
1065         dir = xzalloc(dirn * sizeof(dir[0]));
1066         for (i = 0; i < dirn; ++i) {
1067                 dir[i].fddir = -1;
1068                 dir[i].fdcur = -1;
1069                 ////dir[i].btmp = xmalloc(buflen);
1070                 /*dir[i].ppid = 0;*/
1071         }
1072         /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
1073         fndir = argv;
1074         /* We cannot set NONBLOCK on fd #0 permanently - this setting
1075          * _isn't_ per-process! It is shared among all other processes
1076          * with the same stdin */
1077         fl_flag_0 = fcntl(0, F_GETFL);
1078
1079         sigemptyset(&blocked_sigset);
1080         sigaddset(&blocked_sigset, SIGTERM);
1081         sigaddset(&blocked_sigset, SIGCHLD);
1082         sigaddset(&blocked_sigset, SIGALRM);
1083         sigaddset(&blocked_sigset, SIGHUP);
1084         sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
1085         bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
1086         bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
1087         bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
1088         bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
1089
1090         /* Without timestamps, we don't have to print each line
1091          * separately, so we can look for _last_ newline, not first,
1092          * thus batching writes. If filtering is enabled in config,
1093          * logdirs_reopen resets it to memchr.
1094          */
1095         memRchr = (timestamp ? memchr : memrchr);
1096
1097         logdirs_reopen();
1098
1099         setvbuf(stderr, NULL, _IOFBF, linelen);
1100
1101         /* Each iteration processes one or more lines */
1102         while (1) {
1103                 char stamp[FMT_PTIME];
1104                 char *lineptr;
1105                 char *printptr;
1106                 char *np;
1107                 int printlen;
1108                 char ch;
1109
1110                 lineptr = line;
1111                 if (timestamp)
1112                         lineptr += 26;
1113
1114                 /* lineptr[0..linemax-1] - buffer for stdin */
1115                 /* (possibly has some unprocessed data from prev loop) */
1116
1117                 /* Refill the buffer if needed */
1118                 np = memRchr(lineptr, '\n', stdin_cnt);
1119                 if (!np && !exitasap) {
1120                         i = linemax - stdin_cnt; /* avail. bytes at tail */
1121                         if (i >= 128) {
1122                                 i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
1123                                 if (i <= 0) /* EOF or error on stdin */
1124                                         exitasap = 1;
1125                                 else {
1126                                         np = memRchr(lineptr + stdin_cnt, '\n', i);
1127                                         stdin_cnt += i;
1128                                 }
1129                         }
1130                 }
1131                 if (stdin_cnt <= 0 && exitasap)
1132                         break;
1133
1134                 /* Search for '\n' (in fact, np already holds the result) */
1135                 linelen = stdin_cnt;
1136                 if (np) {
1137  print_to_nl:
1138                         /* NB: starting from here lineptr may point
1139                          * farther out into line[] */
1140                         linelen = np - lineptr + 1;
1141                 }
1142                 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1143                 ch = lineptr[linelen-1];
1144
1145                 /* Biggest performance hit was coming from the fact
1146                  * that we did not buffer writes. We were reading many lines
1147                  * in one read() above, but wrote one line per write().
1148                  * We are using stdio to fix that */
1149
1150                 /* write out lineptr[0..linelen-1] to each log destination
1151                  * (or lineptr[-26..linelen-1] if timestamping) */
1152                 printlen = linelen;
1153                 printptr = lineptr;
1154                 if (timestamp) {
1155                         if (timestamp == 1)
1156                                 fmt_time_bernstein_25(stamp);
1157                         else /* 2: */
1158                                 fmt_time_human_30nul(stamp);
1159                         printlen += 26;
1160                         printptr -= 26;
1161                         memcpy(printptr, stamp, 25);
1162                         printptr[25] = ' ';
1163                 }
1164                 for (i = 0; i < dirn; ++i) {
1165                         struct logdir *ld = &dir[i];
1166                         if (ld->fddir == -1)
1167                                 continue;
1168                         if (ld->inst)
1169                                 logmatch(ld);
1170                         if (ld->matcherr == 'e') {
1171                                 /* runit-1.8.0 compat: if timestamping, do it on stderr too */
1172                                 ////full_write(STDERR_FILENO, printptr, printlen);
1173                                 fwrite(printptr, 1, printlen, stderr);
1174                         }
1175                         if (ld->match != '+')
1176                                 continue;
1177                         buffer_pwrite(i, printptr, printlen);
1178                 }
1179
1180                 /* If we didn't see '\n' (long input line), */
1181                 /* read/write repeatedly until we see it */
1182                 while (ch != '\n') {
1183                         /* lineptr is emptied now, safe to use as buffer */
1184                         stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1185                         if (stdin_cnt <= 0) { /* EOF or error on stdin */
1186                                 exitasap = 1;
1187                                 lineptr[0] = ch = '\n';
1188                                 linelen = 1;
1189                                 stdin_cnt = 1;
1190                         } else {
1191                                 linelen = stdin_cnt;
1192                                 np = memRchr(lineptr, '\n', stdin_cnt);
1193                                 if (np)
1194                                         linelen = np - lineptr + 1;
1195                                 ch = lineptr[linelen-1];
1196                         }
1197                         /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1198                         for (i = 0; i < dirn; ++i) {
1199                                 if (dir[i].fddir == -1)
1200                                         continue;
1201                                 if (dir[i].matcherr == 'e') {
1202                                         ////full_write(STDERR_FILENO, lineptr, linelen);
1203                                         fwrite(lineptr, 1, linelen, stderr);
1204                                 }
1205                                 if (dir[i].match != '+')
1206                                         continue;
1207                                 buffer_pwrite(i, lineptr, linelen);
1208                         }
1209                 }
1210
1211                 stdin_cnt -= linelen;
1212                 if (stdin_cnt > 0) {
1213                         lineptr += linelen;
1214                         /* If we see another '\n', we don't need to read
1215                          * next piece of input: can print what we have */
1216                         np = memRchr(lineptr, '\n', stdin_cnt);
1217                         if (np)
1218                                 goto print_to_nl;
1219                         /* Move unprocessed data to the front of line */
1220                         memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1221                 }
1222                 fflush_all();////
1223         }
1224
1225         for (i = 0; i < dirn; ++i) {
1226                 if (dir[i].ppid)
1227                         while (!processorstop(&dir[i]))
1228                                 continue;
1229                 logdir_close(&dir[i]);
1230         }
1231         return 0;
1232 }