--- /dev/null
+/* 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 "config.h"
+#include "kvstorage.h"
+#include "kvstorage_file.h"
+#include "util.h"
+#include "main.h"
+
+struct file_op {
+ struct rspamd_kv_element *elt;
+ enum {
+ FILE_OP_INSERT,
+ FILE_OP_DELETE,
+ FILE_OP_REPLACE
+ } op;
+};
+
+/* Main file structure */
+struct rspamd_file_backend {
+ backend_init init_func; /*< this callback is called on kv storage initialization */
+ backend_insert insert_func; /*< this callback is called when element is inserted */
+ backend_replace replace_func; /*< this callback is called when element is replaced */
+ backend_lookup lookup_func; /*< this callback is used for lookup of element */
+ backend_delete delete_func; /*< this callback is called when an element is deleted */
+ backend_sync sync_func; /*< this callback is called when backend need to be synced */
+ backend_destroy destroy_func; /*< this callback is used for destroying all elements inside backend */
+ gchar *filename;
+ gchar *dirname;
+ guint sync_ops;
+ guint levels;
+ GQueue *ops_queue;
+ GHashTable *ops_hash;
+ gboolean initialized;
+};
+
+static const gchar hexdigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+/* Process single file operation */
+static gboolean
+file_process_single_op (struct rspamd_file_backend *db, struct file_op *op)
+{
+ gboolean res = FALSE;
+
+ return res;
+}
+
+/* Process operations queue */
+static gboolean
+file_process_queue (struct rspamd_kv_backend *backend)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+ struct file_op *op;
+ GList *cur;
+
+ cur = db->ops_queue->head;
+ while (cur) {
+ op = cur->data;
+ if (! file_process_single_op (db, op)) {
+ return FALSE;
+ }
+ cur = g_list_next (cur);
+ }
+
+ /* Clean the queue */
+ cur = db->ops_queue->head;
+ while (cur) {
+ op = cur->data;
+ if (op->op == FILE_OP_DELETE || (op->elt->flags & KV_ELT_NEED_FREE) != 0) {
+ /* Also clean memory */
+ g_slice_free1 (ELT_SIZE (op->elt), op->elt);
+ }
+ g_slice_free1 (sizeof (struct file_op), op);
+ cur = g_list_next (cur);
+ }
+
+ g_hash_table_remove_all (db->ops_hash);
+ g_queue_clear (db->ops_queue);
+
+ return TRUE;
+
+}
+
+
+/* Make 16 directories for each level */
+static gboolean
+rspamd_recursive_mkdir (guint levels)
+{
+ guint i, j;
+ gchar nbuf[5];
+
+ /* Create directories for backend */
+ if (levels > 0) {
+ /* Create 16 directories */
+ for (j = 0; j < 16; j ++) {
+ rspamd_snprintf (nbuf, sizeof (nbuf), "./%c", hexdigits[j]);
+ if (mkdir (nbuf, 0755) != 0 && errno != EEXIST) {
+ return FALSE;
+ }
+ else if (levels > 1) {
+ chdir (nbuf);
+ if (! rspamd_recursive_mkdir (levels - 1)) {
+ return FALSE;
+ }
+ chdir ("../");
+ }
+ }
+ }
+ return TRUE;
+
+}
+
+/* Backend callbacks */
+static void
+rspamd_file_init (struct rspamd_kv_backend *backend)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+ gint ret;
+ gchar pathbuf[PATH_MAX];
+
+ /* Save current directory */
+ if (getcwd (pathbuf, sizeof (pathbuf) - 1) == NULL) {
+ pathbuf[0] = '\0';
+ msg_err ("getcwd failed: %s", strerror (errno));
+ goto err;
+ }
+
+ /* Chdir to the working dir */
+ if (chdir (db->dirname) == -1) {
+ msg_err ("chdir failed: %s", strerror (errno));
+ goto err;
+ }
+
+ /* Create directories for backend */
+ rspamd_recursive_mkdir (db->levels);
+
+ db->initialized = TRUE;
+
+ chdir (pathbuf);
+ return;
+err:
+ if (pathbuf[0] != '\0') {
+ chdir (pathbuf);
+ }
+}
+
+static gboolean
+rspamd_file_insert (struct rspamd_kv_backend *backend, gpointer key, struct rspamd_kv_element *elt)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+ struct file_op *op;
+
+ if (!db->initialized) {
+ return FALSE;
+ }
+
+ if ((op = g_hash_table_lookup (db->ops_hash, key)) != NULL) {
+ /* We found another op with such key in this queue */
+ if (op->op == FILE_OP_DELETE || (op->elt->flags & KV_ELT_NEED_FREE) != 0) {
+ /* Also clean memory */
+ g_slice_free1 (ELT_SIZE (op->elt), op->elt);
+ }
+ op->op = FILE_OP_INSERT;
+ op->elt = elt;
+ }
+ else {
+ op = g_slice_alloc (sizeof (struct file_op));
+ op->op = FILE_OP_INSERT;
+ op->elt = elt;
+ elt->flags |= KV_ELT_DIRTY;
+
+ g_queue_push_head (db->ops_queue, op);
+ g_hash_table_insert (db->ops_hash, ELT_KEY (elt), op);
+ }
+
+ if (db->sync_ops > 0 && g_queue_get_length (db->ops_queue) >= db->sync_ops) {
+ return file_process_queue (backend);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+rspamd_file_replace (struct rspamd_kv_backend *backend, gpointer key, struct rspamd_kv_element *elt)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+ struct file_op *op;
+
+ if (!db->initialized) {
+ return FALSE;
+ }
+ if ((op = g_hash_table_lookup (db->ops_hash, key)) != NULL) {
+ /* We found another op with such key in this queue */
+ if (op->op == FILE_OP_DELETE || (op->elt->flags & KV_ELT_NEED_FREE) != 0) {
+ /* Also clean memory */
+ g_slice_free1 (ELT_SIZE (op->elt), op->elt);
+ }
+ op->op = FILE_OP_REPLACE;
+ op->elt = elt;
+ }
+ else {
+ op = g_slice_alloc (sizeof (struct file_op));
+ op->op = FILE_OP_REPLACE;
+ op->elt = elt;
+ elt->flags |= KV_ELT_DIRTY;
+
+ g_queue_push_head (db->ops_queue, op);
+ g_hash_table_insert (db->ops_hash, ELT_KEY (elt), op);
+ }
+
+ if (db->sync_ops > 0 && g_queue_get_length (db->ops_queue) >= db->sync_ops) {
+ return file_process_queue (backend);
+ }
+
+ return TRUE;
+}
+
+static struct rspamd_kv_element*
+rspamd_file_lookup (struct rspamd_kv_backend *backend, gpointer key)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+ struct file_op *op;
+ struct rspamd_kv_element *elt = NULL;
+ gint l;
+ gconstpointer d;
+
+ if (!db->initialized) {
+ return NULL;
+ }
+ /* First search in ops queue */
+ if ((op = g_hash_table_lookup (db->ops_hash, key)) != NULL) {
+ if (op->op == FILE_OP_DELETE) {
+ /* To delete, so assume it as not found */
+ return NULL;
+ }
+ return op->elt;
+ }
+ return elt;
+}
+
+static void
+rspamd_file_delete (struct rspamd_kv_backend *backend, gpointer key)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+ struct file_op *op;
+ struct rspamd_kv_element *elt;
+
+ if (!db->initialized) {
+ return;
+ }
+
+ if ((op = g_hash_table_lookup (db->ops_hash, key)) != NULL) {
+ op->op = FILE_OP_DELETE;
+ return;
+ }
+
+ elt = rspamd_file_lookup (backend, key);
+ if (elt == NULL) {
+ return;
+ }
+ op = g_slice_alloc (sizeof (struct file_op));
+ op->op = FILE_OP_DELETE;
+ op->elt = elt;
+ elt->flags |= KV_ELT_DIRTY;
+
+ g_queue_push_head (db->ops_queue, op);
+ g_hash_table_insert (db->ops_hash, ELT_KEY(elt), op);
+
+ if (db->sync_ops > 0 && g_queue_get_length (db->ops_queue) >= db->sync_ops) {
+ file_process_queue (backend);
+ }
+
+ return;
+}
+
+static void
+rspamd_file_destroy (struct rspamd_kv_backend *backend)
+{
+ struct rspamd_file_backend *db = (struct rspamd_file_backend *)backend;
+
+ if (db->initialized) {
+ file_process_queue (backend);
+ g_free (db->filename);
+ g_free (db->dirname);
+ g_queue_free (db->ops_queue);
+ g_hash_table_unref (db->ops_hash);
+ g_slice_free1 (sizeof (struct rspamd_file_backend), db);
+ }
+}
+
+/* Create new file backend */
+struct rspamd_kv_backend *
+rspamd_kv_file_new (const gchar *filename, guint sync_ops, guint levels)
+{
+ struct rspamd_file_backend *new;
+ struct stat st;
+ gchar *dirname;
+
+ if (filename == NULL) {
+ return NULL;
+ }
+
+ dirname = g_path_get_dirname (filename);
+ if (dirname == NULL || stat (dirname, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ /* Inaccessible path */
+ if (dirname != NULL) {
+ g_free (dirname);
+ }
+ msg_err ("invalid file: %s", filename);
+ return NULL;
+ }
+
+ new = g_slice_alloc0 (sizeof (struct rspamd_file_backend));
+ new->dirname = dirname;
+ new->filename = g_strdup (filename);
+ new->sync_ops = sync_ops;
+ new->levels = levels;
+ new->ops_queue = g_queue_new ();
+ new->ops_hash = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal);
+
+ /* Init callbacks */
+ new->init_func = rspamd_file_init;
+ new->insert_func = rspamd_file_insert;
+ new->lookup_func = rspamd_file_lookup;
+ new->delete_func = rspamd_file_delete;
+ new->replace_func = rspamd_file_replace;
+ new->sync_func = file_process_queue;
+ new->destroy_func = rspamd_file_destroy;
+
+ return (struct rspamd_kv_backend *)new;
+}
+