/*
** Copyright (c) 2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
*/
#ifdef QUERY_CACHE
#ifndef lint
static char dkim_cache_c_id[] = "@(#)$Id: dkim-cache.c,v 1.20 2007/10/24 06:32:56 msk Exp $";
#endif /* !lint */
/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
/* libsm includes */
#include <sm/string.h>
/* libdb includes */
#include <db.h>
/* libdkim includes */
#include "dkim.h"
#include "dkim-cache.h"
/* limits, macros, etc. */
#define BUFRSZ 1024
#define DB_MODE (S_IRUSR|S_IWUSR)
#ifndef DB_NOTFOUND
# define DB_NOTFOUND 1
#endif /* ! DB_NOTFOUND */
#ifndef DB_VERSION_MAJOR
# define DB_VERSION_MAJOR 1
#endif /* ! DB_VERSION_MAJOR */
#define DB_VERSION_CHECK(x,y,z) ((DB_VERSION_MAJOR == (x) && \
DB_VERSION_MINOR >= (y)) || \
DB_VERSION_MAJOR > (x))
/* data types */
struct dkim_cache_entry
{
int cache_ttl;
time_t cache_when;
char cache_data[BUFRSZ + 1];
};
/* globals */
static pthread_mutex_t cache_stats_lock; /* stats lock */
static u_int c_hits = 0; /* cache hits */
static u_int c_queries = 0; /* cache queries */
static u_int c_expired = 0; /* expired cache hits */
#if DB_VERSION_MAJOR == 1
static pthread_mutex_t cache_lock; /* cache lock */
#else /* DB_VERSION_MAJOR == 1 */
# ifdef PTHREAD_RWLOCK_INITIALIZER
static pthread_rwlock_t cache_rwlock; /* cache lock */
# else /* PTHREAD_RWLOCK_INITIALIZER */
static pthread_mutex_t cache_lock; /* cache lock */
static u_int r_wait; /* readers waiting */
static u_int r_active; /* readers active */
static u_int w_wait; /* writers waiting */
static u_int w_active; /* writers waiting */
static pthread_cond_t writer_go; /* writer should enter */
static pthread_cond_t reader_go; /* reader should enter */
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#endif /* DB_VERSION_MAJOR == 1 */
/*
** DKIM_CACHE_READ_LOCK -- acquire a read lock
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkim_cache_read_lock(void)
{
#if DB_VERSION_CHECK(2,0,0)
# ifdef PTHREAD_RWLOCK_INITIALIZER
(void) pthread_rwlock_rdlock(&cache_rwlock);
# else /* PTHREAD_RWLOCK_INITIALIZER */
(void) pthread_mutex_lock(&cache_lock);
if (w_wait > 0 || w_active > 0)
{
r_wait++;
while (w_wait > 0 || w_active > 0)
(void) pthread_cond_wait(&reader_go, &cache_lock);
r_wait--;
}
r_active++;
(void) pthread_mutex_unlock(&cache_lock);
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#else /* DB_VERSION_CHECK(2,0,0) */
(void) pthread_mutex_lock(&cache_lock);
#endif /* DB_VERSION_CHECK(2,0,0) */
}
/*
** DKIM_CACHE_READ_UNLOCK -- release a read lock
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkim_cache_read_unlock(void)
{
#if DB_VERSION_CHECK(2,0,0)
# ifdef PTHREAD_RWLOCK_INITIALIZER
(void) pthread_rwlock_unlock(&cache_rwlock);
# else /* PTHREAD_RWLOCK_INITIALIZER */
(void) pthread_mutex_lock(&cache_lock);
r_active--;
if (r_active == 0 && w_wait > 0)
(void) pthread_cond_signal(&writer_go);
(void) pthread_mutex_unlock(&cache_lock);
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#else /* DB_VERSION_CHECK(2,0,0) */
(void) pthread_mutex_unlock(&cache_lock);
#endif /* DB_VERSION_CHECK(2,0,0) */
}
/*
** DKIM_CACHE_WRITE_LOCK -- acquire a write lock
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkim_cache_write_lock(void)
{
#if DB_VERSION_CHECK(2,0,0)
# ifdef PTHREAD_RWLOCK_INITIALIZER
(void) pthread_rwlock_wrlock(&cache_rwlock);
# else /* PTHREAD_RWLOCK_INITIALIZER */
(void) pthread_mutex_lock(&cache_lock);
if (w_active > 0 || r_active > 0)
{
w_wait++;
while (w_active > 0 || r_active > 0)
(void) pthread_cond_wait(&writer_go, &cache_lock);
w_wait--;
}
w_active++;
assert(w_active == 1);
(void) pthread_mutex_unlock(&cache_lock);
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#else /* DB_VERSION_CHECK(2,0,0) */
(void) pthread_mutex_lock(&cache_lock);
#endif /* DB_VERSION_CHECK(2,0,0) */
}
/*
** DKIM_CACHE_WRITE_UNLOCK -- release a write lock
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkim_cache_write_unlock(void)
{
#if DB_VERSION_CHECK(2,0,0)
# ifdef PTHREAD_RWLOCK_INITIALIZER
(void) pthread_rwlock_unlock(&cache_rwlock);
# else /* PTHREAD_RWLOCK_INITIALIZER */
(void) pthread_mutex_lock(&cache_lock);
w_active--;
assert(w_active == 0);
if (w_wait > 0)
(void) pthread_cond_broadcast(&writer_go);
else if (r_wait > 0)
(void) pthread_cond_broadcast(&reader_go);
(void) pthread_mutex_unlock(&cache_lock);
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#else /* DB_VERSION_CHECK(2,0,0) */
(void) pthread_mutex_unlock(&cache_lock);
#endif /* DB_VERSION_CHECK(2,0,0) */
}
/*
** DKIM_CACHE_INIT -- initialize an on-disk cache of entries
**
** Parameters:
** err -- error code (returned)
** tmpdir -- temporary directory to use (may be NULL)
**
** Return value:
** A DB handle referring to the cache, or NULL on error.
*/
DB *
dkim_cache_init(int *err, char *tmpdir)
{
int status = 0;
DB *cache = NULL;
c_hits = 0;
c_queries = 0;
c_expired = 0;
(void) pthread_mutex_init(&cache_stats_lock, NULL);
#if DB_VERSION_MAJOR == 1
(void) pthread_mutex_init(&cache_lock, NULL);
#else /* DB_VERSION_MAJOR == 1 */
# ifdef PTHREAD_RWLOCK_INITIALIZER
(void) pthread_rwlock_init(&cache_rwlock, NULL);
# else /* PTHREAD_RWLOCK_INITIALIZER */
(void) pthread_mutex_init(&cache_lock, NULL);
(void) pthread_cond_init(&reader_go, NULL);
(void) pthread_cond_init(&writer_go, NULL);
r_wait = 0;
r_active = 0;
w_wait = 0;
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#endif /* DB_VERSION_MAJOR == 1 */
#if DB_VERSION_CHECK(3,0,0)
status = db_create(&cache, NULL, 0);
if (status == 0)
{
# if DB_VERSION_CHECK(4,0,0)
# if DB_VERSION_CHECK(4,2,0)
if (tmpdir != NULL && tmpdir[0] != '\0')
{
DB_ENV *env = NULL;
# if DB_VERSION_CHECK(4,3,0)
env = cache->get_env(cache);
# else /* DB_VERSION_CHECK(4,3,0) */
(void) cache->get_env(cache, &env);
# endif /* DB_VERISON_CHECK(4,3,0) */
if (env != NULL)
(void) env->set_tmp_dir(env, tmpdir);
}
# endif /* DB_VERISON_CHECK(4,2,0) */
status = cache->open(cache, NULL, NULL, NULL, DB_HASH,
(DB_CREATE|DB_THREAD), DB_MODE);
# else /* DB_VERSION_CHECK(4,0,0) */
status = cache->open(cache, NULL, NULL, DB_HASH,
(DB_CREATE|DB_THREAD), DB_MODE);
# endif /* DB_VERSION_CHECK(4,0,0) */
}
#elif DB_VERSION_CHECK(2,0,0)
status = db_open(NULL, DB_HASH, (DB_CREATE|DB_THREAD), DB_MODE,
NULL, NULL, &cache);
#else /* ! DB_VERSION_CHECK(2,0,0) */
cache = dbopen(NULL, (O_CREAT|O_RDWR), DB_MODE, DB_HASH, NULL);
if (cache == NULL)
status = errno;
#endif /* DB_VERSION_CHECK */
if (status != 0)
{
if (err != NULL)
*err = status;
return NULL;
}
#ifdef DEBUG
printf("libdkim cache initialized\n");
#endif /* DEBUG */
return cache;
}
/*
** DKIM_CACHE_QUERY -- query an on-disk cache of entries
**
** Parameters:
** db -- DB handle referring to the cache
** str -- key to query
** ttl -- time-to-live; ignore any record older than this; if 0, apply
** the TTL in the record
** buf -- buffer into which to write any cached data found
** buflen -- number of bytes at "buffer" (returned); caller should set
** this to the maximum space available and use the returned
** value as the length of the data returned
** err -- error code (returned)
**
** Return value:
** -1 -- error; caller should check "err"
** 0 -- no error; record found and data returned
** 1 -- no data found or data has expired
*/
int
dkim_cache_query(DB *db, char *str, int ttl, char *buf, size_t *buflen,
int *err)
{
int status;
time_t now;
DBT q;
DBT d;
struct dkim_cache_entry ce;
assert(db != NULL);
assert(str != NULL);
assert(buf != NULL);
assert(err != NULL);
memset(&q, '\0', sizeof q);
memset(&d, '\0', sizeof d);
q.data = str;
q.size = strlen(q.data);
#ifdef DEBUG
printf("libdkim cache query for `%s'\n", str);
#endif /* DEBUG */
#if DB_VERSION_CHECK(3,0,0)
d.flags = DB_DBT_USERMEM;
d.data = (void *) &ce;
d.ulen = sizeof ce;
#endif /* DB_VERSION_CHECK(3,0,0) */
(void) time(&now);
pthread_mutex_lock(&cache_stats_lock);
c_queries++;
pthread_mutex_unlock(&cache_stats_lock);
dkim_cache_read_lock();
#if DB_VERSION_CHECK(2,0,0)
status = db->get(db, NULL, &q, &d, 0);
#else /* DB_VERSION_CHECK(2,0,0) */
status = db->get(db, &q, &d, 0);
#endif /* DB_VERSION_CHECK(2,0,0) */
dkim_cache_read_unlock();
if (status == 0)
{
#if !DB_VERSION_CHECK(2,0,0)
memset(&ce, '\0', sizeof ce);
memcpy(&ce, d.data, MIN(sizeof ce, d.size));
#endif /* ! DB_VERSION_CHECK(2,0,0) */
if (ttl != 0)
ce.cache_ttl = ttl;
if (ce.cache_when + ce.cache_ttl < now)
{
#ifdef DEBUG
printf("libdkim cache query for `%s' expired\n", str);
#endif /* DEBUG */
pthread_mutex_lock(&cache_stats_lock);
c_expired++;
pthread_mutex_unlock(&cache_stats_lock);
return 1;
}
pthread_mutex_lock(&cache_stats_lock);
c_hits++;
pthread_mutex_unlock(&cache_stats_lock);
sm_strlcpy(buf, ce.cache_data, *buflen);
*buflen = strlen(ce.cache_data);
#ifdef DEBUG
printf("libdkim cache query for `%s' found: `%s'\n", str, buf);
#endif /* DEBUG */
return 0;
}
else if (status != DB_NOTFOUND)
{
*err = status;
#ifdef DEBUG
printf("libdkim cache query for `%s' error\n");
#endif /* DEBUG */
return -1;
}
else
{
#ifdef DEBUG
printf("libdkim cache query for `%s' not found\n", str);
#endif /* DEBUG */
return 1;
}
}
/*
** DKIM_CACHE_INSERT -- insert data into an on-disk cache of entries
**
** Parameters:
** db -- DB handle referring to the cache
** str -- key to insert
** data -- data to insert
** ttl -- time-to-live
** err -- error code (returned)
**
** Return value:
** -1 -- error; caller should check "err"
** 0 -- cache updated
*/
int
dkim_cache_insert(DB *db, char *str, char *data, int ttl, int *err)
{
int status;
time_t now;
DBT q;
DBT d;
struct dkim_cache_entry ce;
assert(db != NULL);
assert(str != NULL);
assert(data != NULL);
assert(err != NULL);
(void) time(&now);
memset(&q, '\0', sizeof q);
memset(&d, '\0', sizeof d);
q.data = str;
q.size = strlen(str);
d.data = (void *) &ce;
d.size = sizeof ce;
ce.cache_when = now;
ce.cache_ttl = ttl;
sm_strlcpy(ce.cache_data, data, sizeof ce.cache_data);
dkim_cache_write_lock();
#if DB_VERSION_CHECK(2,0,0)
status = db->put(db, NULL, &q, &d, 0);
#else /* DB_VERSION_CHECK(2,0,0) */
status = db->put(db, &q, &d, 0);
#endif /* DB_VERSION_CHECK(2,0,0) */
dkim_cache_write_unlock();
if (status == 0)
{
#ifdef DEBUG
printf("libdkim cache insert for `%s': `%s'\n", str, data);
#endif /* DEBUG */
return 0;
}
else
{
*err = status;
#ifdef DEBUG
printf("libdkim cache insert for `%s' error\n");
#endif /* DEBUG */
return -1;
}
}
/*
** DKIM_CACHE_EXPIRE -- expire records in an on-disk cache of entries
**
** Parameters:
** db -- DB handle referring to the cache
** ttl -- time-to-live; delete any record older than this; if 0, apply
** the TTL in the record
** err -- error code (returned)
**
** Return value:
** -1 -- error; caller should check "err"
** otherwise -- count of deleted records
*/
int
dkim_cache_expire(DB *db, int ttl, int *err)
{
#if !DB_VERSION_CHECK(2,0,0)
bool first = TRUE;
#endif /* ! DB_VERSION_CHECK(2,0,0) */
bool delete;
int deleted = 0;
int status;
time_t now;
#if DB_VERSION_CHECK(2,0,0)
DBC *dbc;
#endif /* DB_VERSION_CHECK(2,0,0) */
DBT q;
DBT d;
char name[DKIM_MAXHOSTNAMELEN + 1];
struct dkim_cache_entry ce;
assert(db != NULL);
assert(err != NULL);
memset(&q, '\0', sizeof q);
memset(&d, '\0', sizeof d);
(void) time(&now);
dkim_cache_write_lock();
#if DB_VERSION_CHECK(2,0,0)
status = db->cursor(db, NULL, &dbc, 0);
if (status != 0)
{
*err = status;
dkim_cache_write_unlock();
return -1;
}
#endif /* DB_VERSION_CHECK(2,0,0) */
for (;;)
{
memset(name, '\0', sizeof name);
memset(&ce, '\0', sizeof ce);
#if DB_VERSION_CHECK(3,0,0)
q.data = name;
q.flags = DB_DBT_USERMEM;
q.ulen = sizeof name;
#endif /* DB_VERSION_CHECK(3,0,0) */
#if DB_VERSION_CHECK(3,0,0)
d.data = (void *) &ce;
d.flags = DB_DBT_USERMEM;
d.ulen = sizeof ce;
#endif /* DB_VERSION_CHECK(3,0,0) */
#if DB_VERSION_CHECK(2,0,0)
status = dbc->c_get(dbc, &q, &d, DB_NEXT);
if (status == DB_NOTFOUND)
{
break;
}
else if (status != 0)
{
*err = status;
break;
}
#else /* DB_VERSION_CHECK(2,0,0) */
status = db->seq(db, &q, &d, first ? R_FIRST : R_NEXT);
if (status == DB_NOTFOUND)
{
break;
}
else if (status != 0)
{
*err = status;
break;
}
first = FALSE;
memcpy(name, q.data, MIN(sizeof name, q.size));
memcpy((void *) &ce, d.data, MIN(sizeof ce, d.size));
#endif /* DB_VERSION_CHECK(2,0,0) */
delete = FALSE;
if (ttl == 0)
{
if (ce.cache_when + ce.cache_ttl < now)
delete = TRUE;
}
else
{
if (ce.cache_when + ttl < now)
delete = TRUE;
}
if (delete)
{
#if DB_VERSION_CHECK(2,0,0)
status = dbc->c_del(dbc, 0);
#else /* DB_VERSION_CHECK(2,0,0) */
status = db->del(db, &q, R_CURSOR);
#endif /* DB_VERSION_CHECK(2,0,0) */
if (status != 0)
{
*err = status;
deleted = -1;
break;
}
deleted++;
}
}
#if DB_VERSION_CHECK(2,0,0)
(void) dbc->c_close(dbc);
#endif /* DB_VERSION_CHECK(2,0,0) */
dkim_cache_write_unlock();
return deleted;
}
/*
** DKIM_CACHE_CLOSE -- close a cache database
**
** Parameters:
** db -- cache DB handle
**
** Return value:
** None.
*/
void
dkim_cache_close(DB *db)
{
assert(db != NULL);
#if DB_VERSION_CHECK(2,0,0)
(void) db->close(db, 0);
#else /* DB_VERSION_CHECK(2,0,0) */
(void) db->close(db);
#endif /* DB_VERSION_CHECK(2,0,0) */
#if DB_VERSION_MAJOR == 1
(void) pthread_mutex_destroy(&cache_lock);
#else /* DB_VERSION_MAJOR == 1 */
# ifdef PTHREAD_RWLOCK_INITIALIZER
(void) pthread_rwlock_destroy(&cache_rwlock);
# else /* PTHREAD_RWLOCK_INITIALIZER */
(void) pthread_mutex_destroy(&cache_lock);
(void) pthread_cond_destroy(&reader_go);
(void) pthread_cond_destroy(&writer_go);
# endif /* PTHREAD_RWLOCK_INITIALIZER */
#endif /* DB_VERSION_MAJOR == 1 */
}
/*
** DKIM_CACHE_STATS -- retrieve cache performance statistics
**
** Parameters:
** queries -- number of queries handled (returned)
** hits -- number of cache hits (returned)
** expired -- number of expired hits (returned)
**
** Return value:
** None.
**
** Notes:
** Any of the parameters may be NULL if the corresponding datum
** is not of interest.
*/
void
dkim_cache_stats(u_int *queries, u_int *hits, u_int *expired)
{
pthread_mutex_lock(&cache_stats_lock);
if (queries != NULL)
*queries = c_queries;
if (hits != NULL)
*hits = c_hits;
if (expired != NULL)
*expired = c_expired;
pthread_mutex_unlock(&cache_stats_lock);
}
#endif /* QUERY_CACHE */
syntax highlighted by Code2HTML, v. 0.9.1