aboutsummaryrefslogtreecommitdiffstats
path: root/camel/camel-block-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/camel-block-file.c')
-rw-r--r--camel/camel-block-file.c1124
1 files changed, 1124 insertions, 0 deletions
diff --git a/camel/camel-block-file.c b/camel/camel-block-file.c
new file mode 100644
index 0000000000..0340f9ecdf
--- /dev/null
+++ b/camel/camel-block-file.c
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (C) 2001 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "e-util/e-msgport.h"
+
+#include "camel-block-file.h"
+
+#ifdef ENABLE_THREADS
+#include <pthread.h>
+#endif
+
+#define d(x) /*(printf("%s(%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
+
+#ifdef ENABLE_THREADS
+
+/* Locks must be obtained in the order defined */
+
+struct _CamelBlockFilePrivate {
+ /* We use the private structure to form our lru list from */
+ struct _CamelBlockFilePrivate *next;
+ struct _CamelBlockFilePrivate *prev;
+
+ struct _CamelBlockFile *base;
+
+ pthread_mutex_t root_lock; /* for modifying the root block */
+ pthread_mutex_t cache_lock; /* for refcounting, flag manip, cache manip */
+ pthread_mutex_t io_lock; /* for all io ops */
+};
+
+#define CAMEL_BLOCK_FILE_LOCK(kf, lock) (pthread_mutex_lock(&(kf)->priv->lock))
+#define CAMEL_BLOCK_FILE_TRYLOCK(kf, lock) (pthread_mutex_trylock(&(kf)->priv->lock))
+#define CAMEL_BLOCK_FILE_UNLOCK(kf, lock) (pthread_mutex_unlock(&(kf)->priv->lock))
+
+#define LOCK(x) pthread_mutex_lock(&x)
+#define UNLOCK(x) pthread_mutex_unlock(&x)
+
+static pthread_mutex_t block_file_lock = PTHREAD_MUTEX_INITIALIZER;
+
+#else
+#define CAMEL_BLOCK_FILE_LOCK(kf, lock)
+#define CAMEL_BLOCK_FILE_TRYLOCK(kf, lock)
+#define CAMEL_BLOCK_FILE_UNLOCK(kf, lock)
+#define LOCK(x)
+#define UNLOCK(x)
+#endif
+
+/* lru cache of block files */
+static EDList block_file_list = E_DLIST_INITIALISER(block_file_list);
+/* list to store block files that are actually intialised */
+static EDList block_file_active_list = E_DLIST_INITIALISER(block_file_active_list);
+static int block_file_count = 0;
+static int block_file_threshhold = 10;
+
+#define CBF_CLASS(o) ((CamelBlockFileClass *)(((CamelObject *)o)->classfuncs))
+
+static int sync_nolock(CamelBlockFile *bs);
+static int sync_block_nolock(CamelBlockFile *bs, CamelBlock *bl);
+
+static int
+block_file_validate_root(CamelBlockFile *bs)
+{
+ struct stat st;
+ CamelBlockRoot *br;
+
+ br = bs->root;
+
+ fstat(bs->fd, &st);
+
+ d(printf("Validate root:\n"));
+ d(printf("version: %.8s (%.8s)\n", bs->root->version, bs->version));
+ d(printf("block size: %d (%d)\n", br->block_size, bs->block_size));
+ d(printf("free: %d (%d add size < %d)\n", br->free, br->free / bs->block_size * bs->block_size, (int)st.st_size));
+ d(printf("last: %d (%d and size: %d)\n", br->free, br->free / bs->block_size * bs->block_size, (int)st.st_size));
+
+ if (br->last == 0
+ || memcmp(bs->root->version, bs->version, 8) != 0
+ || br->block_size != bs->block_size
+ || (br->free % bs->block_size) != 0
+ || (br->last % bs->block_size) != 0
+ || fstat(bs->fd, &st) == -1
+ || st.st_size != br->last
+ || br->free > st.st_size
+ || (br->flags & CAMEL_BLOCK_FILE_SYNC) == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+block_file_init_root(CamelBlockFile *bs)
+{
+ CamelBlockRoot *br = bs->root;
+
+ memset(br, 0, bs->block_size);
+ memcpy(br->version, bs->version, 8);
+ br->last = bs->block_size;
+ br->flags = CAMEL_BLOCK_FILE_SYNC;
+ br->free = 0;
+ br->block_size = bs->block_size;
+
+ return 0;
+}
+
+static void
+camel_block_file_class_init(CamelBlockFileClass *klass)
+{
+ klass->validate_root = block_file_validate_root;
+ klass->init_root = block_file_init_root;
+}
+
+static guint
+block_hash_func(const void *v)
+{
+ return ((camel_block_t)v) >> CAMEL_BLOCK_SIZE_BITS;
+}
+
+static void
+camel_block_file_init(CamelBlockFile *bs)
+{
+ struct _CamelBlockFilePrivate *p;
+
+ bs->fd = -1;
+ bs->block_size = CAMEL_BLOCK_SIZE;
+ e_dlist_init(&bs->block_cache);
+ bs->blocks = g_hash_table_new((GHashFunc)block_hash_func, NULL);
+ /* this cache size and the text index size have been tuned for about the best
+ with moderate memory usage. Doubling the memory usage barely affects performance. */
+ bs->block_cache_limit = 256;
+
+ p = bs->priv = g_malloc0(sizeof(*bs->priv));
+ p->base = bs;
+
+#ifdef ENABLE_THREADS
+ pthread_mutex_init(&p->root_lock, NULL);
+ pthread_mutex_init(&p->cache_lock, NULL);
+ pthread_mutex_init(&p->io_lock, NULL);
+#endif
+
+ /* link into lru list */
+ LOCK(block_file_lock);
+ e_dlist_addhead(&block_file_list, (EDListNode *)p);
+
+#if 0
+ {
+ printf("dumping block list\n");
+ printf(" head = %p p = %p\n", block_file_list.head, p);
+ p = block_file_list.head;
+ while (p->next) {
+ printf(" '%s'\n", p->base->path);
+ p = p->next;
+ }
+ }
+#endif
+
+ UNLOCK(block_file_lock);
+}
+
+static void
+camel_block_file_finalise(CamelBlockFile *bs)
+{
+ CamelBlock *bl, *bn;
+ struct _CamelBlockFilePrivate *p;
+
+ p = bs->priv;
+
+ if (bs->root_block)
+ camel_block_file_sync(bs);
+
+ /* remove from lru list */
+ LOCK(block_file_lock);
+ if (bs->fd != -1)
+ block_file_count--;
+ e_dlist_remove((EDListNode *)p);
+ UNLOCK(block_file_lock);
+
+ bl = (CamelBlock *)bs->block_cache.head;
+ bn = bl->next;
+ while (bn) {
+ if (bl->refcount != 0)
+ g_warning("Block '%d' still referenced", bl->id);
+ g_free(bl);
+ bl = bn;
+ bn = bn->next;
+ }
+
+ if (bs->root_block)
+ camel_block_file_unref_block(bs, bs->root_block);
+ g_free(bs->path);
+ close(bs->fd);
+
+#ifdef ENABLE_THREADS
+ pthread_mutex_destroy(&p->io_lock);
+ pthread_mutex_destroy(&p->cache_lock);
+ pthread_mutex_destroy(&p->root_lock);
+#endif
+ g_free(p);
+}
+
+CamelType
+camel_block_file_get_type(void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register(camel_object_get_type(), "CamelBlockFile",
+ sizeof (CamelBlockFile),
+ sizeof (CamelBlockFileClass),
+ (CamelObjectClassInitFunc) camel_block_file_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_block_file_init,
+ (CamelObjectFinalizeFunc) camel_block_file_finalise);
+ }
+
+ return type;
+}
+
+/* 'use' a block file for io */
+static int
+block_file_use(CamelBlockFile *bs)
+{
+ struct _CamelBlockFilePrivate *nw, *nn, *p = bs->priv;
+ CamelBlockFile *bf;
+ int err;
+
+ /* We want to:
+ remove file from active list
+ lock it
+
+ Then when done:
+ unlock it
+ add it back to end of active list
+ */
+
+ CAMEL_BLOCK_FILE_LOCK(bs, io_lock);
+
+ if (bs->fd != -1)
+ return 0;
+ else
+ d(printf("Turning block file online: %s\n", bs->path));
+
+ if ((bs->fd = open(bs->path, bs->flags, 0600)) == -1) {
+ err = errno;
+ CAMEL_BLOCK_FILE_UNLOCK(bs, io_lock);
+ errno = err;
+ return -1;
+ }
+
+ LOCK(block_file_lock);
+ e_dlist_remove((EDListNode *)p);
+ e_dlist_addtail(&block_file_active_list, (EDListNode *)p);
+
+ block_file_count++;
+
+ nw = (struct _CamelBlockFilePrivate *)block_file_list.head;
+ nn = nw->next;
+ while (block_file_count > block_file_threshhold && nn) {
+ /* We never hit the current blockfile here, as its removed from the list first */
+ bf = nw->base;
+ if (bf->fd != -1) {
+ /* Need to trylock, as any of these lock levels might be trying
+ to lock the block_file_lock, so we need to check and abort if so */
+ if (CAMEL_BLOCK_FILE_TRYLOCK(bf, root_lock) == 0) {
+ if (CAMEL_BLOCK_FILE_TRYLOCK(bf, cache_lock) == 0) {
+ if (CAMEL_BLOCK_FILE_TRYLOCK(bf, io_lock) == 0) {
+ d(printf("[%d] Turning block file offline: %s\n", block_file_count-1, bf->path));
+ sync_nolock(bf);
+ close(bf->fd);
+ bf->fd = -1;
+ block_file_count--;
+ CAMEL_BLOCK_FILE_UNLOCK(bf, io_lock);
+ }
+ CAMEL_BLOCK_FILE_UNLOCK(bf, cache_lock);
+ }
+ CAMEL_BLOCK_FILE_UNLOCK(bf, root_lock);
+ }
+ }
+ nw = nn;
+ nn = nw->next;
+ }
+
+ UNLOCK(block_file_lock);
+
+ return 0;
+}
+
+static void
+block_file_unuse(CamelBlockFile *bs)
+{
+ LOCK(block_file_lock);
+ e_dlist_remove((EDListNode *)bs->priv);
+ e_dlist_addtail(&block_file_list, (EDListNode *)bs->priv);
+ UNLOCK(block_file_lock);
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, io_lock);
+}
+
+/*
+o = camel_cache_get(c, key);
+camel_cache_unref(c, key);
+camel_cache_add(c, key, o);
+camel_cache_remove(c, key);
+*/
+
+/**
+ * camel_block_file_new:
+ * @path:
+ * @:
+ * @block_size:
+ *
+ * Allocate a new block file, stored at @path. @version contains an 8 character
+ * version string which must match the head of the file, or the file will be
+ * intitialised.
+ *
+ * @block_size is currently ignored and is set to CAMEL_BLOCK_SIZE.
+ *
+ * Return value: The new block file, or NULL if it could not be created.
+ **/
+CamelBlockFile *camel_block_file_new(const char *path, int flags, const char version[8], size_t block_size)
+{
+ CamelBlockFile *bs;
+
+ bs = (CamelBlockFile *)camel_object_new(camel_block_file_get_type());
+ memcpy(bs->version, version, 8);
+ bs->path = g_strdup(path);
+ bs->flags = flags;
+
+ bs->root_block = camel_block_file_get_block(bs, 0);
+ if (bs->root_block == NULL) {
+ camel_object_unref((CamelObject *)bs);
+ return NULL;
+ }
+ camel_block_file_detach_block(bs, bs->root_block);
+ bs->root = (CamelBlockRoot *)&bs->root_block->data;
+
+ /* we only need these flags on first open */
+ bs->flags &= ~(O_CREAT|O_EXCL|O_TRUNC);
+
+ /* Do we need to init the root block? */
+ if (CBF_CLASS(bs)->validate_root(bs) == -1) {
+ d(printf("Initialise root block: %.8s\n", version));
+
+ CBF_CLASS(bs)->init_root(bs);
+ camel_block_file_touch_block(bs, bs->root_block);
+ if (block_file_use(bs) == -1) {
+ camel_object_unref((CamelObject *)bs);
+ return NULL;
+ }
+ if (sync_block_nolock(bs, bs->root_block) == -1
+ || ftruncate(bs->fd, bs->root->last) == -1) {
+ block_file_unuse(bs);
+ camel_object_unref((CamelObject *)bs);
+ return NULL;
+ }
+ block_file_unuse(bs);
+ }
+
+ return bs;
+}
+
+int
+camel_block_file_rename(CamelBlockFile *bs, const char *path)
+{
+ int ret;
+ struct stat st;
+ int err;
+
+ CAMEL_BLOCK_FILE_LOCK(bs, io_lock);
+
+ ret = rename(bs->path, path);
+ if (ret == -1) {
+ /* Maybe the rename actually worked */
+ err = errno;
+ if (stat(path, &st) == 0
+ && stat(bs->path, &st) == -1
+ && errno == ENOENT)
+ ret = 0;
+ errno = err;
+ }
+
+ if (ret != -1) {
+ g_free(bs->path);
+ bs->path = g_strdup(path);
+ }
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, io_lock);
+
+ return ret;
+}
+
+/**
+ * camel_block_file_new_block:
+ * @bs:
+ *
+ * Allocate a new block, return a pointer to it. Old blocks
+ * may be flushed to disk during this call.
+ *
+ * Return value: The block, or NULL if an error occured.
+ **/
+CamelBlock *camel_block_file_new_block(CamelBlockFile *bs)
+{
+ CamelBlock *bl;
+
+ CAMEL_BLOCK_FILE_LOCK(bs, root_lock);
+
+ if (bs->root->free) {
+ bl = camel_block_file_get_block(bs, bs->root->free);
+ if (bl == NULL)
+ goto fail;
+ bs->root->free = ((camel_block_t *)bl->data)[0];
+ } else {
+ bl = camel_block_file_get_block(bs, bs->root->last);
+ if (bl == NULL)
+ goto fail;
+ bs->root->last += CAMEL_BLOCK_SIZE;
+ }
+
+ bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
+
+ bl->flags |= CAMEL_BLOCK_DIRTY;
+ memset(bl->data, 0, CAMEL_BLOCK_SIZE);
+fail:
+ CAMEL_BLOCK_FILE_UNLOCK(bs, root_lock);
+
+ return bl;
+}
+
+/**
+ * camel_block_file_free_block:
+ * @bs:
+ * @id:
+ *
+ *
+ **/
+int camel_block_file_free_block(CamelBlockFile *bs, camel_block_t id)
+{
+ CamelBlock *bl;
+
+ bl = camel_block_file_get_block(bs, id);
+ if (bl == NULL)
+ return -1;
+
+ CAMEL_BLOCK_FILE_LOCK(bs, root_lock);
+
+ ((camel_block_t *)bl->data)[0] = bs->root->free;
+ bs->root->free = bl->id;
+ bl->flags |= CAMEL_BLOCK_DIRTY;
+ camel_block_file_unref_block(bs, bl);
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, root_lock);
+
+ return 0;
+}
+
+/**
+ * camel_block_file_get_block:
+ * @bs:
+ * @id:
+ *
+ * Retreive a block @id.
+ *
+ * Return value: The block, or NULL if blockid is invalid or a file error
+ * occured.
+ **/
+CamelBlock *camel_block_file_get_block(CamelBlockFile *bs, camel_block_t id)
+{
+ CamelBlock *bl, *flush, *prev;
+
+ /* Sanity check: Dont allow reading of root block (except before its been read)
+ or blocks with invalid block id's */
+ if ((bs->root == NULL && id != 0)
+ || (bs->root != NULL && (id > bs->root->last || id == 0))
+ || (id % bs->block_size) != 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ CAMEL_BLOCK_FILE_LOCK(bs, cache_lock);
+
+ bl = g_hash_table_lookup(bs->blocks, (void *)id);
+
+ d(printf("Get block %08x: %s\n", id, bl?"cached":"must read"));
+
+ if (bl == NULL) {
+ /* LOCK io_lock */
+ if (block_file_use(bs) == -1) {
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+ return NULL;
+ }
+
+ bl = g_malloc0(sizeof(*bl));
+ bl->id = id;
+ if (lseek(bs->fd, id, SEEK_SET) == -1
+ || read(bs->fd, bl->data, CAMEL_BLOCK_SIZE) == -1) {
+ block_file_unuse(bs);
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+ g_free(bl);
+ return NULL;
+ }
+
+ bs->block_cache_count++;
+ g_hash_table_insert(bs->blocks, (void *)bl->id, bl);
+
+ /* flush old blocks */
+ flush = (CamelBlock *)bs->block_cache.tailpred;
+ prev = flush->prev;
+ while (bs->block_cache_count > bs->block_cache_limit && prev) {
+ if (flush->refcount == 0) {
+ if (sync_block_nolock(bs, flush) != -1) {
+ g_hash_table_remove(bs->blocks, (void *)flush->id);
+ e_dlist_remove((EDListNode *)flush);
+ g_free(flush);
+ bs->block_cache_count--;
+ }
+ }
+ flush = prev;
+ prev = prev->prev;
+ }
+ /* UNLOCK io_lock */
+ block_file_unuse(bs);
+ } else {
+ e_dlist_remove((EDListNode *)bl);
+ }
+
+ e_dlist_addhead(&bs->block_cache, (EDListNode *)bl);
+ bl->refcount++;
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+
+ d(printf("Got block %08x\n", id));
+
+ return bl;
+}
+
+/**
+ * camel_block_file_detach_block:
+ * @bs:
+ * @bl:
+ *
+ * Detatch a block from the block file's cache. The block should
+ * be unref'd or attached when finished with. The block file will
+ * perform no writes of this block or flushing of it if the cache
+ * fills.
+ **/
+void camel_block_file_detach_block(CamelBlockFile *bs, CamelBlock *bl)
+{
+ CAMEL_BLOCK_FILE_LOCK(bs, cache_lock);
+
+ g_hash_table_remove(bs->blocks, (void *)bl->id);
+ e_dlist_remove((EDListNode *)bl);
+ bl->flags |= CAMEL_BLOCK_DETACHED;
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+}
+
+/**
+ * camel_block_file_attach_block:
+ * @bs:
+ * @bl:
+ *
+ * Reattach a block that has been detached.
+ **/
+void camel_block_file_attach_block(CamelBlockFile *bs, CamelBlock *bl)
+{
+ CAMEL_BLOCK_FILE_LOCK(bs, cache_lock);
+
+ g_hash_table_insert(bs->blocks, (void *)bl->id, bl);
+ e_dlist_addtail(&bs->block_cache, (EDListNode *)bl);
+ bl->flags &= ~CAMEL_BLOCK_DETACHED;
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+}
+
+/**
+ * camel_block_file_touch_block:
+ * @bs:
+ * @bl:
+ *
+ * Mark a block as dirty. The block will be written to disk if
+ * it ever expires from the cache.
+ **/
+void camel_block_file_touch_block(CamelBlockFile *bs, CamelBlock *bl)
+{
+ CAMEL_BLOCK_FILE_LOCK(bs, root_lock);
+ CAMEL_BLOCK_FILE_LOCK(bs, cache_lock);
+
+ bl->flags |= CAMEL_BLOCK_DIRTY;
+
+ if ((bs->root->flags & CAMEL_BLOCK_FILE_SYNC) && bl != bs->root_block) {
+ d(printf("turning off sync flag\n"));
+ bs->root->flags &= ~CAMEL_BLOCK_FILE_SYNC;
+ bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
+ camel_block_file_sync_block(bs, bs->root_block);
+ }
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+ CAMEL_BLOCK_FILE_UNLOCK(bs, root_lock);
+}
+
+/**
+ * camel_block_file_unref_block:
+ * @bs:
+ * @bl:
+ *
+ * Mark a block as unused. If a block is used it will not be
+ * written to disk, or flushed from memory.
+ *
+ * If a block is detatched and this is the last reference, the
+ * block will be freed.
+ **/
+void camel_block_file_unref_block(CamelBlockFile *bs, CamelBlock *bl)
+{
+ CAMEL_BLOCK_FILE_LOCK(bs, cache_lock);
+
+ if (bl->refcount == 1 && (bl->flags & CAMEL_BLOCK_DETACHED))
+ g_free(bl);
+ else
+ bl->refcount--;
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+}
+
+static int
+sync_block_nolock(CamelBlockFile *bs, CamelBlock *bl)
+{
+ d(printf("Sync block %08x: %s\n", bl->id, (bl->flags & CAMEL_BLOCK_DIRTY)?"dirty":"clean"));
+
+ if (bl->flags & CAMEL_BLOCK_DIRTY) {
+ if (lseek(bs->fd, bl->id, SEEK_SET) == -1
+ || write(bs->fd, bl->data, CAMEL_BLOCK_SIZE) != CAMEL_BLOCK_SIZE) {
+ return -1;
+ }
+ bl->flags &= ~CAMEL_BLOCK_DIRTY;
+ }
+
+ return 0;
+}
+
+static int
+sync_nolock(CamelBlockFile *bs)
+{
+ CamelBlock *bl, *bn;
+ int work = FALSE;
+
+ bl = (CamelBlock *)bs->block_cache.head;
+ bn = bl->next;
+ while (bn) {
+ if (bl->flags & CAMEL_BLOCK_DIRTY) {
+ work = TRUE;
+ if (sync_block_nolock(bs, bl) == -1)
+ return -1;
+ bl = bn;
+ }
+ bn = bn->next;
+ }
+
+ if (!work
+ && (bs->root_block->flags & CAMEL_BLOCK_DIRTY) == 0
+ && (bs->root->flags & CAMEL_BLOCK_FILE_SYNC) != 0)
+ return 0;
+
+ d(printf("turning on sync flag\n"));
+
+ bs->root->flags |= CAMEL_BLOCK_FILE_SYNC;
+
+ return sync_block_nolock(bs, bs->root_block);
+}
+
+/**
+ * camel_block_file_sync_block:
+ * @bs:
+ * @bl:
+ *
+ * Flush a block to disk immediately. The block will only
+ * be flushed to disk if it is marked as dirty (touched).
+ *
+ * Return value: -1 on io error.
+ **/
+int camel_block_file_sync_block(CamelBlockFile *bs, CamelBlock *bl)
+{
+ int ret;
+
+ /* LOCK io_lock */
+ if (block_file_use(bs) == -1)
+ return -1;
+
+ ret = sync_block_nolock(bs, bl);
+
+ block_file_unuse(bs);
+
+ return ret;
+}
+
+/**
+ * camel_block_file_sync:
+ * @bs:
+ *
+ * Sync all dirty blocks to disk, including the root block.
+ *
+ * Return value: -1 on io error.
+ **/
+int camel_block_file_sync(CamelBlockFile *bs)
+{
+ int ret;
+
+ CAMEL_BLOCK_FILE_LOCK(bs, root_lock);
+ CAMEL_BLOCK_FILE_LOCK(bs, cache_lock);
+
+ /* LOCK io_lock */
+ if (block_file_use(bs) == -1)
+ ret = -1;
+ else {
+ ret = sync_nolock(bs);
+ block_file_unuse(bs);
+ }
+
+ CAMEL_BLOCK_FILE_UNLOCK(bs, cache_lock);
+ CAMEL_BLOCK_FILE_UNLOCK(bs, root_lock);
+
+ return ret;
+}
+
+/* ********************************************************************** */
+
+struct _CamelKeyFilePrivate {
+ struct _CamelKeyFilePrivate *next;
+ struct _CamelKeyFilePrivate *prev;
+
+ struct _CamelKeyFile *base;
+
+#ifdef ENABLE_THREADS
+ pthread_mutex_t lock;
+#endif
+};
+
+#ifdef ENABLE_THREADS
+#define CAMEL_KEY_FILE_LOCK(kf, lock) (pthread_mutex_lock(&(kf)->priv->lock))
+#define CAMEL_KEY_FILE_TRYLOCK(kf, lock) (pthread_mutex_trylock(&(kf)->priv->lock))
+#define CAMEL_KEY_FILE_UNLOCK(kf, lock) (pthread_mutex_unlock(&(kf)->priv->lock))
+
+static pthread_mutex_t key_file_lock = PTHREAD_MUTEX_INITIALIZER;
+
+#else
+#define CAMEL_KEY_FILE_LOCK(kf, lock)
+#define CAMEL_KEY_FILE_TRYLOCK(kf, lock)
+#define CAMEL_KEY_FILE_UNLOCK(kf, lock)
+#endif
+
+/* lru cache of block files */
+static EDList key_file_list = E_DLIST_INITIALISER(key_file_list);
+static EDList key_file_active_list = E_DLIST_INITIALISER(key_file_active_list);
+static int key_file_count = 0;
+static int key_file_threshhold = 10;
+
+static void
+camel_key_file_class_init(CamelKeyFileClass *klass)
+{
+}
+
+static void
+camel_key_file_init(CamelKeyFile *bs)
+{
+ struct _CamelKeyFilePrivate *p;
+
+ p = bs->priv = g_malloc0(sizeof(*bs->priv));
+ p->base = bs;
+
+#ifdef ENABLE_THREADS
+ pthread_mutex_init(&p->lock, NULL);
+#endif
+
+ LOCK(key_file_lock);
+ e_dlist_addhead(&key_file_list, (EDListNode *)p);
+ UNLOCK(key_file_lock);
+}
+
+static void
+camel_key_file_finalise(CamelKeyFile *bs)
+{
+ struct _CamelKeyFilePrivate *p = bs->priv;
+
+ LOCK(key_file_lock);
+ e_dlist_remove((EDListNode *)p);
+ UNLOCK(key_file_lock);
+
+ if (bs->fp)
+ fclose(bs->fp);
+ g_free(bs->path);
+
+#ifdef ENABLE_THREADS
+ pthread_mutex_destroy(&p->lock);
+#endif
+
+ g_free(p);
+}
+
+CamelType
+camel_key_file_get_type(void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register(camel_object_get_type(), "CamelKeyFile",
+ sizeof (CamelKeyFile),
+ sizeof (CamelKeyFileClass),
+ (CamelObjectClassInitFunc) camel_key_file_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_key_file_init,
+ (CamelObjectFinalizeFunc) camel_key_file_finalise);
+ }
+
+ return type;
+}
+
+/* 'use' a key file for io */
+static int
+key_file_use(CamelKeyFile *bs)
+{
+ struct _CamelKeyFilePrivate *nw, *nn, *p = bs->priv;
+ CamelKeyFile *bf;
+ int err, fd;
+ char *flag;
+
+ /* We want to:
+ remove file from active list
+ lock it
+
+ Then when done:
+ unlock it
+ add it back to end of active list
+ */
+
+ /* TODO: Check header on reset? */
+
+ CAMEL_KEY_FILE_LOCK(bs, lock);
+
+ if (bs->fp != NULL)
+ return 0;
+ else
+ d(printf("Turning key file online: '%s'\n", bs->path));
+
+ if ((bs->flags & O_ACCMODE) == O_RDONLY)
+ flag = "r";
+ else
+ flag = "a+";
+
+ if ((fd = open(bs->path, bs->flags, 0600)) == -1
+ || (bs->fp = fdopen(fd, flag)) == NULL) {
+ err = errno;
+ close(fd);
+ CAMEL_KEY_FILE_UNLOCK(bs, lock);
+ errno = err;
+ return -1;
+ }
+
+ LOCK(key_file_lock);
+ e_dlist_remove((EDListNode *)p);
+ e_dlist_addtail(&key_file_active_list, (EDListNode *)p);
+
+ key_file_count++;
+
+ nw = (struct _CamelKeyFilePrivate *)key_file_list.head;
+ nn = nw->next;
+ while (key_file_count > key_file_threshhold && nn) {
+ /* We never hit the current keyfile here, as its removed from the list first */
+ bf = nw->base;
+ if (bf->fp != NULL) {
+ /* Need to trylock, as any of these lock levels might be trying
+ to lock the key_file_lock, so we need to check and abort if so */
+ if (CAMEL_BLOCK_FILE_TRYLOCK(bf, lock) == 0) {
+ d(printf("Turning key file offline: %s\n", bf->path));
+ fclose(bf->fp);
+ bf->fp = NULL;
+ key_file_count--;
+ CAMEL_BLOCK_FILE_UNLOCK(bf, lock);
+ }
+ }
+ nw = nn;
+ nn = nw->next;
+ }
+
+ UNLOCK(key_file_lock);
+
+ return 0;
+}
+
+static void
+key_file_unuse(CamelKeyFile *bs)
+{
+ LOCK(key_file_lock);
+ e_dlist_remove((EDListNode *)bs->priv);
+ e_dlist_addtail(&key_file_list, (EDListNode *)bs->priv);
+ UNLOCK(key_file_lock);
+
+ CAMEL_KEY_FILE_UNLOCK(bs, lock);
+}
+
+/**
+ * camel_key_file_new:
+ * @path:
+ * @flags: open flags
+ * @version[]: Version string (header) of file. Currently
+ * written but not checked.
+ *
+ * Create a new key file. A linked list of record blocks.
+ *
+ * Return value: A new key file, or NULL if the file could not
+ * be opened/created/initialised.
+ **/
+CamelKeyFile *
+camel_key_file_new(const char *path, int flags, const char version[8])
+{
+ CamelKeyFile *kf;
+ off_t last;
+ int err;
+
+ d(printf("New key file '%s'\n", path));
+
+ kf = (CamelKeyFile *)camel_object_new(camel_key_file_get_type());
+ kf->path = g_strdup(path);
+ kf->fp = NULL;
+ kf->flags = flags;
+ kf->last = 8;
+
+ if (key_file_use(kf) == -1) {
+ camel_object_unref((CamelObject *)kf);
+ kf = NULL;
+ } else {
+ fseek(kf->fp, 0, SEEK_END);
+ last = ftell(kf->fp);
+ if (last == 0) {
+ fwrite(version, 8, 1, kf->fp);
+ last += 8;
+ }
+ kf->last = last;
+
+ err = ferror(kf->fp);
+ key_file_unuse(kf);
+
+ /* we only need these flags on first open */
+ kf->flags &= ~(O_CREAT|O_EXCL|O_TRUNC);
+
+ if (err) {
+ camel_object_unref((CamelObject *)kf);
+ kf = NULL;
+ }
+ }
+
+ return kf;
+}
+
+int
+camel_key_file_rename(CamelKeyFile *kf, const char *path)
+{
+ int ret;
+ struct stat st;
+ int err;
+
+ CAMEL_KEY_FILE_LOCK(kf, lock);
+
+ ret = rename(kf->path, path);
+ if (ret == -1) {
+ /* Maybe the rename actually worked */
+ err = errno;
+ if (stat(path, &st) == 0
+ && stat(kf->path, &st) == -1
+ && errno == ENOENT)
+ ret = 0;
+ errno = err;
+ }
+
+ if (ret != -1) {
+ g_free(kf->path);
+ kf->path = g_strdup(path);
+ }
+
+ CAMEL_KEY_FILE_UNLOCK(kf, lock);
+
+ return ret;
+}
+
+/**
+ * camel_key_file_write:
+ * @kf:
+ * @parent:
+ * @len:
+ * @records:
+ *
+ * Write a new list of records to the key file.
+ *
+ * Return value: -1 on io error. The key file will remain unchanged.
+ **/
+int
+camel_key_file_write(CamelKeyFile *kf, camel_block_t *parent, size_t len, camel_key_t *records)
+{
+ camel_block_t next;
+ guint32 size;
+ int ret = -1;
+
+ d(printf("write key %08x len = %d\n", *parent, len));
+
+ if (len == 0) {
+ d(printf(" new parent = %08x\n", *parent));
+ return 0;
+ }
+
+ /* LOCK */
+ if (key_file_use(kf) == -1)
+ return -1;
+
+ size = len;
+
+ /* FIXME: Use io util functions? */
+ next = kf->last;
+ fseek(kf->fp, kf->last, SEEK_SET);
+ fwrite(parent, sizeof(*parent), 1, kf->fp);
+ fwrite(&size, sizeof(size), 1, kf->fp);
+ fwrite(records, sizeof(records[0]), len, kf->fp);
+
+ if (ferror(kf->fp)) {
+ clearerr(kf->fp);
+ } else {
+ kf->last = ftell(kf->fp);
+ *parent = next;
+ ret = len;
+ }
+
+ /* UNLOCK */
+ key_file_unuse(kf);
+
+ d(printf(" new parent = %08x\n", *parent));
+
+ return ret;
+}
+
+/**
+ * camel_key_file_read:
+ * @kf:
+ * @start: The record pointer. This will be set to the next record pointer on success.
+ * @len: Number of records read, if != NULL.
+ * @records: Records, allocated, must be freed with g_free, if != NULL.
+ *
+ * Read the next block of data from the key file. Returns the number of
+ * records.
+ *
+ * Return value: -1 on io error.
+ **/
+int
+camel_key_file_read(CamelKeyFile *kf, camel_block_t *start, size_t *len, camel_key_t **records)
+{
+ guint32 size;
+ long pos = *start;
+ camel_block_t next;
+ int ret = -1;
+
+ if (pos == 0)
+ return 0;
+
+ /* LOCK */
+ if (key_file_use(kf) == -1)
+ return -1;
+
+ if (fseek(kf->fp, pos, SEEK_SET) == -1
+ || fread(&next, sizeof(next), 1, kf->fp) != 1
+ || fread(&size, sizeof(size), 1, kf->fp) != 1
+ || size > 1024) {
+ clearerr(kf->fp);
+ goto fail;
+ }
+
+ if (len)
+ *len = size;
+
+ if (records) {
+ camel_key_t *keys = g_malloc(size * sizeof(camel_key_t));
+
+ if (fread(keys, sizeof(camel_key_t), size, kf->fp) != size) {
+ g_free(keys);
+ goto fail;
+ }
+ *records = keys;
+ }
+
+ *start = next;
+
+ ret = 0;
+fail:
+ /* UNLOCK */
+ key_file_unuse(kf);
+
+ return ret;
+}