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