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