#include #include #include #include #include #include #include #include #include #include #include "pmatch.h" #include "fmt_ptime.h" #include "alloc.h" #include "stralloc.h" #include "strerr.h" #include "buffer.h" #include "sig.h" #include "env.h" #include "fd.h" #include "wait.h" #include "error.h" #include "sgetopt.h" #include "open.h" #include "openreadclose.h" #include "coe.h" #include "lock.h" #include "str.h" #include "byte.h" #include "scan.h" #include "direntry.h" #include "taia.h" #include "fmt.h" #include "ndelay.h" #define USAGE " [-tv] [-r c] [-R abc] [-l n ] [-b n] dir ..." #define VERSION "$Id$" #define FATAL "svlogd: fatal: " #define WARNING "svlogd: warning: " #define PAUSE "svlogd: pausing: " #define INFO "svlogd: info: " const char *progname; unsigned int verbose =0; unsigned int timestamp =0; unsigned long linemax =1000; unsigned long buflen =1024; unsigned long linelen; const char *replace =""; char repl =0; const char **fndir; int fdwdir; struct stat st; stralloc sa; int wstat; struct taia now; char *databuf; buffer data; char *line; char stamp[FMT_PTIME]; unsigned int exitasap =0; unsigned int rotateasap =0; unsigned int reopenasap =0; unsigned int linecomplete =1; int fdudp =-1; struct logdir { int fddir; char *btmp; buffer b; stralloc inst; unsigned long size; unsigned long sizemax; unsigned long nmax; stralloc processor; int ppid; char fnsave[FMT_PTIME]; char *name; int fdcur; int fdlock; char match; struct sockaddr_in udpaddr; unsigned int udponly; } *dir; unsigned int dirn =0; void usage() { strerr_die4x(111, "usage: ", progname, USAGE, "\n"); } void die_nomem() { strerr_die2x(111, FATAL, "out of memory."); } void fatal(char *m0) { strerr_die3sys(111, FATAL, m0, ": "); } void fatalx(char *m0) { strerr_die2x(111, FATAL, m0); } void fatal2(char *m0, char *m1) { strerr_die5sys(111, FATAL, m0, ": ", m1, ": "); } void warn(char *m0) { strerr_warn3(WARNING, m0, ": ", &strerr_sys); } void warn2(char *m0, char *m1) { strerr_warn5(WARNING, m0, ": ", m1, ": ", &strerr_sys); } void warnx(char *m0, char *m1) { strerr_warn4(WARNING, m0, ": ", m1, 0); } void pause_nomem() { strerr_warn2(PAUSE, "out of memory.", 0); sleep(3); } void pause1(char *m0) { strerr_warn3(PAUSE, m0, ": ", &strerr_sys); sleep(3); } void pause2(char *m0, char *m1) { strerr_warn5(PAUSE, m0, ": ", m1, ": ", &strerr_sys); sleep(3); } unsigned int processorstart(struct logdir *ld) { int pid; if (! ld->processor.len) return(0); if (ld->ppid) { warnx("processor already running", ld->name); return(0); } while ((pid =fork()) == -1) pause2("unable to fork for processor", ld->name); if (! pid) { char *prog[4]; int fd; /* child */ sig_uncatch(sig_term); sig_uncatch(sig_alarm); sig_uncatch(sig_hangup); sig_unblock(sig_term); sig_unblock(sig_alarm); sig_unblock(sig_hangup); if (verbose) strerr_warn5(INFO, "processing: ", ld->name, "/", ld->fnsave, 0); if ((fd =open_read(ld->fnsave)) == -1) fatal2("unable to open input for processor", ld->name); if (fd_move(0, fd) == -1) fatal2("unable to move filedescriptor for processor", ld->name); ld->fnsave[26] ='t'; if ((fd =open_trunc(ld->fnsave)) == -1) fatal2("unable to open output for processor", ld->name); if (fd_move(1, fd) == -1) fatal2("unable to move filedescriptor for processor", ld->name); if ((fd =open_read("state")) == -1) { if (errno == error_noent) { if ((fd =open_trunc("state")) == -1) fatal2("unable to create empty state for processor", ld->name); close(fd); if ((fd =open_read("state")) == -1) fatal2("unable to open state for processor", ld->name); } else fatal2("unable to open state for processor", ld->name); } if (fd_move(4, fd) == -1) fatal2("unable to move filedescriptor for processor", ld->name); if ((fd =open_trunc("newstate")) == -1) fatal2("unable to open newstate for processor", ld->name); if (fd_move(5, fd) == -1) fatal2("unable to move filedescriptor for processor", ld->name); prog[0] = "sh"; prog[1] = "-c"; prog[2] = ld->processor.s; prog[3] = 0; execve("/bin/sh", prog, environ); fatal2("unable to run processor", ld->name); } ld->ppid =pid; return(1); } unsigned int processorstop(struct logdir *ld) { char f[28]; if (ld->ppid) { sig_unblock(sig_hangup); while (wait_pid(&wstat, ld->ppid) == -1) pause2("error waiting for processor", ld->name); sig_block(sig_hangup); ld->ppid =0; } if (ld->fddir == -1) return(1); while (fchdir(ld->fddir) == -1) pause2("unable to change directory, want processor", ld->name); if (wait_exitcode(wstat) != 0) { warnx("processor failed, restart", ld->name); ld->fnsave[26] ='t'; unlink(ld->fnsave); ld->fnsave[26] ='u'; processorstart(ld); while (fchdir(fdwdir) == -1) pause1("unable to change to initial working directory"); return(ld->processor.len ? 0 : 1); } if (unlink(ld->fnsave) == -1) strerr_warn5(WARNING, "unable to unlink: ", ld->name, "/", ld->fnsave, 0); ld->fnsave[26] ='t'; byte_copy(f, 26, ld->fnsave); f[26] ='s'; f[27] =0; while (rename(ld->fnsave, f) == -1) pause2("unable to rename processed", ld->name); while (chmod(f, 0744) == -1) pause2("unable to set mode of processed", ld->name); while (rename("newstate", "state") == -1) pause2("unable to rename state", ld->name); if (verbose) strerr_warn5(INFO, "processed: ", ld->name, "/", f, 0); while (fchdir(fdwdir) == -1) pause1("unable to change to initial working directory"); return(1); } unsigned int rotate(struct logdir *ld) { DIR *d; direntry *f; int n =0; char tmp[FMT_ULONG +1]; char oldest[FMT_PTIME]; if (ld->fddir == -1) return(0); if (ld->size <= 0) return(1); if (ld->ppid) while(! processorstop(ld)); while (fchdir(ld->fddir) == -1) pause2("unable to change directory, want rotate", ld->name); /* create new filename */ ld->fnsave[25] ='.'; if (ld->processor.len) ld->fnsave[26] ='u'; else ld->fnsave[26] ='s'; ld->fnsave[27] =0; do { taia_now(&now); fmt_taia(ld->fnsave, &now); errno =0; } while ((stat(ld->fnsave, &st) != -1) || (errno != error_noent)); buffer_flush(&ld->b); while (fsync(ld->fdcur) == -1) pause2("unable to fsync current logfile", ld->name); while (fchmod(ld->fdcur, 0744) == -1) pause2("unable to set mode of current", ld->name); close(ld->fdcur); if (verbose) { tmp[0] =' '; tmp[fmt_ulong(tmp +1, ld->size) +1] =0; strerr_warn6(INFO, "rename: ", ld->name, "/current ", ld->fnsave, tmp, 0); } while (rename("current", ld->fnsave) == -1) pause2("unable to rename current", ld->name); while ((ld->fdcur =open_append("current")) == -1) pause2("unable to create new current", ld->name); coe(ld->fdcur); ld->size =0; while (fchmod(ld->fdcur, 0644) == -1) pause2("unable to set mode of current", ld->name); oldest[0] ='A'; oldest[1] =oldest[27] =0; while (! (d =opendir("."))) pause2("unable to open directory, want rotate", ld->name); errno =0; while ((f =readdir(d))) if ((f->d_name[0] == '@') && (str_len(f->d_name) == 27)) { ++n; if (str_diff(f->d_name, oldest) < 0) byte_copy(oldest, 27, f->d_name); } if (errno) warn2("unable to read directory", ld->name); closedir(d); if (ld->nmax && (n >= ld->nmax)) { if (verbose) strerr_warn5(INFO, "delete: ", ld->name, "/", oldest, 0); if ((*oldest == '@') && (unlink(oldest) == -1)) warn2("unable to unlink oldest logfile", ld->name); } processorstart(ld); while (fchdir(fdwdir) == -1) pause1("unable to change to initial working directory"); return(1); } int buffer_pwrite(int n, char *s, unsigned int len) { int i; if ((dir +n)->sizemax) { if ((dir +n)->size >= (dir +n)->sizemax) rotate(dir +n); if (len > ((dir +n)->sizemax -(dir +n)->size)) len =(dir +n)->sizemax -(dir +n)->size; } while ((i =write((dir +n)->fdcur, s, len)) == -1) pause2("unable to write to current", (dir +n)->name); (dir +n)->size +=i; if ((dir +n)->sizemax) if (s[i -1] == '\n') if ((dir +n)->size >= ((dir +n)->sizemax -linemax)) rotate(dir +n); return(i); } void logdir_close(struct logdir *ld) { if (ld->fddir == -1) return; if (verbose) strerr_warn3(INFO, "close: ", ld->name, 0); close(ld->fddir); ld->fddir =-1; if (ld->fdcur == -1) return; /* impossible */ buffer_flush(&ld->b); while (fsync(ld->fdcur) == -1) pause2("unable to fsync current logfile", ld->name); while (fchmod(ld->fdcur, 0744) == -1) pause2("unable to set mode of current", ld->name); close(ld->fdcur); ld->fdcur =-1; if (ld->fdlock == -1) return; /* impossible */ close(ld->fdlock); ld->fdlock =-1; while (! stralloc_copys(&ld->processor, "")) pause_nomem(); } /* taken from libdjbdns */ unsigned int ip4_scan(const char *s,char ip[4]) { unsigned int i; unsigned int len; unsigned long u; len = 0; i = scan_ulong(s,&u); if (!i) return 0; ip[0] = u; s += i; len += i; if (*s != '.') return 0; ++s; ++len; i = scan_ulong(s,&u); if (!i) return 0; ip[1] = u; s += i; len += i; if (*s != '.') return 0; ++s; ++len; i = scan_ulong(s,&u); if (!i) return 0; ip[2] = u; s += i; len += i; if (*s != '.') return 0; ++s; ++len; i = scan_ulong(s,&u); if (!i) return 0; ip[3] = u; s += i; len += i; return len; } unsigned int logdir_open(struct logdir *ld, const char *fn) { int i; if ((ld->fddir =open_read(fn)) == -1) { warn2("unable to open log directory", (char*)fn); return(0); } coe(ld->fddir); if (fchdir(ld->fddir) == -1) { logdir_close(ld); warn2("unable to change directory", (char*)fn); return(0); } ld->fdlock =open_append("lock"); if ((ld->fdlock == -1) || (lock_exnb(ld->fdlock) == -1)) { logdir_close(ld); warn2("unable to lock directory", (char*)fn); while (fchdir(fdwdir) == -1) pause1("unable to change to initial working directory"); return(0); } coe(ld->fdlock); ld->size =0; ld->sizemax =1000000; ld->nmax =10; ld->name =(char*)fn; ld->ppid =0; ld->match ='+'; ld->udpaddr.sin_port =0; ld->udponly =0; while (! stralloc_copys(&ld->inst, "")) pause_nomem(); while (! stralloc_copys(&ld->processor, "")) pause_nomem(); /* read config */ if ((i =openreadclose("config", &sa, 128)) == -1) warn2("unable to read config", ld->name); if (i != 0) { int len, c; unsigned long port; if (verbose) strerr_warn4(INFO, "read: ", ld->name, "/config", 0); for (i =0; i +1 < sa.len; ++i) { if ((len =byte_chr(&sa.s[i], sa.len -i, '\n')) == 1) { ++i; continue; } sa.s[len +i] =0; switch(sa.s[i]) { case '\n': case '#': break; case '+': case '-': case 'e': case 'E': while (! stralloc_catb(&ld->inst, &sa.s[i], len)) pause_nomem(); while (! stralloc_0(&ld->inst)) pause_nomem(); break; case 's': scan_ulong(&sa.s[i +1], &ld->sizemax); break; case 'n': scan_ulong(&sa.s[i +1], &ld->nmax); if (ld->nmax == 1) ld->nmax =2; break; case '!': while (! stralloc_copys(&ld->processor, &sa.s[i +1])) pause_nomem(); while (! stralloc_0(&ld->processor)) pause_nomem(); break; case 'U': ld->udponly =1; case 'u': if (! (c =ip4_scan(sa.s +i +1, (char *)&ld->udpaddr.sin_addr))) { warnx("unable to scan ip address", sa.s +i +1); break; } if (sa.s[i +1 +c] == ':') { scan_ulong(sa.s +i +c +2, &port); if (port == 0) { warnx("unable to scan port number", sa.s +i +c +2); break; } } else port =514; ld->udpaddr.sin_port =htons(port); if (fdudp == -1) { fdudp =socket(AF_INET, SOCK_DGRAM, 0); if (fdudp) if (ndelay_on(fdudp) == -1) { close(fdudp); fdudp =-1; } } break; } i +=len; } } /* open current */ if ((i =stat("current", &st)) != -1) { if (st.st_size && ! (st.st_mode & S_IXUSR)) { ld->fnsave[25] ='.'; ld->fnsave[26] ='u'; ld->fnsave[27] =0; do { taia_now(&now); fmt_taia(ld->fnsave, &now); errno =0; } while ((stat(ld->fnsave, &st) != -1) || (errno != error_noent)); while (rename("current", ld->fnsave) == -1) pause2("unable to rename current", ld->name); i =-1; } else ld->size =st.st_size; } else if (errno != error_noent) { logdir_close(ld); warn2("unable to stat current", ld->name); while (fchdir(fdwdir) == -1) pause1("unable to change to initial working directory"); return(0); } while ((ld->fdcur =open_append("current")) == -1) pause2("unable to open current", ld->name); coe(ld->fdcur); while (fchmod(ld->fdcur, 0644) == -1) pause2("unable to set mode of current", ld->name); buffer_init(&ld->b, buffer_pwrite, ld -dir, ld->btmp, buflen); if (verbose) { if (i == 0) strerr_warn4(INFO, "append: ", ld->name, "/current", 0); else strerr_warn4(INFO, "new: ", ld->name, "/current", 0); } while (fchdir(fdwdir) == -1) pause1("unable to change to initial working directory"); return(1); } void logdirs_reopen(void) { int l; int ok =0; for (l =0; l < dirn; ++l) { logdir_close(&dir[l]); if (logdir_open(&dir[l], fndir[l])) ok =1; } if (! ok) fatalx("no functional log directories."); } int buffer_pread(int fd, char *s, unsigned int len) { int i; for (i =0; i < dirn; ++i) buffer_flush(&dir[i].b); if (rotateasap) { for (i =0; i < dirn; ++i) rotate(dir +i); rotateasap =0; } if (exitasap) { if (linecomplete) return(0); len =1; } if (reopenasap) { logdirs_reopen(); reopenasap =0; } sig_unblock(sig_term); sig_unblock(sig_child); sig_unblock(sig_alarm); sig_unblock(sig_hangup); i =read(fd, s, len); sig_block(sig_term); sig_block(sig_child); sig_block(sig_alarm); sig_block(sig_hangup); if (i == -1) if (errno != error_intr) warn("unable to read standard input"); if (i > 0) linecomplete =(s[i -1] == '\n'); return(i); } void sig_term_handler(void) { if (verbose) strerr_warn2(INFO, "sigterm received.", 0); exitasap =1; } void sig_child_handler(void) { int pid, l; if (verbose) strerr_warn2(INFO, "sigchild received.", 0); while ((pid =wait_nohang(&wstat)) > 0) for (l =0; l < dirn; ++l) if (dir[l].ppid == pid) { dir[l].ppid =0; processorstop(&dir[l]); break; } } void sig_alarm_handler(void) { if (verbose) strerr_warn2(INFO, "sigalarm received.", 0); rotateasap =1; } void sig_hangup_handler(void) { if (verbose) strerr_warn2(INFO, "sighangup received.", 0); reopenasap =1; } void logmatch(struct logdir *ld) { int i; ld->match ='+'; for (i =0; i < ld->inst.len; ++i) { switch(ld->inst.s[i]) { case '+': case '-': if (pmatch(&ld->inst.s[i +1], line, linelen)) ld->match =ld->inst.s[i]; break; case 'e': if (pmatch(&ld->inst.s[i +1], line, linelen)) { if (timestamp) buffer_puts(buffer_2, stamp); buffer_put(buffer_2, line, linelen); if (linelen == linemax) buffer_puts(buffer_2, "..."); buffer_put(buffer_2, "\n", 1); buffer_flush(buffer_2); } break; case 'E': if (! pmatch(&ld->inst.s[i +1], line, linelen)) { if (timestamp) buffer_puts(buffer_2, stamp); buffer_put(buffer_2, line, linelen); if (linelen == linemax) buffer_puts(buffer_2, "..."); buffer_put(buffer_2, "\n", 1); buffer_flush(buffer_2); } break; } i +=byte_chr(&ld->inst.s[i], ld->inst.len -i, 0); } } int main(int argc, const char **argv) { int i; int opt; progname =*argv; while ((opt =getopt(argc, argv, "R:r:l:b:tvV")) != opteof) { switch(opt) { case 'R': replace =optarg; if (! repl) repl ='_'; break; case 'r': repl =*optarg; if (! repl || *(optarg +1)) usage(); break; case 'l': scan_ulong(optarg, &linemax); if (linemax == 0) linemax =1000; break; case 'b': scan_ulong(optarg, &buflen); if (buflen == 0) buflen =1024; break; case 't': ++timestamp; break; case 'v': ++verbose; break; case 'V': strerr_warn1(VERSION, 0); case '?': usage(); } } argv +=optind; dirn =argc -optind; if (dirn <= 0) usage(); if (buflen <= linemax) usage(); if ((fdwdir =open_read(".")) == -1) fatal("unable to open current working directory"); coe(fdwdir); dir =(struct logdir*)alloc(dirn *sizeof(struct logdir)); if (! dir) die_nomem(); for (i =0; i < dirn; ++i) { dir[i].fddir =-1; dir[i].fdcur =-1; dir[i].btmp =(char*)alloc(buflen *sizeof(char)); if (! dir[i].btmp) die_nomem(); dir[i].ppid =0; } databuf =(char*)alloc(buflen *sizeof(char)); if (! databuf) die_nomem(); buffer_init(&data, buffer_pread, 0, databuf, buflen); line =(char*)alloc(linemax *sizeof(char)); if (! line) die_nomem(); fndir =argv; sig_block(sig_term); sig_block(sig_child); sig_block(sig_alarm); sig_block(sig_hangup); sig_catch(sig_term, sig_term_handler); sig_catch(sig_child, sig_child_handler); sig_catch(sig_alarm, sig_alarm_handler); sig_catch(sig_hangup, sig_hangup_handler); logdirs_reopen(); for(;;) { char ch; linelen =0; if (exitasap && ! data.p) break; /* data buffer is empty */ for (linelen =0; linelen < linemax; ++linelen) { if (buffer_GETC(&data, &ch) <= 0) { exitasap =1; break; } if (! linelen && timestamp) { taia_now(&now); switch (timestamp) { case 1: stamp[fmt_taia(stamp, &now)] =' '; stamp[26] =0; break; case 2: stamp[fmt_ptime(stamp, &now)] =' '; stamp[26] =0; break; case 3: stamp[fmt_ptime(stamp, &now)] =0; stamp[19] =' '; stamp[20] =0; break; } } if (ch == '\n') break; if (repl) { if ((ch < 32) || (ch > 126)) ch =repl; else for (i =0; replace[i]; ++i) if (ch == replace[i]) { ch =repl; break; } } line[linelen] =ch; } if (! linelen) continue; for (i =0; i < dirn; ++i) if (dir[i].fddir != -1) { if (dir[i].inst.len) logmatch(&dir[i]); if (dir[i].match != '+') continue; if (! dir[i].udponly) { if (timestamp) buffer_puts(&dir[i].b, stamp); buffer_put(&dir[i].b, line, linelen); } if (dir[i].udpaddr.sin_port != 0) { if (fdudp == -1) { buffer_puts(&dir[i].b, "warning: no udp socket available: "); buffer_put(&dir[i].b, line, linelen); buffer_put(&dir[i].b, "\n", 1); buffer_flush(&dir[i].b); } else { if (linelen >= linemax -1) { line[linemax -4] =line[linemax -3] =line[linemax -2] ='.'; linelen =linemax -1; } if (line[linelen -1] != '\n') line[linelen++] ='\n'; if (sendto(fdudp, line, linelen, 0, (struct sockaddr *)&dir[i].udpaddr, sizeof(dir[i].udpaddr)) != linelen) { buffer_puts(&dir[i].b, "warning: failure sending through udp: "); buffer_put(&dir[i].b, line, linelen); buffer_put(&dir[i].b, "\n", 1); buffer_flush(&dir[i].b); } } } } if (linelen == linemax) for (;;) { if (buffer_GETC(&data, &ch) <= 0) { exitasap =1; break; } if (ch == '\n') break; for (i =0; i < dirn; ++i) if (dir[i].fddir != -1) { if (dir[i].match != '+') continue; if (! dir[i].udponly) buffer_PUTC(&dir[i].b, ch); } } for (i =0; i < dirn; ++i) if (dir[i].fddir != -1) { if (dir[i].match != '+') continue; if (! dir[i].udponly) { ch ='\n'; buffer_PUTC(&dir[i].b, ch); buffer_flush(&dir[i].b); } } } for (i =0; i < dirn; ++i) { if (dir[i].ppid) while (! processorstop(&dir[i])); logdir_close(&dir[i]); } _exit(0); }