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