From: Mark Wooding Date: Fri, 11 Aug 2006 13:07:13 +0000 (+0100) Subject: stamp: New program. X-Git-Tag: 1.2.0~8 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/misc/commitdiff_plain/223bbefb9b4d1dd3e56a49adc2bf3b76927afe20 stamp: New program. Like cat, but prints datestamps on input lines. --- diff --git a/.gitignore b/.gitignore index a11c7b3..3ea5846 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ build pause xtitle.so *.tar.gz - +*.o +stamp diff --git a/Makefile b/Makefile index 8d75d34..ae6a213 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BINSCRIPTS = \ splitconf z buf create inplace SBINSCRIPTS = shadowfix SCRIPTS = $(BINSCRIPTS) $(SBINSCRIPTS) -BINPROGS = not cdb-probe cdb-check-domain gorp locking if-mtu pause +BINPROGS = not cdb-probe cdb-check-domain gorp locking if-mtu pause stamp SBINPROGS = qmail-checkspam PROGS = $(BINPROGS) $(SBINPROGS) PERLLIBS = MdwOpt.pm @@ -18,7 +18,7 @@ LIBS = xtitle.so DISTMAN1 = \ not.1 z.1 cdb-assign.1 cdb-map.1 cdb-list.1 cdb-probe.1 \ cdb-check-domain.1 \ - gorp.1 unfwd.1 splitconf.1 locking.1 if-mtu.1 pause.1 \ + gorp.1 unfwd.1 splitconf.1 locking.1 if-mtu.1 pause.1 stamp.1 \ buf.1 create.1 inplace.1 MAN1 = $(DISTMAN1) DISTMAN8 = qmail-checkspam.8 @@ -98,6 +98,9 @@ locking: locking.o if-mtu: if-mtu.o $(LINK) +stamp: stamp.o + $(LINK) -lmLib + shadowfix.8: shadowfix pod2man --section 8 shadowfix >shadowfix.8.new mv shadowfix.8.new shadowfix.8 diff --git a/debian/control b/debian/control index 5fbde52..f574ae6 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,8 @@ Package: nsict-utils Architecture: all Section: utils Depends: mdwopt-perl, nsict-cdb, locking, qmail-checkspam, nsict-mail, - if-mtu, shadowfix, zz, gorp, splitconf, xtitle, pause, buf, create, inplace + if-mtu, shadowfix, zz, gorp, splitconf, xtitle, pause, buf, create, inplace, + stamp Description: Dummy package for convenience. Package: mdwopt-perl @@ -100,3 +101,8 @@ Package: inplace Architecture: all Section: utils Description: Update files in place safely. + +Package: stamp +Architecture: any +Section: utils +Description: Like cat, but prefixing each line with a datestamp. diff --git a/debian/inst b/debian/inst index bbdbbe4..d22050d 100644 --- a/debian/inst +++ b/debian/inst @@ -33,6 +33,8 @@ shadowfix shadowfix /usr/sbin shadowfix.8 shadowfix /usr/share/man/man8 splitconf splitconf /usr/bin splitconf.1 splitconf /usr/share/man/man1 +stamp stamp /usr/bin +stamp.1 stamp /usr/share/man/man1 unfwd nsict-mail /usr/bin unfwd.1 nsict-mail /usr/share/man/man1 xtitle.so xtitle /usr/lib/bash diff --git a/stamp.1 b/stamp.1 new file mode 100644 index 0000000..5cbe9f0 --- /dev/null +++ b/stamp.1 @@ -0,0 +1,54 @@ +.\" -*-nroff-*- +.TH stamp 1 "11 August 2006" "Straylight/Edgeware" +.SH NAME +stamp \- copy files, prefixing each line with a datestamp +.SH SYNOPSIS +.B stamp +.RB [ \-z ] +.RB [ \-f +.IR format ] +.RI [ file ...] +.SH DESCRIPTION +Copies files, like +.BR cat (1), +except that it prefixes each line with a datestamp indicating when the +line was recieved. The datestamp is formatted using +.BR strftime (3), +and can use any formatting codes permitted by that function. +.PP +Options available: +.TP +.B \-h, \-\-help +Show a help message for +.BR stamp . +.TP +.B \-v, \-\-version +Show version number for +.BR stamp . +.TP +.B \-u, \-\-usage +Show usage message for +.BR stamp . +.TP +.BI "\-f, \-\-format=" format +Use +.I format +as the +.BR strftime (3) +format string for the datestamps. Note that if the datestamp is meant +to be separated from the rest of the line by anything, then that +separator should be part of the format string. +.TP +.B \-z, \-\-utc, \-\-zulu +Use UTC (Zulu time) instead of local time for datestamps. +.SH BUGS +While +.B stamp +ensures that its input is line-buffered only, it's possible that +whatever is providing that input delays it arbitrarily due to its own +buffering. There's not much to be done about this. +.SH AUTHOR +Mark Wooding, +.SH SEE ALSO +.BR cat (1), +.BR strftime (3). diff --git a/stamp.c b/stamp.c new file mode 100644 index 0000000..a5655a6 --- /dev/null +++ b/stamp.c @@ -0,0 +1,138 @@ +/* -*- + * + * Like cat, with datestamps + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static const char *fmt = "%Y-%m-%d %H:%M:%S %Z: "; +static struct tm *(*cvt)(const time_t *) = localtime; + +static void version(void) { pquis(stdout, "$ " VERSION); } +static void usage(FILE *fp) + { pquis(fp, "Usage: $ [-z] [-f FORMAT] [FILE...]"); } + +static void help(void) +{ + version(); putchar('\n'); + usage(stdout); + fputs("\n\ +Copy the FILEs (or standard input) to standard output, prefixing each line\n\ +with a datestamp.\n\ +\n\ +-h, --help Show this help text.\n\ +-v, --version Show the program's version number.\n\ +-u, --usage Show a brief usage message.\n\ +\n\ +-f, --format=FORMAT Write datestamps using the strftime(3) FORMAT.\n\ +-z, --utc, --zulu Use UTC rather than local time for datestamps.\n\ +", stdout); +} + +static void cat(FILE *in) +{ + unsigned ln = 1; + time_t t; + struct tm *tm; + char buf[256]; + int ch; + + for (;;) { + if ((ch = getc(in)) == EOF) + break; + if (ln) { + t = time(0); + tm = cvt(&t); + strftime(buf, sizeof(buf), fmt, tm); + fwrite(buf, 1, strlen(buf), stdout); + } + putchar(ch); + ln = (ch == '\n'); + } + if (!ln) + putchar('\n'); +} + +int main(int argc, char *argv[]) +{ + int i; + FILE *fp; + unsigned f = 0; +#define F_BOGUS 1u + + ego(argv[0]); + setvbuf(stdin, 0, _IOLBF, 0); + + for (;;) { + static const struct option opt[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + { "format", OPTF_ARGREQ, 0, 'f' }, + { "utc", 0, 0, 'z' }, + { "zulu", 0, 0, 'z' }, + { 0, 0, 0, 0 } + }; + + if ((i = mdwopt(argc, argv, "hvuf:z", opt, 0, 0, 0)) < 0) + break; + switch (i) { + case 'h': + help(); + exit(0); + case 'v': + version(); + exit(0); + case 'u': + usage(stdout); + exit(0); + case 'f': + fmt = optarg; + break; + case 'z': + cvt = gmtime; + break; + default: + f |= F_BOGUS; + break; + } + } + + if (f & F_BOGUS) { + usage(stderr); + exit(EXIT_FAILURE); + } + + if (optind == argc) { + if (isatty(STDIN_FILENO)) + die(EXIT_FAILURE, "no arguments, and stdin is a terminal"); + cat(stdin); + } else for (i = optind; i < argc; i++) { + if (strcmp(argv[i], "-") == 0) + cat(stdin); + else if ((fp = fopen(argv[i], "r")) == 0) { + moan("failed to open `%s': %s", argv[i], strerror(errno)); + f |= F_BOGUS; + } else { + cat(fp); + fclose(fp); + } + } + + if (ferror(stdout) || fflush(stdout) || fclose(stdout)) { + moan("error writing output: %s", strerror(errno)); + f |= F_BOGUS; + } + + return ((f & F_BOGUS) ? EXIT_FAILURE : 0); +}