/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include "camel-block-file.h"
#include "camel-file-utils.h"
#define d(x) /*(printf("%s(%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
/* Locks must be obtained in the order defined */
struct _CamelBlockFilePrivate {
struct _CamelBlockFile *base;
GMutex root_lock; /* for modifying the root block */
GMutex cache_lock; /* for refcounting, flag manip, cache manip */
GMutex io_lock; /* for all io ops */
guint deleted : 1;
};
#define CAMEL_BLOCK_FILE_LOCK(kf, lock) (g_mutex_lock(&(kf)->priv->lock))
#define CAMEL_BLOCK_FILE_TRYLOCK(kf, lock) (g_mutex_trylock(&(kf)->priv->lock))
#define CAMEL_BLOCK_FILE_UNLOCK(kf, lock) (g_mutex_unlock(&(kf)->priv->lock))
#define LOCK(x) g_mutex_lock(&x)
#define UNLOCK(x) g_mutex_unlock(&x)
static GMutex block_file_lock;
/* lru cache of block files */
static GQueue block_file_list = G_QUEUE_INIT;
/* list to store block files that are actually intialised */
static GQueue block_file_active_list = G_QUEUE_INIT;
static gint block_file_count = 0;
static gint block_file_threshhold = 10;
static gint sync_nolock (CamelBlockFile *bs);
static gint sync_block_nolock (CamelBlockFile *bs, CamelBlock *bl);
G_DEFINE_TYPE (CamelBlockFile, camel_block_file, G_TYPE_OBJECT)
static gint
block_file_validate_root (CamelBlockFile *bs)
{
CamelBlockRoot *br;
struct stat st;
gint retval;
br = bs->root;
retval = fstat (bs->fd, &st);
d (printf ("Validate root: '%s'\n", bs->path));
d (printf ("version: %.8s (%.8s)\n", bs->root->version, bs->version));
d (printf (
"block size: %d (%d)%s\n",
br->block_size, bs->block_size,
br->block_size != bs->block_size ? " BAD":" OK"));
d (printf (
"free: %ld (%d add size < %ld)%s\n",
(glong) br->free,
br->free / bs->block_size * bs->block_size,
(glong) st.st_size,
(br->free > st.st_size) ||
(br->free % bs->block_size) != 0 ? " BAD":" OK"));
d (printf (
"last: %ld (%d and size: %ld)%s\n",
(glong) br->last,
br->last / bs->block_size * bs->block_size,
(glong) st.st_size,
(br->last != st.st_size) ||
((br->last % bs->block_size) != 0) ? " BAD": " OK"));
d (printf (
"flags: %s\n",
(br->flags & CAMEL_BLOCK_FILE_SYNC) ? "SYNC" : "unSYNC"));
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
|| retval == -1
|| st.st_size != br->last
|| br->free > st.st_size
|| (br->flags & CAMEL_BLOCK_FILE_SYNC) == 0) {
return -1;
}
return 0;
}
static gint
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
block_file_finalize (GObject *object)
{
CamelBlockFile *bs = CAMEL_BLOCK_FILE (object);
CamelBlock *bl;
if (bs->root_block)
camel_block_file_sync (bs);
/* remove from lru list */
LOCK (block_file_lock);
if (bs->fd != -1)
block_file_count--;
/* XXX This is only supposed to be in one block file list
* at a time, but not sure if we can guarantee which,
* so try removing from both lists. */
g_queue_remove (&block_file_list, bs->priv);
g_queue_remove (&block_file_active_list, bs->priv);
UNLOCK (block_file_lock);
while ((bl = g_queue_pop_head (&bs->block_cache)) != NULL) {
if (bl->refcount != 0)
g_warning ("Block '%u' still referenced", bl->id);
g_free (bl);
}
g_hash_table_destroy (bs->blocks);
if (bs->root_block)
camel_block_file_unref_block (bs, bs->root_block);
g_free (bs->path);
if (bs->fd != -1)
close (bs->fd);
g_mutex_clear (&bs->priv->io_lock);
g_mutex_clear (&bs->priv->cache_lock);
g_mutex_clear (&bs->priv->root_lock);
g_free (bs->priv);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_block_file_parent_class)->finalize (object);
}
static void
camel_block_file_class_init (CamelBlockFileClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->finalize = block_file_finalize;
class->validate_root = block_file_validate_root;
class->init_root = block_file_init_root;
}
static guint
block_hash_func (gconstpointer v)
{
return ((camel_block_t) GPOINTER_TO_UINT (v)) >> CAMEL_BLOCK_SIZE_BITS;
}
static void
camel_block_file_init (CamelBlockFile *bs)
{
bs->fd = -1;
bs->block_size = CAMEL_BLOCK_SIZE;
g_queue_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;
bs->priv = g_malloc0 (sizeof (*bs->priv));
bs->priv->base = bs;
g_mutex_init (&bs->priv->root_lock);
g_mutex_init (&bs->priv->cache_lock);
g_mutex_init (&bs->priv->io_lock);
/* link into lru list */
LOCK (block_file_lock);
g_queue_push_head (&block_file_list, bs->priv);
UNLOCK (block_file_lock);
}
/* 'use' a block file for io */
static gint
block_file_use (CamelBlockFile *bs)
{
CamelBlockFile *bf;
GList *link;
gint 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 if (bs->priv->deleted) {
CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
errno = ENOENT;
return -1;
} else {
d (printf ("Turning block file online: %s\n", bs->path));
}
if ((bs->fd = g_open (bs->path, bs->flags | O_BINARY, 0600)) == -1) {
err = errno;
CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
errno = err;
return -1;
}
LOCK (block_file_lock);
link = g_queue_find (&block_file_list, bs->priv);
if (link != NULL) {
g_queue_unlink (&block_file_list, link);
g_queue_push_tail_link (&block_file_active_list, link);
}
block_file_count++;
link = g_queue_peek_head_link (&block_file_list);
while (link != NULL && block_file_count > block_file_threshhold) {
struct _CamelBlockFilePrivate *nw = link->data;
/* 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)) {
if (CAMEL_BLOCK_FILE_TRYLOCK (bf, cache_lock)) {
if (CAMEL_BLOCK_FILE_TRYLOCK (bf, io_lock)) {
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);
}
}
link = g_list_next (link);
}
UNLOCK (block_file_lock);
return 0;
}
static void
block_file_unuse (CamelBlockFile *bs)
{
GList *link;
LOCK (block_file_lock);
link = g_queue_find (&block_file_active_list, bs->priv);
if (link != NULL) {
g_queue_unlink (&block_file_active_list, link);
g_queue_push_tail_link (&block_file_list, link);
}
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.
*
* Returns: The new block file, or NULL if it could not be created.
**/
CamelBlockFile *
camel_block_file_new (const gchar *path,
gint flags,
const gchar version[8],
gsize block_size)
{
CamelBlockFileClass *class;
CamelBlockFile *bs;
bs = g_object_new (CAMEL_TYPE_BLOCK_FILE, NULL);
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) {
g_object_unref (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);
class = CAMEL_BLOCK_FILE_GET_CLASS (bs);
/* Do we need to init the root block? */
if (class->validate_root (bs) == -1) {
d (printf ("Initialise root block: %.8s\n", version));
class->init_root (bs);
camel_block_file_touch_block (bs, bs->root_block);
if (block_file_use (bs) == -1) {
g_object_unref (bs);
return NULL;
}
if (sync_block_nolock (bs, bs->root_block) == -1
|| ftruncate (bs->fd, bs->root->last) == -1) {
block_file_unuse (bs);
g_object_unref (bs);
return NULL;
}
block_file_unuse (bs);
}
return bs;
}
gint
camel_block_file_rename (CamelBlockFile *bs,
const gchar *path)
{
gint ret;
struct stat st;
gint err;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
g_return_val_if_fail (path != NULL, -1);
CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
ret = g_rename (bs->path, path);
if (ret == -1) {
/* Maybe the rename actually worked */
err = errno;
if (g_stat (path, &st) == 0
&& g_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;
}
gint
camel_block_file_delete (CamelBlockFile *bs)
{
gint ret;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
if (bs->fd != -1) {
LOCK (block_file_lock);
block_file_count--;
UNLOCK (block_file_lock);
close (bs->fd);
bs->fd = -1;
}
bs->priv->deleted = TRUE;
ret = g_unlink (bs->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.
*
* Returns: The block, or NULL if an error occurred.
**/
CamelBlock *
camel_block_file_new_block (CamelBlockFile *bs)
{
CamelBlock *bl;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
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:
*
*
**/
gint
camel_block_file_free_block (CamelBlockFile *bs,
camel_block_t id)
{
CamelBlock *bl;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
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;
bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
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.
*
* Returns: The block, or NULL if blockid is invalid or a file error
* occurred.
**/
CamelBlock *
camel_block_file_get_block (CamelBlockFile *bs,
camel_block_t id)
{
CamelBlock *bl;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
/* 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, GUINT_TO_POINTER (id));
d (printf ("Get block %08x: %s\n", id, bl?"cached":"must read"));
if (bl == NULL) {
GQueue trash = G_QUEUE_INIT;
GList *link;
/* 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 ||
camel_read (bs->fd, (gchar *) bl->data, CAMEL_BLOCK_SIZE, NULL, NULL) == -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, GUINT_TO_POINTER (bl->id), bl);
/* flush old blocks */
link = g_queue_peek_tail_link (&bs->block_cache);
while (link != NULL && bs->block_cache_count > bs->block_cache_limit) {
CamelBlock *flush = link->data;
if (flush->refcount == 0) {
if (sync_block_nolock (bs, flush) != -1) {
g_hash_table_remove (bs->blocks, GUINT_TO_POINTER (flush->id));
g_queue_push_tail (&trash, link);
link->data = NULL;
g_free (flush);
bs->block_cache_count--;
}
}
link = g_list_previous (link);
}
/* Remove deleted blocks from the cache. */
while ((link = g_queue_pop_head (&trash)) != NULL)
g_queue_delete_link (&bs->block_cache, link);
/* UNLOCK io_lock */
block_file_unuse (bs);
} else {
g_queue_remove (&bs->block_cache, bl);
}
g_queue_push_head (&bs->block_cache, 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)
{
g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
g_return_if_fail (bl != NULL);
CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
g_hash_table_remove (bs->blocks, GUINT_TO_POINTER (bl->id));
g_queue_remove (&bs->block_cache, 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)
{
g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
g_return_if_fail (bl != NULL);
CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
g_hash_table_insert (bs->blocks, GUINT_TO_POINTER (bl->id), bl);
g_queue_push_tail (&bs->block_cache, 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)
{
g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
g_return_if_fail (bl != NULL);
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)
{
g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
g_return_if_fail (bl != NULL);
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 gint
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 gint
sync_nolock (CamelBlockFile *bs)
{
GList *head, *link;
gint work = FALSE;
head = g_queue_peek_head_link (&bs->block_cache);
for (link = head; link != NULL; link = g_list_next (link)) {
CamelBlock *bl = link->data;
if (bl->flags & CAMEL_BLOCK_DIRTY) {
work = TRUE;
if (sync_block_nolock (bs, bl) == -1)
return -1;
}
}
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;
bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
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).
*
* Returns: -1 on io error.
**/
gint
camel_block_file_sync_block (CamelBlockFile *bs,
CamelBlock *bl)
{
gint ret;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
g_return_val_if_fail (bl != NULL, -1);
/* 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.
*
* Returns: -1 on io error.
**/
gint
camel_block_file_sync (CamelBlockFile *bs)
{
gint ret;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
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 _CamelKeyFile *base;
GMutex lock;
guint deleted : 1;
};
#define CAMEL_KEY_FILE_LOCK(kf, lock) (g_mutex_lock(&(kf)->priv->lock))
#define CAMEL_KEY_FILE_TRYLOCK(kf, lock) (g_mutex_trylock(&(kf)->priv->lock))
#define CAMEL_KEY_FILE_UNLOCK(kf, lock) (g_mutex_unlock(&(kf)->priv->lock))
static GMutex key_file_lock;
/* lru cache of block files */
static GQueue key_file_list = G_QUEUE_INIT;
static GQueue key_file_active_list = G_QUEUE_INIT;
static gint key_file_count = 0;
static const gint key_file_threshhold = 10;
G_DEFINE_TYPE (CamelKeyFile, camel_key_file, G_TYPE_OBJECT)
static void
key_file_finalize (GObject *object)
{
CamelKeyFile *bs = CAMEL_KEY_FILE (object);
LOCK (key_file_lock);
/* XXX This is only supposed to be in one key file list
* at a time, but not sure if we can guarantee which,
* so try removing from both lists. */
g_queue_remove (&key_file_list, bs->priv);
g_queue_remove (&key_file_active_list, bs->priv);
if (bs-> fp) {
key_file_count--;
fclose (bs->fp);
}
UNLOCK (key_file_lock);
g_free (bs->path);
g_mutex_clear (&bs->priv->lock);
g_free (bs->priv);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_key_file_parent_class)->finalize (object);
}
static void
camel_key_file_class_init (CamelKeyFileClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->finalize = key_file_finalize;
}
static void
camel_key_file_init (CamelKeyFile *bs)
{
bs->priv = g_malloc0 (sizeof (*bs->priv));
bs->priv->base = bs;
g_mutex_init (&bs->priv->lock);
LOCK (key_file_lock);
g_queue_push_head (&key_file_list, bs->priv);
UNLOCK (key_file_lock);
}
/* 'use' a key file for io */
static gint
key_file_use (CamelKeyFile *bs)
{
CamelKeyFile *bf;
gint err, fd;
const gchar *flag;
GList *link;
/* 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 if (bs->priv->deleted) {
CAMEL_KEY_FILE_UNLOCK (bs, lock);
errno = ENOENT;
return -1;
} else {
d (printf ("Turning key file online: '%s'\n", bs->path));
}
if ((bs->flags & O_ACCMODE) == O_RDONLY)
flag = "rb";
else
flag = "a+b";
if ((fd = g_open (bs->path, bs->flags | O_BINARY, 0600)) == -1
|| (bs->fp = fdopen (fd, flag)) == NULL) {
err = errno;
if (fd != -1)
close (fd);
CAMEL_KEY_FILE_UNLOCK (bs, lock);
errno = err;
return -1;
}
LOCK (key_file_lock);
link = g_queue_find (&key_file_list, bs->priv);
if (link != NULL) {
g_queue_unlink (&key_file_list, link);
g_queue_push_tail_link (&key_file_active_list, link);
}
key_file_count++;
link = g_queue_peek_head_link (&key_file_list);
while (link != NULL && key_file_count > key_file_threshhold) {
struct _CamelKeyFilePrivate *nw = link->data;
/* 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)) {
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);
}
}
link = g_list_next (link);
}
UNLOCK (key_file_lock);
return 0;
}
static void
key_file_unuse (CamelKeyFile *bs)
{
GList *link;
LOCK (key_file_lock);
link = g_queue_find (&key_file_active_list, bs->priv);
if (link != NULL) {
g_queue_unlink (&key_file_active_list, link);
g_queue_push_tail_link (&key_file_list, link);
}
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.
*
* Returns: A new key file, or NULL if the file could not
* be opened/created/initialised.
**/
CamelKeyFile *
camel_key_file_new (const gchar *path,
gint flags,
const gchar version[8])
{
CamelKeyFile *kf;
goffset last;
gint err;
d (printf ("New key file '%s'\n", path));
kf = g_object_new (CAMEL_TYPE_KEY_FILE, NULL);
kf->path = g_strdup (path);
kf->fp = NULL;
kf->flags = flags;
kf->last = 8;
if (key_file_use (kf) == -1) {
g_object_unref (kf);
kf = NULL;
} else {
fseek (kf->fp, 0, SEEK_END);
last = ftell (kf->fp);
if (last == 0) {
fwrite (version, sizeof (gchar), 8, 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) {
g_object_unref (kf);
kf = NULL;
}
}
return kf;
}
gint
camel_key_file_rename (CamelKeyFile *kf,
const gchar *path)
{
gint ret;
struct stat st;
gint err;
g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
g_return_val_if_fail (path != NULL, -1);
CAMEL_KEY_FILE_LOCK (kf, lock);
ret = g_rename (kf->path, path);
if (ret == -1) {
/* Maybe the rename actually worked */
err = errno;
if (g_stat (path, &st) == 0
&& g_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;
}
gint
camel_key_file_delete (CamelKeyFile *kf)
{
gint ret;
g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
CAMEL_KEY_FILE_LOCK (kf, lock);
if (kf->fp) {
LOCK (key_file_lock);
key_file_count--;
UNLOCK (key_file_lock);
fclose (kf->fp);
kf->fp = NULL;
}
kf->priv->deleted = TRUE;
ret = g_unlink (kf->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.
*
* Returns: -1 on io error. The key file will remain unchanged.
**/
gint
camel_key_file_write (CamelKeyFile *kf,
camel_block_t *parent,
gsize len,
camel_key_t *records)
{
camel_block_t next;
guint32 size;
gint ret = -1;
g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
g_return_val_if_fail (parent != NULL, -1);
g_return_val_if_fail (records != NULL, -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;
if (fseek (kf->fp, kf->last, SEEK_SET) == -1)
return -1;
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.
*
* Returns: -1 on io error.
**/
gint
camel_key_file_read (CamelKeyFile *kf,
camel_block_t *start,
gsize *len,
camel_key_t **records)
{
guint32 size;
glong pos;
camel_block_t next;
gint ret = -1;
g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
g_return_val_if_fail (start != NULL, -1);
pos = *start;
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;
}