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