summaryrefslogtreecommitdiffabout
path: root/src/binlog.c
authorSergey Poznyakoff <gray@gnu.org.ua>2013-10-10 21:12:43 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2013-10-11 09:22:57 (GMT)
commitdf83b714395d41b096f7ad8cc3a090c9341f7598 (patch) (side-by-side diff)
tree1c3bcc412e633d3fc678e2cc083ea947e5dcd8e8 /src/binlog.c
downloadvmod-binlog-df83b714395d41b096f7ad8cc3a090c9341f7598.tar.gz
vmod-binlog-df83b714395d41b096f7ad8cc3a090c9341f7598.tar.bz2
Initial commit
Diffstat (limited to 'src/binlog.c') (more/less context) (ignore whitespace changes)
-rw-r--r--src/binlog.c516
1 files changed, 516 insertions, 0 deletions
diff --git a/src/binlog.c b/src/binlog.c
new file mode 100644
index 0000000..f896803
--- a/dev/null
+++ b/src/binlog.c
@@ -0,0 +1,516 @@
+/* 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 <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include "vrt.h"
+#include "vcc_if.h"
+#include "bin/varnishd/cache.h"
+#include "vmod-binlog.h"
+
+#ifndef O_SEARCH
+# define O_SEARCH 0
+#endif
+
+#define BLF_ROUNDTS 0x01
+
+struct binlog_config {
+ size_t size; /* maximum file size */
+ unsigned interval; /* file rotation interval */
+ char *pattern; /* file name pattern */
+ int umask; /* umask for new files and directories */
+ char *dir; /* root storage directory */
+ int dd; /* directory descriptor */
+ char *fname; /* current file name */
+ int fd; /* current file descriptor */
+ union binlog_header *base; /* mmap base */
+ struct binlog_record *recbase; /* record base */
+ size_t recnum; /* number of records in recbase */
+ size_t recidx; /* index of the next free entry in recbase */
+ time_t stoptime; /* when to rotate the current file */
+ pthread_mutex_t mutex;
+ int debug;
+ int flags;
+};
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void
+binlog_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vsyslog(LOG_DAEMON|LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+void
+binlog_debug(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vsyslog(LOG_DAEMON|LOG_DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+#define debug(c,l,s) do { if ((c)->debug>=(l)) binlog_debug s; } while(0)
+
+int
+module_init(struct vmod_priv *priv, const struct VCL_conf *vclconf)
+{
+ struct binlog_config *conf = calloc(1, sizeof(*conf));
+ AN(conf);
+ priv->priv = conf;
+ return 0;
+}
+
+static char *
+findparam(const char *param, char *name)
+{
+ const char *p;
+ char *q;
+
+ while (*param) {
+ for (p = param, q = name; *p && *q && *p == *q; p++, q++);
+ for (param = p; *param && *param != ';'; param++);
+ if (*q == 0 && *p == '=') {
+ size_t len = param - p - 1;
+ q = malloc(len + 1);
+ AN(q);
+ memcpy(q, p + 1, len);
+ q[len] = 0;
+ return q;
+ }
+ if (*param)
+ ++param;
+ }
+ return NULL;
+}
+
+static unsigned long
+getinterval(char *p, char **endp)
+{
+ int n;
+ unsigned long hours = 0, minutes = 0, seconds = 0;
+
+ for (;;) {
+ n = 0;
+ while (*p && isdigit(*p)) {
+ n = 10*n + *p - '0';
+ p++;
+ }
+
+ switch (*p) {
+ case 'h':
+ case 'H':
+ if (hours) {
+ *endp = p;
+ return -1;
+ }
+ hours = n;
+ break;
+ case 'm':
+ case 'M':
+ if (minutes) {
+ *endp = p;
+ return -1;
+ }
+ minutes = n;
+ break;
+ case 's':
+ case 'S':
+ if (seconds) {
+ *endp = p;
+ return -1;
+ }
+ seconds = n;
+ break;
+ default:
+ *endp = p;
+ if (!hours && !minutes && !seconds)
+ return n;
+ return (hours*60 + minutes)*60 + seconds;
+ }
+ p++;
+ }
+}
+
+void
+vmod_init(struct sess *sp, struct vmod_priv *priv, const char *param)
+{
+ struct binlog_config *conf = priv->priv;
+ struct stat st;
+ char *dir, *p, *q;
+ unsigned long n;
+
+ p = findparam(param, "debug");
+ if (p) {
+ conf->debug = atoi(p);
+ free(p);
+ }
+
+ dir = findparam(param, "dir");
+ if (!dir) {
+ binlog_error("parameter \"dir\" not set");
+ abort();
+ }
+ if (stat(dir, &st)) {
+ if (errno == ENOENT)
+ binlog_error("logging directory does not exist");
+ else
+ binlog_error("cannot stat logging directory %s: %s",
+ dir, strerror(errno));
+ abort();
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ binlog_error("%s exists, but is not a directory");
+ abort();
+ }
+ conf->dd = open(dir, O_SEARCH | O_DIRECTORY);
+ if (conf->dd == -1) {
+ binlog_error("cannot open directory %s: %s",
+ dir, strerror(errno));
+ abort();
+ }
+ conf->dir = dir;
+
+ p = findparam(param, "pattern");
+ if (!p) {
+ p = strdup(BINLOG_PATTERN);
+ AN(p);
+ }
+ conf->pattern = p;
+
+ p = findparam(param, "size");
+ if (p) {
+ uintmax_t u;
+
+ errno = 0;
+ u = strtoul(p, &q, 10);
+ if (errno) {
+ binlog_error("invalid size value");
+ abort();
+ }
+ free(p);
+ switch (*q) {
+ case 'g':
+ case 'G':
+ u <<= 10;
+ case 'm':
+ case 'M':
+ u <<= 10;
+ case 'k':
+ case 'K':
+ u <<= 10;
+ }
+ if (u > SIZE_T_MAX) {
+ binlog_error("invalid size value");
+ abort();
+ }
+ conf->size = u;
+ } else
+ conf->size = BINLOG_SIZE;
+
+ p = findparam(param, "interval");
+ if (p) {
+ conf->interval = getinterval(p, &q);
+ free(p);
+ if (*q) {
+ binlog_error("invalid interval (near \"%s\")", q);
+ abort();
+ }
+ } else
+ conf->interval = BINLOG_INTERVAL;
+
+ p = findparam(param, "umask");
+ if (p) {
+ n = strtoul(p, &q, 8);
+ free(p);
+ if (n & ~0777) {
+ binlog_error("umask out of range");
+ abort();
+ }
+ if (*q) {
+ binlog_error("invalid umask (near \"%s\")", q);
+ abort();
+ }
+ } else
+ conf->umask = BINLOG_UMASK;
+
+ p = findparam(param, "roundts");
+ if (p) {
+ if (atoi(p))
+ conf->flags |= BLF_ROUNDTS;
+ else
+ conf->flags &= ~BLF_ROUNDTS;
+ free(p);
+ }
+
+ conf->fd = -1;
+ conf->base = NULL;
+ conf->stoptime = time(NULL);
+ pthread_mutex_init(&conf->mutex, NULL);
+}
+
+static char *
+mkfilename(struct sess *sp, struct binlog_config *conf)
+{
+ time_t ts = time(NULL);
+ size_t u, n;
+ char *p, *q;
+
+ if (conf->flags & BLF_ROUNDTS)
+ ts -= ts % conf->interval;
+ u = WS_Reserve(sp->wrk->ws, 0);
+ p = sp->wrk->ws->f;
+ n = strftime(p, u, conf->pattern, gmtime(&ts));
+ if (n == 0) {
+ WS_Release(sp->wrk->ws, 0);
+ return NULL;
+ }
+ q = strdup(p);
+ AN(q);
+ WS_Release(sp->wrk->ws, 0);
+ return q;
+}
+
+static int
+mkdir_p(struct binlog_config *conf, char *dir)
+{
+ int rc = 0;
+ char *p;
+ struct stat st;
+
+ for (p = dir; rc == 0 && *p; p++) {
+ if (*p != '/')
+ continue;
+ *p = 0;
+ rc = fstatat(conf->dd, dir, &st, 0);
+ if (rc) {
+ if (errno == ENOENT) {
+ rc = mkdirat(conf->dd, dir,
+ 0777 & ~conf->umask);
+ if (rc)
+ binlog_error("cannot create %s: %s",
+ dir,
+ strerror(errno));
+ } else
+ binlog_error("cannot stat %s: %s", dir,
+ strerror(errno));
+ } else if (!S_ISDIR(st.st_mode)) {
+ binlog_error("component \"%s\" is not a directory",
+ dir);
+ rc = -1;
+ }
+ *p = '/';
+ }
+ return rc;
+}
+
+static int
+createfile(struct sess *sp, struct binlog_config *conf)
+{
+ char *fname;
+ int fd;
+
+ conf->fname = NULL;
+ conf->fd = -1;
+
+ fname = mkfilename(sp, conf);
+ if (!fname)
+ return -1;
+ if (mkdir_p(conf, fname)) {
+ free(fname);
+ return -1;
+ }
+
+ fd = openat(conf->dd, fname, O_CREAT|O_RDWR|O_TRUNC,
+ 0666 & ~conf->umask);
+ if (fd == -1) {
+ binlog_error("cannot create log file %s/%s: %s",
+ conf->dir, fname, strerror(errno));
+ free(fname);
+ }
+
+ conf->fname = fname;
+ conf->fd = fd;
+ return 0;
+}
+
+static void
+reset(struct binlog_config *conf)
+{
+ conf->fname = NULL;
+ conf->fd = -1;
+ conf->base = NULL;
+ conf->recbase = NULL;
+ conf->recnum = 0;
+ conf->recidx = 0;
+}
+
+static int
+setstoptime(struct binlog_config *conf)
+{
+ time_t ts;
+
+ ts = time(NULL);
+ conf->stoptime = ts - ts % conf->interval + conf->interval;
+}
+
+static int
+newfile(struct sess *sp, struct binlog_config *conf)
+{
+ int c;
+ void *base;
+
+ setstoptime(conf);
+
+ if (createfile(sp, conf))
+ return -1;
+ if (lseek(conf->fd, conf->size, SEEK_SET) == -1) {
+ binlog_error("seek in log file %s/%s failed: %s",
+ conf->dir, conf->fname, strerror(errno));
+ unlinkat(conf->dd, conf->fname, 0);
+ close(conf->fd);
+ free(conf->fname);
+ reset(conf);
+ return -1;
+ }
+ c = 0;
+ write(conf->fd, &c, 1);
+ base = mmap((caddr_t)0, conf->size,
+ PROT_READ|PROT_WRITE, MAP_SHARED,
+ conf->fd, 0);
+ if (base == MAP_FAILED) {
+ binlog_error("mmap: %s", strerror(errno));
+ unlinkat(conf->dd, conf->fname, 0);
+ close(conf->fd);
+ free(conf->fname);
+ reset(conf);
+ return -1;
+ }
+
+ conf->base = base;
+ memcpy(conf->base->hdr.magic, BINLOG_MAGIC_STR, BINLOG_MAGIC_LEN);
+ conf->base->hdr.version = BINLOG_VERSION;
+ conf->base->hdr.recsize = sizeof(struct binlog_record);
+ conf->base->hdr.recnum = 0;
+
+ conf->recbase = (struct binlog_record *) (conf->base + 1);
+ conf->recnum = binlog_recnum(conf);
+ conf->recidx = 0;
+
+ debug(conf,1,("created new log file %s",conf->fname));
+ return 0;
+}
+
+static void
+closefile(struct sess *sp, struct binlog_config *conf)
+{
+ if (conf->fd == -1)
+ return;
+ debug(conf,1,("closing log file %s",conf->fname));
+ munmap(conf->base, conf->size);
+ if (ftruncate(conf->fd, binlog_size(conf)))
+ binlog_error("error truncating \"%s/%s\": %s",
+ conf->dir, conf->fname, strerror(errno));
+ close(conf->fd);
+ free(conf->fname);
+ reset(conf);
+}
+
+
+void
+vmod_append(struct sess *sp, struct vmod_priv *priv, int nid, int aid)
+{
+ struct binlog_config *conf = priv->priv;
+ time_t ts;
+
+ if (!conf)
+ return;
+
+ ts = time(NULL);
+
+ if (ts >= conf->stoptime) {
+ AZ(pthread_mutex_lock(&conf->mutex));
+ closefile(sp, conf);
+ newfile(sp, conf);
+ AZ(pthread_mutex_unlock(&conf->mutex));
+ }
+ if (conf->fd == -1)
+ return;
+
+ AZ(pthread_mutex_lock(&conf->mutex));
+ if (conf->recidx == conf->recnum) {
+ binlog_error("overflow of %s/%s", conf->dir, conf->fname);
+ } else {
+ struct binlog_record *p = conf->recbase + conf->recidx++;
+ p->nid = nid;
+ p->aid = aid;
+ p->ts = ts;
+ conf->base->hdr.recnum++;
+ }
+ AZ(pthread_mutex_unlock(&conf->mutex));
+}
+
+void
+vmod_sappend(struct sess *sp, struct vmod_priv *priv, const char *nid,
+ const char *aid)
+{
+ vmod_append(sp, priv, nid ? atoi(nid): 0, aid ? atoi(aid) : 0);
+}
+
+void
+vmod_sync(struct sess *sp, struct vmod_priv *priv)
+{
+ struct binlog_config *conf = priv->priv;
+
+ if (!conf)
+ return;
+
+ AZ(pthread_mutex_lock(&conf->mutex));
+ if (conf->base)
+ msync(conf->base, binlog_size(conf), 0);
+ AZ(pthread_mutex_unlock(&conf->mutex));
+}
+
+void
+vmod_close(struct sess *sp, struct vmod_priv *priv)
+{
+ struct binlog_config *conf = priv->priv;
+
+ if (!conf)
+ return;
+
+ mutex = conf->mutex;
+ AZ(pthread_mutex_lock(&mutex));
+ closefile(sp, conf);
+ close(conf->dd);
+ free(conf->dir);
+ free(conf->pattern);
+ free(conf);
+ AZ(pthread_mutex_unlock(&mutex));
+}
+

Return to:

Send suggestions and report system problems to the System administrator.