aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2013-10-14 15:28:51 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2013-10-14 17:54:41 +0300
commitf035194d7d1b6cc0846ad7a5d86e0d6fa9463c67 (patch)
tree494d180cee9a078727c8d4965caa65cb2052f33d
parent58f7dbc0658b3d73816a1bc91b75c2bdac733510 (diff)
downloadvmod-binlog-f035194d7d1b6cc0846ad7a5d86e0d6fa9463c67.tar.gz
vmod-binlog-f035194d7d1b6cc0846ad7a5d86e0d6fa9463c67.tar.bz2
New utility binlogsel
* configure.ac: Check for yacc. * src/.gitignore: Update. * src/Makefile.am (libbinlog_a_SOURCES): Add new files. Build binlogsel. * src/binlogcat.c: Use xmalloc. * src/binlogsel.c: New file. * src/parse-datetime.h: New file. * src/parse-datetime.y: New file. * src/xalloc.c: New file. * src/xalloc.h: New file.
-rw-r--r--configure.ac2
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am9
-rw-r--r--src/binlogcat.c17
-rw-r--r--src/binlogsel.c474
-rw-r--r--src/parse-datetime.h3
-rw-r--r--src/parse-datetime.y1598
-rw-r--r--src/xalloc.c53
-rw-r--r--src/xalloc.h3
9 files changed, 2145 insertions, 16 deletions
diff --git a/configure.ac b/configure.ac
index ea7c061..2bb8f38 100644
--- a/configure.ac
+++ b/configure.ac
@@ -29,13 +29,13 @@ AC_GNU_SOURCE
AC_PROG_CC
AC_PROG_CC_STDC
if test "x$ac_cv_prog_cc_c99" = xno; then
AC_MSG_ERROR([could not find a C99 compatible compiler])
fi
AC_PROG_CPP
-
+AC_PROG_YACC
AC_PROG_INSTALL
AC_PROG_LIBTOOL
AC_PROG_MAKE_SET
# Check for pkg-config
PKG_PROG_PKG_CONFIG
diff --git a/src/.gitignore b/src/.gitignore
index a75fe6d..8f5a6ab 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1,3 +1,5 @@
binlogcat
+binlogsel
+parse-datetime.c
vcc_if.c
vcc_if.h
diff --git a/src/Makefile.am b/src/Makefile.am
index 4e31e89..565444a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -14,32 +14,35 @@
# You should have received a copy of the GNU General Public License
# along with vmod-binlog. If not, see <http://www.gnu.org/licenses/>.
AM_CPPFLAGS = -I$(VARNISHSRC)/include -I$(VARNISHSRC)
noinst_LIBRARIES = libbinlog.a
-libbinlog_a_SOURCES = pack.c err.c
+libbinlog_a_SOURCES = pack.c err.c parse-datetime.y xalloc.c
libbinlog_a_CFLAGS = $(AM_CFLAGS)
-bin_PROGRAMS = binlogcat
+bin_PROGRAMS = binlogcat binlogsel
binlogcat_SOURCES = binlogcat.c
binlogcat_LDADD = ./libbinlog.a
+binlogsel_SOURCES = binlogsel.c
+binlogsel_LDADD = ./libbinlog.a
+
vmoddir = $(VMODDIR)
vmod_LTLIBRARIES = libvmod_binlog.la
libvmod_binlog_la_LDFLAGS = -module -export-dynamic -avoid-version
libvmod_binlog_la_LIBADD=
libvmod_binlog_la_SOURCES = \
binlog.c\
pack.c\
vmod-binlog.h\
vcc_if.c vcc_if.h
-noinst_HEADERS = pack.h err.h
+noinst_HEADERS = pack.h err.h parse-datetime.h xalloc.h
BUILT_SOURCES = vcc_if.c vcc_if.h
vcc_if.c vcc_if.h: $(VARNISHSRC)/lib/libvmod_std/vmod.py $(top_srcdir)/src/vmod.vcc
@PYTHON@ $(VARNISHSRC)/lib/libvmod_std/vmod.py $(top_srcdir)/src/vmod.vcc
diff --git a/src/binlogcat.c b/src/binlogcat.c
index 505bbc2..656d8f2 100644
--- a/src/binlogcat.c
+++ b/src/binlogcat.c
@@ -24,12 +24,13 @@
#include <errno.h>
#include <time.h>
#include <string.h>
#include "vmod-binlog.h"
#include "pack.h"
#include "err.h"
+#include "xalloc.h"
char *timefmt = "%c";
int number_option;
int verbose_option;
int timediff_option;
@@ -72,17 +73,13 @@ catlog(const char *fname)
if (header.version != BINLOG_VERSION) {
error("%s: unknown version", fname);
exit(1);
}
size = header.hdrsize - sizeof(header);
- dataspec = malloc(size);
- if (!dataspec) {
- error("not enough memory");
- abort();
- }
+ dataspec = xmalloc(size);
if (fread(dataspec, size, 1, fp) != 1) {
error("error reading header of %s: %s",
fname, strerror(errno));
exit(1);
}
@@ -91,26 +88,22 @@ catlog(const char *fname)
printf("# %s; format=%s; recsize=%lu; recnum=%lu\n",
fname, dataspec, header.recsize, header.recnum);
inst = packcomp(dataspec, &p);
if (!inst) {
if (errno == EINVAL) {
- error("%s: bad dataspec near %s", dataspec, p);
+ error("%s: %s: bad dataspec near %s", fname, dataspec, p);
exit(1);
}
error("%s", strerror(errno));
exit(1);
}
free(dataspec);
- rec = malloc(header.recsize);
- if (!rec) {
- error("not enough memory");
- exit(1);
- }
+ rec = xmalloc(header.recsize);
env = packenv_create(header.recsize -
offsetof(struct binlog_record,data));
env->fp = stdout;
for (i = 0; i < header.recnum; i++) {
if (fread(rec, header.recsize, 1, fp) != 1) {
@@ -144,13 +137,13 @@ catlog(const char *fname)
fclose(fp);
}
void
help()
{
- printf("usage: %s [-dhnv] [t FORMAT] [FILE...]\n", progname);
+ printf("usage: %s [-dhnv] [-t FORMAT] [FILE...]\n", progname);
}
int
main(int argc, char **argv)
{
int c;
diff --git a/src/binlogsel.c b/src/binlogsel.c
new file mode 100644
index 0000000..62299e5
--- /dev/null
+++ b/src/binlogsel.c
@@ -0,0 +1,474 @@
+/* This file is part of vmod-binlog
+ Copyright (C) 2013 Sergey Poznyakoff
+
+ Vmod-binlog is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ Vmod-binlog is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with vmod-binlog. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <time.h>
+#include <string.h>
+#include <glob.h>
+#include "vmod-binlog.h"
+#include "pack.h"
+#include "err.h"
+#include "xalloc.h"
+#include "parse-datetime.h"
+
+char *timefmt = "%c";
+int number_option;
+int verbose_option;
+int timediff_option;
+char *pattern;
+
+#define FROM_TIME 0x01
+#define TO_TIME 0x02
+int timemask;
+time_t from_time, to_time;
+
+void
+help()
+{
+ printf("usage: %s [-dhnv] [-t FORMAT] [-F FROMTIME] [-T TOTIME] [-p PATTERN] [-D DIR] [FILE...]\n", progname);
+}
+
+/* Convert strftime-like pattern into globbing pattern */
+void
+convpattern(const char *dir)
+{
+ char *p, *q;
+ char *newpat;
+ size_t size = strlen(pattern) + 1;
+
+ if (dir)
+ size += strlen(dir) + 1;
+
+ newpat = xmalloc(size);
+
+ p = newpat;
+ if (dir) {
+ strcpy(p, dir);
+ p += strlen(dir);
+ *p++ = '/';
+ }
+
+ for (q = pattern; *q; ) {
+ if (*q == '%') {
+ *p++ = '*';
+ q += 2;
+ } else
+ *p++ = *q++;
+ }
+ *p = 0;
+ pattern = newpat;
+}
+
+#define getrec(base, recsize, n) \
+ ((struct binlog_record*)((char *)(base) + (n) * (recsize)))
+
+int
+searchts(void *base, size_t recsize, size_t from, size_t to, time_t ts,
+ size_t *ret)
+{
+ struct binlog_record *rec;
+ size_t middle;
+
+ rec = getrec(base, recsize, from);
+
+ if (ts < rec->ts) {
+ *ret = from;
+ return -1;
+ }
+
+ if (to == from) {
+ *ret = from;
+ return 0;
+ }
+
+ rec = getrec(base, recsize, to);
+ if (ts > rec->ts) {
+ *ret = from;
+ return 1;
+ }
+
+ middle = (to + from) / 2;
+
+ rec = getrec(base, recsize, middle);
+
+ if (ts == rec->ts) {
+ *ret = middle;
+ return 0;
+ }
+
+ if (ts > rec->ts)
+ return searchts(base, recsize, middle + 1, to, ts, ret);
+
+ return searchts(base, recsize, from, middle, ts, ret);
+}
+
+void
+selmem(const char *name, void *base)
+{
+ struct binlog_file_header *hdr;
+ struct binlog_record *rec;
+ struct packenv *env;
+ struct packinst *inst;
+ char *p;
+ size_t i, start;
+ time_t start_ts;
+ char timebuf[128];
+ char *dataspec;
+
+ hdr = base;
+
+ if (memcmp(hdr->magic, BINLOG_MAGIC_STR, BINLOG_MAGIC_LEN)) {
+ error("%s is not a binlog file", name);
+ return;
+ }
+
+ if (hdr->version != BINLOG_VERSION) {
+ error("%s: unknown version", name);
+ return;
+ }
+
+ dataspec = (char*)(hdr + 1);
+
+ if (verbose_option)
+ printf("# %s; format=%s; recsize=%lu; recnum=%lu\n",
+ name, dataspec, hdr->recsize, hdr->recnum);
+
+ inst = packcomp(dataspec, &p);
+ if (!inst) {
+ if (errno == EINVAL) {
+ error("%s: %s: bad dataspec near %s", name,
+ dataspec, p);
+ return;
+ }
+
+ error("%s", strerror(errno));
+ return;
+ }
+
+ env = packenv_create(hdr->recsize -
+ offsetof(struct binlog_record,data));
+ env->fp = stdout;
+
+ base = (char*)base + hdr->hdrsize;
+
+ if (timemask & FROM_TIME) {
+ switch (searchts(base, hdr->recsize, 0, hdr->recnum - 1,
+ from_time, &start)) {
+ case 0:
+ break;
+ case -1:
+ for (; start < hdr->recnum; start++) {
+ if (getrec(base, hdr->recsize, start)->ts > from_time)
+ break;
+ }
+ break;
+ case 1:
+ for (; start + 1 >= 0; start--) {
+ if (from_time < getrec(base, hdr->recsize, start)->ts)
+ break;
+ }
+ ++start;
+ }
+ } else
+ start = 0;
+
+ for (i = 0; start < hdr->recnum; i++, start++) {
+ rec = getrec(base, hdr->recsize, start);
+
+ if ((timemask & TO_TIME) && rec->ts > to_time)
+ break;
+
+ if (timediff_option) {
+ if (i == 0)
+ start_ts = rec->ts;
+ rec->ts -= start_ts;
+ }
+ strftime(timebuf, sizeof timebuf, timefmt,
+ localtime(&rec->ts));
+ if (number_option)
+ printf("%lu ", (unsigned long) start);
+ printf("%s ", timebuf);
+ memcpy(env->buf_base, rec->data, env->buf_size);
+ env->buf_pos = 0;
+
+ packout(inst, env);
+ fputc('\n', stdout);
+ }
+}
+
+static int
+fchecktime(FILE *fp, const char *fname, time_t *ts)
+{
+ struct binlog_file_header header;
+ struct binlog_record rec;
+ time_t start, end;
+
+ if (fread(&header, sizeof(header), 1, fp) != 1) {
+ error("error reading header of %s: %s",
+ fname, strerror(errno));
+ return -1;
+ }
+
+ if (memcmp(header.magic, BINLOG_MAGIC_STR, BINLOG_MAGIC_LEN)) {
+ error("%s is not a binlog file", fname);
+ return -1;
+ }
+
+ if (header.version != BINLOG_VERSION) {
+ error("%s: unknown version", fname);
+ return -1;
+ }
+
+ if (fseek(fp, header.hdrsize, SEEK_SET)) {
+ error("%s: seek error: %s", fname, strerror(errno));
+ return -1;
+ }
+
+ if (fread(&rec, sizeof(rec), 1, fp) != 1) {
+ error("%s: %s", fname, strerror(errno));
+ return -1;
+ }
+ start = rec.ts;
+
+ if (fseek(fp, header.hdrsize +
+ header.recsize * (header.recnum - 1),
+ SEEK_SET)) {
+ error("%s: seek error: %s", fname, strerror(errno));
+ return -1;
+ }
+ if (fread(&rec, sizeof(rec), 1, fp) != 1) {
+ error("%s: %s", fname, strerror(errno));
+ return -1;
+ }
+ end = rec.ts;
+
+ if ((timemask & TO_TIME) && header.recnum > 1) {
+ if (to_time < start)
+ return 1;
+
+ if (timemask & FROM_TIME) {
+ if (from_time > end)
+ return 1;
+ }
+ } else if ((timemask & FROM_TIME) && from_time > end)
+ return 1;
+
+ *ts = start;
+ return 0;
+}
+
+static int
+checktime(const char *name, time_t *ts)
+{
+ int rc;
+ FILE *fp = fopen(name, "r");
+
+ if (!fp) {
+ error("can't open %s: %s", name, strerror(errno));
+ return -1;
+ }
+ rc = fchecktime(fp, name, ts);
+ fclose(fp);
+ return rc;
+}
+
+void
+selfile(char *name)
+{
+ int fd;
+ struct stat st;
+ void *base;
+
+ fd = open(name, O_RDONLY);
+ if (fd == -1) {
+ error("can't open %s: %s", name, strerror(errno));
+ exit(1);
+ }
+
+ if (fstat(fd, &st)) {
+ error("can't stat %s: %s", name, strerror(errno));
+ exit(1);
+ }
+
+ base = mmap((caddr_t)0, st.st_size,
+ PROT_READ, MAP_SHARED,
+ fd, 0);
+ if (base == MAP_FAILED) {
+ error("mmap(%s): %s", name, strerror(errno));
+ exit(1);
+ }
+
+ selmem(name, base);
+ munmap(base, st.st_size);
+ close(fd);
+}
+
+void
+selfilelist(char **argv)
+{
+ for (;*argv;++argv)
+ selfile(*argv);
+}
+
+int
+globerrfunc (const char *epath, int eerrno)
+{
+ error("%s: %s", strerror(eerrno));
+ return 0;
+}
+
+struct logfile {
+ char *name;
+ time_t start;
+};
+
+static int
+tsort(const void *a, const void *b)
+{
+ struct logfile const *la = a;
+ struct logfile const *lb = b;
+ if (la->start > lb->start)
+ return 1;
+ if (la->start < lb->start)
+ return -1;
+ return 0;
+}
+
+void
+selpattern(void)
+{
+ size_t i, j;
+ glob_t gl;
+ struct logfile *logfiles;
+
+ switch (glob(pattern, GLOB_ERR|GLOB_NOSORT, globerrfunc, &gl)) {
+ case 0:
+ break;
+ case GLOB_NOSPACE:
+ error("out of memory");
+ exit(1);
+
+ case GLOB_ABORTED:
+ error("read error");
+ exit(1);
+
+ case GLOB_NOMATCH:
+ error("no files matched pattern");
+ exit(1);
+ }
+
+ logfiles = xcalloc(gl.gl_pathc, sizeof(*logfiles));
+
+ for (i = j = 0; i < gl.gl_pathc; i++) {
+ time_t t;
+ if (checktime(gl.gl_pathv[i], &t) == 0) {
+ logfiles[j].name = gl.gl_pathv[i];
+ logfiles[j].start = t;
+ ++j;
+ }
+ }
+
+ /* sort the returned array */
+ qsort(logfiles, j, sizeof(*logfiles), tsort);
+
+ for (i = 0; i < j; i++)
+ selfile(logfiles[i].name);
+
+ free(logfiles);
+ globfree(&gl);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ struct timespec ts;
+ char *directory;
+
+ setprogname(argv[0]);
+ while ((c = getopt(argc, argv, "D:dF:hp:T:t:nv")) != EOF)
+ switch (c) {
+ case 'D':
+ directory = optarg;
+ break;
+ case 'd':
+ timediff_option = 1;
+ timefmt = "%s";
+ break;
+ case 'F':
+ if (!parse_datetime(&ts, optarg, NULL)) {
+ error("invalid timespec: %s", optarg);
+ exit(1);
+ }
+ from_time = ts.tv_sec;
+ timemask |= FROM_TIME;
+ break;
+ case 'h':
+ help();
+ return 0;
+ case 'p':
+ pattern = optarg;
+ break;
+ case 'T':
+ if (!parse_datetime(&ts, optarg, NULL)) {
+ error("invalid timespec: %s", optarg);
+ exit(1);
+ }
+ to_time = ts.tv_sec;
+ timemask |= TO_TIME;
+ break;
+ case 't':
+ timefmt = optarg;
+ break;
+ case 'n':
+ number_option = 1;
+ break;
+ case 'v':
+ verbose_option = 1;
+ break;
+ default:
+ exit(1);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc) {
+ if (pattern) {
+ error("either files or pattern (-p) must be given, "
+ "but not both");
+ exit(1);
+ }
+ selfilelist(argv);
+ } else {
+ if (!pattern)
+ pattern = BINLOG_PATTERN;
+ convpattern(directory);
+ selpattern();
+ }
+ exit(0);
+}
+
diff --git a/src/parse-datetime.h b/src/parse-datetime.h
new file mode 100644
index 0000000..933a54a
--- /dev/null
+++ b/src/parse-datetime.h
@@ -0,0 +1,3 @@
+#include <time.h>
+int parse_datetime (struct timespec *result, char const *p,
+ struct timespec const *now);
diff --git a/src/parse-datetime.y b/src/parse-datetime.y
new file mode 100644
index 0000000..2dd5e1c
--- /dev/null
+++ b/src/parse-datetime.y
@@ -0,0 +1,1598 @@
+%{
+/* Parse a string into an internal time stamp.
+
+ Copyright (C) 1999-2000, 2002-2013 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Originally written by Steven M. Bellovin <smb@research.att.com> while
+ at the University of North Carolina at Chapel Hill. Later tweaked by
+ a couple of people on Usenet. Completely overhauled by Rich $alz
+ <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+
+ Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ the right thing about local DST. Also modified by Paul Eggert
+ <eggert@cs.ucla.edu> in February 2004 to support
+ nanosecond-resolution time stamps, and in October 2004 to support
+ TZ strings in dates. */
+
+/* FIXME: Check for arithmetic overflow in all cases, not just
+ some of them. */
+
+#include <config.h>
+#include <time.h>
+#include <sys/time.h>
+
+/* There's no need to extend the stack, so there's no need to involve
+ alloca. */
+#define YYSTACK_USE_ALLOCA 0
+
+/* Tell Bison how much stack space is needed. 20 should be plenty for
+ this grammar, which is not right recursive. Beware setting it too
+ high, since that might cause problems on machines whose
+ implementations have lame stack-overflow checking. */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
+
+/* Since the code of parse-datetime.y is not included in the Emacs executable
+ itself, there is no need to #define static in this file. Even if
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+# undef static
+#endif
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "err.h"
+#include "xalloc.h"
+
+/* ISDIGIT differs from isdigit, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char
+ or EOF.
+ - It's typically faster.
+ POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
+ isdigit unless it's important to use the locale's definition
+ of "digit" even when the host does not conform to POSIX. */
+#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
+
+/* Shift A right by B bits portably, by dividing A by 2**B and
+ truncating towards minus infinity. A and B should be free of side
+ effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ INT_BITS is the number of useful bits in an int. GNU code can
+ assume that INT_BITS is at least 32.
+
+ ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ right in the usual way when A < 0, so SHR falls back on division if
+ ordinary A >> B doesn't seem to be the usual signed shift. */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
+#define EPOCH_YEAR 1970
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+/* Convert a possibly-signed character to an unsigned character. This is
+ a bit safer than casting to unsigned char, since it catches some type
+ errors that the cast doesn't. */
+static unsigned char to_uchar (char ch) { return ch; }
+
+#define long_time_t time_t
+
+/* An integer value, and the number of digits in its textual
+ representation. */
+typedef struct
+{
+ int negative;
+ long int value;
+ size_t digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct
+{
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
+/* Relative times. */
+typedef struct
+{
+ /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+ long int year;
+ long int month;
+ long int day;
+ long int hour;
+ long int minutes;
+ long_time_t seconds;
+ long int ns;
+} relative_time;
+
+#if HAVE_COMPOUND_LITERALS
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+#else
+static relative_time const RELATIVE_TIME_0;
+#endif
+
+/* Information passed to and from the parser. */
+typedef struct
+{
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ long int day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ long int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
+ textint year;
+ long int month;
+ long int day;
+ long int hour;
+ long int minutes;
+ struct timespec seconds; /* includes nanoseconds */
+
+ /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+ relative_time rel;
+
+ /* Presence or counts of nonterminals of various flavors parsed so far. */
+ int timespec_seen;
+ int rels_seen;
+ size_t dates_seen;
+ size_t days_seen;
+ size_t local_zones_seen;
+ size_t dsts_seen;
+ size_t times_seen;
+ size_t zones_seen;
+
+ /* Table of local time zone abbreviations, terminated by a null entry. */
+ table local_time_zone_table[3];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static long int time_zone_hhmm (parser_control *, textint, long int);
+
+/* Extract into *PC any date and time info from a string of digits
+ of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+ YYYY, ...). */
+static void
+digits_to_date_time (parser_control *pc, textint text_int)
+{
+ if (pc->dates_seen && ! pc->year.digits
+ && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
+ pc->year = text_int;
+ else
+ {
+ if (4 < text_int.digits)
+ {
+ pc->dates_seen++;
+ pc->day = text_int.value % 100;
+ pc->month = (text_int.value / 100) % 100;
+ pc->year.value = text_int.value / 10000;
+ pc->year.digits = text_int.digits - 4;
+ }
+ else
+ {
+ pc->times_seen++;
+ if (text_int.digits <= 2)
+ {
+ pc->hour = text_int.value;
+ pc->minutes = 0;
+ }
+ else
+ {
+ pc->hour = text_int.value / 100;
+ pc->minutes = text_int.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
+static void
+apply_relative_time (parser_control *pc, relative_time rel, int factor)
+{
+ pc->rel.ns += factor * rel.ns;
+ pc->rel.seconds += factor * rel.seconds;
+ pc->rel.minutes += factor * rel.minutes;
+ pc->rel.hour += factor * rel.hour;
+ pc->rel.day += factor * rel.day;
+ pc->rel.month += factor * rel.month;
+ pc->rel.year += factor * rel.year;
+ pc->rels_seen = 1;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
+static void
+set_hhmmss (parser_control *pc, long int hour, long int minutes,
+ time_t sec, long int nsec)
+{
+ pc->hour = hour;
+ pc->minutes = minutes;
+ pc->seconds.tv_sec = sec;
+ pc->seconds.tv_nsec = nsec;
+}
+
+%}
+
+/* We want a reentrant parser, even if the TZ manipulation and the calls to
+ localtime and gmtime are not reentrant. */
+%pure-parser
+%parse-param { parser_control *pc }
+%lex-param { parser_control *pc }
+
+/* This grammar has 31 shift/reduce conflicts. */
+%expect 31
+
+%union
+{
+ long int intval;
+ textint textintval;
+ struct timespec timespec;
+ relative_time rel;
+}
+
+%token <intval> tAGO
+%token tDST
+
+%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
+%token <intval> tDAY_UNIT tDAY_SHIFT
+
+%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
+%token <intval> tMONTH tORDINAL tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
+
+%type <intval> o_colon_minutes
+%type <timespec> seconds signed_seconds unsigned_seconds
+
+%type <rel> relunit relunit_snumber dayshift
+
+%%
+
+spec:
+ timespec
+ | items
+ ;
+
+timespec:
+ '@' seconds
+ {
+ pc->seconds = $2;
+ pc->timespec_seen = 1;
+ }
+ ;
+
+items:
+ /* empty */
+ | items item
+ ;
+
+item:
+ datetime
+ { pc->times_seen++; pc->dates_seen++; }
+ | time
+ { pc->times_seen++; }
+ | local_zone
+ { pc->local_zones_seen++; }
+ | zone
+ { pc->zones_seen++; }
+ | date
+ { pc->dates_seen++; }
+ | day
+ { pc->days_seen++; }
+ | rel
+ | number
+ | hybrid
+ ;
+
+datetime:
+ iso_8601_datetime
+ ;
+
+iso_8601_datetime:
+ iso_8601_date 'T' iso_8601_time
+ ;
+
+time:
+ tUNUMBER tMERIDIAN
+ {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER tMERIDIAN
+ {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
+ {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = $6;
+ }
+ | iso_8601_time
+ ;
+
+iso_8601_time:
+ tUNUMBER zone_offset
+ {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = MER24;
+ }
+ | tUNUMBER ':' tUNUMBER o_zone_offset
+ {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = MER24;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
+ {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = MER24;
+ }
+ ;
+
+o_zone_offset:
+ /* empty */
+ | zone_offset
+ ;
+
+zone_offset:
+ tSNUMBER o_colon_minutes
+ {
+ pc->zones_seen++;
+ pc->time_zone = time_zone_hhmm (pc, $1, $2);
+ }
+ ;
+
+local_zone:
+ tLOCAL_ZONE
+ {
+ pc->local_isdst = $1;
+ pc->dsts_seen += (0 < $1);
+ }
+ | tLOCAL_ZONE tDST
+ {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < $1) + 1;
+ }
+ ;
+
+/* Note 'T' is a special case, as it is used as the separator in ISO
+ 8601 date and time of day representation. */
+zone:
+ tZONE
+ { pc->time_zone = $1; }
+ | 'T'
+ { pc->time_zone = HOUR(7); }
+ | tZONE relunit_snumber
+ { pc->time_zone = $1;
+ apply_relative_time (pc, $2, 1); }
+ | 'T' relunit_snumber
+ { pc->time_zone = HOUR(7);
+ apply_relative_time (pc, $2, 1); }
+ | tZONE tSNUMBER o_colon_minutes
+ { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
+ | tDAYZONE
+ { pc->time_zone = $1 + 60; }
+ | tZONE tDST
+ { pc->time_zone = $1 + 60; }
+ ;
+
+day:
+ tDAY
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tDAY ','
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tORDINAL tDAY
+ {
+ pc->day_ordinal = $1;
+ pc->day_number = $2;
+ }
+ | tUNUMBER tDAY
+ {
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
+ }
+ ;
+
+date:
+ tUNUMBER '/' tUNUMBER
+ {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER
+ {
+ /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ otherwise as MM/DD/YY.
+ The goal in recognizing YYYY/MM/DD is solely to support legacy
+ machine-generated dates like those in an RCS log listing. If
+ you want portability, use the ISO 8601 format. */
+ if (4 <= $1.digits)
+ {
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
+ }
+ else
+ {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ pc->year = $5;
+ }
+ }
+ | tUNUMBER tMONTH tSNUMBER
+ {
+ /* e.g. 17-JUN-1992. */
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tSNUMBER tSNUMBER
+ {
+ /* e.g. JUN-17-1992. */
+ pc->month = $1;
+ pc->day = -$2.value;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
+ }
+ | tUNUMBER tMONTH
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
+ }
+ | iso_8601_date
+ ;
+
+iso_8601_date:
+ tUNUMBER tSNUMBER tSNUMBER
+ {
+ /* ISO 8601 format. YYYY-MM-DD. */
+ pc->year = $1;
+ pc->month = -$2.value;
+ pc->day = -$3.value;
+ }
+ ;
+
+rel:
+ relunit tAGO
+ { apply_relative_time (pc, $1, $2); }
+ | relunit
+ { apply_relative_time (pc, $1, 1); }
+ | dayshift
+ { apply_relative_time (pc, $1, 1); }
+ ;
+
+relunit:
+ tORDINAL tYEAR_UNIT
+ { $$ = RELATIVE_TIME