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