#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 memcache_t {
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 svn_error_t *
build_key(const char **mc_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,
(char *)NULL);
long_key_len = strlen(long_key);
if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN)
{
svn_checksum_t *checksum;
SVN_ERR(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),
(char *)NULL);
}
*mc_key = long_key;
return SVN_NO_ERROR;
}
static svn_error_t *
memcache_internal_get(char **data,
apr_size_t *size,
svn_boolean_t *found,
void *cache_void,
const void *key,
apr_pool_t *pool)
{
memcache_t *cache = cache_void;
apr_status_t apr_err;
const char *mc_key;
apr_pool_t *subpool = svn_pool_create(pool);
SVN_ERR(build_key(&mc_key, cache, key, subpool));
apr_err = apr_memcache_getp(cache->memcache,
pool,
mc_key,
data,
size,
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"));
*found = TRUE;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
memcache_get(void **value_p,
svn_boolean_t *found,
void *cache_void,
const void *key,
apr_pool_t *result_pool)
{
memcache_t *cache = cache_void;
char *data;
apr_size_t data_len;
SVN_ERR(memcache_internal_get(&data,
&data_len,
found,
cache_void,
key,
result_pool));
if (*found)
{
if (cache->deserialize_func)
{
SVN_ERR((cache->deserialize_func)(value_p, data, data_len,
result_pool));
}
else
{
svn_string_t *value = apr_pcalloc(result_pool, sizeof(*value));
value->data = data;
value->len = data_len;
*value_p = value;
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
memcache_internal_set(void *cache_void,
const void *key,
const char *data,
apr_size_t len,
apr_pool_t *scratch_pool)
{
memcache_t *cache = cache_void;
const char *mc_key;
apr_status_t apr_err;
SVN_ERR(build_key(&mc_key, cache, key, scratch_pool));
apr_err = apr_memcache_set(cache->memcache, mc_key, (char *)data, len, 0, 0);
if (apr_err != APR_SUCCESS)
return svn_error_wrap_apr(apr_err,
_("Unknown memcached error while writing"));
return SVN_NO_ERROR;
}
static svn_error_t *
memcache_set(void *cache_void,
const void *key,
void *value,
apr_pool_t *scratch_pool)
{
memcache_t *cache = cache_void;
apr_pool_t *subpool = svn_pool_create(scratch_pool);
char *data;
apr_size_t data_len;
svn_error_t *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;
}
err = memcache_internal_set(cache_void, key, data, data_len, subpool);
svn_pool_destroy(subpool);
return err;
}
static svn_error_t *
memcache_get_partial(void **value_p,
svn_boolean_t *found,
void *cache_void,
const void *key,
svn_cache__partial_getter_func_t func,
void *baton,
apr_pool_t *result_pool)
{
svn_error_t *err = SVN_NO_ERROR;
char *data;
apr_size_t size;
SVN_ERR(memcache_internal_get(&data,
&size,
found,
cache_void,
key,
result_pool));
return *found
? func(value_p, data, size, baton, result_pool)
: err;
}
static svn_error_t *
memcache_set_partial(void *cache_void,
const void *key,
svn_cache__partial_setter_func_t func,
void *baton,
apr_pool_t *scratch_pool)
{
svn_error_t *err = SVN_NO_ERROR;
char *data;
apr_size_t size;
svn_boolean_t found = FALSE;
apr_pool_t *subpool = svn_pool_create(scratch_pool);
SVN_ERR(memcache_internal_get(&data,
&size,
&found,
cache_void,
key,
subpool));
if (found)
{
SVN_ERR(func(&data, &size, baton, subpool));
err = memcache_internal_set(cache_void, key, data, size, subpool);
}
svn_pool_destroy(subpool);
return err;
}
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 *scratch_pool)
{
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Can't iterate a memcached cache"));
}
static svn_boolean_t
memcache_is_cachable(void *unused, apr_size_t size)
{
(void)unused;
return size < 1000000;
}
static svn_error_t *
memcache_get_info(void *cache_void,
svn_cache__info_t *info,
svn_boolean_t reset,
apr_pool_t *result_pool)
{
memcache_t *cache = cache_void;
info->id = apr_pstrdup(result_pool, cache->prefix);
info->used_size = 0;
info->total_size = 0;
info->data_size = 0;
info->used_entries = 0;
info->total_entries = 0;
return SVN_NO_ERROR;
}
static svn_cache__vtable_t memcache_vtable = {
memcache_get,
memcache_set,
memcache_iter,
memcache_is_cachable,
memcache_get_partial,
memcache_set_partial,
memcache_get_info
};
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,
apr_time_from_sec(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
}