HardenedBSD/usr.sbin/nscd/cachelib.c
Stefan Eßer a397989d4e Add the possibility to specify a threshold for the number of negative cache
results required to have the cache return lookup failure.

A new configuration parameter is introduced, which must be set to a value
greater than 1 to activate this feature. The default behavior is unchanged.

The purpose of this change is to allow probes for the existence of an entry
(which are expected to fail), before that entry is added to one of the
queried databases, without the cache returning the stale information from
the probe query until that cache entry expires. If, for example, a new user
account is created after checking that the new account name is available,
the negative cache entry would prevent immediate access to the account.

For that example, the new configuration option

negative-confidence-threshold passwd 2

will require a second negative query result to consider the negative cache
entry for a passwd entry valid, but if the user account has been created
between the queries, then the positive query result from the second query
will be cached and returned.
2012-07-04 09:02:12 +00:00

1245 lines
34 KiB
C

/*-
* Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/time.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "cachelib.h"
#include "debug.h"
#define INITIAL_ENTRIES_CAPACITY 32
#define ENTRIES_CAPACITY_STEP 32
#define STRING_SIMPLE_HASH_BODY(in_var, var, a, M) \
for ((var) = 0; *(in_var) != '\0'; ++(in_var)) \
(var) = ((a)*(var) + *(in_var)) % (M)
#define STRING_SIMPLE_MP2_HASH_BODY(in_var, var, a, M) \
for ((var) = 0; *(in_var) != 0; ++(in_var)) \
(var) = ((a)*(var) + *(in_var)) & (M - 1)
static int cache_elemsize_common_continue_func(struct cache_common_entry_ *,
struct cache_policy_item_ *);
static int cache_lifetime_common_continue_func(struct cache_common_entry_ *,
struct cache_policy_item_ *);
static void clear_cache_entry(struct cache_entry_ *);
static void destroy_cache_entry(struct cache_entry_ *);
static void destroy_cache_mp_read_session(struct cache_mp_read_session_ *);
static void destroy_cache_mp_write_session(struct cache_mp_write_session_ *);
static int entries_bsearch_cmp_func(const void *, const void *);
static int entries_qsort_cmp_func(const void *, const void *);
static struct cache_entry_ ** find_cache_entry_p(struct cache_ *,
const char *);
static void flush_cache_entry(struct cache_entry_ *);
static void flush_cache_policy(struct cache_common_entry_ *,
struct cache_policy_ *, struct cache_policy_ *,
int (*)(struct cache_common_entry_ *,
struct cache_policy_item_ *));
static int ht_items_cmp_func(const void *, const void *);
static int ht_items_fixed_size_left_cmp_func(const void *, const void *);
static hashtable_index_t ht_item_hash_func(const void *, size_t);
/*
* Hashing and comparing routines, that are used with the hash tables
*/
static int
ht_items_cmp_func(const void *p1, const void *p2)
{
struct cache_ht_item_data_ *hp1, *hp2;
size_t min_size;
int result;
hp1 = (struct cache_ht_item_data_ *)p1;
hp2 = (struct cache_ht_item_data_ *)p2;
assert(hp1->key != NULL);
assert(hp2->key != NULL);
if (hp1->key_size != hp2->key_size) {
min_size = (hp1->key_size < hp2->key_size) ? hp1->key_size :
hp2->key_size;
result = memcmp(hp1->key, hp2->key, min_size);
if (result == 0)
return ((hp1->key_size < hp2->key_size) ? -1 : 1);
else
return (result);
} else
return (memcmp(hp1->key, hp2->key, hp1->key_size));
}
static int
ht_items_fixed_size_left_cmp_func(const void *p1, const void *p2)
{
struct cache_ht_item_data_ *hp1, *hp2;
size_t min_size;
int result;
hp1 = (struct cache_ht_item_data_ *)p1;
hp2 = (struct cache_ht_item_data_ *)p2;
assert(hp1->key != NULL);
assert(hp2->key != NULL);
if (hp1->key_size != hp2->key_size) {
min_size = (hp1->key_size < hp2->key_size) ? hp1->key_size :
hp2->key_size;
result = memcmp(hp1->key, hp2->key, min_size);
if (result == 0)
if (min_size == hp1->key_size)
return (0);
else
return ((hp1->key_size < hp2->key_size) ? -1 : 1);
else
return (result);
} else
return (memcmp(hp1->key, hp2->key, hp1->key_size));
}
static hashtable_index_t
ht_item_hash_func(const void *p, size_t cache_entries_size)
{
struct cache_ht_item_data_ *hp;
size_t i;
hashtable_index_t retval;
hp = (struct cache_ht_item_data_ *)p;
assert(hp->key != NULL);
retval = 0;
for (i = 0; i < hp->key_size; ++i)
retval = (127 * retval + (unsigned char)hp->key[i]) %
cache_entries_size;
return retval;
}
HASHTABLE_PROTOTYPE(cache_ht_, cache_ht_item_, struct cache_ht_item_data_);
HASHTABLE_GENERATE(cache_ht_, cache_ht_item_, struct cache_ht_item_data_, data,
ht_item_hash_func, ht_items_cmp_func);
/*
* Routines to sort and search the entries by name
*/
static int
entries_bsearch_cmp_func(const void *key, const void *ent)
{
assert(key != NULL);
assert(ent != NULL);
return (strcmp((char const *)key,
(*(struct cache_entry_ const **)ent)->name));
}
static int
entries_qsort_cmp_func(const void *e1, const void *e2)
{
assert(e1 != NULL);
assert(e2 != NULL);
return (strcmp((*(struct cache_entry_ const **)e1)->name,
(*(struct cache_entry_ const **)e2)->name));
}
static struct cache_entry_ **
find_cache_entry_p(struct cache_ *the_cache, const char *entry_name)
{
return ((struct cache_entry_ **)(bsearch(entry_name, the_cache->entries,
the_cache->entries_size, sizeof(struct cache_entry_ *),
entries_bsearch_cmp_func)));
}
static void
destroy_cache_mp_write_session(struct cache_mp_write_session_ *ws)
{
struct cache_mp_data_item_ *data_item;
TRACE_IN(destroy_cache_mp_write_session);
assert(ws != NULL);
while (!TAILQ_EMPTY(&ws->items)) {
data_item = TAILQ_FIRST(&ws->items);
TAILQ_REMOVE(&ws->items, data_item, entries);
free(data_item->value);
free(data_item);
}
free(ws);
TRACE_OUT(destroy_cache_mp_write_session);
}
static void
destroy_cache_mp_read_session(struct cache_mp_read_session_ *rs)
{
TRACE_IN(destroy_cache_mp_read_session);
assert(rs != NULL);
free(rs);
TRACE_OUT(destroy_cache_mp_read_session);
}
static void
destroy_cache_entry(struct cache_entry_ *entry)
{
struct cache_common_entry_ *common_entry;
struct cache_mp_entry_ *mp_entry;
struct cache_mp_read_session_ *rs;
struct cache_mp_write_session_ *ws;
struct cache_ht_item_ *ht_item;
struct cache_ht_item_data_ *ht_item_data;
TRACE_IN(destroy_cache_entry);
assert(entry != NULL);
if (entry->params->entry_type == CET_COMMON) {
common_entry = (struct cache_common_entry_ *)entry;
HASHTABLE_FOREACH(&(common_entry->items), ht_item) {
HASHTABLE_ENTRY_FOREACH(ht_item, data, ht_item_data)
{
free(ht_item_data->key);
free(ht_item_data->value);
}
HASHTABLE_ENTRY_CLEAR(ht_item, data);
}
HASHTABLE_DESTROY(&(common_entry->items), data);
/* FIFO policy is always first */
destroy_cache_fifo_policy(common_entry->policies[0]);
switch (common_entry->common_params.policy) {
case CPT_LRU:
destroy_cache_lru_policy(common_entry->policies[1]);
break;
case CPT_LFU:
destroy_cache_lfu_policy(common_entry->policies[1]);
break;
default:
break;
}
free(common_entry->policies);
} else {
mp_entry = (struct cache_mp_entry_ *)entry;
while (!TAILQ_EMPTY(&mp_entry->ws_head)) {
ws = TAILQ_FIRST(&mp_entry->ws_head);
TAILQ_REMOVE(&mp_entry->ws_head, ws, entries);
destroy_cache_mp_write_session(ws);
}
while (!TAILQ_EMPTY(&mp_entry->rs_head)) {
rs = TAILQ_FIRST(&mp_entry->rs_head);
TAILQ_REMOVE(&mp_entry->rs_head, rs, entries);
destroy_cache_mp_read_session(rs);
}
if (mp_entry->completed_write_session != NULL)
destroy_cache_mp_write_session(
mp_entry->completed_write_session);
if (mp_entry->pending_write_session != NULL)
destroy_cache_mp_write_session(
mp_entry->pending_write_session);
}
free(entry->name);
free(entry);
TRACE_OUT(destroy_cache_entry);
}
static void
clear_cache_entry(struct cache_entry_ *entry)
{
struct cache_mp_entry_ *mp_entry;
struct cache_common_entry_ *common_entry;
struct cache_ht_item_ *ht_item;
struct cache_ht_item_data_ *ht_item_data;
struct cache_policy_ *policy;
struct cache_policy_item_ *item, *next_item;
size_t entry_size;
unsigned int i;
if (entry->params->entry_type == CET_COMMON) {
common_entry = (struct cache_common_entry_ *)entry;
entry_size = 0;
HASHTABLE_FOREACH(&(common_entry->items), ht_item) {
HASHTABLE_ENTRY_FOREACH(ht_item, data, ht_item_data)
{
free(ht_item_data->key);
free(ht_item_data->value);
}
entry_size += HASHTABLE_ENTRY_SIZE(ht_item, data);
HASHTABLE_ENTRY_CLEAR(ht_item, data);
}
common_entry->items_size -= entry_size;
for (i = 0; i < common_entry->policies_size; ++i) {
policy = common_entry->policies[i];
next_item = NULL;
item = policy->get_first_item_func(policy);
while (item != NULL) {
next_item = policy->get_next_item_func(policy,
item);
policy->remove_item_func(policy, item);
policy->destroy_item_func(item);
item = next_item;
}
}
} else {
mp_entry = (struct cache_mp_entry_ *)entry;
if (mp_entry->rs_size == 0) {
if (mp_entry->completed_write_session != NULL) {
destroy_cache_mp_write_session(
mp_entry->completed_write_session);
mp_entry->completed_write_session = NULL;
}
memset(&mp_entry->creation_time, 0,
sizeof(struct timeval));
memset(&mp_entry->last_request_time, 0,
sizeof(struct timeval));
}
}
}
/*
* When passed to the flush_cache_policy, ensures that all old elements are
* deleted.
*/
static int
cache_lifetime_common_continue_func(struct cache_common_entry_ *entry,
struct cache_policy_item_ *item)
{
return ((item->last_request_time.tv_sec - item->creation_time.tv_sec >
entry->common_params.max_lifetime.tv_sec) ? 1: 0);
}
/*
* When passed to the flush_cache_policy, ensures that all elements, that
* exceed the size limit, are deleted.
*/
static int
cache_elemsize_common_continue_func(struct cache_common_entry_ *entry,
struct cache_policy_item_ *item)
{
return ((entry->items_size > entry->common_params.satisf_elemsize) ? 1
: 0);
}
/*
* Removes the elements from the cache entry, while the continue_func returns 1.
*/
static void
flush_cache_policy(struct cache_common_entry_ *entry,
struct cache_policy_ *policy,
struct cache_policy_ *connected_policy,
int (*continue_func)(struct cache_common_entry_ *,
struct cache_policy_item_ *))
{
struct cache_policy_item_ *item, *next_item, *connected_item;
struct cache_ht_item_ *ht_item;
struct cache_ht_item_data_ *ht_item_data, ht_key;
hashtable_index_t hash;
assert(policy != NULL);
next_item = NULL;
item = policy->get_first_item_func(policy);
while ((item != NULL) && (continue_func(entry, item) == 1)) {
next_item = policy->get_next_item_func(policy, item);
connected_item = item->connected_item;
policy->remove_item_func(policy, item);
memset(&ht_key, 0, sizeof(struct cache_ht_item_data_));
ht_key.key = item->key;
ht_key.key_size = item->key_size;
hash = HASHTABLE_CALCULATE_HASH(cache_ht_, &entry->items,
&ht_key);
assert(hash < HASHTABLE_ENTRIES_COUNT(&entry->items));
ht_item = HASHTABLE_GET_ENTRY(&(entry->items), hash);
ht_item_data = HASHTABLE_ENTRY_FIND(cache_ht_, ht_item,
&ht_key);
assert(ht_item_data != NULL);
free(ht_item_data->key);
free(ht_item_data->value);
HASHTABLE_ENTRY_REMOVE(cache_ht_, ht_item, ht_item_data);
--entry->items_size;
policy->destroy_item_func(item);
if (connected_item != NULL) {
connected_policy->remove_item_func(connected_policy,
connected_item);
connected_policy->destroy_item_func(connected_item);
}
item = next_item;
}
}
static void
flush_cache_entry(struct cache_entry_ *entry)
{
struct cache_mp_entry_ *mp_entry;
struct cache_common_entry_ *common_entry;
struct cache_policy_ *policy, *connected_policy;
connected_policy = NULL;
if (entry->params->entry_type == CET_COMMON) {
common_entry = (struct cache_common_entry_ *)entry;
if ((common_entry->common_params.max_lifetime.tv_sec != 0) ||
(common_entry->common_params.max_lifetime.tv_usec != 0)) {
policy = common_entry->policies[0];
if (common_entry->policies_size > 1)
connected_policy = common_entry->policies[1];
flush_cache_policy(common_entry, policy,
connected_policy,
cache_lifetime_common_continue_func);
}
if ((common_entry->common_params.max_elemsize != 0) &&
common_entry->items_size >
common_entry->common_params.max_elemsize) {
if (common_entry->policies_size > 1) {
policy = common_entry->policies[1];
connected_policy = common_entry->policies[0];
} else {
policy = common_entry->policies[0];
connected_policy = NULL;
}
flush_cache_policy(common_entry, policy,
connected_policy,
cache_elemsize_common_continue_func);
}
} else {
mp_entry = (struct cache_mp_entry_ *)entry;
if ((mp_entry->mp_params.max_lifetime.tv_sec != 0)
|| (mp_entry->mp_params.max_lifetime.tv_usec != 0)) {
if (mp_entry->last_request_time.tv_sec -
mp_entry->last_request_time.tv_sec >
mp_entry->mp_params.max_lifetime.tv_sec)
clear_cache_entry(entry);
}
}
}
struct cache_ *
init_cache(struct cache_params const *params)
{
struct cache_ *retval;
TRACE_IN(init_cache);
assert(params != NULL);
retval = calloc(1, sizeof(*retval));
assert(retval != NULL);
assert(params != NULL);
memcpy(&retval->params, params, sizeof(struct cache_params));
retval->entries = calloc(1,
sizeof(*retval->entries) * INITIAL_ENTRIES_CAPACITY);
assert(retval->entries != NULL);
retval->entries_capacity = INITIAL_ENTRIES_CAPACITY;
retval->entries_size = 0;
TRACE_OUT(init_cache);
return (retval);
}
void
destroy_cache(struct cache_ *the_cache)
{
TRACE_IN(destroy_cache);
assert(the_cache != NULL);
if (the_cache->entries != NULL) {
size_t i;
for (i = 0; i < the_cache->entries_size; ++i)
destroy_cache_entry(the_cache->entries[i]);
free(the_cache->entries);
}
free(the_cache);
TRACE_OUT(destroy_cache);
}
int
register_cache_entry(struct cache_ *the_cache,
struct cache_entry_params const *params)
{
int policies_size;
size_t entry_name_size;
struct cache_common_entry_ *new_common_entry;
struct cache_mp_entry_ *new_mp_entry;
TRACE_IN(register_cache_entry);
assert(the_cache != NULL);
if (find_cache_entry(the_cache, params->entry_name) != NULL) {
TRACE_OUT(register_cache_entry);
return (-1);
}
if (the_cache->entries_size == the_cache->entries_capacity) {
struct cache_entry_ **new_entries;
size_t new_capacity;
new_capacity = the_cache->entries_capacity +
ENTRIES_CAPACITY_STEP;
new_entries = calloc(1,
sizeof(*new_entries) * new_capacity);
assert(new_entries != NULL);
memcpy(new_entries, the_cache->entries,
sizeof(struct cache_entry_ *)
* the_cache->entries_size);
free(the_cache->entries);
the_cache->entries = new_entries;
}
entry_name_size = strlen(params->entry_name) + 1;
switch (params->entry_type)
{
case CET_COMMON:
new_common_entry = calloc(1,
sizeof(*new_common_entry));
assert(new_common_entry != NULL);
memcpy(&new_common_entry->common_params, params,
sizeof(struct common_cache_entry_params));
new_common_entry->params =
(struct cache_entry_params *)&new_common_entry->common_params;
new_common_entry->common_params.cep.entry_name = calloc(1,
entry_name_size);
assert(new_common_entry->common_params.cep.entry_name != NULL);
strlcpy(new_common_entry->common_params.cep.entry_name,
params->entry_name, entry_name_size);
new_common_entry->name =
new_common_entry->common_params.cep.entry_name;
HASHTABLE_INIT(&(new_common_entry->items),
struct cache_ht_item_data_, data,
new_common_entry->common_params.cache_entries_size);
if (new_common_entry->common_params.policy == CPT_FIFO)
policies_size = 1;
else
policies_size = 2;
new_common_entry->policies = calloc(1,
sizeof(*new_common_entry->policies) * policies_size);
assert(new_common_entry->policies != NULL);
new_common_entry->policies_size = policies_size;
new_common_entry->policies[0] = init_cache_fifo_policy();
if (policies_size > 1) {
switch (new_common_entry->common_params.policy) {
case CPT_LRU:
new_common_entry->policies[1] =
init_cache_lru_policy();
break;
case CPT_LFU:
new_common_entry->policies[1] =
init_cache_lfu_policy();
break;
default:
break;
}
}
new_common_entry->get_time_func =
the_cache->params.get_time_func;
the_cache->entries[the_cache->entries_size++] =
(struct cache_entry_ *)new_common_entry;
break;
case CET_MULTIPART:
new_mp_entry = calloc(1,
sizeof(*new_mp_entry));
assert(new_mp_entry != NULL);
memcpy(&new_mp_entry->mp_params, params,
sizeof(struct mp_cache_entry_params));
new_mp_entry->params =
(struct cache_entry_params *)&new_mp_entry->mp_params;
new_mp_entry->mp_params.cep.entry_name = calloc(1,
entry_name_size);
assert(new_mp_entry->mp_params.cep.entry_name != NULL);
strlcpy(new_mp_entry->mp_params.cep.entry_name, params->entry_name,
entry_name_size);
new_mp_entry->name = new_mp_entry->mp_params.cep.entry_name;
TAILQ_INIT(&new_mp_entry->ws_head);
TAILQ_INIT(&new_mp_entry->rs_head);
new_mp_entry->get_time_func = the_cache->params.get_time_func;
the_cache->entries[the_cache->entries_size++] =
(struct cache_entry_ *)new_mp_entry;
break;
}
qsort(the_cache->entries, the_cache->entries_size,
sizeof(struct cache_entry_ *), entries_qsort_cmp_func);
TRACE_OUT(register_cache_entry);
return (0);
}
int
unregister_cache_entry(struct cache_ *the_cache, const char *entry_name)
{
struct cache_entry_ **del_ent;
TRACE_IN(unregister_cache_entry);
assert(the_cache != NULL);
del_ent = find_cache_entry_p(the_cache, entry_name);
if (del_ent != NULL) {
destroy_cache_entry(*del_ent);
--the_cache->entries_size;
memmove(del_ent, del_ent + 1,
(&(the_cache->entries[--the_cache->entries_size]) -
del_ent) * sizeof(struct cache_entry_ *));
TRACE_OUT(unregister_cache_entry);
return (0);
} else {
TRACE_OUT(unregister_cache_entry);
return (-1);
}
}
struct cache_entry_ *
find_cache_entry(struct cache_ *the_cache, const char *entry_name)
{
struct cache_entry_ **result;
TRACE_IN(find_cache_entry);
result = find_cache_entry_p(the_cache, entry_name);
if (result == NULL) {
TRACE_OUT(find_cache_entry);
return (NULL);
} else {
TRACE_OUT(find_cache_entry);
return (*result);
}
}
/*
* Tries to read the element with the specified key from the cache. If the
* value_size is too small, it will be filled with the proper number, and
* the user will need to call cache_read again with the value buffer, that
* is large enough.
* Function returns 0 on success, -1 on error, and -2 if the value_size is too
* small.
*/
int
cache_read(struct cache_entry_ *entry, const char *key, size_t key_size,
char *value, size_t *value_size)
{
struct cache_common_entry_ *common_entry;
struct cache_ht_item_data_ item_data, *find_res;
struct cache_ht_item_ *item;
hashtable_index_t hash;
struct cache_policy_item_ *connected_item;
TRACE_IN(cache_read);
assert(entry != NULL);
assert(key != NULL);
assert(value_size != NULL);
assert(entry->params->entry_type == CET_COMMON);
common_entry = (struct cache_common_entry_ *)entry;
memset(&item_data, 0, sizeof(struct cache_ht_item_data_));
/* can't avoid the cast here */
item_data.key = (char *)key;
item_data.key_size = key_size;
hash = HASHTABLE_CALCULATE_HASH(cache_ht_, &common_entry->items,
&item_data);
assert(hash < HASHTABLE_ENTRIES_COUNT(&common_entry->items));
item = HASHTABLE_GET_ENTRY(&(common_entry->items), hash);
find_res = HASHTABLE_ENTRY_FIND(cache_ht_, item, &item_data);
if (find_res == NULL) {
TRACE_OUT(cache_read);
return (-1);
}
/* pretend that entry was not found if confidence is below threshold*/
if (find_res->confidence <
common_entry->common_params.confidence_threshold) {
TRACE_OUT(cache_read);
return (-1);
}
if ((common_entry->common_params.max_lifetime.tv_sec != 0) ||
(common_entry->common_params.max_lifetime.tv_usec != 0)) {
if (find_res->fifo_policy_item->last_request_time.tv_sec -
find_res->fifo_policy_item->creation_time.tv_sec >
common_entry->common_params.max_lifetime.tv_sec) {
free(find_res->key);
free(find_res->value);
connected_item =
find_res->fifo_policy_item->connected_item;
if (connected_item != NULL) {
common_entry->policies[1]->remove_item_func(
common_entry->policies[1],
connected_item);
common_entry->policies[1]->destroy_item_func(
connected_item);
}
common_entry->policies[0]->remove_item_func(
common_entry->policies[0],
find_res->fifo_policy_item);
common_entry->policies[0]->destroy_item_func(
find_res->fifo_policy_item);
HASHTABLE_ENTRY_REMOVE(cache_ht_, item, find_res);
--common_entry->items_size;
}
}
if ((*value_size < find_res->value_size) || (value == NULL)) {
*value_size = find_res->value_size;
TRACE_OUT(cache_read);
return (-2);
}
*value_size = find_res->value_size;
memcpy(value, find_res->value, find_res->value_size);
++find_res->fifo_policy_item->request_count;
common_entry->get_time_func(
&find_res->fifo_policy_item->last_request_time);
common_entry->policies[0]->update_item_func(common_entry->policies[0],
find_res->fifo_policy_item);
if (find_res->fifo_policy_item->connected_item != NULL) {
connected_item = find_res->fifo_policy_item->connected_item;
memcpy(&connected_item->last_request_time,
&find_res->fifo_policy_item->last_request_time,
sizeof(struct timeval));
connected_item->request_count =
find_res->fifo_policy_item->request_count;
common_entry->policies[1]->update_item_func(
common_entry->policies[1], connected_item);
}
TRACE_OUT(cache_read);
return (0);
}
/*
* Writes the value with the specified key into the cache entry.
* Functions returns 0 on success, and -1 on error.
*/
int
cache_write(struct cache_entry_ *entry, const char *key, size_t key_size,
char const *value, size_t value_size)
{
struct cache_common_entry_ *common_entry;
struct cache_ht_item_data_ item_data, *find_res;
struct cache_ht_item_ *item;
hashtable_index_t hash;
struct cache_policy_ *policy, *connected_policy;
struct cache_policy_item_ *policy_item;
struct cache_policy_item_ *connected_policy_item;
TRACE_IN(cache_write);
assert(entry != NULL);
assert(key != NULL);
assert(value != NULL);
assert(entry->params->entry_type == CET_COMMON);
common_entry = (struct cache_common_entry_ *)entry;
memset(&item_data, 0, sizeof(struct cache_ht_item_data_));
/* can't avoid the cast here */
item_data.key = (char *)key;
item_data.key_size = key_size;
hash = HASHTABLE_CALCULATE_HASH(cache_ht_, &common_entry->items,
&item_data);
assert(hash < HASHTABLE_ENTRIES_COUNT(&common_entry->items));
item = HASHTABLE_GET_ENTRY(&(common_entry->items), hash);
find_res = HASHTABLE_ENTRY_FIND(cache_ht_, item, &item_data);
if (find_res != NULL) {
if (find_res->confidence < common_entry->common_params.confidence_threshold) {
/* duplicate entry is no error, if confidence is low */
if ((find_res->value_size == value_size) &&
(memcmp(find_res->value, value, value_size) == 0)) {
/* increase confidence on exact match (key and values) */
find_res->confidence++;
} else {
/* create new entry with low confidence, if value changed */
free(item_data.value);
item_data.value = malloc(value_size);
assert(item_data.value != NULL);
memcpy(item_data.value, value, value_size);
item_data.value_size = value_size;
find_res->confidence = 1;
}
TRACE_OUT(cache_write);
return (0);
}
TRACE_OUT(cache_write);
return (-1);
}
item_data.key = malloc(key_size);
memcpy(item_data.key, key, key_size);
item_data.value = malloc(value_size);
assert(item_data.value != NULL);
memcpy(item_data.value, value, value_size);
item_data.value_size = value_size;
item_data.confidence = 1;
policy_item = common_entry->policies[0]->create_item_func();
policy_item->key = item_data.key;
policy_item->key_size = item_data.key_size;
common_entry->get_time_func(&policy_item->creation_time);
if (common_entry->policies_size > 1) {
connected_policy_item =
common_entry->policies[1]->create_item_func();
memcpy(&connected_policy_item->creation_time,
&policy_item->creation_time,
sizeof(struct timeval));
connected_policy_item->key = policy_item->key;
connected_policy_item->key_size = policy_item->key_size;
connected_policy_item->connected_item = policy_item;
policy_item->connected_item = connected_policy_item;
}
item_data.fifo_policy_item = policy_item;
common_entry->policies[0]->add_item_func(common_entry->policies[0],
policy_item);
if (common_entry->policies_size > 1)
common_entry->policies[1]->add_item_func(
common_entry->policies[1], connected_policy_item);
HASHTABLE_ENTRY_STORE(cache_ht_, item, &item_data);
++common_entry->items_size;
if ((common_entry->common_params.max_elemsize != 0) &&
(common_entry->items_size >
common_entry->common_params.max_elemsize)) {
if (common_entry->policies_size > 1) {
policy = common_entry->policies[1];
connected_policy = common_entry->policies[0];
} else {
policy = common_entry->policies[0];
connected_policy = NULL;
}
flush_cache_policy(common_entry, policy, connected_policy,
cache_elemsize_common_continue_func);
}
TRACE_OUT(cache_write);
return (0);
}
/*
* Initializes the write session for the specified multipart entry. This
* session then should be filled with data either committed or abandoned by
* using close_cache_mp_write_session or abandon_cache_mp_write_session
* respectively.
* Returns NULL on errors (when there are too many opened write sessions for
* the entry).
*/
struct cache_mp_write_session_ *
open_cache_mp_write_session(struct cache_entry_ *entry)
{
struct cache_mp_entry_ *mp_entry;
struct cache_mp_write_session_ *retval;
TRACE_IN(open_cache_mp_write_session);
assert(entry != NULL);
assert(entry->params->entry_type == CET_MULTIPART);
mp_entry = (struct cache_mp_entry_ *)entry;
if ((mp_entry->mp_params.max_sessions > 0) &&
(mp_entry->ws_size == mp_entry->mp_params.max_sessions)) {
TRACE_OUT(open_cache_mp_write_session);
return (NULL);
}
retval = calloc(1,
sizeof(*retval));
assert(retval != NULL);
TAILQ_INIT(&retval->items);
retval->parent_entry = mp_entry;
TAILQ_INSERT_HEAD(&mp_entry->ws_head, retval, entries);
++mp_entry->ws_size;
TRACE_OUT(open_cache_mp_write_session);
return (retval);
}
/*
* Writes data to the specified session. Return 0 on success and -1 on errors
* (when write session size limit is exceeded).
*/
int
cache_mp_write(struct cache_mp_write_session_ *ws, char *data,
size_t data_size)
{
struct cache_mp_data_item_ *new_item;
TRACE_IN(cache_mp_write);
assert(ws != NULL);
assert(ws->parent_entry != NULL);
assert(ws->parent_entry->params->entry_type == CET_MULTIPART);
if ((ws->parent_entry->mp_params.max_elemsize > 0) &&
(ws->parent_entry->mp_params.max_elemsize == ws->items_size)) {
TRACE_OUT(cache_mp_write);
return (-1);
}
new_item = calloc(1,
sizeof(*new_item));
assert(new_item != NULL);
new_item->value = malloc(data_size);
assert(new_item->value != NULL);
memcpy(new_item->value, data, data_size);
new_item->value_size = data_size;
TAILQ_INSERT_TAIL(&ws->items, new_item, entries);
++ws->items_size;
TRACE_OUT(cache_mp_write);
return (0);
}
/*
* Abandons the write session and frees all the connected resources.
*/
void
abandon_cache_mp_write_session(struct cache_mp_write_session_ *ws)
{
TRACE_IN(abandon_cache_mp_write_session);
assert(ws != NULL);
assert(ws->parent_entry != NULL);
assert(ws->parent_entry->params->entry_type == CET_MULTIPART);
TAILQ_REMOVE(&ws->parent_entry->ws_head, ws, entries);
--ws->parent_entry->ws_size;
destroy_cache_mp_write_session(ws);
TRACE_OUT(abandon_cache_mp_write_session);
}
/*
* Commits the session to the entry, for which it was created.
*/
void
close_cache_mp_write_session(struct cache_mp_write_session_ *ws)
{
TRACE_IN(close_cache_mp_write_session);
assert(ws != NULL);
assert(ws->parent_entry != NULL);
assert(ws->parent_entry->params->entry_type == CET_MULTIPART);
TAILQ_REMOVE(&ws->parent_entry->ws_head, ws, entries);
--ws->parent_entry->ws_size;
if (ws->parent_entry->completed_write_session == NULL) {
/*
* If there is no completed session yet, this will be the one
*/
ws->parent_entry->get_time_func(
&ws->parent_entry->creation_time);
ws->parent_entry->completed_write_session = ws;
} else {
/*
* If there is a completed session, then we'll save our session
* as a pending session. If there is already a pending session,
* it would be destroyed.
*/
if (ws->parent_entry->pending_write_session != NULL)
destroy_cache_mp_write_session(
ws->parent_entry->pending_write_session);
ws->parent_entry->pending_write_session = ws;
}
TRACE_OUT(close_cache_mp_write_session);
}
/*
* Opens read session for the specified entry. Returns NULL on errors (when
* there are no data in the entry, or the data are obsolete).
*/
struct cache_mp_read_session_ *
open_cache_mp_read_session(struct cache_entry_ *entry)
{
struct cache_mp_entry_ *mp_entry;
struct cache_mp_read_session_ *retval;
TRACE_IN(open_cache_mp_read_session);
assert(entry != NULL);
assert(entry->params->entry_type == CET_MULTIPART);
mp_entry = (struct cache_mp_entry_ *)entry;
if (mp_entry->completed_write_session == NULL) {
TRACE_OUT(open_cache_mp_read_session);
return (NULL);
}
if ((mp_entry->mp_params.max_lifetime.tv_sec != 0)
|| (mp_entry->mp_params.max_lifetime.tv_usec != 0)) {
if (mp_entry->last_request_time.tv_sec -
mp_entry->last_request_time.tv_sec >
mp_entry->mp_params.max_lifetime.tv_sec) {
flush_cache_entry(entry);
TRACE_OUT(open_cache_mp_read_session);
return (NULL);
}
}
retval = calloc(1,
sizeof(*retval));
assert(retval != NULL);
retval->parent_entry = mp_entry;
retval->current_item = TAILQ_FIRST(
&mp_entry->completed_write_session->items);
TAILQ_INSERT_HEAD(&mp_entry->rs_head, retval, entries);
++mp_entry->rs_size;
mp_entry->get_time_func(&mp_entry->last_request_time);
TRACE_OUT(open_cache_mp_read_session);
return (retval);
}
/*
* Reads the data from the read session - step by step.
* Returns 0 on success, -1 on error (when there are no more data), and -2 if
* the data_size is too small. In the last case, data_size would be filled
* the proper value.
*/
int
cache_mp_read(struct cache_mp_read_session_ *rs, char *data, size_t *data_size)
{
TRACE_IN(cache_mp_read);
assert(rs != NULL);
if (rs->current_item == NULL) {
TRACE_OUT(cache_mp_read);
return (-1);
}
if (rs->current_item->value_size > *data_size) {
*data_size = rs->current_item->value_size;
if (data == NULL) {
TRACE_OUT(cache_mp_read);
return (0);
}
TRACE_OUT(cache_mp_read);
return (-2);
}
*data_size = rs->current_item->value_size;
memcpy(data, rs->current_item->value, rs->current_item->value_size);
rs->current_item = TAILQ_NEXT(rs->current_item, entries);
TRACE_OUT(cache_mp_read);
return (0);
}
/*
* Closes the read session. If there are no more read sessions and there is
* a pending write session, it will be committed and old
* completed_write_session will be destroyed.
*/
void
close_cache_mp_read_session(struct cache_mp_read_session_ *rs)
{
TRACE_IN(close_cache_mp_read_session);
assert(rs != NULL);
assert(rs->parent_entry != NULL);
TAILQ_REMOVE(&rs->parent_entry->rs_head, rs, entries);
--rs->parent_entry->rs_size;
if ((rs->parent_entry->rs_size == 0) &&
(rs->parent_entry->pending_write_session != NULL)) {
destroy_cache_mp_write_session(
rs->parent_entry->completed_write_session);
rs->parent_entry->completed_write_session =
rs->parent_entry->pending_write_session;
rs->parent_entry->pending_write_session = NULL;
}
destroy_cache_mp_read_session(rs);
TRACE_OUT(close_cache_mp_read_session);
}
int
transform_cache_entry(struct cache_entry_ *entry,
enum cache_transformation_t transformation)
{
TRACE_IN(transform_cache_entry);
switch (transformation) {
case CTT_CLEAR:
clear_cache_entry(entry);
TRACE_OUT(transform_cache_entry);
return (0);
case CTT_FLUSH:
flush_cache_entry(entry);
TRACE_OUT(transform_cache_entry);
return (0);
default:
TRACE_OUT(transform_cache_entry);
return (-1);
}
}
int
transform_cache_entry_part(struct cache_entry_ *entry,
enum cache_transformation_t transformation, const char *key_part,
size_t key_part_size, enum part_position_t part_position)
{
struct cache_common_entry_ *common_entry;
struct cache_ht_item_ *ht_item;
struct cache_ht_item_data_ *ht_item_data, ht_key;
struct cache_policy_item_ *item, *connected_item;
TRACE_IN(transform_cache_entry_part);
if (entry->params->entry_type != CET_COMMON) {
TRACE_OUT(transform_cache_entry_part);
return (-1);
}
if (transformation != CTT_CLEAR) {
TRACE_OUT(transform_cache_entry_part);
return (-1);
}
memset(&ht_key, 0, sizeof(struct cache_ht_item_data_));
ht_key.key = (char *)key_part; /* can't avoid casting here */
ht_key.key_size = key_part_size;
common_entry = (struct cache_common_entry_ *)entry;
HASHTABLE_FOREACH(&(common_entry->items), ht_item) {
do {
ht_item_data = HASHTABLE_ENTRY_FIND_SPECIAL(cache_ht_,
ht_item, &ht_key,
ht_items_fixed_size_left_cmp_func);
if (ht_item_data != NULL) {
item = ht_item_data->fifo_policy_item;
connected_item = item->connected_item;
common_entry->policies[0]->remove_item_func(
common_entry->policies[0],
item);
free(ht_item_data->key);
free(ht_item_data->value);
HASHTABLE_ENTRY_REMOVE(cache_ht_, ht_item,
ht_item_data);
--common_entry->items_size;
common_entry->policies[0]->destroy_item_func(
item);
if (common_entry->policies_size == 2) {
common_entry->policies[1]->remove_item_func(
common_entry->policies[1],
connected_item);
common_entry->policies[1]->destroy_item_func(
connected_item);
}
}
} while (ht_item_data != NULL);
}
TRACE_OUT(transform_cache_entry_part);
return (0);
}