/* Copyright (c) 2011, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 ''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 AUTHOR 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 "config.h" #include "kvstorage.h" #include "main.h" #include "radix.h" #ifdef WITH_JUDY #include #endif #define MAX_EXPIRE_STEPS 10 /** Create new kv storage */ struct rspamd_kv_storage * rspamd_kv_storage_new (gint id, const gchar *name, struct rspamd_kv_cache *cache, struct rspamd_kv_backend *backend, struct rspamd_kv_expire *expire, gsize max_elts, gsize max_memory, gboolean no_overwrite) { struct rspamd_kv_storage *new; new = g_slice_alloc (sizeof (struct rspamd_kv_storage)); new->elts = 0; new->memory = 0; new->cache = cache; new->backend = backend; new->expire = expire; new->max_elts = max_elts; new->max_memory = max_memory; new->id = id; new->no_overwrite = no_overwrite; if (name != NULL) { new->name = g_strdup (name); } else { /* Name is absent, use ID as name */ new->name = g_malloc (sizeof ("18446744073709551616")); rspamd_snprintf (new->name, sizeof ("18446744073709551616"), "%d", id); } #if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30)) g_rw_lock_init (&new->rwlock); #else g_static_rw_lock_init (&new->rwlock); #endif /* Init structures */ if (new->cache->init_func) { new->cache->init_func (new->cache); } if (new->backend && new->backend->init_func) { new->backend->init_func (new->backend); } if (new->expire && new->expire->init_func) { new->expire->init_func (new->expire); } return new; } /** Internal insertion to the kv storage from backend */ gboolean rspamd_kv_storage_insert_cache (struct rspamd_kv_storage *storage, gpointer key, guint keylen, gpointer data, gsize len, gint flags, guint expire, struct rspamd_kv_element **pelt) { gint steps = 0; struct rspamd_kv_element *elt; RW_W_LOCK (&storage->rwlock); /* Hard limit */ if (storage->max_memory > 0) { if (len > storage->max_memory) { msg_info ("<%s>: trying to insert value of length %z while limit is %z", storage->name, len, storage->max_memory); RW_W_UNLOCK (&storage->rwlock); return FALSE; } /* Now check limits */ while (storage->memory + len > storage->max_memory) { if (storage->expire) { storage->expire->step_func (storage->expire, storage, time (NULL), steps); } else { msg_warn ("<%s>: storage is full and no expire function is defined", storage->name); } if (++steps > MAX_EXPIRE_STEPS) { RW_W_UNLOCK (&storage->rwlock); msg_warn ("<%s>: cannot expire enough keys in storage", storage->name); return FALSE; } } } /* Insert elt to the cache */ elt = storage->cache->insert_func (storage->cache, key, keylen, data, len); /* Copy data */ elt->flags = flags; elt->expire = expire; if (pelt != NULL) { *pelt = elt; } /* Insert to the expire */ if (storage->expire) { storage->expire->insert_func (storage->expire, elt); } storage->elts ++; storage->memory += ELT_SIZE (elt); RW_W_UNLOCK (&storage->rwlock); return TRUE; } /** Insert new element to the kv storage */ gboolean rspamd_kv_storage_insert (struct rspamd_kv_storage *storage, gpointer key, guint keylen, gpointer data, gsize len, gint flags, guint expire) { gint steps = 0; struct rspamd_kv_element *elt; gboolean res = TRUE; glong longval; /* Hard limit */ RW_W_LOCK (&storage->rwlock); if (storage->max_memory > 0) { if (len + sizeof (struct rspamd_kv_element) + keylen >= storage->max_memory) { msg_warn ("<%s>: trying to insert value of length %z while limit is %z", storage->name, len, storage->max_memory); RW_W_UNLOCK (&storage->rwlock); return FALSE; } /* Now check limits */ while (storage->memory + len + keylen > storage->max_memory) { if (storage->expire) { storage->expire->step_func (storage->expire, storage, time (NULL), steps); } else { msg_warn ("<%s>: storage is full and no expire function is defined", storage->name); } if (++steps > MAX_EXPIRE_STEPS) { RW_W_UNLOCK (&storage->rwlock); msg_warn ("<%s>: cannot expire enough keys in storage", storage->name); return FALSE; } } } if (storage->max_elts > 0 && storage->elts > storage->max_elts) { /* More expire */ steps = 0; while (storage->elts > storage->max_elts) { if (storage->expire) { storage->expire->step_func (storage->expire, storage, time (NULL), steps); } else { msg_warn ("<%s>: storage is full and no expire function is defined", storage->name); } if (++steps > MAX_EXPIRE_STEPS) { RW_W_UNLOCK (&storage->rwlock); msg_warn ("<%s>: cannot expire enough keys in storage", storage->name); return FALSE; } } } /* First try to search it in cache */ elt = storage->cache->lookup_func (storage->cache, key, keylen); if (elt) { if (!storage->no_overwrite) { /* Remove old elt */ if (storage->expire) { storage->expire->delete_func (storage->expire, elt); } storage->memory -= ELT_SIZE (elt); storage->cache->steal_func (storage->cache, elt); if (elt->flags & KV_ELT_DIRTY) { /* Element is in backend storage queue */ elt->flags |= KV_ELT_NEED_FREE; } else { g_slice_free1 (ELT_SIZE (elt), elt); } } else { /* Just do incref and nothing more */ if (storage->backend && storage->backend->incref_func) { if (storage->backend->incref_func (storage->backend, key, keylen)) { RW_W_UNLOCK (&storage->rwlock); return TRUE; } else { RW_W_UNLOCK (&storage->rwlock); return FALSE; } } } } /* Insert elt to the cache */ /* First of all check element for integer */ if (rspamd_strtol (data, len, &longval)) { elt = storage->cache->insert_func (storage->cache, key, keylen, &longval, sizeof (glong)); if (elt == NULL) { return FALSE; } else { elt->flags |= KV_ELT_INTEGER; } } else { elt = storage->cache->insert_func (storage->cache, key, keylen, data, len); if (elt == NULL) { RW_W_UNLOCK (&storage->rwlock); return FALSE; } } elt->flags |= flags; elt->expire = expire; if (expire == 0) { elt->flags |= KV_ELT_PERSISTENT; } /* Place to the backend */ if (storage->backend) { res = storage->backend->insert_func (storage->backend, key, keylen, elt); } /* Insert to the expire */ if (storage->expire) { storage->expire->insert_func (storage->expire, elt); } storage->elts ++; storage->memory += ELT_SIZE (elt); RW_W_UNLOCK (&storage->rwlock); return res; } /** Replace an element in the kv storage */ gboolean rspamd_kv_storage_replace (struct rspamd_kv_storage *storage, gpointer key, guint keylen, struct rspamd_kv_element *elt) { gboolean res = TRUE; gint steps = 0; /* Hard limit */ if (storage->max_memory > 0) { if (elt->size > storage->max_memory) { msg_info ("<%s>: trying to replace value of length %z while limit is %z", storage->name, elt->size, storage->max_memory); return FALSE; } /* Now check limits */ while (storage->memory + ELT_SIZE (elt) > storage->max_memory) { if (storage->expire) { RW_W_LOCK (&storage->rwlock); storage->expire->step_func (storage->expire, storage, time (NULL), steps); RW_W_UNLOCK (&storage->rwlock); } else { msg_warn ("<%s>: storage is full and no expire function is defined", storage->name); } if (++steps > MAX_EXPIRE_STEPS) { msg_warn ("<%s>: cannot expire enough keys in storage", storage->name); return FALSE; } } } RW_W_LOCK (&storage->rwlock); /* Insert elt to the cache */ res = storage->cache->replace_func (storage->cache, key, keylen, elt); /* Place to the backend */ if (res && storage->backend) { res = storage->backend->replace_func (storage->backend, key, keylen, elt); } RW_W_UNLOCK (&storage->rwlock); return res; } /** Increment value in kvstorage */ gboolean rspamd_kv_storage_increment (struct rspamd_kv_storage *storage, gpointer key, guint keylen, glong *value) { struct rspamd_kv_element *elt = NULL, *belt; glong *lp; /* First try to look at cache */ RW_W_LOCK (&storage->rwlock); elt = storage->cache->lookup_func (storage->cache, key, keylen); if (elt == NULL && storage->backend) { belt = storage->backend->lookup_func (storage->backend, key, keylen); if (belt) { /* Put this element into cache */ if ((belt->flags & KV_ELT_INTEGER) != 0) { RW_W_UNLOCK (&storage->rwlock); rspamd_kv_storage_insert_cache (storage, ELT_KEY (belt), keylen, ELT_DATA (belt), belt->size, belt->flags, belt->expire, &elt); RW_W_LOCK (&storage->rwlock); } if ((belt->flags & KV_ELT_DIRTY) == 0) { g_free (belt); } } } if (elt && (elt->flags & KV_ELT_INTEGER) != 0) { lp = &ELT_LONG (elt); /* Handle need expire here */ if (elt->flags & KV_ELT_NEED_EXPIRE) { *lp = *value; } else { *lp += *value; *value = *lp; } elt->age = time (NULL); if (storage->backend) { if (storage->backend->replace_func (storage->backend, key, keylen, elt)) { RW_W_UNLOCK (&storage->rwlock); return TRUE; } else { RW_W_UNLOCK (&storage->rwlock); return FALSE; } } else { RW_W_UNLOCK (&storage->rwlock); return TRUE; } } RW_W_UNLOCK (&storage->rwlock); return FALSE; } /** Lookup an element inside kv storage */ struct rspamd_kv_element* rspamd_kv_storage_lookup (struct rspamd_kv_storage *storage, gpointer key, guint keylen, time_t now) { struct rspamd_kv_element *elt = NULL, *belt; /* First try to look at cache */ RW_R_LOCK (&storage->rwlock); elt = storage->cache->lookup_func (storage->cache, key, keylen); /* Next look at the backend */ if (elt == NULL && storage->backend) { belt = storage->backend->lookup_func (storage->backend, key, keylen); if (belt) { /* Put this element into cache */ if ((belt->flags & KV_ELT_DIRTY) == 0) { belt->flags |= KV_ELT_NEED_INSERT; return belt; } else { elt = belt; } } } if (elt && (elt->flags & KV_ELT_PERSISTENT) == 0 && elt->expire > 0) { /* Check expiration */ if (now - elt->age > (gint)elt->expire) { /* Set need expire as we have no write lock here */ elt->flags |= KV_ELT_NEED_EXPIRE; elt = NULL; } } /* RWlock is still locked */ return elt; } /** Expire an element from kv storage */ struct rspamd_kv_element * rspamd_kv_storage_delete (struct rspamd_kv_storage *storage, gpointer key, guint keylen) { struct rspamd_kv_element *elt; /* First delete key from cache */ RW_W_LOCK (&storage->rwlock); elt = storage->cache->delete_func (storage->cache, key, keylen); /* Now delete from backend */ if (storage->backend) { storage->backend->delete_func (storage->backend, key, keylen); } /* Notify expire */ if (elt) { if (storage->expire) { storage->expire->delete_func (storage->expire, elt); } storage->elts --; storage->memory -= elt->size; if ((elt->flags & KV_ELT_DIRTY) != 0) { elt->flags |= KV_ELT_NEED_FREE; } else { g_slice_free1 (ELT_SIZE (elt), elt); } } RW_W_UNLOCK (&storage->rwlock); return elt; } /** Destroy kv storage */ void rspamd_kv_storage_destroy (struct rspamd_kv_storage *storage) { RW_W_LOCK (&storage->rwlock); if (storage->backend && storage->backend->destroy_func) { storage->backend->destroy_func (storage->backend); } if (storage->expire && storage->expire->destroy_func) { storage->expire->destroy_func (storage->expire); } if (storage->cache && storage->cache->destroy_func) { storage->cache->destroy_func (storage->cache); } g_free (storage->name); RW_W_UNLOCK (&storage->rwlock); g_slice_free1 (sizeof (struct rspamd_kv_storage), storage); } /** Insert array */ gboolean rspamd_kv_storage_insert_array (struct rspamd_kv_storage *storage, gpointer key, guint keylen, guint elt_size, gpointer data, gsize len, gint flags, guint expire) { struct rspamd_kv_element *elt; guint *es; gpointer arr_data; /* Make temporary copy */ arr_data = g_slice_alloc (len + sizeof (guint)); es = arr_data; *es = elt_size; memcpy (arr_data, (gchar *)data + sizeof (guint), len); if (!rspamd_kv_storage_insert_cache (storage, key, keylen, arr_data, len + sizeof (guint), flags, expire, &elt)) { g_slice_free1 (len + sizeof (guint), arr_data); return FALSE; } /* Now set special data of element */ elt->flags |= KV_ELT_ARRAY; g_slice_free1 (len + sizeof (guint), arr_data); /* Place to the backend */ if (storage->backend) { return storage->backend->insert_func (storage->backend, key, keylen, elt); } return TRUE; } /** Set element inside array */ gboolean rspamd_kv_storage_set_array (struct rspamd_kv_storage *storage, gpointer key, guint keylen, guint elt_num, gpointer data, gsize len, time_t now) { struct rspamd_kv_element *elt; guint *es; gpointer target; elt = rspamd_kv_storage_lookup (storage, key, keylen, now); if (elt == NULL) { return FALSE; } if ((elt->flags & KV_ELT_ARRAY) == 0) { return FALSE; } /* Get element size */ es = (guint *)ELT_DATA (elt); if (elt_num > (elt->size - sizeof (guint)) / (*es)) { /* Invalid index */ return FALSE; } target = (gchar *)ELT_DATA (elt) + sizeof (guint) + (*es) * elt_num; if (len != *es) { /* Invalid size */ return FALSE; } memcpy (target, data, len); /* Place to the backend */ if (storage->backend) { return storage->backend->replace_func (storage->backend, key, keylen, elt); } return TRUE; } /** Get element inside array */ gboolean rspamd_kv_storage_get_array (struct rspamd_kv_storage *storage, gpointer key, guint keylen, guint elt_num, gpointer *data, gsize *len, time_t now) { struct rspamd_kv_element *elt; guint *es; gpointer target; elt = rspamd_kv_storage_lookup (storage, key, keylen, now); if (elt == NULL) { return FALSE; } if ((elt->flags & KV_ELT_ARRAY) == 0) { return FALSE; } /* Get element size */ es = (guint *)ELT_DATA (elt); if (elt_num > (elt->size - sizeof (guint)) / (*es)) { /* Invalid index */ return FALSE; } target = ELT_DATA (elt) + sizeof (guint) + (*es) * elt_num; *len = *es; *data = target; return TRUE; } /** * LRU expire functions */ struct rspamd_kv_lru_expire { expire_init init_func; /*< this callback is called on kv storage initialization */ expire_insert insert_func; /*< this callback is called when element is inserted */ expire_step step_func; /*< this callback is used when cache is full */ expire_delete delete_func; /*< this callback is called when an element is deleted */ expire_destroy destroy_func; /*< this callback is used for destroying all elements inside expire */ TAILQ_HEAD (eltq, rspamd_kv_element) head; }; /** * Insert an element into expire queue */ static void rspamd_lru_insert (struct rspamd_kv_expire *e, struct rspamd_kv_element *elt) { struct rspamd_kv_lru_expire *expire = (struct rspamd_kv_lru_expire *)e; /* Get a proper queue */ TAILQ_INSERT_TAIL (&expire->head, elt, entry); } /** * Delete an element from expire queue */ static void rspamd_lru_delete (struct rspamd_kv_expire *e, struct rspamd_kv_element *elt) { struct rspamd_kv_lru_expire *expire = (struct rspamd_kv_lru_expire *)e; /* Unlink element */ TAILQ_REMOVE (&expire->head, elt, entry); } /** * Expire elements */ static gboolean rspamd_lru_expire_step (struct rspamd_kv_expire *e, struct rspamd_kv_storage *storage, time_t now, gboolean forced) { struct rspamd_kv_lru_expire *expire = (struct rspamd_kv_lru_expire *)e; struct rspamd_kv_element *elt, *oldest_elt = NULL, *temp; time_t diff; gboolean res = FALSE; elt = TAILQ_FIRST (&expire->head); if (elt && (forced || (elt->flags & (KV_ELT_PERSISTENT|KV_ELT_DIRTY)) == 0)) { diff = elt->expire - (now - elt->age); if (diff > 0 || (forced && elt->expire == 0)) { oldest_elt = elt; } else { /* This element is already expired */ storage->cache->steal_func (storage->cache, elt); storage->memory -= ELT_SIZE (elt); storage->elts --; TAILQ_REMOVE (&expire->head, elt, entry); /* Free memory */ if ((elt->flags & (KV_ELT_DIRTY|KV_ELT_NEED_INSERT)) != 0) { elt->flags |= KV_ELT_NEED_FREE; } else { g_slice_free1 (ELT_SIZE (elt), elt); } res = TRUE; /* Check other elements in this queue */ TAILQ_FOREACH_SAFE (elt, &expire->head, entry, temp) { if ((!forced && (elt->flags & (KV_ELT_PERSISTENT|KV_ELT_DIRTY)) != 0) || (gint)elt->expire < (now - elt->age)) { break; } storage->memory -= ELT_SIZE (elt); storage->elts --; storage->cache->steal_func (storage->cache, elt); TAILQ_REMOVE (&expire->head, elt, entry); /* Free memory */ if ((elt->flags & (KV_ELT_DIRTY|KV_ELT_NEED_INSERT)) != 0) { elt->flags |= KV_ELT_NEED_FREE; } else { g_slice_free1 (ELT_SIZE (elt), elt); } } } } if (!res && oldest_elt != NULL) { storage->memory -= ELT_SIZE (oldest_elt); storage->elts --; storage->cache->steal_func (storage->cache, oldest_elt); TAILQ_REMOVE (&expire->head, oldest_elt, entry); /* Free memory */ if ((oldest_elt->flags & (KV_ELT_DIRTY|KV_ELT_NEED_INSERT)) != 0) { oldest_elt->flags |= KV_ELT_NEED_FREE; } else { g_slice_free1 (ELT_SIZE (oldest_elt), oldest_elt); } } return TRUE; } /** * Destroy LRU expire memory */ static void rspamd_lru_destroy (struct rspamd_kv_expire *e) { struct rspamd_kv_lru_expire *expire = (struct rspamd_kv_lru_expire *)e; g_slice_free1 (sizeof (struct rspamd_kv_lru_expire), expire); } /** * Create new LRU cache */ struct rspamd_kv_expire* rspamd_lru_expire_new () { struct rspamd_kv_lru_expire *new; new = g_slice_alloc (sizeof (struct rspamd_kv_lru_expire)); TAILQ_INIT (&new->head); /* Set callbacks */ new->init_func = NULL; new->insert_func = rspamd_lru_insert; new->delete_func = rspamd_lru_delete; new->step_func = rspamd_lru_expire_step; new->destroy_func = rspamd_lru_destroy; return (struct rspamd_kv_expire *)new; } /* * KV cache hash table */ struct rspamd_kv_hash_cache { cache_init init_func; /*< this callback is called on kv storage initialization */ cache_insert insert_func; /*< this callback is called when element is inserted */ cache_replace replace_func; /*< this callback is called when element is replace */ cache_lookup lookup_func; /*< this callback is used for lookup of element */ cache_delete delete_func; /*< this callback is called when an element is deleted */ cache_steal steal_func; /*< this callback is used to replace duplicates in cache */ cache_destroy destroy_func; /*< this callback is used for destroying all elements inside cache */ GHashTable *hash; }; /** * Insert an element inside cache */ static struct rspamd_kv_element* rspamd_kv_hash_insert (struct rspamd_kv_cache *c, gpointer key, guint keylen, gpointer value, gsize len) { struct rspamd_kv_element *elt; struct rspamd_kv_hash_cache *cache = (struct rspamd_kv_hash_cache *)c; struct rspamd_kv_element search_elt; search_elt.keylen = keylen; search_elt.p = key; if ((elt = g_hash_table_lookup (cache->hash, &search_elt)) == NULL) { elt = g_slice_alloc (sizeof (struct rspamd_kv_element) + len + keylen + 1); elt->age = time (NULL); elt->keylen = keylen; elt->size = len; elt->flags = 0; memcpy (ELT_KEY (elt), key, keylen + 1); memcpy (ELT_DATA (elt), value, len); elt->p = &elt->data; g_hash_table_insert (cache->hash, elt, elt); } else { g_hash_table_steal (cache->hash, elt); if ((elt->flags & KV_ELT_DIRTY) != 0) { elt->flags |= KV_ELT_NEED_FREE; } else { /* Free it by self */ g_slice_free1 (ELT_SIZE (elt), elt); } elt = g_slice_alloc (sizeof (struct rspamd_kv_element) + len + keylen + 1); elt->age = time (NULL); elt->keylen = keylen; elt->size = len; elt->flags = 0; memcpy (ELT_KEY (elt), key, keylen + 1); memcpy (ELT_DATA (elt), value, len); elt->p = &elt->data; g_hash_table_insert (cache->hash, elt, elt); } return elt; } /** * Lookup an item inside hash */ static struct rspamd_kv_element* rspamd_kv_hash_lookup (struct rspamd_kv_cache *c, gpointer key, guint keylen) { struct rspamd_kv_hash_cache *cache = (struct rspamd_kv_hash_cache *)c; struct rspamd_kv_element search_elt; search_elt.keylen = keylen; search_elt.p = key; return g_hash_table_lookup (cache->hash, &search_elt); } /** * Replace an element inside cache */ static gboolean rspamd_kv_hash_replace (struct rspamd_kv_cache *c, gpointer key, guint keylen, struct rspamd_kv_element *elt) { struct rspamd_kv_hash_cache *cache = (struct rspamd_kv_hash_cache *)c; struct rspamd_kv_element *oldelt, search_elt; search_elt.keylen = keylen; search_elt.p = key; if ((oldelt = g_hash_table_lookup (cache->hash, &search_elt)) != NULL) { g_hash_table_steal (cache->hash, oldelt); if ((oldelt->flags & KV_ELT_DIRTY) != 0) { oldelt->flags |= KV_ELT_NEED_FREE; } else { /* Free it by self */ g_slice_free1 (ELT_SIZE (oldelt), oldelt); } g_hash_table_insert (cache->hash, elt, elt); return TRUE; } return FALSE; } /** * Delete an element from cache */ static struct rspamd_kv_element * rspamd_kv_hash_delete (struct rspamd_kv_cache *c, gpointer key, guint keylen) { struct rspamd_kv_hash_cache *cache = (struct rspamd_kv_hash_cache *)c; struct rspamd_kv_element *elt; struct rspamd_kv_element search_elt; search_elt.keylen = keylen; search_elt.p = key; elt = g_hash_table_lookup (cache->hash, &search_elt); if (elt) { g_hash_table_steal (cache->hash, &search_elt); } return elt; } /** * Steal an element from cache */ static void rspamd_kv_hash_steal (struct rspamd_kv_cache *c, struct rspamd_kv_element *elt) { struct rspamd_kv_hash_cache *cache = (struct rspamd_kv_hash_cache *)c; g_hash_table_steal (cache->hash, elt); } /** * Destroy the whole cache */ static void rspamd_kv_hash_destroy_cb (gpointer key, gpointer value, gpointer unused) { struct rspamd_kv_element *elt = value; g_slice_free1 (ELT_SIZE (elt), elt); } static void rspamd_kv_hash_destroy (struct rspamd_kv_cache *c) { struct rspamd_kv_hash_cache *cache = (struct rspamd_kv_hash_cache *)c; g_hash_table_foreach (cache->hash, rspamd_kv_hash_destroy_cb, NULL); g_hash_table_destroy (cache->hash); g_slice_free1 (sizeof (struct rspamd_kv_hash_cache), cache); } /** * Make hash for element */ #define rot(x,k) (((x)<<(k)) ^ ((x)>>(32-(k)))) #define mix(a,b,c) \ { \ a -= c; a ^= rot(c, 4); c += b; \ b -= a; b ^= rot(a, 6); a += c; \ c -= b; c ^= rot(b, 8); b += a; \ a -= c; a ^= rot(c,16); c += b; \ b -= a; b ^= rot(a,19); a += c; \ c -= b; c ^= rot(b, 4); b += a; \ } #define final(a,b,c) \ { \ c ^= b; c -= rot(b,14); \ a ^= c; a -= rot(c,11); \ b ^= a; b -= rot(a,25); \ c ^= b; c -= rot(b,16); \ a ^= c; a -= rot(c,4); \ b ^= a; b -= rot(a,14); \ c ^= b; c -= rot(b,24); \ } /* * The hash function used here is by Bob Jenkins, 1996: * * "By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. * You may use this code any way you wish, private, educational, * or commercial. It's free." * */ guint kv_elt_hash_func (gconstpointer e) { struct rspamd_kv_element *elt = (struct rspamd_kv_element *)e; guint32 a, b, c; union { const void *ptr; size_t i; } u; guint length; /* Set up the internal state */ length = elt->keylen; a = b = c = 0xdeadbeef + length; u.ptr = elt->p; if (((u.i & 0x3) == 0)) { const guint32 *k = (const guint32 *)elt->p; /* read 32-bit chunks */ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; b += k[1]; c += k[2]; mix (a,b,c); length -= 12; k += 3; } /*----------------------------- handle the last (probably partial) block */ /* * "k[2]&0xffffff" actually reads beyond the end of the string, but * then masks off the part it's not allowed to read. Because the * string is aligned, the masked-off tail is in the same word as the * rest of the string. Every machine with memory protection I've seen * does it on word boundaries, so is OK with this. But VALGRIND will * still catch it and complain. The masking trick does make the hash * noticably faster for short strings (like English words). */ switch (length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=k[1]&0xffffff; a+=k[0]; break; case 6 : b+=k[1]&0xffff; a+=k[0]; break; case 5 : b+=k[1]&0xff; a+=k[0]; break; case 4 : a+=k[0]; break; case 3 : a+=k[0]&0xffffff; break; case 2 : a+=k[0]&0xffff; break; case 1 : a+=k[0]&0xff; break; case 0 : return c; /* zero length strings require no mixing */ } } else if (((u.i & 0x1) == 0)) { const guint16 *k = (const guint16 *)elt->p; /* read 16-bit chunks */ const guint8 *k8; /*--------------- all but last block: aligned reads and different mixing */ while (length > 12) { a += k[0] + (((guint32)k[1])<<16); b += k[2] + (((guint32)k[3])<<16); c += k[4] + (((guint32)k[5])<<16); mix (a,b,c); length -= 12; k += 6; } /*----------------------------- handle the last (probably partial) block */ k8 = (const guint8 *)k; switch (length) { case 12: c+=k[4]+(((guint32)k[5])<<16); b+=k[2]+(((guint32)k[3])<<16); a+=k[0]+(((guint32)k[1])<<16); break; case 11: c+=((guint32)k8[10])<<16; /* @fallthrough */ case 10: c+=k[4]; /* @fallthrough@ */ b+=k[2]+(((guint32)k[3])<<16); a+=k[0]+(((guint32)k[1])<<16); break; case 9 : c+=k8[8]; /* @fallthrough */ case 8 : b+=k[2]+(((guint32)k[3])<<16); a+=k[0]+(((guint32)k[1])<<16); break; case 7 : b+=((guint32)k8[6])<<16; /* @fallthrough */ case 6 : b+=k[2]; a+=k[0]+(((guint32)k[1])<<16); break; case 5 : b+=k8[4]; /* @fallthrough */ case 4 : a+=k[0]+(((guint32)k[1])<<16); break; case 3 : a+=((guint32)k8[2])<<16; /* @fallthrough */ case 2 : a+=k[0]; break; case 1 : a+=k8[0]; break; case 0 : return c; /* zero length strings require no mixing */ } } else { /* need to read the key one byte at a time */ const guint8 *k = elt->p; /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; a += ((guint32)k[1])<<8; a += ((guint32)k[2])<<16; a += ((guint32)k[3])<<24; b += k[4]; b += ((guint32)k[5])<<8; b += ((guint32)k[6])<<16; b += ((guint32)k[7])<<24; c += k[8]; c += ((guint32)k[9])<<8; c += ((guint32)k[10])<<16; c += ((guint32)k[11])<<24; mix(a,b,c); length -= 12; k += 12; } /*-------------------------------- last block: affect all 32 bits of (c) */ switch (length) /* all the case statements fall through */ { case 12: c+=((guint32)k[11])<<24; case 11: c+=((guint32)k[10])<<16; case 10: c+=((guint32)k[9])<<8; case 9 : c+=k[8]; case 8 : b+=((guint32)k[7])<<24; case 7 : b+=((guint32)k[6])<<16; case 6 : b+=((guint32)k[5])<<8; case 5 : b+=k[4]; case 4 : a+=((guint32)k[3])<<24; case 3 : a+=((guint32)k[2])<<16; case 2 : a+=((guint32)k[1])<<8; case 1 : a+=k[0]; break; case 0 : return c; /* zero length strings require no mixing */ } } final (a,b,c); return c; /* zero length strings require no mixing */ } gboolean kv_elt_compare_func (gconstpointer e1, gconstpointer e2) { struct rspamd_kv_element *elt1 = (struct rspamd_kv_element *) e1, *elt2 = (struct rspamd_kv_element *) e2; if (elt1->keylen == elt2->keylen) { return memcmp (elt1->p, elt2->p, elt1->keylen) == 0; } return FALSE; } /** * Create new hash kv cache */ struct rspamd_kv_cache* rspamd_kv_hash_new (void) { struct rspamd_kv_hash_cache *new; new = g_slice_alloc (sizeof (struct rspamd_kv_hash_cache)); new->hash = g_hash_table_new_full (kv_elt_hash_func, kv_elt_compare_func, NULL, NULL); new->init_func = NULL; new->insert_func = rspamd_kv_hash_insert; new->lookup_func = rspamd_kv_hash_lookup; new->replace_func = rspamd_kv_hash_replace; new->delete_func = rspamd_kv_hash_delete; new->steal_func = rspamd_kv_hash_steal; new->destroy_func = rspamd_kv_hash_destroy; return (struct rspamd_kv_cache *)new; } /* * Radix cache hash table */ struct rspamd_kv_radix_cache { cache_init init_func; /*< this callback is called on kv storage initialization */ cache_insert insert_func; /*< this callback is called when element is inserted */ cache_replace replace_func; /*< this callback is called when element is replace */ cache_lookup lookup_func; /*< this callback is used for lookup of element */ cache_delete delete_func; /*< this callback is called when an element is deleted */ cache_steal steal_func; /*< this callback is used to replace duplicates in cache */ cache_destroy destroy_func; /*< this callback is used for destroying all elements inside cache */ radix_tree_t *tree; }; /** * Validate a key for radix */ static guint32 rspamd_kv_radix_validate (gpointer key, guint keylen) { struct in_addr addr; if (inet_aton (key, &addr) == 0) { return 0; } return addr.s_addr; } /** * Insert an element inside cache */ static struct rspamd_kv_element* rspamd_kv_radix_insert (struct rspamd_kv_cache *c, gpointer key, guint keylen, gpointer value, gsize len) { struct rspamd_kv_element *elt; struct rspamd_kv_radix_cache *cache = (struct rspamd_kv_radix_cache *)c; guint32 rkey = rspamd_kv_radix_validate (key, keylen); if (rkey == 0) { return NULL; } elt = (struct rspamd_kv_element *)radix32tree_find (cache->tree, rkey); if ((uintptr_t)elt == RADIX_NO_VALUE) { elt = g_slice_alloc (sizeof (struct rspamd_kv_element) + len + keylen + 1); elt->age = time (NULL); elt->keylen = keylen; elt->size = len; elt->flags = 0; memcpy (ELT_KEY (elt), key, keylen + 1); memcpy (ELT_DATA (elt), value, len); elt->p = &elt->data; radix32tree_insert (cache->tree, rkey, 0xffffffff, (uintptr_t)elt); } else { radix32tree_delete (cache->tree, rkey, 0xffffffff); if ((elt->flags & KV_ELT_DIRTY) != 0) { elt->flags |= KV_ELT_NEED_FREE; } else { /* Free it by self */ g_slice_free1 (ELT_SIZE (elt), elt); } elt = g_slice_alloc (sizeof (struct rspamd_kv_element) + len + keylen + 1); elt->age = time (NULL); elt->keylen = keylen; elt->size = len; elt->flags = 0; memcpy (ELT_KEY (elt), key, keylen + 1); memcpy (ELT_DATA (elt), value, len); elt->p = &elt->data; radix32tree_insert (cache->tree, rkey, 0xffffffff, (uintptr_t)elt); } return elt; } /** * Lookup an item inside radix */ static struct rspamd_kv_element* rspamd_kv_radix_lookup (struct rspamd_kv_cache *c, gpointer key, guint keylen) { struct rspamd_kv_radix_cache *cache = (struct rspamd_kv_radix_cache *)c; guint32 rkey = rspamd_kv_radix_validate (key, keylen); struct rspamd_kv_element *elt; elt = (struct rspamd_kv_element *)radix32tree_find (cache->tree, rkey); if ((uintptr_t)elt == RADIX_NO_VALUE) { return NULL; } return elt; } /** * Replace an element inside cache */ static gboolean rspamd_kv_radix_replace (struct rspamd_kv_cache *c, gpointer key, guint keylen, struct rspamd_kv_element *elt) { struct rspamd_kv_radix_cache *cache = (struct rspamd_kv_radix_cache *)c; guint32 rkey = rspamd_kv_radix_validate (key, keylen); struct rspamd_kv_element *oldelt; oldelt = (struct rspamd_kv_element *)radix32tree_find (cache->tree, rkey); if ((uintptr_t)oldelt != RADIX_NO_VALUE) { radix32tree_delete (cache->tree, rkey, 0xffffffff); if ((oldelt->flags & KV_ELT_DIRTY) != 0) { oldelt->flags |= KV_ELT_NEED_FREE; } else { /* Free it by self */ g_slice_free1 (ELT_SIZE (oldelt), oldelt); } radix32tree_insert (cache->tree, rkey, 0xffffffff, (uintptr_t)elt); return TRUE; } return FALSE; } /** * Delete an element from cache */ static struct rspamd_kv_element * rspamd_kv_radix_delete (struct rspamd_kv_cache *c, gpointer key, guint keylen) { struct rspamd_kv_radix_cache *cache = (struct rspamd_kv_radix_cache *)c; struct rspamd_kv_element *elt; guint32 rkey = rspamd_kv_radix_validate (key, keylen); elt = (struct rspamd_kv_element *)radix32tree_find (cache->tree, rkey); if ((uintptr_t)elt != RADIX_NO_VALUE) { radix32tree_delete (cache->tree, rkey, 0xffffffff); } else { return NULL; } return elt; } /** * Delete an element from cache */ static void rspamd_kv_radix_steal (struct rspamd_kv_cache *c, struct rspamd_kv_element *elt) { struct rspamd_kv_radix_cache *cache = (struct rspamd_kv_radix_cache *)c; guint32 rkey = rspamd_kv_radix_validate (ELT_KEY (elt), elt->keylen); radix32tree_delete (cache->tree, rkey, 0xffffffff); } /** * Destroy the whole cache */ static void rspamd_kv_radix_destroy (struct rspamd_kv_cache *c) { struct rspamd_kv_radix_cache *cache = (struct rspamd_kv_radix_cache *)c; radix_tree_free (cache->tree); g_slice_free1 (sizeof (struct rspamd_kv_radix_cache), cache); } /** * Create new radix kv cache */ struct rspamd_kv_cache* rspamd_kv_radix_new (void) { struct rspamd_kv_radix_cache *new; new = g_slice_alloc (sizeof (struct rspamd_kv_radix_cache)); new->tree = radix_tree_create (); new->init_func = NULL; new->insert_func = rspamd_kv_radix_insert; new->lookup_func = rspamd_kv_radix_lookup; new->replace_func = rspamd_kv_radix_replace; new->delete_func = rspamd_kv_radix_delete; new->steal_func = rspamd_kv_radix_steal; new->destroy_func = rspamd_kv_radix_destroy; return (struct rspamd_kv_cache *)new; } #ifdef WITH_JUDY /* * KV cache hash table */ struct rspamd_kv_judy_cache { cache_init init_func; /*< this callback is called on kv storage initialization */ cache_insert insert_func; /*< this callback is called when element is inserted */ cache_replace replace_func; /*< this callback is called when element is replace */ cache_lookup lookup_func; /*< this callback is used for lookup of element */ cache_delete delete_func; /*< this callback is called when an element is deleted */ cache_steal steal_func; /*< this callback is used to replace duplicates in cache */ cache_destroy destroy_func; /*< this callback is used for destroying all elements inside cache */ Pvoid_t judy; }; /** * Lookup an item inside judy */ static struct rspamd_kv_element* rspamd_kv_judy_lookup (struct rspamd_kv_cache *c, gpointer key, guint keylen) { struct rspamd_kv_judy_cache *cache = (struct rspamd_kv_judy_cache *)c; struct rspamd_kv_element *elt = NULL, **pelt; JHSG (pelt, cache->judy, key, keylen); if (pelt != NULL) { elt = *pelt; } return elt; } /** * Delete an element from cache */ static struct rspamd_kv_element * rspamd_kv_judy_delete (struct rspamd_kv_cache *c, gpointer key, guint keylen) { struct rspamd_kv_judy_cache *cache = (struct rspamd_kv_judy_cache *)c; struct rspamd_kv_element *elt; gint rc; elt = rspamd_kv_judy_lookup (c, key, keylen); if (elt) { JHSD (rc, cache->judy, ELT_KEY (elt), elt->keylen); } return elt; } /** * Steal an element from cache */ static void rspamd_kv_judy_steal (struct rspamd_kv_cache *c, struct rspamd_kv_element *elt) { struct rspamd_kv_judy_cache *cache = (struct rspamd_kv_judy_cache *)c; gint rc; JHSD (rc, cache->judy, ELT_KEY (elt), elt->keylen); } /** * Insert an element inside cache */ static struct rspamd_kv_element* rspamd_kv_judy_insert (struct rspamd_kv_cache *c, gpointer key, guint keylen, gpointer value, gsize len) { struct rspamd_kv_element *elt, **pelt; struct rspamd_kv_judy_cache *cache = (struct rspamd_kv_judy_cache *)c; if ((elt = rspamd_kv_judy_lookup (c, key, keylen)) == NULL) { elt = g_slice_alloc (sizeof (struct rspamd_kv_element) + len + keylen + 1); elt->age = time (NULL); elt->keylen = keylen; elt->size = len; elt->flags = 0; memcpy (ELT_KEY (elt), key, keylen); memcpy (ELT_DATA (elt), value, len); JHSI (pelt, cache->judy, ELT_KEY (elt), elt->keylen); elt->p = &elt->data; *pelt = elt; } else { rspamd_kv_judy_steal (c, elt); if ((elt->flags & KV_ELT_DIRTY) != 0) { elt->flags |= KV_ELT_NEED_FREE; } else { /* Free it by self */ g_slice_free1 (ELT_SIZE (elt), elt); } elt = g_slice_alloc0 (sizeof (struct rspamd_kv_element) + len + keylen + 1); elt->age = time (NULL); elt->keylen = keylen; elt->size = len; elt->flags = 0; memcpy (ELT_KEY (elt), key, keylen); memcpy (ELT_DATA (elt), value, len); elt->p = &elt->data; JHSI (pelt, cache->judy, ELT_KEY (elt), elt->keylen); *pelt = elt; } return elt; } /** * Replace an element inside cache */ static gboolean rspamd_kv_judy_replace (struct rspamd_kv_cache *c, gpointer key, guint keylen, struct rspamd_kv_element *elt) { struct rspamd_kv_judy_cache *cache = (struct rspamd_kv_judy_cache *)c; struct rspamd_kv_element *oldelt, **pelt; if ((oldelt = rspamd_kv_judy_lookup (c, key, keylen)) != NULL) { rspamd_kv_judy_steal (c, elt); if ((oldelt->flags & KV_ELT_DIRTY) != 0) { oldelt->flags |= KV_ELT_NEED_FREE; } else { /* Free it by self */ g_slice_free1 (ELT_SIZE (oldelt), oldelt); } JHSI (pelt, cache->judy, ELT_KEY (elt), elt->keylen); *pelt = elt; return TRUE; } return FALSE; } /** * Destroy the whole cache */ static void rspamd_kv_judy_destroy (struct rspamd_kv_cache *c) { struct rspamd_kv_judy_cache *cache = (struct rspamd_kv_judy_cache *)c; glong bytes; JHSFA (bytes, cache->judy); g_slice_free1 (sizeof (struct rspamd_kv_judy_cache), cache); } /** * Judy tree */ struct rspamd_kv_cache* rspamd_kv_judy_new (void) { struct rspamd_kv_judy_cache *new; new = g_slice_alloc (sizeof (struct rspamd_kv_judy_cache)); new->judy = NULL; new->init_func = NULL; new->insert_func = rspamd_kv_judy_insert; new->lookup_func = rspamd_kv_judy_lookup; new->replace_func = rspamd_kv_judy_replace; new->delete_func = rspamd_kv_judy_delete; new->steal_func = rspamd_kv_judy_steal; new->destroy_func = rspamd_kv_judy_destroy; return (struct rspamd_kv_cache *)new; } #endif