/* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 *directory;
char *pattern;
enum binlog_index_type index_type = index_year;
#define FROM_TIME 0x01
#define TO_TIME 0x02
int timemask;
time_t from_time, to_time;
static int matchnames(const char *dir, const char *pat, glob_t *gl);
void selglob(const char *dir, const char *pattern);
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 */
char *
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 == '%') {
if (p > newpat && p[-1] != '*')
*p++ = '*';
q += 2;
} else
*p++ = *q++;
}
*p = 0;
return 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);
}
static char *
mkfilename(const char *dir, const char *file)
{
size_t dirlen, size;
char *ret;
dirlen = strlen(dir);
while (dirlen > 0 && dir[dirlen-1] == '/')
--dirlen;
size = dirlen + 1 + strlen(file) + 1;
ret = xmalloc(size);
memcpy(ret, dir, dirlen);
ret[dirlen++] = '/';
strcpy(ret + dirlen, file);
return ret;
}
int
filename_to_int(char *name)
{
char *p = strrchr(name, '/');
if (!p)
abort();
return atoi(p + 1);
}
void
selidx_day(const char *dir)
{
int from_day, to_day;
struct tm *tm;
glob_t gl;
int glinit = 0;
char *dirbuf;
size_t dirlen;
if (index_type == index_month) {
selglob(dir, BINLOG_GLOB_PATTERN);
return;
}
if (timemask & FROM_TIME)
from_day = gmtime(&from_time)->tm_mday;
else {
glinit = matchnames(dir, "[0-9][0-9]", &gl);
if (glinit)
from_day = filename_to_int(gl.gl_pathv[0]);
else {
error("no matching files");
exit(1);
}
}
if (timemask & TO_TIME)
to_day = gmtime(&to_time)->tm_mday;
else {
if (!glinit) {
glinit = matchnames(dir, "[0-9][0-9]", &gl);
if (!glinit) {
error("no matching files");
exit(1);
}
}
to_day = filename_to_int(gl.gl_pathv[gl.gl_pathc - 1]);
}
dirlen = strlen(dir) + 4;
dirbuf = xmalloc(dirlen);
for (;from_day <= to_day; from_day++) {
snprintf(dirbuf, dirlen, "%s/%02d", dir, from_day);
selglob(dirbuf, BINLOG_GLOB_PATTERN);
}
free(dirbuf);
if (glinit)
globfree(&gl);
}
void
selidx_month(const char *dir)
{
int from_month, to_month;
struct tm *tm;
glob_t gl;
int glinit = 0;
char *dirbuf;
size_t dirlen;
if (index_type == index_year) {
selglob(dir, BINLOG_GLOB_PATTERN);
return;
}
if (timemask & FROM_TIME)
from_month = 1 + gmtime(&from_time)->tm_mon;
else {
glinit = matchnames(dir, "[0-9][0-9]", &gl);
if (glinit)
from_month = filename_to_int(gl.gl_pathv[0]);
else {
error("no matching files");
exit(1);
}
}
if (timemask & TO_TIME)
to_month = 1 + gmtime(&to_time)->tm_mon;
else {
if (!glinit) {
glinit = matchnames(dir, "[0-9][0-9]", &gl);
if (!glinit) {
error("no matching files");
exit(1);
}
}
to_month = filename_to_int(gl.gl_pathv[gl.gl_pathc - 1]);
}
dirlen = strlen(dir) + 4;
dirbuf = xmalloc(dirlen);
for (;from_month <= to_month; from_month++) {
snprintf(dirbuf, dirlen, "%s/%02d", dir, from_month);
selidx_day(dirbuf);
}
free(dirbuf);
if (glinit)
globfree(&gl);
}
void
selidx_year(const char *dir)
{
int from_year, to_year;
struct tm *tm;
glob_t gl;
int glinit = 0;
char *dirbuf;
size_t dirlen;
if (timemask & FROM_TIME)
from_year = 1900 + gmtime(&from_time)->tm_year;
else {
glinit = matchnames(dir, "[0-9][0-9][0-9][0-9]", &gl);
if (glinit)
from_year = filename_to_int(gl.gl_pathv[0]);
else {
error("no matching files");
exit(1);
}
}
if (timemask & TO_TIME)
to_year = 1900 + gmtime(&to_time)->tm_year;
else {
if (!glinit) {
glinit = matchnames(dir, "[0-9][0-9][0-9][0-9]", &gl);
if (!glinit) {
error("no matching files");
exit(1);
}
}
to_year = filename_to_int(gl.gl_pathv[gl.gl_pathc - 1]);
}
dirlen = strlen(dir) + 6;
dirbuf = xmalloc(dirlen);
for (;from_year <= to_year; from_year++) {
snprintf(dirbuf, dirlen, "%s/%04d", dir, from_year);
selidx_month(dirbuf);
}
free(dirbuf);
if (glinit)
globfree(&gl);
}
int
globerrfunc (const char *epath, int eerrno)
{
error("%s: %s", epath, strerror(eerrno));
return 0;
}
static int
matchnames(const char *dir, const char *pat, glob_t *gl)
{
char *p = mkfilename(dir, pat);
int rc = glob(p, GLOB_ERR, globerrfunc, gl);
free(p);
switch (rc) {
case 0:
break;
case GLOB_NOSPACE:
error("out of memory");
exit(1);
case GLOB_ABORTED:
error("read error");
exit(1);
case GLOB_NOMATCH:
return 0;
}
return 1;
}
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
selglob(const char *dir, const char *pattern)
{
size_t i, j;
glob_t gl;
struct logfile *logfiles;
char *p = mkfilename(dir, pattern);
switch (glob(p, 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);
}
free(p);
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;
setprogname(argv[0]);
while ((c = getopt(argc, argv, "D:dF:hi:p: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 'i':
index_type = atoi(optarg);
if (index_type < 0 || index_type > index_last) {
error("invalid index type: %s", optarg);
exit(1);
}
break;
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 || directory) {
error("either files or pattern (-p) must be given, "
"but not both");
exit(1);
}
selfilelist(argv);
} else if (pattern) {
selglob(directory, convpattern(pattern));
} else {
selidx_year(directory);
}
exit(0);
}