diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-08-01 19:20:54 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-08-02 10:48:54 +0300 |
commit | 73f605f76d82aec40a7f1bbd309e2097d9abfd42 (patch) | |
tree | 5820713d17efcb482a8801f4f805b0ae121903f7 | |
parent | d78a58be5fa2385987a3141ccbe942107332e677 (diff) | |
download | vmod-tbf-73f605f76d82aec40a7f1bbd309e2097d9abfd42.tar.gz vmod-tbf-73f605f76d82aec40a7f1bbd309e2097d9abfd42.tar.bz2 |
Switch to CDB environment.
* configure.ac: Version 0.99.91
* src/tbf.c (dbdir): New static.
(tbf_set_db_name): Remove.
(tbf_set_db_dir): New function.
(tbf_open): Rename the parameter and related variables.
Create a CDB environment and the database in it.
(vmod_open): Change semantics of the first argument.
* src/vmod-tbf.3: Update.
* tests/Makefile.am (distclean-local): New rule, instead of
DISTCLEANFILES.
* tests/test00.vtc: Update call to tbf.open.
* tests/test01.vtc: Likewise.
* tests/test02.vtc: Likewise.
* tests/test03.vtc: Likewise.
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | src/tbf.c | 212 | ||||
-rw-r--r-- | src/vmod-tbf.3 | 32 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rw-r--r-- | tests/test00.vtc | 2 | ||||
-rw-r--r-- | tests/test01.vtc | 2 | ||||
-rw-r--r-- | tests/test02.vtc | 2 | ||||
-rw-r--r-- | tests/test03.vtc | 2 |
8 files changed, 175 insertions, 83 deletions
diff --git a/configure.ac b/configure.ac index 9c228f6..4ca4e6f 100644 --- a/configure.ac +++ b/configure.ac @@ -11,13 +11,13 @@ # 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/>. AC_PREREQ(2.69) -AC_INIT([vmod-tbf], 0.99.90, [gray@gnu.org]) +AC_INIT([vmod-tbf], 0.99.91, [gray@gnu.org]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR(src/vmod_tbf.vcc) AM_CONFIG_HEADER(config.h) AC_CANONICAL_SYSTEM @@ -17,12 +17,13 @@ #include <config.h> #include <stdlib.h> #include <stdio.h> #include <stdbool.h> #include <syslog.h> #include <inttypes.h> +#include <sys/stat.h> #include <db.h> #include "vrt.h" #include "vcc_if.h" #include "bin/varnishd/cache.h" static int debug_level; @@ -38,20 +39,25 @@ debugprt(const char *fmt, ...) #define debug(n,c) do { if (debug_level>=(n)) debugprt c; } while (0) #ifndef USEC_PER_SEC # define USEC_PER_SEC 1000000L #endif +#define DEFDBNAME "tbf.bdb" +#define DEFOPENPARAMS "truncate" +#define DBFILEMODE 0640 + +static char *dbdir; static char *dbname; +static DB_ENV *dbenv; static DB *db; static uint64_t autosync_max; static uint64_t autosync_count; static int tbf_disabled; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -#define DBFILEMODE 0640 /* The keylock structure serializes accesses to each db record, ensuring that no other thread could modify the data between calls to get and put */ @@ -119,184 +125,258 @@ keylock_remove_safe(struct keylock *kp) VTAILQ_REMOVE(&keylock_head, kp, list); VTAILQ_INSERT_TAIL(&keylock_avail, kp, list); pthread_mutex_unlock(&mutex); } static void -tbf_set_db_name(const char *file_name) +tbf_set_db_dir(const char *dir) { - if (dbname) - free(dbname); - dbname = strdup(file_name); - if (!dbname) - abort(); + if (dbdir) + free(dbdir); + dbdir = strdup(dir); + AN(dbdir); } -struct mode_kw { - char *mkw_str; - int mkw_len; - int mkw_tok; +struct param_kw { + char *pkw_str; + int pkw_len; + int pkw_tok; }; enum { - MKW_TRUNCATE, - MKW_MODE, - MKW_SYNC, - MKW_DEBUG, + PKW_TRUNCATE, + PKW_MODE, + PKW_SYNC, + PKW_DEBUG, + PKW_DBNAME }; -static struct mode_kw mode_kw_tab[] = { +static struct param_kw param_kw_tab[] = { #define S(s) #s, sizeof(#s)-1 - { S(truncate), MKW_TRUNCATE }, - { S(trunc), MKW_TRUNCATE }, - { S(mode=), MKW_MODE }, - { S(sync=), MKW_SYNC }, - { S(debug=), MKW_DEBUG }, + { S(truncate), PKW_TRUNCATE }, + { S(trunc), PKW_TRUNCATE }, + { S(mode=), PKW_MODE }, + { S(sync=), PKW_SYNC }, + { S(debug=), PKW_DEBUG }, + { S(dbname=), PKW_DBNAME }, { NULL } #undef S }; static void -tbf_open(const char *mode) +tbf_open(const char *params) { int rc; - int flags = DB_CREATE|DB_THREAD; int filemode = DBFILEMODE; uint64_t n; char *p; + struct stat st; + int truncate = 0; - if (!dbname) - tbf_set_db_name(LOCALSTATEDIR "/tbf.db"); - - rc = db_create(&db, NULL, 0); - if (rc) { - syslog(LOG_DAEMON|LOG_ERR, "cannot create db struct"); - return; + if (!dbdir) { + dbdir = strdup(LOCALSTATEDIR "/vmod-tbf"); + AN(dbdir); } - - while (*mode) { - struct mode_kw *mkw; + if (!dbname) { + dbname = strdup(DEFDBNAME); + AN(dbname); + } + + while (*params) { + struct param_kw *pkw; - for (mkw = mode_kw_tab; mkw->mkw_str; mkw++) { - if (strncmp(mode, mkw->mkw_str, mkw->mkw_len) == 0) + for (pkw = param_kw_tab; pkw->pkw_str; pkw++) { + if (strncmp(params, pkw->pkw_str, pkw->pkw_len) == 0) break; } - if (!mkw->mkw_str) { - syslog(LOG_DAEMON|LOG_ERR, "invalid keyword %s", mode); + if (!pkw->pkw_str) { + syslog(LOG_DAEMON|LOG_ERR, "invalid keyword %s", params); break; } - mode += mkw->mkw_len; + params += pkw->pkw_len; - switch (mkw->mkw_tok) { - case MKW_TRUNCATE: - flags |= DB_TRUNCATE; + switch (pkw->pkw_tok) { + case PKW_TRUNCATE: + truncate = 1; break; - case MKW_MODE: + case PKW_MODE: errno = 0; - n = strtoul(mode, &p, 8); + n = strtoul(params, &p, 8); if (errno || (n & ~0777) || !(*p == 0 || *p == ';')) { syslog(LOG_DAEMON|LOG_ERR, "invalid file mode near %s", p); - mode += strlen(mode); + params += strlen(params); } else { filemode = n; - mode = p; + params = p; } break; - case MKW_SYNC: + case PKW_SYNC: errno = 0; - n = strtoul(mode, &p, 10); + n = strtoul(params, &p, 10); if (errno || !(*p == 0 || *p == ';')) { syslog(LOG_DAEMON|LOG_ERR, "invalid count near %s", p); - mode += strlen(mode); + params += strlen(params); } else { autosync_max = n; autosync_count = 0; - mode = p; + params = p; } break; - case MKW_DEBUG: + case PKW_DEBUG: errno = 0; - n = strtoul(mode, &p, 10); + n = strtoul(params, &p, 10); if (errno || !(*p == 0 || *p == ';')) { syslog(LOG_DAEMON|LOG_ERR, "invalid debug level near %s", p); - mode += strlen(mode); + params += strlen(params); } else { debug_level = n; - mode = p; - } + params = p; + } + break; + + case PKW_DBNAME: + if (dbname) + free(dbname); + n = strcspn(params, ";"); + dbname = malloc(n + 1); + AN(dbname); + memcpy(dbname, params, n); + dbname[n] = 0; + params += n; + break; } - if (*mode == 0) + if (*params == 0) break; - else if (*mode == ';') - mode++; + else if (*params == ';') + params++; else { syslog(LOG_DAEMON|LOG_ERR, - "expected ';' near %s", mode); + "expected ';' near %s", params); break; } } - debug(1, ("opening database %s", dbname)); - rc = db->open(db, NULL, dbname, NULL, DB_HASH, flags, filemode); + debug(1, ("opening database %s/%s", dbdir, dbname)); + + if (rc = db_env_create(&dbenv, 0)) { + syslog(LOG_DAEMON|LOG_ERR, "cannot create db environment: %s", + db_strerror(rc)); + return; + } + + if (stat(dbdir, &st)) { + if (errno == ENOENT) { + if (mkdir(dbdir, + filemode | 0100 | + ((filemode & 0060) ? 0010 : 0) | + ((filemode & 0006) ? 0001 : 0))) { + syslog(LOG_DAEMON|LOG_ERR, + "cannot create db environment directory %s: %m", + dbdir); + } + } else { + syslog(LOG_DAEMON|LOG_ERR, + "cannot stat db environment directory %s: %m", + dbdir); + return; + } + } else if (!S_ISDIR(st.st_mode)) { + syslog(LOG_DAEMON|LOG_ERR, "%s is not a directory", + dbdir); + return; + } + + rc = dbenv->open(dbenv, dbdir, + DB_THREAD | DB_CREATE | DB_INIT_MPOOL | DB_INIT_CDB, + 0); if (rc) { - syslog(LOG_DAEMON|LOG_ERR, "cannot open %s: %s", + syslog(LOG_DAEMON|LOG_ERR, "cannot open db environment %s: %s", + dbdir, db_strerror(rc)); + tbf_disabled = 1; + return; + } + + rc = db_create(&db, dbenv, 0); + if (rc) { + syslog(LOG_DAEMON|LOG_ERR, "cannot create db struct"); + return; + } + + rc = db->open(db, NULL, dbname, NULL, DB_HASH, + DB_THREAD | DB_CREATE, filemode); + if (rc) { + syslog(LOG_DAEMON|LOG_ERR, "cannot open database %s: %s", dbname, db_strerror (rc)); db->close(db, 0); db = NULL; + dbenv->close(dbenv, 0); + dbenv = NULL; tbf_disabled = 1; } + + if (truncate) { + rc = db->truncate(db, NULL, NULL, 0); + if (rc) + syslog(LOG_DAEMON|LOG_WARNING, + "failed to truncate database %s: %s", + dbname, db_strerror(rc)); + } } static DB * -tbf_open_safe(const char *mode) +tbf_open_safe(const char *params) { if (tbf_disabled) return NULL; pthread_mutex_lock(&mutex); if (!db) - tbf_open(mode ? mode : "truncate"); + tbf_open(params ? params : DEFOPENPARAMS); pthread_mutex_unlock(&mutex); return db; } int tbf_init(struct vmod_priv *priv, const struct VCL_conf *vclconf) { VTAILQ_INIT(&keylock_head); VTAILQ_INIT(&keylock_avail); } void -vmod_open(struct sess *sp, const char *file_name, const char *mode) +vmod_open(struct sess *sp, const char *dir, const char *params) { if (db) { syslog(LOG_DAEMON|LOG_ERR, "tbf.open called twice"); return; } - tbf_set_db_name(file_name); - tbf_open_safe(mode); + tbf_set_db_dir(dir); + tbf_open_safe(params); } void vmod_close(struct sess *sp) { + pthread_mutex_lock(&mutex); if (db) { debug(1, ("closing database %s", dbname)); db->close(db, 0); db = NULL; + dbenv->close(dbenv, 0); + dbenv = NULL; tbf_disabled = 0; } + pthread_mutex_unlock(&mutex); } void vmod_sync(struct sess *sp) { if (db) { diff --git a/src/vmod-tbf.3 b/src/vmod-tbf.3 index cf12ea7..bd7b233 100644 --- a/src/vmod-tbf.3 +++ b/src/vmod-tbf.3 @@ -10,19 +10,19 @@ .\" 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/>. -.TH VMOD-TBF 1 "July 23, 2013" "VMOD-TBF" "User Reference" +.TH VMOD-TBF 1 "August 2, 2013" "VMOD-TBF" "User Reference" .SH NAME vmod-tbf \- token bucket filtering for Varnish .SH SYNOPSIS .B import tbf; -.BI "VOID tbf.open(STRING " dbfile ", STRING " params ");" +.BI "VOID tbf.open(STRING " dbdir ", STRING " params ");" .B VOID tbf.close(); .BI "BOOL tbf.rate(STRING " key ", INT " COST ", DURATION " interval ", INT " burst_size ");" .BI "BOOL tbf.check(STRING " key ", STRING " rate ");" @@ -105,19 +105,23 @@ sub vcl_recv { error 429 "Request rate exceeded." } } .EE .SS Storage .PP -Buckets are kept in a Berkeley DB file. The \fBtbf.open\fR function -controls its location and permissions. The \fBdbfile\fR argument -supplies the full pathname to the file. The \fBparams\fR argument is -a semicolon separated list of the following parameters: +Buckets are kept in a Berkeley database file. The \fBtbf.open\fR function +controls its location and permissions. The \fBdbdir\fR argument +supplies the full pathname to the directory where the database is +located. The \fBparams\fR argument is a semicolon separated list of +the following parameters: +.TP +.BI dbname= NAME +Sets the name of the database file. Default is \fBtbf.bdb\fR. .TP .BR truncate " or " trunc -Truncate the file if it already exists. +Truncate the database if it already exists. .TP .BI mode= OCT Set the file mode. \fIOCT\fR is an octal number. The default file mode is \fB640\fR. Note that this parameter takes effect only when creating the file. If the database file already exists by the time \fBtbf.open\fR is called, its mode will not be altered. @@ -138,16 +142,22 @@ sub vcl_init { .EE .PP Note that the directory where the database file is located must be writable for the user \fBVarnish\fR runs as. .PP Unless the \fBtbf.open\fR function was called, both \fBtbf.rate\fR and -\fBtbf.check\fR will attempt to use the file \fIlocalstatedir\fB/tbf.db\fR, -where \fIlocalstatedir\fR is the directory for modifiable -single-machine data, which is set when configuring the package -(e.g. \fB/var/run/tbf\fR or the like). +\fBtbf.check\fR will attempt to use the database located in +\fIlocalstatedir\fB/vmod-tbf\fR, where \fIlocalstatedir\fR is the +directory for modifiable single-machine data, which is set when +configuring the package (e.g. \fB/var/run\fR or the like). +.PP +If the database directory does not exist, \fBtbf.open\fR will attempt +to create it, deducing its mode from the database file mode (see the +\fBmode=\fR parameter above) by setting executable +bit in each triplet that has read or write bit set (e.g. \fB640\fR +will become \fB750\fR). .PP The \fBtbf.close\fR function flushes the data and closes the database. It is normally called from the \fBvcl_fini\fR subroutine: .PP .EX sub vcl_fini { diff --git a/tests/Makefile.am b/tests/Makefile.am index 383f18a..4790115 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -3,13 +3,15 @@ VMOD_TESTS = \ test01.vtc\ test02.vtc\ test03.vtc\ time00.vtc EXTRA_DIST=$(VMOD_TESTS) -DISTCLEANFILES=tbf.db +distclean-local: + rm -fr tbf + check: cd $(abs_srcdir); \ for t in $(VMOD_TESTS); do \ $(VARNISHSRC)/bin/varnishtest/varnishtest \ -Dvarnishd=$(VARNISHSRC)/bin/varnishd/varnishd \ -Dvmod_topsrc=$(abs_top_srcdir) \ diff --git a/tests/test00.vtc b/tests/test00.vtc index 10f99ca..f69f5ed 100644 --- a/tests/test00.vtc +++ b/tests/test00.vtc @@ -5,13 +5,13 @@ server s1 { txresp } -start varnish v1 -vcl+backend { import tbf from "${vmod_topbuild}/src/.libs/libvmod_tbf.so"; sub vcl_init { - tbf.open("${vmod_topbuild}/tests/tbf.db", "truncate"); + tbf.open("${vmod_topbuild}/tests/tbf", "truncate"); } sub vcl_fini { tbf.close(); } sub vcl_recv { if (!tbf.rate("url:"+req.url, 1, 20 s, 5)) { diff --git a/tests/test01.vtc b/tests/test01.vtc index ad6f7d1..39eb4c3 100644 --- a/tests/test01.vtc +++ b/tests/test01.vtc @@ -5,13 +5,13 @@ server s1 { txresp } -start varnish v1 -vcl+backend { import tbf from "${vmod_topbuild}/src/.libs/libvmod_tbf.so"; sub vcl_init { - tbf.open("${vmod_topbuild}/tests/tbf.db", "trunc"); + tbf.open("${vmod_topbuild}/tests/tbf", "trunc"); } sub vcl_fini { tbf.close(); } sub vcl_deliver { set resp.http.result = tbf.rate("url:"+req.url, 1, 1s, 5); diff --git a/tests/test02.vtc b/tests/test02.vtc index b957181..8df6730 100644 --- a/tests/test02.vtc +++ b/tests/test02.vtc @@ -5,13 +5,13 @@ server s1 { txresp } -start varnish v1 -vcl+backend { import tbf from "${vmod_topbuild}/src/.libs/libvmod_tbf.so"; sub vcl_init { - tbf.open("${vmod_topbuild}/tests/tbf.db", "truncate"); + tbf.open("${vmod_topbuild}/tests/tbf", "truncate"); } sub vcl_fini { tbf.close(); } sub vcl_recv { if (!tbf.check("url:"+req.url, "4 req/s")) { diff --git a/tests/test03.vtc b/tests/test03.vtc index 5ffba9a..aa23da4 100644 --- a/tests/test03.vtc +++ b/tests/test03.vtc @@ -5,13 +5,13 @@ server s1 { txresp } -start varnish v1 -vcl+backend { import tbf from "${vmod_topbuild}/src/.libs/libvmod_tbf.so"; sub vcl_init { - tbf.open("${vmod_topbuild}/tests/tbf.db", "trunc"); + tbf.open("${vmod_topbuild}/tests/tbf", "trunc"); } sub vcl_fini { tbf.close(); } sub vcl_deliver { set resp.http.result = tbf.check("url:"+req.url, "4req/s"); |