diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 39 | ||||
-rw-r--r-- | addts.1 | 78 | ||||
-rw-r--r-- | addts.c | 125 |
4 files changed, 247 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..426b81a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +addts +.emacs* +Makefile +*~ +*.tar.gz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8b6dec7 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +PREFIX=/usr/local +PROJECT=addts +VERSION=1.0 + +# Installation directories: +INSTALLDIR=$(PREFIX)/bin +MANDIR=$(PREFIX)/share/man +MAN1DIR=$(MANDIR)/man1 + +addts: addts.c + $(CC) $(CFLAGS) $(CPPFLAGS) -oaddts addts.c + +clean: + rm -f *.o addts + +install: addts + install addts $(DESTDIR)$(INSTALLDIR) + install addts.1 $(DESTDIR)$(MAN1DIR) + +DISTDIR=$(PROJECT)-$(VERSION) +DISTFILES=addts.c addts.1 Makefile + +distdir: + test -d $(DISTDIR) || mkdir $(DISTDIR) + cp $(DISTFILES) $(DISTDIR) + +dist: distdir + tar zcf $(DISTDIR).tar.gz $(DISTDIR) + rm -rf $(DISTDIR) + +distcheck: dist + tar xfz $(DISTDIR).tar.gz + if $(MAKE) -C $(DISTDIR) $(DISTCHECKFLAGS); then \ + echo "$(DISTDIR).tar.gz ready for distribution"; \ + rm -rf $(DISTDIR); \ + else \ + exit 2; \ + fi + @@ -0,0 +1,78 @@ +.TH ADDTS 1 "June 20, 2018" ADDTS "User Commands" +.SH NAME +addts \- add timestamps at the beginning of each line +.SH SYNOPSIS +.nh +.na +\fBaddts\fR\ + [\fB\-au\fR]\ + [\fB\-f \fIFMT\fR]\ + [\fB\-i \fINUM\fR]\ + [\fB\-w \fICHR\fR]\ + [\fIFILE\fR] +.PP +\fBaddts\fR \fB\-?\fR +.ad +.hy +.SH DESCRIPTION +Reads standard input and writes it to standard output, adding a +timestamp at the begginning of each line. If \fIFILE\fR is suppied, +writes to it instead. Typical usage is for Apache forensic log: +.PP +.EX +ForensicLog "|/usr/bin/addts -a /var/log/httpd/forensic.log" +.EE +.SH OPTIONS +.TP +.B \-a +Append to \fIFILE\fR. Without this option, \fIFILE\fR is truncated +after opening. +.TP +.BI \-f " FMT" +Define timestamp format. \fIFMT\fR is a +.BR strftime (3) +format string, with an additional format conversion specifier +.BR %@ , +which is replaced with micronseconds. +.sp +See also \fB\-w\fR, below. +.TP +.BI \-i " NUM" +Pass first \fINUM\fR lines unchanged. +.TP +.B \-u +Report times in UTC. +.TP +.BI \-w " CHR" +Replace each occurrense of character \fICHR\fR in format string with +single whitespace. Use this option if your Apache version is unable to +correctly process quoted arguments in the \fBForensicLog\fR statement. +Example usage (newlines added for readability): +.sp +.EX +ForensicLog "|/usr/bin/addts -w_ -f %Y-%m-%d_%H:%M:%S.%@:_ \\ + -a /var/log/httpd/forensic.log" +.EE +.SH BUGS +Formatted timestamp cannot be longer than 511 bytes. +.SH "SEE ALSO" +.BR strftime (3). +.SH AUTHORS +Sergey Poznyakoff <gray@gnu.org.ua>. +.SH COPYRIGHT +Copyright \(co 2018 Sergey Poznyakoff +.br +.na +License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> +.br +.ad +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +.\" Local variables: +.\" eval: (add-hook 'write-file-hooks 'time-stamp) +.\" time-stamp-start: ".TH [A-Z_][A-Z0-9_.\\-]* [0-9] \"" +.\" time-stamp-format: "%:B %:d, %:y" +.\" time-stamp-end: "\"" +.\" time-stamp-line-limit: 20 +.\" end: + @@ -0,0 +1,125 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <sys/time.h> + +static char default_fmt[] = "%Y-%m-%d %H:%M:%S: "; + +int +main(int argc, char **argv) +{ + int c; + char buf[512]; + int eol = 0; + int utc_opt = 0; + int append_opt = 0; + int ws_opt = 0; + unsigned long ignore = 0; + char *p; + char *fmt = NULL; + FILE *fp; + + while ((c = getopt(argc, argv, "?af:i:uw:")) != EOF) { + switch (c) { + case 'a': + append_opt = 1; + break; + case 'f': + fmt = optarg; + break; + case 'i': + errno = 0; + ignore = strtoul(optarg, &p, 10); + if (errno || *p) { + fprintf(stderr, + "%s: -i argument is not a number\n", + argv[0]); + return 1; + } + break; + case 'u': + utc_opt = 1; + break; + case 'w': + ws_opt = optarg[0]; + break; + default: + if (optopt == 0) { + printf("usage: %s [-a] [-f FMT] [-i N] [-u] [OUT-FILE]\n", argv[0]); + return 0; + } + return 1; + } + } + + if (fmt) { + if (ws_opt) { + for (p = fmt; *p; p++) + if (*p == ws_opt) + *p = ' '; + } + } else + fmt = default_fmt; + + argc -= optind; + argv += optind; + + switch (argc) { + case 0: + fp = stdout; + break; + case 1: + fp = fopen(argv[0], append_opt ? "a" : "w"); + if (!fp) { + perror(argv[0]); + return 1; + } + break; + default: + fprintf(stderr, "%s: too many arguments\n", argv[0]); + return 1; + } + setvbuf(fp, NULL, _IOLBF, 0); + + if (ignore) + ignore--; + else + eol = 1; + + while ((c = getchar()) != EOF) { + if (c == '\n') { + if (ignore) + ignore--; + else + eol = 1; + } else if (eol) { + struct timeval tv; + struct tm *tm; + size_t sz; + char *start, *p; + + gettimeofday(&tv, NULL); + tm = (utc_opt ? gmtime : localtime)(&tv.tv_sec); + sz = strftime(buf, sizeof(buf), fmt, tm); + if (sz == 0 || sz == sizeof(buf)) { + strcpy(buf, "[OVERFLOW]: "); + sz = strlen(buf); + } + start = buf; + while ((p = strstr(start, "%@")) != NULL) { + fwrite(start, 1, p - start, fp); + fprintf(fp, "%06d", tv.tv_usec); + sz -= p - start + 2; + start = p + 2; + } + fwrite(start, 1, sz, fp); + eol = 0; + } + fputc(c, fp); + } + fclose(fp); + return 0; +} |