#include <apr_md5.h>
#include "svn_pools.h"
#include "svn_base64.h"
#include "svn_path.h"
#include "svn_private_config.h"
#include "private/svn_cache.h"
#include "cache.h"
#ifdef SVN_HAVE_MEMCACHE
#include <apr_memcache.h>
typedef struct {
apr_memcache_t *memcache;
const char *prefix;
apr_ssize_t klen;
svn_cache__serialize_func_t serialize_func;
svn_cache__deserialize_func_t deserialize_func;
} memcache_t;
struct svn_memcache_t {
apr_memcache_t *c;
};
#define MAX_MEMCACHED_KEY_LEN 249
#define MEMCACHED_KEY_UNHASHED_LEN (MAX_MEMCACHED_KEY_LEN - \
2 * APR_MD5_DIGESTSIZE)
static const char *
build_key(memcache_t *cache,
const void *raw_key,
apr_pool_t *pool)
{
const char *encoded_suffix;
const char *long_key;
apr_size_t long_key_len;
if (cache->klen == APR_HASH_KEY_STRING)
encoded_suffix = svn_path_uri_encode(raw_key, pool);
else
{
const svn_string_t *raw = svn_string_ncreate(raw_key, cache->klen, pool);
const svn_string_t *encoded = svn_base64_encode_string2(raw, FALSE,
pool);
encoded_suffix = encoded->data;
}
long_key = apr_pstrcat(pool, "SVN:", cache->prefix, ":", encoded_suffix,
NULL);
long_key_len = strlen(long_key);
if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN)
{
svn_checksum_t *checksum;
svn_checksum(&checksum, svn_checksum_md5, long_key, long_key_len, pool);
long_key = apr_pstrcat(pool,
apr_pstrmemdup(pool, long_key,
MEMCACHED_KEY_UNHASHED_LEN),
svn_checksum_to_cstring_display(checksum, pool),
NULL);
}
return long_key;
}
static svn_error_t *
memcache_get(void **value_p,
svn_boolean_t *found,
void *cache_void,
const void *key,
apr_pool_t *pool)
{
memcache_t *cache = cache_void;
apr_status_t apr_err;
char *data;
const char *mc_key;
apr_size_t data_len;
apr_pool_t *subpool = svn_pool_create(pool);
mc_key = build_key(cache, key, subpool);
apr_err = apr_memcache_getp(cache->memcache,
(cache->deserialize_func ? subpool : pool),
mc_key,
&data,
&data_len,
NULL );
if (apr_err == APR_NOTFOUND)
{
*found = FALSE;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
else if (apr_err != APR_SUCCESS || !data)
return svn_error_wrap_apr(apr_err,
_("Unknown memcached error while reading"));
if (cache->deserialize_func)
{
SVN_ERR((cache->deserialize_func)(value_p, data, data_len, pool));
}
else
{
svn_string_t *value = apr_pcalloc(pool, sizeof(*value));
value->data = data;
value->len = data_len;
*value_p = value;
}
*found = TRUE;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
memcache_set(void *cache_void,
const void *key,
void *value,
apr_pool_t *pool)
{
memcache_t *cache = cache_void;
apr_pool_t *subpool = svn_pool_create(pool);
char *data;
const char *mc_key = build_key(cache, key, subpool);
apr_size_t data_len;
apr_status_t apr_err;
if (cache->serialize_func)
{
SVN_ERR((cache->serialize_func)(&data, &data_len, value, subpool));
}
else
{
svn_stringbuf_t *value_str = value;
data = value_str->data;
data_len = value_str->len;
}
apr_err = apr_memcache_set(cache->memcache, mc_key, data, data_len, 0, 0);
if (apr_err != APR_SUCCESS)
return svn_error_wrap_apr(apr_err,
_("Unknown memcached error while writing"));
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
memcache_iter(svn_boolean_t *completed,
void *cache_void,
svn_iter_apr_hash_cb_t user_cb,
void *user_baton,
apr_pool_t *pool)
{
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Can't iterate a memcached cache"));
}
static svn_cache__vtable_t memcache_vtable = {
memcache_get,
memcache_set,
memcache_iter
};
svn_error_t *
svn_cache__create_memcache(svn_cache__t **cache_p,
svn_memcache_t *memcache,
svn_cache__serialize_func_t serialize_func,
svn_cache__deserialize_func_t deserialize_func,
apr_ssize_t klen,
const char *prefix,
apr_pool_t *pool)
{
svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
memcache_t *cache = apr_pcalloc(pool, sizeof(*cache));
cache->serialize_func = serialize_func;
cache->deserialize_func = deserialize_func;
cache->klen = klen;
cache->prefix = svn_path_uri_encode(prefix, pool);
cache->memcache = memcache->c;
wrapper->vtable = &memcache_vtable;
wrapper->cache_internal = cache;
wrapper->error_handler = 0;
wrapper->error_baton = 0;
*cache_p = wrapper;
return SVN_NO_ERROR;
}
struct ams_baton {
apr_memcache_t *memcache;
apr_pool_t *memcache_pool;
svn_error_t *err;
};
static svn_boolean_t
add_memcache_server(const char *name,
const char *value,
void *baton,
apr_pool_t *pool)
{
struct ams_baton *b = baton;
char *host, *scope;
apr_port_t port;
apr_status_t apr_err;
apr_memcache_server_t *server;
apr_err = apr_parse_addr_port(&host, &scope, &port,
value, pool);
if (apr_err != APR_SUCCESS)
{
b->err = svn_error_wrap_apr(apr_err,
_("Error parsing memcache server '%s'"),
name);
return FALSE;
}
if (scope)
{
b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL,
_("Scope not allowed in memcache server "
"'%s'"),
name);
return FALSE;
}
if (!host || !port)
{
b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL,
_("Must specify host and port for memcache "
"server '%s'"),
name);
return FALSE;
}
apr_err = apr_memcache_server_create(b->memcache_pool,
host,
port,
0,
5,
10,
50,
&server);
if (apr_err != APR_SUCCESS)
{
b->err = svn_error_wrap_apr(apr_err,
_("Unknown error creating memcache server"));
return FALSE;
}
apr_err = apr_memcache_add_server(b->memcache, server);
if (apr_err != APR_SUCCESS)
{
b->err = svn_error_wrap_apr(apr_err,
_("Unknown error adding server to memcache"));
return FALSE;
}
return TRUE;
}
#else
struct svn_memcache_t {
void *unused;
};
svn_error_t *
svn_cache__create_memcache(svn_cache__t **cache_p,
svn_memcache_t *memcache,
svn_cache__serialize_func_t serialize_func,
svn_cache__deserialize_func_t deserialize_func,
apr_ssize_t klen,
const char *prefix,
apr_pool_t *pool)
{
return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL);
}
#endif
static svn_boolean_t
nop_enumerator(const char *name,
const char *value,
void *baton,
apr_pool_t *pool)
{
return TRUE;
}
svn_error_t *
svn_cache__make_memcache_from_config(svn_memcache_t **memcache_p,
svn_config_t *config,
apr_pool_t *pool)
{
apr_uint16_t server_count;
apr_pool_t *subpool = svn_pool_create(pool);
server_count =
svn_config_enumerate2(config,
SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
nop_enumerator, NULL, subpool);
if (server_count == 0)
{
*memcache_p = NULL;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
#ifdef SVN_HAVE_MEMCACHE
{
struct ams_baton b;
svn_memcache_t *memcache = apr_pcalloc(pool, sizeof(*memcache));
apr_status_t apr_err = apr_memcache_create(pool,
server_count,
0,
&(memcache->c));
if (apr_err != APR_SUCCESS)
return svn_error_wrap_apr(apr_err,
_("Unknown error creating apr_memcache_t"));
b.memcache = memcache->c;
b.memcache_pool = pool;
b.err = SVN_NO_ERROR;
svn_config_enumerate2(config,
SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
add_memcache_server, &b,
subpool);
if (b.err)
return b.err;
*memcache_p = memcache;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
#else
{
return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL);
}
#endif
}