2 Copyright (c) 2001-2006, Gerrit Pape
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
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.
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.
28 /* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
34 #include "runit_lib.h"
36 static unsigned verbose;
37 static int linemax = 1000;
38 ////static int buflen = 1024;
44 static struct taia trotate;
47 static smallint exitasap;
48 static smallint rotateasap;
49 static smallint reopenasap;
50 static smallint linecomplete = 1;
52 static smallint tmaxflag;
55 static const char *replace = "";
57 sigset_t blocked_sigset;
58 static iopause_fd input;
61 static struct logdir {
63 /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
71 /* int (not long) because of taia_uint() usage: */
79 char fnsave[FMT_PTIME];
85 #define FATAL "fatal: "
86 #define WARNING "warning: "
87 #define PAUSE "pausing: "
90 #define usage() bb_show_usage()
91 static void fatalx(const char *m0)
93 bb_error_msg_and_die(FATAL"%s", m0);
95 static void warn(const char *m0) {
96 bb_perror_msg(WARNING"%s", m0);
98 static void warn2(const char *m0, const char *m1)
100 bb_perror_msg(WARNING"%s: %s", m0, m1);
102 static void warnx(const char *m0, const char *m1)
104 bb_error_msg(WARNING"%s: %s", m0, m1);
106 static void pause_nomem(void)
108 bb_error_msg(PAUSE"out of memory");
111 static void pause1cannot(const char *m0)
113 bb_perror_msg(PAUSE"cannot %s", m0);
116 static void pause2cannot(const char *m0, const char *m1)
118 bb_perror_msg(PAUSE"cannot %s %s", m0, m1);
122 static char* wstrdup(const char *str)
125 while (!(s = strdup(str)))
130 static unsigned processorstart(struct logdir *ld)
134 if (!ld->processor) return 0;
136 warnx("processor already running", ld->name);
139 while ((pid = fork()) == -1)
140 pause2cannot("fork for processor", ld->name);
146 sig_uncatch(SIGTERM);
147 sig_uncatch(SIGALRM);
149 sig_unblock(SIGTERM);
150 sig_unblock(SIGALRM);
154 bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
155 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
156 if (fd_move(0, fd) == -1)
157 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
158 ld->fnsave[26] = 't';
159 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
160 if (fd_move(1, fd) == -1)
161 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
162 fd = open_read("state");
165 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "open state for", ld->name);
166 close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
167 fd = xopen("state", O_RDONLY|O_NDELAY);
169 if (fd_move(4, fd) == -1)
170 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
171 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
172 if (fd_move(5, fd) == -1)
173 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
176 prog[0] = (char*)"sh";
177 prog[1] = (char*)"-c";
178 prog[2] = ld->processor;
180 execve("/bin/sh", prog, environ);
181 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name);
187 static unsigned processorstop(struct logdir *ld)
193 while (wait_pid(&wstat, ld->ppid) == -1)
194 pause2cannot("wait for processor", ld->name);
198 if (ld->fddir == -1) return 1;
199 while (fchdir(ld->fddir) == -1)
200 pause2cannot("change directory, want processor", ld->name);
201 if (wait_exitcode(wstat) != 0) {
202 warnx("processor failed, restart", ld->name);
203 ld->fnsave[26] = 't';
205 ld->fnsave[26] = 'u';
207 while (fchdir(fdwdir) == -1)
208 pause1cannot("change to initial working directory");
209 return ld->processor ? 0 : 1;
211 ld->fnsave[26] = 't';
212 memcpy(f, ld->fnsave, 26);
215 while (rename(ld->fnsave, f) == -1)
216 pause2cannot("rename processed", ld->name);
217 while (chmod(f, 0744) == -1)
218 pause2cannot("set mode of processed", ld->name);
219 ld->fnsave[26] = 'u';
220 if (unlink(ld->fnsave) == -1)
221 bb_error_msg(WARNING"cannot unlink: %s/%s", ld->name, ld->fnsave);
222 while (rename("newstate", "state") == -1)
223 pause2cannot("rename state", ld->name);
225 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
226 while (fchdir(fdwdir) == -1)
227 pause1cannot("change to initial working directory");
231 static void rmoldest(struct logdir *ld)
235 char oldest[FMT_PTIME];
238 oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
239 while (!(d = opendir(".")))
240 pause2cannot("open directory, want rotate", ld->name);
242 while ((f = readdir(d))) {
243 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
244 if (f->d_name[26] == 't') {
245 if (unlink(f->d_name) == -1)
246 warn2("cannot unlink processor leftover", f->d_name);
249 if (strcmp(f->d_name, oldest) < 0)
250 memcpy(oldest, f->d_name, 27);
256 warn2("cannot read directory", ld->name);
259 if (ld->nmax && (n > ld->nmax)) {
261 bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
262 if ((*oldest == '@') && (unlink(oldest) == -1))
263 warn2("cannot unlink oldest logfile", ld->name);
267 static unsigned rotate(struct logdir *ld)
272 if (ld->fddir == -1) {
277 while (!processorstop(ld))
280 while (fchdir(ld->fddir) == -1)
281 pause2cannot("change directory, want rotate", ld->name);
283 /* create new filename */
284 ld->fnsave[25] = '.';
285 ld->fnsave[26] = 's';
287 ld->fnsave[26] = 'u';
288 ld->fnsave[27] = '\0';
291 fmt_taia25(ld->fnsave, &now);
293 stat(ld->fnsave, &st);
294 } while (errno != ENOENT);
296 if (ld->tmax && taia_less(&ld->trotate, &now)) {
297 taia_uint(&ld->trotate, ld->tmax);
298 taia_add(&ld->trotate, &now, &ld->trotate);
299 if (taia_less(&ld->trotate, &trotate))
300 trotate = ld->trotate;
304 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
305 pause2cannot("fsync current logfile", ld->name);
306 while (fchmod(ld->fdcur, 0744) == -1)
307 pause2cannot("set mode of current", ld->name);
308 ////close(ld->fdcur);
312 bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
313 ld->fnsave, ld->size);
315 while (rename("current", ld->fnsave) == -1)
316 pause2cannot("rename current", ld->name);
317 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
318 pause2cannot("create new current", ld->name);
319 /* we presume this cannot fail */
320 ld->filecur = fdopen(ld->fdcur, "a"); ////
321 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
324 while (fchmod(ld->fdcur, 0644) == -1)
325 pause2cannot("set mode of current", ld->name);
330 while (fchdir(fdwdir) == -1)
331 pause1cannot("change to initial working directory");
335 static int buffer_pwrite(int n, char *s, unsigned len)
338 struct logdir *ld = &dir[n];
341 if (ld->size >= ld->sizemax)
343 if (len > (ld->sizemax - ld->size))
344 len = ld->sizemax - ld->size;
347 ////i = full_write(ld->fdcur, s, len);
348 ////if (i != -1) break;
349 i = fwrite(s, 1, len, ld->filecur);
352 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
355 char oldest[FMT_PTIME];
358 while (fchdir(ld->fddir) == -1)
359 pause2cannot("change directory, want remove old logfile",
362 oldest[1] = oldest[27] = '\0';
363 while (!(d = opendir(".")))
364 pause2cannot("open directory, want remove old logfile",
367 while ((f = readdir(d)))
368 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
370 if (strcmp(f->d_name, oldest) < 0)
371 memcpy(oldest, f->d_name, 27);
373 if (errno) warn2("cannot read directory, want remove old logfile",
378 if (*oldest == '@') {
379 bb_error_msg(WARNING"out of disk space, delete: %s/%s",
382 if (unlink(oldest) == -1) {
383 warn2("cannot unlink oldest logfile", ld->name);
386 while (fchdir(fdwdir) == -1)
387 pause1cannot("change to initial working directory");
392 pause2cannot("write to current", ld->name);
398 if (ld->size >= (ld->sizemax - linemax))
403 static void logdir_close(struct logdir *ld)
408 bb_error_msg(INFO"close: %s", ld->name);
412 return; /* impossible */
413 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
414 pause2cannot("fsync current logfile", ld->name);
415 while (fchmod(ld->fdcur, 0744) == -1)
416 pause2cannot("set mode of current", ld->name);
417 ////close(ld->fdcur);
420 if (ld->fdlock == -1)
421 return; /* impossible */
425 ld->processor = NULL;
428 static unsigned logdir_open(struct logdir *ld, const char *fn)
436 ld->fddir = open(fn, O_RDONLY|O_NDELAY);
437 if (ld->fddir == -1) {
438 warn2("cannot open log directory", (char*)fn);
442 if (fchdir(ld->fddir) == -1) {
444 warn2("cannot change directory", (char*)fn);
447 ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
448 if ((ld->fdlock == -1)
449 || (lock_exnb(ld->fdlock) == -1)
452 warn2("cannot lock directory", (char*)fn);
453 while (fchdir(fdwdir) == -1)
454 pause1cannot("change to initial working directory");
460 ld->sizemax = 1000000;
461 ld->nmax = ld->nmin = 10;
463 ld->name = (char*)fn;
466 free(ld->inst); ld->inst = NULL;
467 free(ld->processor); ld->processor = NULL;
470 i = open_read_close("config", buf, sizeof(buf));
472 warn2("cannot read config", ld->name);
474 if (verbose) bb_error_msg(INFO"read: %s/config", ld->name);
477 np = strchr(s, '\n');
478 if (np) *np++ = '\0';
485 int l = asprintf(&new, "%s%s\n", ld->inst?:"", s);
486 if (l >= 0 && new) break;
493 static const struct suffix_mult km_suffixes[] = {
498 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
502 ld->nmax = xatoi_u(&s[1]);
505 ld->nmin = xatoi_u(&s[1]);
508 static const struct suffix_mult mh_suffixes[] = {
511 /*{ "d", 24*60*60 },*/
514 ld->tmax = xatou_sfx(&s[1], mh_suffixes);
516 taia_uint(&ld->trotate, ld->tmax);
517 taia_add(&ld->trotate, &now, &ld->trotate);
518 if (!tmaxflag || taia_less(&ld->trotate, &trotate))
519 trotate = ld->trotate;
527 ld->processor = wstrdup(s);
533 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
536 np = strchr(s, '\n');
537 if (np) *np++ = '\0';
543 i = stat("current", &st);
545 if (st.st_size && ! (st.st_mode & S_IXUSR)) {
546 ld->fnsave[25] = '.';
547 ld->fnsave[26] = 'u';
548 ld->fnsave[27] = '\0';
551 fmt_taia25(ld->fnsave, &now);
553 stat(ld->fnsave, &st);
554 } while (errno != ENOENT);
555 while (rename("current", ld->fnsave) == -1)
556 pause2cannot("rename current", ld->name);
560 /* Be paranoid: st.st_size can be not just bigger, but WIDER! */
561 /* (bug in original svlogd. remove this comment when fixed there) */
562 ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
565 if (errno != ENOENT) {
567 warn2("cannot stat current", ld->name);
568 while (fchdir(fdwdir) == -1)
569 pause1cannot("change to initial working directory");
573 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
574 pause2cannot("open current", ld->name);
575 /* we presume this cannot fail */
576 ld->filecur = fdopen(ld->fdcur, "a"); ////
577 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
580 while (fchmod(ld->fdcur, 0644) == -1)
581 pause2cannot("set mode of current", ld->name);
584 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
585 else bb_error_msg(INFO"new: %s/current", ld->name);
588 while (fchdir(fdwdir) == -1)
589 pause1cannot("change to initial working directory");
593 static void logdirs_reopen(void)
601 for (l = 0; l < dirn; ++l) {
602 logdir_close(&dir[l]);
603 if (logdir_open(&dir[l], fndir[l])) ok = 1;
605 if (!ok) fatalx("no functional log directories");
608 /* Will look good in libbb one day */
609 static ssize_t ndelay_read(int fd, void *buf, size_t count)
611 if (!(fl_flag_0 & O_NONBLOCK))
612 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
613 count = safe_read(fd, buf, count);
614 if (!(fl_flag_0 & O_NONBLOCK))
615 fcntl(fd, F_SETFL, fl_flag_0);
619 /* Used for reading stdin */
620 static int buffer_pread(int fd, char *s, unsigned len, struct taia *now)
625 for (i = 0; i < dirn; ++i)
638 taia_uint(&trotate, 2744);
639 taia_add(&trotate, now, &trotate);
640 for (i = 0; i < dirn; ++i)
642 if (taia_less(&dir[i].trotate, now))
644 if (taia_less(&dir[i].trotate, &trotate))
645 trotate = dir[i].trotate;
649 sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
650 iopause(&input, 1, &trotate, now);
651 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
652 i = ndelay_read(fd, s, len);
654 if (errno != EAGAIN) {
655 warn("cannot read standard input");
658 /* else: EAGAIN - normal, repeat silently */
663 linecomplete = (s[i-1] == '\n');
670 if (ch < 32 || ch > 126)
674 for (j = 0; replace[j]; ++j) {
675 if (ch == replace[j]) {
689 static void sig_term_handler(int sig_no)
692 bb_error_msg(INFO"sig%s received", "term");
696 static void sig_child_handler(int sig_no)
701 bb_error_msg(INFO"sig%s received", "child");
702 while ((pid = wait_nohang(&wstat)) > 0)
703 for (l = 0; l < dirn; ++l)
704 if (dir[l].ppid == pid) {
706 processorstop(&dir[l]);
711 static void sig_alarm_handler(int sig_no)
714 bb_error_msg(INFO"sig%s received", "alarm");
718 static void sig_hangup_handler(int sig_no)
721 bb_error_msg(INFO"sig%s received", "hangup");
725 static void logmatch(struct logdir *ld)
736 if (pmatch(s+1, line, linelen))
741 if (pmatch(s+1, line, linelen))
749 int svlogd_main(int argc, char **argv)
752 ssize_t stdin_cnt = 0;
755 unsigned timestamp = 0;
756 void* (*memRchr)(const void *, int, size_t) = memchr;
758 #define line bb_common_bufsiz1
760 opt_complementary = "tt:vv";
761 opt = getopt32(argc, argv, "r:R:l:b:tv",
762 &r, &replace, &l, &b, ×tamp, &verbose);
765 if (!repl || r[1]) usage();
767 if (opt & 2) if (!repl) repl = '_'; // -R
769 linemax = xatou_range(l, 0, BUFSIZ-26);
770 if (linemax == 0) linemax = BUFSIZ-26;
771 if (linemax < 256) linemax = 256;
773 ////if (opt & 8) { // -b
774 //// buflen = xatoi_u(b);
775 //// if (buflen == 0) buflen = 1024;
777 //if (opt & 0x10) timestamp++; // -t
778 //if (opt & 0x20) verbose++; // -v
779 //if (timestamp > 2) timestamp = 2;
784 if (dirn <= 0) usage();
785 ////if (buflen <= linemax) usage();
786 fdwdir = xopen(".", O_RDONLY|O_NDELAY);
788 dir = xmalloc(dirn * sizeof(struct logdir));
789 for (i = 0; i < dirn; ++i) {
792 ////dir[i].btmp = xmalloc(buflen);
795 /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
798 input.events = IOPAUSE_READ;
799 /* We cannot set NONBLOCK on fd #0 permanently - this setting
800 * _isn't_ per-process! It is shared among all other processes
801 * with the same stdin */
802 fl_flag_0 = fcntl(0, F_GETFL, 0);
804 sigemptyset(&blocked_sigset);
805 sigaddset(&blocked_sigset, SIGTERM);
806 sigaddset(&blocked_sigset, SIGCHLD);
807 sigaddset(&blocked_sigset, SIGALRM);
808 sigaddset(&blocked_sigset, SIGHUP);
809 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
810 sig_catch(SIGTERM, sig_term_handler);
811 sig_catch(SIGCHLD, sig_child_handler);
812 sig_catch(SIGALRM, sig_alarm_handler);
813 sig_catch(SIGHUP, sig_hangup_handler);
817 /* Without timestamps, we don't have to print each line
818 * separately, so we can look for _last_ newline, not first,
819 * thus batching writes */
823 setvbuf(stderr, NULL, _IOFBF, linelen);
825 /* Each iteration processes one or more lines */
828 char stamp[FMT_PTIME];
837 /* Prepare timestamp if needed */
841 fmt_taia25(stamp, &now);
843 default: /* case 2: */
844 fmt_ptime30nul(stamp, &now);
850 /* lineptr[0..linemax-1] - buffer for stdin */
851 /* (possibly has some unprocessed data from prev loop) */
853 /* Refill the buffer if needed */
854 np = memRchr(lineptr, '\n', stdin_cnt);
855 if (!np && !exitasap) {
856 i = linemax - stdin_cnt; /* avail. bytes at tail */
858 i = buffer_pread(0, lineptr + stdin_cnt, i, &now);
859 if (i <= 0) /* EOF or error on stdin */
862 np = memRchr(lineptr + stdin_cnt, '\n', i);
867 if (stdin_cnt <= 0 && exitasap)
870 /* Search for '\n' (in fact, np already holds the result) */
873 print_to_nl: /* NB: starting from here lineptr may point
874 * farther out into line[] */
875 linelen = np - lineptr + 1;
877 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
878 ch = lineptr[linelen-1];
880 /* Biggest performance hit was coming from the fact
881 * that we did not buffer writes. We were reading many lines
882 * in one read() above, but wrote one line per write().
883 * We are using stdio to fix that */
885 /* write out lineptr[0..linelen-1] to each log destination
886 * (or lineptr[-26..linelen-1] if timestamping) */
892 memcpy(printptr, stamp, 25);
895 for (i = 0; i < dirn; ++i) {
896 struct logdir *ld = &dir[i];
897 if (ld->fddir == -1) continue;
900 if (ld->matcherr == 'e')
901 ////full_write(2, printptr, printlen);
902 fwrite(lineptr, 1, linelen, stderr);
903 if (ld->match != '+') continue;
904 buffer_pwrite(i, printptr, printlen);
907 /* If we didn't see '\n' (long input line), */
908 /* read/write repeatedly until we see it */
910 /* lineptr is emptied now, safe to use as buffer */
912 stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax, &now);
913 if (stdin_cnt <= 0) { /* EOF or error on stdin */
915 lineptr[0] = ch = '\n';
920 np = memRchr(lineptr, '\n', stdin_cnt);
922 linelen = np - lineptr + 1;
923 ch = lineptr[linelen-1];
925 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
926 for (i = 0; i < dirn; ++i) {
927 if (dir[i].fddir == -1) continue;
928 if (dir[i].matcherr == 'e')
929 ////full_write(2, lineptr, linelen);
930 fwrite(lineptr, 1, linelen, stderr);
931 if (dir[i].match != '+') continue;
932 buffer_pwrite(i, lineptr, linelen);
936 stdin_cnt -= linelen;
939 /* If we see another '\n', we don't need to read
940 * next piece of input: can print what we have */
941 np = memRchr(lineptr, '\n', stdin_cnt);
944 /* Move unprocessed data to the front of line */
945 memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
950 for (i = 0; i < dirn; ++i) {
952 while (!processorstop(&dir[i]))
954 logdir_close(&dir[i]);