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 unsigned exitasap;
48 static unsigned rotateasap;
49 static unsigned reopenasap;
50 static unsigned linecomplete = 1;
51 static unsigned tmaxflag;
52 static iopause_fd input;
54 static const char *replace = "";
57 static struct logdir {
59 /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
67 /* int (not long) because of taia_uint() usage: */
74 char fnsave[FMT_PTIME];
80 #define FATAL "fatal: "
81 #define WARNING "warning: "
82 #define PAUSE "pausing: "
85 #define usage() bb_show_usage()
86 static void fatalx(const char *m0)
88 bb_error_msg_and_die(FATAL"%s", m0);
90 static void warn(const char *m0) {
91 bb_perror_msg(WARNING"%s", m0);
93 static void warn2(const char *m0, const char *m1)
95 bb_perror_msg(WARNING"%s: %s", m0, m1);
97 static void warnx(const char *m0, const char *m1)
99 bb_error_msg(WARNING"%s: %s", m0, m1);
101 static void pause_nomem(void)
103 bb_error_msg(PAUSE"out of memory");
106 static void pause1cannot(const char *m0)
108 bb_perror_msg(PAUSE"cannot %s", m0);
111 static void pause2cannot(const char *m0, const char *m1)
113 bb_perror_msg(PAUSE"cannot %s %s", m0, m1);
117 static char* wstrdup(const char *str)
120 while (!(s = strdup(str)))
125 static unsigned processorstart(struct logdir *ld)
129 if (!ld->processor) return 0;
131 warnx("processor already running", ld->name);
134 while ((pid = fork()) == -1)
135 pause2cannot("fork for processor", ld->name);
141 sig_uncatch(SIGTERM);
142 sig_uncatch(SIGALRM);
144 sig_unblock(SIGTERM);
145 sig_unblock(SIGALRM);
149 bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
150 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
151 if (fd_move(0, fd) == -1)
152 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
153 ld->fnsave[26] = 't';
154 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
155 if (fd_move(1, fd) == -1)
156 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
157 fd = open_read("state");
160 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "open state for", ld->name);
161 close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
162 fd = xopen("state", O_RDONLY|O_NDELAY);
164 if (fd_move(4, fd) == -1)
165 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
166 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
167 if (fd_move(5, fd) == -1)
168 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name);
171 prog[0] = (char*)"sh";
172 prog[1] = (char*)"-c";
173 prog[2] = ld->processor;
175 execve("/bin/sh", prog, environ);
176 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name);
182 static unsigned processorstop(struct logdir *ld)
188 while (wait_pid(&wstat, ld->ppid) == -1)
189 pause2cannot("wait for processor", ld->name);
193 if (ld->fddir == -1) return 1;
194 while (fchdir(ld->fddir) == -1)
195 pause2cannot("change directory, want processor", ld->name);
196 if (wait_exitcode(wstat) != 0) {
197 warnx("processor failed, restart", ld->name);
198 ld->fnsave[26] = 't';
200 ld->fnsave[26] = 'u';
202 while (fchdir(fdwdir) == -1)
203 pause1cannot("change to initial working directory");
204 return ld->processor ? 0 : 1;
206 ld->fnsave[26] = 't';
207 memcpy(f, ld->fnsave, 26);
210 while (rename(ld->fnsave, f) == -1)
211 pause2cannot("rename processed", ld->name);
212 while (chmod(f, 0744) == -1)
213 pause2cannot("set mode of processed", ld->name);
214 ld->fnsave[26] = 'u';
215 if (unlink(ld->fnsave) == -1)
216 bb_error_msg(WARNING"cannot unlink: %s/%s", ld->name, ld->fnsave);
217 while (rename("newstate", "state") == -1)
218 pause2cannot("rename state", ld->name);
220 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
221 while (fchdir(fdwdir) == -1)
222 pause1cannot("change to initial working directory");
226 static void rmoldest(struct logdir *ld)
230 char oldest[FMT_PTIME];
233 oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
234 while (!(d = opendir(".")))
235 pause2cannot("open directory, want rotate", ld->name);
237 while ((f = readdir(d))) {
238 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
239 if (f->d_name[26] == 't') {
240 if (unlink(f->d_name) == -1)
241 warn2("cannot unlink processor leftover", f->d_name);
244 if (strcmp(f->d_name, oldest) < 0)
245 memcpy(oldest, f->d_name, 27);
251 warn2("cannot read directory", ld->name);
254 if (ld->nmax && (n > ld->nmax)) {
256 bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
257 if ((*oldest == '@') && (unlink(oldest) == -1))
258 warn2("cannot unlink oldest logfile", ld->name);
262 static unsigned rotate(struct logdir *ld)
267 if (ld->fddir == -1) {
272 while (!processorstop(ld))
275 while (fchdir(ld->fddir) == -1)
276 pause2cannot("change directory, want rotate", ld->name);
278 /* create new filename */
279 ld->fnsave[25] = '.';
280 ld->fnsave[26] = 's';
282 ld->fnsave[26] = 'u';
283 ld->fnsave[27] = '\0';
286 fmt_taia25(ld->fnsave, &now);
288 stat(ld->fnsave, &st);
289 } while (errno != ENOENT);
291 if (ld->tmax && taia_less(&ld->trotate, &now)) {
292 taia_uint(&ld->trotate, ld->tmax);
293 taia_add(&ld->trotate, &now, &ld->trotate);
294 if (taia_less(&ld->trotate, &trotate))
295 trotate = ld->trotate;
299 while (fsync(ld->fdcur) == -1)
300 pause2cannot("fsync current logfile", ld->name);
301 while (fchmod(ld->fdcur, 0744) == -1)
302 pause2cannot("set mode of current", ld->name);
305 bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
306 ld->fnsave, ld->size);
308 while (rename("current", ld->fnsave) == -1)
309 pause2cannot("rename current", ld->name);
310 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
311 pause2cannot("create new current", ld->name);
314 while (fchmod(ld->fdcur, 0644) == -1)
315 pause2cannot("set mode of current", ld->name);
320 while (fchdir(fdwdir) == -1)
321 pause1cannot("change to initial working directory");
325 static int buffer_pwrite(int n, char *s, unsigned len)
328 struct logdir *ld = &dir[n];
331 if (ld->size >= ld->sizemax)
333 if (len > (ld->sizemax - ld->size))
334 len = ld->sizemax - ld->size;
336 while ((i = write(ld->fdcur, s, len)) == -1) {
337 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
340 char oldest[FMT_PTIME];
343 while (fchdir(ld->fddir) == -1)
344 pause2cannot("change directory, want remove old logfile",
347 oldest[1] = oldest[27] = '\0';
348 while (!(d = opendir(".")))
349 pause2cannot("open directory, want remove old logfile",
352 while ((f = readdir(d)))
353 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
355 if (strcmp(f->d_name, oldest) < 0)
356 memcpy(oldest, f->d_name, 27);
358 if (errno) warn2("cannot read directory, want remove old logfile",
363 if (*oldest == '@') {
364 bb_error_msg(WARNING"out of disk space, delete: %s/%s",
367 if (unlink(oldest) == -1) {
368 warn2("cannot unlink oldest logfile", ld->name);
371 while (fchdir(fdwdir) == -1)
372 pause1cannot("change to initial working directory");
377 pause2cannot("write to current", ld->name);
383 if (ld->size >= (ld->sizemax - linemax))
388 static void logdir_close(struct logdir *ld)
393 bb_error_msg(INFO"close: %s", ld->name);
397 return; /* impossible */
398 while (fsync(ld->fdcur) == -1)
399 pause2cannot("fsync current logfile", ld->name);
400 while (fchmod(ld->fdcur, 0744) == -1)
401 pause2cannot("set mode of current", ld->name);
404 if (ld->fdlock == -1)
405 return; /* impossible */
409 ld->processor = NULL;
412 static unsigned logdir_open(struct logdir *ld, const char *fn)
420 ld->fddir = open(fn, O_RDONLY|O_NDELAY);
421 if (ld->fddir == -1) {
422 warn2("cannot open log directory", (char*)fn);
426 if (fchdir(ld->fddir) == -1) {
428 warn2("cannot change directory", (char*)fn);
431 ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
432 if ((ld->fdlock == -1)
433 || (lock_exnb(ld->fdlock) == -1)
436 warn2("cannot lock directory", (char*)fn);
437 while (fchdir(fdwdir) == -1)
438 pause1cannot("change to initial working directory");
444 ld->sizemax = 1000000;
445 ld->nmax = ld->nmin = 10;
447 ld->name = (char*)fn;
450 free(ld->inst); ld->inst = NULL;
451 free(ld->processor); ld->processor = NULL;
454 i = open_read_close("config", buf, sizeof(buf));
456 warn2("cannot read config", ld->name);
458 if (verbose) bb_error_msg(INFO"read: %s/config", ld->name);
461 np = strchr(s, '\n');
462 if (np) *np++ = '\0';
469 int l = asprintf(&new, "%s%s\n", ld->inst?:"", s);
470 if (l >= 0 && new) break;
477 static const struct suffix_mult km_suffixes[] = {
482 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
486 ld->nmax = xatoi_u(&s[1]);
489 ld->nmin = xatoi_u(&s[1]);
492 static const struct suffix_mult mh_suffixes[] = {
495 /*{ "d", 24*60*60 },*/
498 ld->tmax = xatou_sfx(&s[1], mh_suffixes);
500 taia_uint(&ld->trotate, ld->tmax);
501 taia_add(&ld->trotate, &now, &ld->trotate);
502 if (!tmaxflag || taia_less(&ld->trotate, &trotate))
503 trotate = ld->trotate;
511 ld->processor = wstrdup(s);
517 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
520 np = strchr(s, '\n');
521 if (np) *np++ = '\0';
527 i = stat("current", &st);
529 if (st.st_size && ! (st.st_mode & S_IXUSR)) {
530 ld->fnsave[25] = '.';
531 ld->fnsave[26] = 'u';
532 ld->fnsave[27] = '\0';
535 fmt_taia25(ld->fnsave, &now);
537 stat(ld->fnsave, &st);
538 } while (errno != ENOENT);
539 while (rename("current", ld->fnsave) == -1)
540 pause2cannot("rename current", ld->name);
544 /* Be paranoid: st.st_size can be not just bigger, but WIDER! */
545 /* (bug in original svlogd. remove this comment when fixed there) */
546 ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
549 if (errno != ENOENT) {
551 warn2("cannot stat current", ld->name);
552 while (fchdir(fdwdir) == -1)
553 pause1cannot("change to initial working directory");
557 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
558 pause2cannot("open current", ld->name);
560 while (fchmod(ld->fdcur, 0644) == -1)
561 pause2cannot("set mode of current", ld->name);
564 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
565 else bb_error_msg(INFO"new: %s/current", ld->name);
568 while (fchdir(fdwdir) == -1)
569 pause1cannot("change to initial working directory");
573 static void logdirs_reopen(void)
581 for (l = 0; l < dirn; ++l) {
582 logdir_close(&dir[l]);
583 if (logdir_open(&dir[l], fndir[l])) ok = 1;
585 if (!ok) fatalx("no functional log directories");
588 /* Used for reading stdin */
589 static int buffer_pread(int fd, char *s, unsigned len)
595 for (i = 0; i < dirn; ++i)
609 taia_uint(&trotate, 2744);
610 taia_add(&trotate, &now, &trotate);
611 for (i = 0; i < dirn; ++i)
613 if (taia_less(&dir[i].trotate, &now))
615 if (taia_less(&dir[i].trotate, &trotate))
616 trotate = dir[i].trotate;
621 sig_unblock(SIGTERM);
622 sig_unblock(SIGCHLD);
623 sig_unblock(SIGALRM);
625 iopause(&input, 1, &trotate, &now);
630 i = safe_read(fd, s, len);
632 if (errno != EAGAIN) {
633 warn("cannot read standard input");
636 /* else: EAGAIN - normal, repeat silently */
641 linecomplete = (s[i-1] == '\n');
648 if (ch < 32 || ch > 126)
652 for (j = 0; replace[j]; ++j) {
653 if (ch == replace[j]) {
667 static void sig_term_handler(int sig_no)
670 bb_error_msg(INFO"sig%s received", "term");
674 static void sig_child_handler(int sig_no)
679 bb_error_msg(INFO"sig%s received", "child");
680 while ((pid = wait_nohang(&wstat)) > 0)
681 for (l = 0; l < dirn; ++l)
682 if (dir[l].ppid == pid) {
684 processorstop(&dir[l]);
689 static void sig_alarm_handler(int sig_no)
692 bb_error_msg(INFO"sig%s received", "alarm");
696 static void sig_hangup_handler(int sig_no)
699 bb_error_msg(INFO"sig%s received", "hangup");
703 static void logmatch(struct logdir *ld)
714 if (pmatch(s+1, line, linelen))
719 if (pmatch(s+1, line, linelen))
727 int svlogd_main(int argc, char **argv)
731 ssize_t stdin_cnt = 0;
734 unsigned timestamp = 0;
735 void* (*memRchr)(const void *, int, size_t) = memchr;
737 #define line bb_common_bufsiz1
739 opt_complementary = "tt:vv";
740 opt = getopt32(argc, argv, "r:R:l:b:tv",
741 &r, &replace, &l, &b, ×tamp, &verbose);
744 if (!repl || r[1]) usage();
746 if (opt & 2) if (!repl) repl = '_'; // -R
748 linemax = xatou_range(l, 0, BUFSIZ-26);
749 if (linemax == 0) linemax = BUFSIZ-26;
750 if (linemax < 256) linemax = 256;
752 //// if (opt & 8) { // -b
753 //// buflen = xatoi_u(b);
754 //// if (buflen == 0) buflen = 1024;
756 //if (opt & 0x10) timestamp++; // -t
757 //if (opt & 0x20) verbose++; // -v
758 //if (timestamp > 2) timestamp = 2;
763 if (dirn <= 0) usage();
764 ////if (buflen <= linemax) usage();
765 fdwdir = xopen(".", O_RDONLY|O_NDELAY);
767 dir = xmalloc(dirn * sizeof(struct logdir));
768 for (i = 0; i < dirn; ++i) {
771 ////dir[i].btmp = xmalloc(buflen);
774 /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
777 input.events = IOPAUSE_READ;
778 /* I be damned. Linux 2.6.18: this somehow affects
779 * OTHER processes! Konsole starts to redraw itself much slower!
780 * This persists even after svlogd exits */
786 sig_catch(SIGTERM, sig_term_handler);
787 sig_catch(SIGCHLD, sig_child_handler);
788 sig_catch(SIGALRM, sig_alarm_handler);
789 sig_catch(SIGHUP, sig_hangup_handler);
793 /* Without timestamps, we don't have to print each line
794 * separately, so we can look for _last_ newline, not first,
795 * thus batching writes */
799 /* Each iteration processes one or more lines */
801 char stamp[FMT_PTIME];
809 /* Prepare timestamp if needed */
814 fmt_taia25(stamp, &now);
816 default: /* case 2: */
817 fmt_ptime30nul(stamp, &now);
823 /* lineptr[0..linemax-1] - buffer for stdin */
824 /* (possibly has some unprocessed data from prev loop) */
826 /* Refill the buffer if needed */
827 np = memRchr(lineptr, '\n', stdin_cnt);
828 if (!np && !exitasap) {
829 i = linemax - stdin_cnt; /* avail. bytes at tail */
831 i = buffer_pread(0, lineptr + stdin_cnt, i);
832 if (i <= 0) /* EOF or error on stdin */
835 np = memRchr(lineptr + stdin_cnt, '\n', i);
840 if (stdin_cnt <= 0 && exitasap)
843 /* Search for '\n' (in fact, np already holds the result) */
846 print_to_nl: /* NB: starting from here lineptr may point
847 * farther out into line[] */
848 linelen = np - lineptr + 1;
850 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
851 ch = lineptr[linelen-1];
853 /* write out lineptr[0..linelen-1] to each log destination */
854 /* (or lineptr[-26..linelen-1] if timestamping) */
860 memcpy(printptr, stamp, 25);
863 for (i = 0; i < dirn; ++i) {
864 struct logdir *ld = &dir[i];
865 if (ld->fddir == -1) continue;
868 if (ld->matcherr == 'e')
869 full_write(2, printptr, printlen);
870 if (ld->match != '+') continue;
871 buffer_pwrite(i, printptr, printlen);
874 /* If we didn't see '\n' (long input line), */
875 /* read/write repeatedly until we see it */
877 /* lineptr is emptied now, safe to use as buffer */
878 stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax);
879 if (stdin_cnt <= 0) { /* EOF or error on stdin */
881 lineptr[0] = ch = '\n';
886 np = memRchr(lineptr, '\n', stdin_cnt);
888 linelen = np - lineptr + 1;
889 ch = lineptr[linelen-1];
891 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
892 for (i = 0; i < dirn; ++i) {
893 if (dir[i].fddir == -1) continue;
894 if (dir[i].matcherr == 'e')
895 full_write(2, lineptr, linelen);
896 if (dir[i].match != '+') continue;
897 buffer_pwrite(i, lineptr, linelen);
901 stdin_cnt -= linelen;
904 /* If we see another '\n', we don't need to read
905 * next piece of input: can print what we have */
906 np = memRchr(lineptr, '\n', stdin_cnt);
909 /* Move unprocessed data to the front of line */
910 memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
914 for (i = 0; i < dirn; ++i) {
916 while (!processorstop(&dir[i]))
918 logdir_close(&dir[i]);