diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2015-02-10 16:48:15 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2015-02-10 16:48:15 +0200 |
commit | b771edaa218bee8a8365cc8e4ee9af4005ad61b2 (patch) | |
tree | b7042536b1517282b786a2d264a52d858305ba86 /src/variable.c | |
download | vmod-variable-b771edaa218bee8a8365cc8e4ee9af4005ad61b2.tar.gz vmod-variable-b771edaa218bee8a8365cc8e4ee9af4005ad61b2.tar.bz2 |
Initial commit
Diffstat (limited to 'src/variable.c')
-rw-r--r-- | src/variable.c | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/src/variable.c b/src/variable.c new file mode 100644 index 0000000..c8ab8e6 --- /dev/null +++ b/src/variable.c @@ -0,0 +1,688 @@ +/* This file is part of vmod-tbf + Copyright (C) 2013-2015 Sergey Poznyakoff + + Vmod-tbf 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-tbf 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-tbf. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <config.h> +#include <stdlib.h> +#include <stdarg.h> +#include <syslog.h> +#include <ctype.h> +#include <pcre.h> +#include "vrt.h" +#include "vcc_if.h" +#include "pthread.h" +#if VARNISHVERSION == 3 +# include "bin/varnishd/cache.h" +# define VCL_VOID void +# define VCL_STRING const char * +# define VCL_REAL double +# define VCL_DURATION double +# define WS_Copy(w,s,l) WS_Dup(w,s) +# define VARIABLE_CTX struct sess * +# define WSPTR(s) ((s)->wrk->ws) +#else +# include "bin/varnishd/cache/cache.h" +# define VARIABLE_CTX const struct vrt_ctx * +# define WSPTR(s) ((s)->ws) +#endif + +/* |hash_size| defines a sequence of symbol table sizes. These are prime + numbers, each of which is approximately twice its predecessor. */ + +static unsigned int hash_size[] = { + 7, 17, 37, 101, 229, 487, 1009, 2039, 4091, 8191, 16411 +}; + +/* max_rehash keeps the number of entries in |hash_size| table. */ +static unsigned int max_rehash = sizeof(hash_size) / sizeof(hash_size[0]); + +enum variable_type { + variable_unset, + variable_string, + variable_int, + variable_real, + variable_duration +}; + +static char *typestr[] = { + "UNSET", + "STRING", + "INT", + "REAL", + "DURATION" +}; + +union value { + char *s; + int i; + double r; + double d; +}; + +struct variable { + char *name; + enum variable_type type; + union value v; +}; + +struct symtab { + uint32_t vxid; + unsigned int hash_num; /* Index to hash_size table */ + struct variable **tab; +}; + +static struct variable * +var_alloc(const char *name) +{ + struct variable *ent; + + ent = malloc(sizeof(struct variable)); + AN(ent); + ent->name = strdup(name); + ent->type = variable_unset; + AN(ent->name); + return ent; +} + +static unsigned +hash_string(const char *name, unsigned long hashsize) +{ + unsigned i; + + for (i = 0; *name; name++) { + i <<= 1; + i ^= *(unsigned char*) name; + } + return i % hashsize; +} + +static unsigned +var_hash(struct variable *var, unsigned long hashsize) +{ + return hash_string(var->name, hashsize); +} + +static void +var_free(struct variable *var) +{ + free(var->name); + if (var->type == variable_string) + free(var->v.s); + free(var); +} + +static unsigned +symtab_insert_pos(struct symtab *st, struct variable *elt) +{ + unsigned i; + unsigned pos = var_hash(elt, hash_size[st->hash_num]); + + for (i = pos; st->tab[i];) { + if (++i >= hash_size[st->hash_num]) + i = 0; + if (i == pos) + abort(); + } + return i; +} + +static int +symtab_rehash(struct symtab *st) +{ + struct variable **old_tab = st->tab; + struct variable **new_tab; + unsigned int i; + unsigned int hash_num = st->hash_num + 1; + + if (hash_num >= max_rehash) + return E2BIG; + + new_tab = calloc(hash_size[hash_num], sizeof(*new_tab)); + AN(new_tab); + st->tab = new_tab; + if (old_tab) { + st->hash_num = hash_num; + for (i = 0; i < hash_size[hash_num-1]; i++) { + struct variable *elt = old_tab[i]; + if (elt->name) { + unsigned n = symtab_insert_pos(st, elt); + new_tab[n] = elt; + } + } + free(old_tab); + } + return 0; +} + +static int +symtab_remove(struct symtab *st, const char *name) +{ + unsigned int pos, i, j, r; + struct variable *entry; + + pos = hash_string(name, hash_size[st->hash_num]); + for (i = pos; (entry = st->tab[i]);) { + if (strcmp(entry->name, name) == 0) + break; + if (++i >= hash_size[st->hash_num]) + i = 0; + if (i == pos) + return ENOENT; + } + + var_free(entry); + + for (;;) { + st->tab[i] = NULL; + j = i; + + do { + if (++i >= hash_size[st->hash_num]) + i = 0; + if (!st->tab[i]) + return 0; + r = hash_string(st->tab[i]->name, hash_size[st->hash_num]); + } + while ((j < r && r <= i) + || (i < j && j < r) || (r <= i && i < j)); + st->tab[j] = st->tab[i]; + } + return 0; +} + +static int +symtab_get_index(unsigned *idx, struct symtab *st, + const char *name, int *install) +{ + int rc; + unsigned i, pos; + struct variable *elem; + + if (!st->tab) { + if (install) { + rc = symtab_rehash(st); + if (rc) + return rc; + } else + return ENOENT; + } + + pos = hash_string(name, hash_size[st->hash_num]); + + for (i = pos; (elem = st->tab[i]);) { + if (strcmp(elem->name, name) == 0) { + if (install) + *install = 0; + *idx = i; + return 0; + } + + if (++i >= hash_size[st->hash_num]) + i = 0; + if (i == pos) + break; + } + + if (!install) + return ENOENT; + + if (!elem) { + *install = 1; + *idx = i; + return 0; + } + + if ((rc = symtab_rehash(st)) != 0) + return rc; + + return symtab_get_index(idx, st, name, install); +} + +static struct variable * +symtab_lookup_or_install(struct symtab *st, const char *name, int *install) +{ + unsigned i; + int rc = symtab_get_index(&i, st, name, install); + if (rc == 0) { + if (install && *install == 1) { + struct variable *ent = var_alloc(name); + if (!ent) { + errno = ENOMEM; + return NULL; + } + st->tab[i] = ent; + return ent; + } else + return st->tab[i]; + } + errno = rc; + return NULL; +} + +static void +symtab_clear(struct symtab *st) +{ + unsigned i, hs; + + if (!st || !st->tab) + return; + + hs = hash_size[st->hash_num]; + for (i = 0; i < hs; i++) { + struct variable *elem = st->tab[i]; + if (elem) { + var_free(elem); + st->tab[i] = NULL; + } + } +} + +static struct symtab * +symtab_create() +{ + struct symtab *st = calloc(1, sizeof(*st)); + AN(st); + st->tab = calloc(hash_size[st->hash_num], sizeof(*st->tab)); + AN(st->tab); + return st; +} + +static void +symtab_free(struct symtab *st) +{ + if (st) { + symtab_clear(st); + free(st->tab); + free(st); + } +} + + +static struct symtab **symtabv; +static size_t symtabc; +static pthread_mutex_t symtab_mtx = PTHREAD_MUTEX_INITIALIZER; + +static struct symtab * +get_symtab(VARIABLE_CTX ctx) +{ + struct symtab *st; + int fd = ctx->req->sp->fd; + AZ(pthread_mutex_lock(&symtab_mtx)); + if (symtabc <= fd) { + size_t n = fd + 1; + symtabv = realloc(symtabv, n * sizeof(symtabv[0])); + while (symtabc < n) + symtabv[symtabc++] = NULL; + } + if (!symtabv[fd]) + symtabv[fd] = symtab_create(); + st = symtabv[fd]; + if (st->vxid != ctx->req->sp->vxid) + symtab_clear(st); + st->vxid = ctx->req->sp->vxid; + AZ(pthread_mutex_unlock(&symtab_mtx)); + return st; +} + +#define getvar(vt, name) symtab_lookup_or_install(vt, name, NULL) + +struct variable * +defvar(struct symtab *vt, const char *name, enum variable_type t, + union value *val) +{ + int inst = 1; + struct variable *var = symtab_lookup_or_install(vt, name, &inst); + if (!inst && var->type == variable_string) + free(var->v.s); + if (t != variable_unset && val) { + var->type = t; + var->v = *val; + } else + var->type = variable_unset; + return var; +} + +VCL_VOID +vmod_clear(VARIABLE_CTX ctx) +{ + symtab_clear(get_symtab(ctx)); +} + +VCL_STRING +vmod_get_string(VARIABLE_CTX ctx, VCL_STRING name) +{ + struct variable *var = getvar(get_symtab(ctx), name); + if (var && var->type == variable_string) + return var->v.s; + return NULL; +} + +VCL_VOID +vmod_set_string(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value) +{ + struct symtab *vt = get_symtab(ctx); + struct variable *var = defvar(vt, name, variable_unset, NULL); + var->type = variable_string; + var->v.s = strdup(value ? value : ""); + AN(var->v.s); +} + +VCL_STRING +vmod_get(VARIABLE_CTX ctx, VCL_STRING name) +{ + return vmod_get_string(ctx, name); +} + +VCL_VOID +vmod_set(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value) +{ + vmod_set_string(ctx, name, value); +} + +#define __cat__(a,b) a ## b +#define DEFGET(r_type, vcl_type, memb) \ +vcl_type \ +__cat__(vmod_get_,r_type)(VARIABLE_CTX ctx, VCL_STRING name) \ +{ \ + struct variable *var = getvar(get_symtab(ctx), name); \ + if (var && var->type == __cat__(variable_,r_type)) \ + return var->v.memb; \ + return 0; \ +} + +#define DEFSET(r_type, vcl_type, memb) \ +VCL_VOID \ +__cat__(vmod_set_,r_type)(VARIABLE_CTX ctx, VCL_STRING name, \ + vcl_type value) \ +{ \ + struct symtab *vt = get_symtab(ctx); \ + struct variable *var = defvar(vt, name, variable_unset, NULL); \ + var->type = __cat__(variable_,r_type); \ + var->v.memb = value; \ +} + +#define DEF(name, vcl_type, memb) \ +DEFGET(name, vcl_type, memb) \ +DEFSET(name, vcl_type, memb) + +DEF(int, VCL_INT, i) +DEF(real, VCL_REAL, r) +DEF(duration, VCL_DURATION, d) + +VCL_INT +vmod_defined(VARIABLE_CTX ctx, VCL_STRING name) +{ + return !!getvar(get_symtab(ctx), name); +} + +VCL_STRING +vmod_type_of(VARIABLE_CTX ctx, VCL_STRING name) +{ + struct variable *var = getvar(get_symtab(ctx), name); + return typestr[var ? var->type : variable_unset]; +} + +VCL_VOID +vmod_undef(VARIABLE_CTX ctx, VCL_STRING name) +{ + symtab_remove(get_symtab(ctx), name); +} + +void +log_error(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_DAEMON|LOG_NOTICE, fmt, ap); + va_end(ap); +} + +#define S(s) ((s) ? (s) : "NULL") + +struct vardef { + struct vardef *next; + enum variable_type type; + char name[1]; +}; + +static void +vardef_free(struct vardef *v) +{ + while (v) { + struct vardef *n = v->next; + free(v); + v = n; + } +} + +static enum variable_type +str2type(const char *s, size_t len) +{ + int i; + + for (i = 0; i < sizeof(typestr)/sizeof(typestr[0]); i++) { + if (strlen(typestr[i]) < len) + continue; + if (strncasecmp(typestr[i], s, len) == 0) + return i; + } + return variable_unset; +} + +static double +str2duration(const char *str) +{ + char *p; + double r; + + errno = 0; + r = strtod(str, &p); + if (errno) + return 0.0; + while (*p && isspace(*p)) + p++; + + switch (*p++) { + case 's': + break; + case 'm': + if (*p == 's') { + r *= 1e-3; + p++; + } else + r *= 60.; + break; + case 'h': + r *= 60.*60.; + break; + case 'd': + r *= 60.*60.*24.; + break; + case 'w': + r *= 60.*60.*24.*7.; + break; + case 'y': + r *= 60.*60.*24.*365.; + break; + default: + return 0.0; + } + while (*p && isspace(*p)) + p++; + return *p ? 0 : r; +} + +VCL_VOID +vmod_batchset(VARIABLE_CTX ctx, + VCL_STRING vars, VCL_STRING rxs, VCL_STRING input) +{ + struct symtab *vt = get_symtab(ctx); + struct vardef *head = NULL, *tail = NULL, *def; + size_t count = 0; + size_t n; + const char *v = vars; + const char *error_ptr; + int error_offset; + int cflags = 0; + long lval; + union value value; + char *p; + int ovsize; + int *ovector; + int i; + int rc; + pcre *re; + + if (!vars || !rxs || !input) { + log_error("variable.batchset: bad arguments: vars=%s, rxs=%s, input=%s", + S(vars), S(rxs), S(input)); + return; + } + + while (*v) { + size_t n = strcspn(v, "="); + + if (!v[n]) { + log_error("variable.batchset: vars argument invalid near %s", + v); + vardef_free(head); + return; + } + def = malloc(sizeof(def[0]) + n); + def->next = NULL; + memcpy(def->name, v, n); + def->name[n] = 0; + if (tail) + tail->next = def; + else + head = def; + tail = def; + ++count; + + v += n + 1; + if (!*v) { + log_error("variable.batchset: no type for %s", def->name); + vardef_free(head); + return; + } + + n = strcspn(v, ","); + def->type = str2type(v, n); + if (def->type == variable_unset) { + log_error("variable.batchset: invalid type for %s", + def->name); + vardef_free(head); + return; + } + + v += n; + if (*v) + ++v; + } + + re = pcre_compile(rxs, cflags, &error_ptr, &error_offset, NULL); + if (!re) { + log_error("variable.batchset: %s: compilation failed near %s: %s", + rxs, rxs + error_offset, error_ptr); + vardef_free(head); + return; + } + + rc = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &n); + if (rc) { + log_error("pcre_fullinfo() failed: %d", rc); + vardef_free(head); + return; + } + + if (n < count) { + log_error("variable.batchset: %s: too few subexpressions to satisfy %s", + rxs, vars); + vardef_free(head); + return; + } + + ovsize = (count + 1) * 3; + ovector = calloc(ovsize, sizeof(*ovector)); + + rc = pcre_exec(re, 0, input, strlen(input), 0, 0, ovector, ovsize); + if (rc <= 0) { + if (rc == 0) + log_error("matched, but too many substrings"); + else + /*FIXME*/; + vardef_free(head); + return; + } + + for (def = head, i = 1; def; def = def->next, i++) { + const char *s; + + rc = pcre_get_substring(input, ovector, ovsize, i, &s); + if (rc < 0) { + log_error("variable.batchset: %s(%s) can't get subexpr #%d: %d", + rxs, input, i, rc); + vardef_free(head); + return; + } + + switch (def->type) { + case variable_string: + value.s = (char*)s; + break; + case variable_int: + errno = 0; + lval = strtol(s, &p, 10); + if (*p) { + log_error("variable.batchset: %s(%s)#%d: not an integer", rxs, input, i); + value.i = 0; + } else if (errno) { + log_error("variable.batchset: %s(%s)#%d: %s", + rxs, input, i, strerror(errno)); + value.i = 0; + } else if (lval < INT_MIN || lval > INT_MAX) { + log_error("variable.batchset: %s(%s)#%d: value out of range", + rxs, input, i); + value.i = 0; + } else + value.i = lval; + break; + case variable_real: + errno = 0; + value.r = strtod(s, &p); + if (*p) { + log_error("variable.batchset: %s(%s)#%d: not a valid number", rxs, input, i); + value.r = 0; + } else if (errno) { + log_error("variable.batchset: %s(%s)#%d: %s", + rxs, input, i, strerror(errno)); + value.r = 0; + } + break; + + case variable_duration: + value.d = str2duration(s); + break; + default: + abort(); + } + defvar(vt, def->name, def->type, &value); + } + + pcre_free(re); + free(ovector); + vardef_free(head); +} |