/* Copyright (c) 2010, 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 "kvstorage_config.h" #include "main.h" #include "cfg_xml.h" #ifdef WITH_DB #include "kvstorage_bdb.h" #endif #ifdef WITH_SQLITE #include "kvstorage_sqlite.h" #endif #include "kvstorage_file.h" #define FILE_STORAGE_LEVELS 3 /* Global hash of storages indexed by id */ GHashTable *storages = NULL; /* Last used id for explicit numbering */ gint last_id = 0; struct kvstorage_config_parser { enum { KVSTORAGE_STATE_INIT, KVSTORAGE_STATE_PARAM, KVSTORAGE_STATE_BACKEND, KVSTORAGE_STATE_EXPIRE, KVSTORAGE_STATE_ID, KVSTORAGE_STATE_NAME, KVSTORAGE_STATE_CACHE_TYPE, KVSTORAGE_STATE_CACHE_MAX_ELTS, KVSTORAGE_STATE_CACHE_MAX_MEM, KVSTORAGE_STATE_CACHE_NO_OVERWRITE, KVSTORAGE_STATE_BACKEND_TYPE, KVSTORAGE_STATE_BACKEND_FILENAME, KVSTORAGE_STATE_BACKEND_SYNC_OPS, KVSTORAGE_STATE_BACKEND_DO_FSYNC, KVSTORAGE_STATE_BACKEND_DO_REF, KVSTORAGE_STATE_EXPIRE_TYPE, KVSTORAGE_STATE_ERROR } state; struct kvstorage_config *current_storage; rspamd_mempool_t *pool; gchar *cur_elt; }; static void kvstorage_config_destroy (gpointer k) { struct kvstorage_config *kconf = k; if (kconf->name) { g_free (kconf->name); } if (kconf->storage) { rspamd_kv_storage_destroy (kconf->storage); } g_free (kconf); } /* Init kvstorage */ static void kvstorage_init_callback (const gpointer key, const gpointer value, gpointer unused) { struct kvstorage_config *kconf = value; struct rspamd_kv_cache *cache; struct rspamd_kv_backend *backend = NULL; struct rspamd_kv_expire *expire = NULL; switch (kconf->cache.type) { case KVSTORAGE_TYPE_CACHE_HASH: cache = rspamd_kv_hash_new (); break; case KVSTORAGE_TYPE_CACHE_RADIX: cache = rspamd_kv_radix_new (); break; #ifdef WITH_JUDY case KVSTORAGE_TYPE_CACHE_JUDY: cache = rspamd_kv_judy_new (); break; #endif default: msg_err ("unknown cache type, internal error"); return; } switch (kconf->backend.type) { case KVSTORAGE_TYPE_BACKEND_NULL: case KVSTORAGE_TYPE_BACKEND_MAX: backend = NULL; break; case KVSTORAGE_TYPE_BACKEND_FILE: backend = rspamd_kv_file_new (kconf->backend.filename, kconf->backend.sync_ops, FILE_STORAGE_LEVELS, kconf->backend.do_fsync, kconf->backend.do_ref); break; #ifdef WITH_DB case KVSTORAGE_TYPE_BACKEND_BDB: backend = rspamd_kv_bdb_new (kconf->backend.filename, kconf->backend.sync_ops); break; #endif #ifdef WITH_SQLITE case KVSTORAGE_TYPE_BACKEND_SQLITE: backend = rspamd_kv_sqlite_new (kconf->backend.filename, kconf->backend.sync_ops); break; #endif } switch (kconf->expire.type) { case KVSTORAGE_TYPE_EXPIRE_LRU: expire = rspamd_lru_expire_new (); break; } kconf->storage = rspamd_kv_storage_new (kconf->id, kconf->name, cache, backend, expire, kconf->cache.max_elements, kconf->cache.max_memory, kconf->cache.no_overwrite); } /* XML parse callbacks */ /* Called for open tags */ void kvstorage_xml_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { struct kvstorage_config_parser *kv_parser = user_data; switch (kv_parser->state) { case KVSTORAGE_STATE_INIT: /* XXX: never get this state */ break; case KVSTORAGE_STATE_PARAM: if (kv_parser->current_storage == NULL) { /* Make temporary pool */ if (kv_parser->pool != NULL) { rspamd_mempool_delete (kv_parser->pool); } kv_parser->pool = rspamd_mempool_new (rspamd_mempool_suggest_size ()); /* Create new kvstorage_config */ kv_parser->current_storage = g_malloc0 (sizeof (struct kvstorage_config)); kv_parser->current_storage->id = last_id++; } if (g_ascii_strcasecmp (element_name, "type") == 0) { kv_parser->state = KVSTORAGE_STATE_CACHE_TYPE; kv_parser->cur_elt = "type"; } else if (g_ascii_strcasecmp (element_name, "max_elements") == 0) { kv_parser->state = KVSTORAGE_STATE_CACHE_MAX_ELTS; kv_parser->cur_elt = "max_elements"; } else if (g_ascii_strcasecmp (element_name, "max_memory") == 0) { kv_parser->state = KVSTORAGE_STATE_CACHE_MAX_MEM; kv_parser->cur_elt = "max_memory"; } else if (g_ascii_strcasecmp (element_name, "no_overwrite") == 0) { kv_parser->state = KVSTORAGE_STATE_CACHE_NO_OVERWRITE; kv_parser->cur_elt = "no_overwrite"; } else if (g_ascii_strcasecmp (element_name, "id") == 0) { kv_parser->state = KVSTORAGE_STATE_ID; kv_parser->cur_elt = "id"; } else if (g_ascii_strcasecmp (element_name, "name") == 0) { kv_parser->state = KVSTORAGE_STATE_NAME; kv_parser->cur_elt = "name"; } else if (g_ascii_strcasecmp (element_name, "backend") == 0) { kv_parser->state = KVSTORAGE_STATE_BACKEND; kv_parser->cur_elt = "backend"; } else if (g_ascii_strcasecmp (element_name, "expire") == 0) { kv_parser->state = KVSTORAGE_STATE_EXPIRE; kv_parser->cur_elt = "expire"; } else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is unexpected", element_name); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; case KVSTORAGE_STATE_BACKEND: if (g_ascii_strcasecmp (element_name, "type") == 0) { kv_parser->state = KVSTORAGE_STATE_BACKEND_TYPE; kv_parser->cur_elt = "type"; } else if (g_ascii_strcasecmp (element_name, "filename") == 0) { kv_parser->state = KVSTORAGE_STATE_BACKEND_FILENAME; kv_parser->cur_elt = "filename"; } else if (g_ascii_strcasecmp (element_name, "sync_ops") == 0) { kv_parser->state = KVSTORAGE_STATE_BACKEND_SYNC_OPS; kv_parser->cur_elt = "sync_ops"; } else if (g_ascii_strcasecmp (element_name, "fsync") == 0) { kv_parser->state = KVSTORAGE_STATE_BACKEND_DO_FSYNC; kv_parser->cur_elt = "fsync"; } else if (g_ascii_strcasecmp (element_name, "ref") == 0) { kv_parser->state = KVSTORAGE_STATE_BACKEND_DO_REF; kv_parser->cur_elt = "ref"; } else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is unexpected in backend definition", element_name); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; case KVSTORAGE_STATE_EXPIRE: if (g_ascii_strcasecmp (element_name, "type") == 0) { kv_parser->state = KVSTORAGE_STATE_EXPIRE_TYPE; kv_parser->cur_elt = "type"; } else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is unexpected in expire definition", element_name); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; default: /* Do nothing at other states */ break; } } #define CHECK_TAG(s) \ do { \ if (g_ascii_strcasecmp (element_name, kv_parser->cur_elt) == 0) { \ kv_parser->state = (s); \ } \ else { \ if (*error == NULL) *error = g_error_new ( \ xml_error_quark (), XML_UNMATCHED_TAG, "element %s is unexpected in this state, expected %s", element_name, \ kv_parser->cur_elt); \ kv_parser->state = KVSTORAGE_STATE_ERROR; \ } \ } while (0) /* Called for close tags */ void kvstorage_xml_end_element (GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error) { struct kvstorage_config_parser *kv_parser = user_data; switch (kv_parser->state) { case KVSTORAGE_STATE_INIT: case KVSTORAGE_STATE_PARAM: if (g_ascii_strcasecmp (element_name, "keystorage") == 0) { /* XXX: Init actual storage */ return; } if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "end element %s is unexpected, expected start element", element_name); } kv_parser->state = KVSTORAGE_STATE_ERROR; break; case KVSTORAGE_STATE_ID: case KVSTORAGE_STATE_NAME: case KVSTORAGE_STATE_CACHE_TYPE: case KVSTORAGE_STATE_CACHE_MAX_ELTS: case KVSTORAGE_STATE_CACHE_MAX_MEM: case KVSTORAGE_STATE_CACHE_NO_OVERWRITE: CHECK_TAG (KVSTORAGE_STATE_PARAM); break; case KVSTORAGE_STATE_BACKEND_TYPE: case KVSTORAGE_STATE_BACKEND_FILENAME: case KVSTORAGE_STATE_BACKEND_SYNC_OPS: case KVSTORAGE_STATE_BACKEND_DO_FSYNC: case KVSTORAGE_STATE_BACKEND_DO_REF: CHECK_TAG (KVSTORAGE_STATE_BACKEND); break; case KVSTORAGE_STATE_EXPIRE_TYPE: CHECK_TAG (KVSTORAGE_STATE_EXPIRE); break; case KVSTORAGE_STATE_BACKEND: if (g_ascii_strcasecmp (element_name, "backend") == 0) { kv_parser->state = KVSTORAGE_STATE_PARAM; } else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is unexpected", element_name); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; case KVSTORAGE_STATE_EXPIRE: if (g_ascii_strcasecmp (element_name, "expire") == 0) { kv_parser->state = KVSTORAGE_STATE_PARAM; } else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "element %s is unexpected", element_name); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; default: /* Do nothing at other states */ break; } } #undef CHECK_TAG /* text is not nul-terminated */ void kvstorage_xml_text (GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, GError **error) { struct kvstorage_config_parser *kv_parser = user_data; gchar *err_str; /* Strip space symbols */ while (*text && g_ascii_isspace (*text)) { text++; } if (*text == '\0') { /* Skip empty text */ return; } switch (kv_parser->state) { case KVSTORAGE_STATE_INIT: case KVSTORAGE_STATE_PARAM: if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "text is unexpected, expected start element"); } kv_parser->state = KVSTORAGE_STATE_ERROR; break; case KVSTORAGE_STATE_ID: kv_parser->current_storage->id = strtoul (text, &err_str, 10); if ((gsize)(err_str - text) != text_len) { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "invalid number: %*s", (int)text_len, text); } kv_parser->state = KVSTORAGE_STATE_ERROR; } else { last_id++; last_id = MAX (kv_parser->current_storage->id, last_id); } break; case KVSTORAGE_STATE_NAME: kv_parser->current_storage->name = g_malloc (text_len + 1); rspamd_strlcpy (kv_parser->current_storage->name, text, text_len + 1); break; case KVSTORAGE_STATE_CACHE_MAX_ELTS: kv_parser->current_storage->cache.max_elements = rspamd_config_parse_limit (text, text_len); break; case KVSTORAGE_STATE_CACHE_MAX_MEM: kv_parser->current_storage->cache.max_memory = rspamd_config_parse_limit (text, text_len); break; case KVSTORAGE_STATE_CACHE_NO_OVERWRITE: kv_parser->current_storage->cache.no_overwrite = rspamd_config_parse_flag (text); break; case KVSTORAGE_STATE_CACHE_TYPE: if (g_ascii_strncasecmp (text, "hash", MIN (text_len, sizeof ("hash") - 1)) == 0) { kv_parser->current_storage->cache.type = KVSTORAGE_TYPE_CACHE_HASH; } else if (g_ascii_strncasecmp (text, "radix", MIN (text_len, sizeof ("radix") - 1)) == 0) { kv_parser->current_storage->cache.type = KVSTORAGE_TYPE_CACHE_RADIX; } #ifdef WITH_JUDY else if (g_ascii_strncasecmp (text, "judy", MIN (text_len, sizeof ("judy") - 1)) == 0) { kv_parser->current_storage->cache.type = KVSTORAGE_TYPE_CACHE_JUDY; } #endif else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "invalid cache type: %*s", (int)text_len, text); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; case KVSTORAGE_STATE_BACKEND_TYPE: if (g_ascii_strncasecmp (text, "null", MIN (text_len, sizeof ("null") - 1)) == 0) { kv_parser->current_storage->backend.type = KVSTORAGE_TYPE_BACKEND_NULL; } else if (g_ascii_strncasecmp (text, "file", MIN (text_len, sizeof ("file") - 1)) == 0) { kv_parser->current_storage->backend.type = KVSTORAGE_TYPE_BACKEND_FILE; } #ifdef WITH_DB else if (g_ascii_strncasecmp (text, "bdb", MIN (text_len, sizeof ("bdb") - 1)) == 0) { kv_parser->current_storage->backend.type = KVSTORAGE_TYPE_BACKEND_BDB; } #endif #ifdef WITH_SQLITE else if (g_ascii_strncasecmp (text, "sqlite", MIN (text_len, sizeof ("sqlite") - 1)) == 0) { kv_parser->current_storage->backend.type = KVSTORAGE_TYPE_BACKEND_SQLITE; } #endif else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "invalid backend type: %*s", (int)text_len, text); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; case KVSTORAGE_STATE_BACKEND_FILENAME: kv_parser->current_storage->backend.filename = g_malloc (text_len + 1); rspamd_strlcpy (kv_parser->current_storage->backend.filename, text, text_len + 1); break; case KVSTORAGE_STATE_BACKEND_SYNC_OPS: kv_parser->current_storage->backend.sync_ops = rspamd_config_parse_limit (text, text_len); break; case KVSTORAGE_STATE_BACKEND_DO_FSYNC: kv_parser->current_storage->backend.do_fsync = rspamd_config_parse_flag (text); break; case KVSTORAGE_STATE_BACKEND_DO_REF: kv_parser->current_storage->backend.do_ref = rspamd_config_parse_flag ( text); break; case KVSTORAGE_STATE_EXPIRE_TYPE: if (g_ascii_strncasecmp (text, "lru", MIN (text_len, sizeof ("lru") - 1)) == 0) { kv_parser->current_storage->expire.type = KVSTORAGE_TYPE_EXPIRE_LRU; } else { if (*error == NULL) { *error = g_error_new ( xml_error_quark (), XML_EXTRA_ELEMENT, "invalid expire type: %*s", (int)text_len, text); } kv_parser->state = KVSTORAGE_STATE_ERROR; } break; default: /* Do nothing at other states */ break; } } /* Called on error, including one set by other * methods in the vtable. The GError should not be freed. */ void kvstorage_xml_error (GMarkupParseContext *context, GError *error, gpointer user_data) { msg_err ("kvstorage xml parser error: %s", error->message); } /* * Cleanup kvstorage after end tag was read */ static void kvstorage_cleanup (gpointer ud) { struct kvstorage_config_parser *kv_parser = ud; g_hash_table_insert (storages, &kv_parser->current_storage->id, kv_parser->current_storage); kv_parser->state = KVSTORAGE_STATE_INIT; g_hash_table_foreach (storages, kvstorage_init_callback, NULL); kv_parser->current_storage = NULL; } /** Public API */ /* Init subparser of kvstorage config */ void init_kvstorage_config (void) { GMarkupParser *parser; struct kvstorage_config_parser *kv_parser; if (storages == NULL) { storages = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, kvstorage_config_destroy); } else { /* Create new global table */ g_hash_table_destroy (storages); storages = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, kvstorage_config_destroy); } /* Create and register subparser */ parser = g_malloc0 (sizeof (GMarkupParser)); parser->start_element = kvstorage_xml_start_element; parser->end_element = kvstorage_xml_end_element; parser->error = kvstorage_xml_error; parser->text = kvstorage_xml_text; kv_parser = g_malloc0 (sizeof (struct kvstorage_config_parser)); kv_parser->state = KVSTORAGE_STATE_PARAM; kv_parser->pool = rspamd_mempool_new (rspamd_mempool_suggest_size ()); register_subparser ("keystorage", 0, parser, kvstorage_cleanup, kv_parser); } /* Get configuration for kvstorage with specified ID */ struct kvstorage_config * get_kvstorage_config (gint id) { if (storages == NULL) { return NULL; } return g_hash_table_lookup (storages, &id); } void destroy_kvstorage_config (void) { if (storages != NULL) { g_hash_table_destroy (storages); storages = NULL; } }