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