/* 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); }