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