/* -*- 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 "camel-block-file.h"
#include "camel-partition-table.h"
/* Do we synchronously write table updates - makes the
* tables consistent after program crash without sync */
/*#define SYNC_UPDATES*/
#define d(x) /*(printf ("%s (%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
/* key index debug */
#define k(x) /*(printf ("%s (%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
#define CAMEL_PARTITION_TABLE_LOCK(kf, lock) \
(g_mutex_lock (&(kf)->priv->lock))
#define CAMEL_PARTITION_TABLE_UNLOCK(kf, lock) \
(g_mutex_unlock (&(kf)->priv->lock))
#define CAMEL_PARTITION_TABLE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_PARTITION_TABLE, CamelPartitionTablePrivate))
struct _CamelPartitionTablePrivate {
GMutex lock; /* for locking partition */
};
G_DEFINE_TYPE (CamelPartitionTable, camel_partition_table, G_TYPE_OBJECT)
static void
partition_table_finalize (GObject *object)
{
CamelPartitionTable *table = CAMEL_PARTITION_TABLE (object);
CamelBlock *bl;
if (table->blocks != NULL) {
while ((bl = g_queue_pop_head (&table->partition)) != NULL) {
camel_block_file_sync_block (table->blocks, bl);
camel_block_file_unref_block (table->blocks, bl);
}
camel_block_file_sync (table->blocks);
g_object_unref (table->blocks);
}
g_mutex_clear (&table->priv->lock);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_partition_table_parent_class)->finalize (object);
}
static void
camel_partition_table_class_init (CamelPartitionTableClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (CamelPartitionTablePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = partition_table_finalize;
}
static void
camel_partition_table_init (CamelPartitionTable *cpi)
{
cpi->priv = CAMEL_PARTITION_TABLE_GET_PRIVATE (cpi);
g_queue_init (&cpi->partition);
g_mutex_init (&cpi->priv->lock);
}
/* ********************************************************************** */
/*
* Have 2 hashes:
* Name -> nameid
* Word -> wordid
*
* nameid is pointer to name file, includes a bit to say if name is deleted
* wordid is a pointer to word file, includes pointer to start of word entries
*
* delete a name -> set it as deleted, do nothing else though
*
* lookup word, if nameid is deleted, mark it in wordlist as unused and mark
* for write (?)
*/
/* ********************************************************************** */
/* This simple hash seems to work quite well */
static camel_hash_t hash_key (const gchar *key)
{
camel_hash_t hash = 0xABADF00D;
while (*key) {
hash = hash * (*key) ^ (*key);
key++;
}
return hash;
}
/* Call with lock held */
static GList *
find_partition (CamelPartitionTable *cpi,
camel_hash_t id,
gint *indexp)
{
gint index, jump;
CamelPartitionMapBlock *ptb;
CamelPartitionMap *part;
GList *head, *link;
/* first, find the block this key might be in, then binary search the block */
head = g_queue_peek_head_link (&cpi->partition);
for (link = head; link != NULL; link = g_list_next (link)) {
CamelBlock *bl = link->data;
ptb = (CamelPartitionMapBlock *) &bl->data;
part = ptb->partition;
if (ptb->used > 0 && id <= part[ptb->used - 1].hashid) {
index = ptb->used / 2;
jump = ptb->used / 4;
if (jump == 0)
jump = 1;
while (1) {
if (id <= part[index].hashid) {
if (index == 0 || id > part[index - 1].hashid)
break;
index -= jump;
} else {
if (index >= ptb->used - 1)
break;
index += jump;
}
jump = jump / 2;
if (jump == 0)
jump = 1;
}
*indexp = index;
return link;
}
}
g_warning ("could not find a partition that could fit! partition table corrupt!");
/* This should never be reached */
return NULL;
}
CamelPartitionTable *
camel_partition_table_new (CamelBlockFile *bs,
camel_block_t root)
{
CamelPartitionTable *cpi;
CamelPartitionMapBlock *ptb;
CamelPartitionKeyBlock *kb;
CamelBlock *block, *pblock;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
cpi = g_object_new (CAMEL_TYPE_PARTITION_TABLE, NULL);
cpi->rootid = root;
cpi->blocks = g_object_ref (bs);
/* read the partition table into memory */
do {
block = camel_block_file_get_block (bs, root);
if (block == NULL)
goto fail;
ptb = (CamelPartitionMapBlock *) &block->data;
d (printf ("Adding partition block, used = %d, hashid = %08x\n", ptb->used, ptb->partition[0].hashid));
/* if we have no data, prime initial block */
if (ptb->used == 0 && g_queue_is_empty (&cpi->partition) && ptb->next == 0) {
pblock = camel_block_file_new_block (bs);
if (pblock == NULL) {
camel_block_file_unref_block (bs, block);
goto fail;
}
kb = (CamelPartitionKeyBlock *) &pblock->data;
kb->used = 0;
ptb->used = 1;
ptb->partition[0].hashid = 0xffffffff;
ptb->partition[0].blockid = pblock->id;
camel_block_file_touch_block (bs, pblock);
camel_block_file_unref_block (bs, pblock);
camel_block_file_touch_block (bs, block);
#ifdef SYNC_UPDATES
camel_block_file_sync_block (bs, block);
#endif
}
root = ptb->next;
camel_block_file_detach_block (bs, block);
g_queue_push_tail (&cpi->partition, block);
} while (root);
return cpi;
fail:
g_object_unref (cpi);
return NULL;
}
/* sync our blocks, the caller must still sync the blockfile itself */
gint
camel_partition_table_sync (CamelPartitionTable *cpi)
{
gint ret = 0;
g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), -1);
CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
if (cpi->blocks) {
GList *head, *link;
head = g_queue_peek_head_link (&cpi->partition);
for (link = head; link != NULL; link = g_list_next (link)) {
CamelBlock *bl = link->data;
ret = camel_block_file_sync_block (cpi->blocks, bl);
if (ret == -1)
goto fail;
}
}
fail:
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return ret;
}
camel_key_t
camel_partition_table_lookup (CamelPartitionTable *cpi,
const gchar *key)
{
CamelPartitionKeyBlock *pkb;
CamelPartitionMapBlock *ptb;
CamelBlock *block, *ptblock;
camel_hash_t hashid;
camel_key_t keyid = 0;
GList *ptblock_link;
gint index, i;
g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), 0);
g_return_val_if_fail (key != NULL, 0);
hashid = hash_key (key);
CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
ptblock_link = find_partition (cpi, hashid, &index);
if (ptblock_link == NULL) {
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return 0;
}
ptblock = (CamelBlock *) ptblock_link->data;
ptb = (CamelPartitionMapBlock *) &ptblock->data;
block = camel_block_file_get_block (
cpi->blocks, ptb->partition[index].blockid);
if (block == NULL) {
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return 0;
}
pkb = (CamelPartitionKeyBlock *) &block->data;
/* What to do about duplicate hash's? */
for (i = 0; i < pkb->used; i++) {
if (pkb->keys[i].hashid == hashid) {
/* !! need to: lookup and compare string value */
/* get_key() if key == key ... */
keyid = pkb->keys[i].keyid;
break;
}
}
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
camel_block_file_unref_block (cpi->blocks, block);
return keyid;
}
gboolean
camel_partition_table_remove (CamelPartitionTable *cpi,
const gchar *key)
{
CamelPartitionKeyBlock *pkb;
CamelPartitionMapBlock *ptb;
CamelBlock *block, *ptblock;
camel_hash_t hashid;
GList *ptblock_link;
gint index, i;
g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
hashid = hash_key (key);
CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
ptblock_link = find_partition (cpi, hashid, &index);
if (ptblock_link == NULL) {
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return TRUE;
}
ptblock = (CamelBlock *) ptblock_link->data;
ptb = (CamelPartitionMapBlock *) &ptblock->data;
block = camel_block_file_get_block (
cpi->blocks, ptb->partition[index].blockid);
if (block == NULL) {
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return FALSE;
}
pkb = (CamelPartitionKeyBlock *) &block->data;
/* What to do about duplicate hash's? */
for (i = 0; i < pkb->used; i++) {
if (pkb->keys[i].hashid == hashid) {
/* !! need to: lookup and compare string value */
/* get_key() if key == key ... */
/* remove this key */
pkb->used--;
for (; i < pkb->used; i++) {
pkb->keys[i].keyid = pkb->keys[i + 1].keyid;
pkb->keys[i].hashid = pkb->keys[i + 1].hashid;
}
camel_block_file_touch_block (cpi->blocks, block);
break;
}
}
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
camel_block_file_unref_block (cpi->blocks, block);
return TRUE;
}
static gint
keys_cmp (gconstpointer ap,
gconstpointer bp)
{
const CamelPartitionKey *a = ap;
const CamelPartitionKey *b = bp;
if (a->hashid < b->hashid)
return -1;
else if (a->hashid > b->hashid)
return 1;
return 0;
}
gint
camel_partition_table_add (CamelPartitionTable *cpi,
const gchar *key,
camel_key_t keyid)
{
camel_hash_t hashid, partid;
gint index, newindex = 0; /* initialisation of this and pkb/nkb is just to silence compiler */
CamelPartitionMapBlock *ptb, *ptn;
CamelPartitionKeyBlock *kb, *newkb, *nkb = NULL, *pkb = NULL;
CamelBlock *block, *ptblock, *ptnblock;
gint i, half, len;
CamelPartitionKey keys[CAMEL_BLOCK_SIZE / 4];
GList *ptblock_link;
gint ret = -1;
g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), -1);
g_return_val_if_fail (key != NULL, -1);
hashid = hash_key (key);
CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
ptblock_link = find_partition (cpi, hashid, &index);
if (ptblock_link == NULL) {
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return -1;
}
ptblock = (CamelBlock *) ptblock_link->data;
ptb = (CamelPartitionMapBlock *) &ptblock->data;
block = camel_block_file_get_block (
cpi->blocks, ptb->partition[index].blockid);
if (block == NULL) {
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return -1;
}
kb = (CamelPartitionKeyBlock *) &block->data;
/* TODO: Keep the key array in sorted order, cheaper lookups and split operation */
if (kb->used < G_N_ELEMENTS (kb->keys)) {
/* Have room, just put it in */
kb->keys[kb->used].hashid = hashid;
kb->keys[kb->used].keyid = keyid;
kb->used++;
} else {
CamelBlock *newblock = NULL, *nblock = NULL, *pblock = NULL;
/* Need to split? See if previous or next has room, then split across that instead */
/* TODO: Should look at next/previous partition table block as well ... */
if (index > 0) {
pblock = camel_block_file_get_block (
cpi->blocks, ptb->partition[index - 1].blockid);
if (pblock == NULL)
goto fail;
pkb = (CamelPartitionKeyBlock *) &pblock->data;
}
if (index < (ptb->used - 1)) {
nblock = camel_block_file_get_block (
cpi->blocks, ptb->partition[index + 1].blockid);
if (nblock == NULL) {
if (pblock)
camel_block_file_unref_block (cpi->blocks, pblock);
goto fail;
}
nkb = (CamelPartitionKeyBlock *) &nblock->data;
}
if (pblock && pkb->used < G_N_ELEMENTS (kb->keys)) {
if (nblock && nkb->used < G_N_ELEMENTS (kb->keys)) {
if (pkb->used < nkb->used) {
newindex = index + 1;
newblock = nblock;
} else {
newindex = index - 1;
newblock = pblock;
}
} else {
newindex = index - 1;
newblock = pblock;
}
} else {
if (nblock && nkb->used < G_N_ELEMENTS (kb->keys)) {
newindex = index + 1;
newblock = nblock;
}
}
/* We had no room, need to split across another block */
if (newblock == NULL) {
/* See if we have room in the partition table for this block or need to split that too */
if (ptb->used >= G_N_ELEMENTS (ptb->partition)) {
/* TODO: Could check next block to see if it'll fit there first */
ptnblock = camel_block_file_new_block (cpi->blocks);
if (ptnblock == NULL) {
if (nblock)
camel_block_file_unref_block (cpi->blocks, nblock);
if (pblock)
camel_block_file_unref_block (cpi->blocks, pblock);
goto fail;
}
camel_block_file_detach_block (cpi->blocks, ptnblock);
/* split block and link on-disk, always sorted */
ptn = (CamelPartitionMapBlock *) &ptnblock->data;
ptn->next = ptb->next;
ptb->next = ptnblock->id;
len = ptb->used / 2;
ptn->used = ptb->used - len;
ptb->used = len;
memcpy (ptn->partition, &ptb->partition[len], ptn->used * sizeof (ptb->partition[0]));
/* link in-memory */
g_queue_insert_after (
&cpi->partition,
ptblock_link, ptnblock);
/* write in right order to ensure structure */
camel_block_file_touch_block (cpi->blocks, ptnblock);
#ifdef SYNC_UPDATES
camel_block_file_sync_block (cpi->blocks, ptnblock);
#endif
if (index > len) {
camel_block_file_touch_block (cpi->blocks, ptblock);
#ifdef SYNC_UPDATES
camel_block_file_sync_block (cpi->blocks, ptblock);
#endif
index -= len;
ptb = ptn;
ptblock = ptnblock;
}
}
/* try get newblock before modifying existing */
newblock = camel_block_file_new_block (cpi->blocks);
if (newblock == NULL) {
if (nblock)
camel_block_file_unref_block (cpi->blocks, nblock);
if (pblock)
camel_block_file_unref_block (cpi->blocks, pblock);
goto fail;
}
for (i = ptb->used - 1; i > index; i--) {
ptb->partition[i + 1].hashid = ptb->partition[i].hashid;
ptb->partition[i + 1].blockid = ptb->partition[i].blockid;
}
ptb->used++;
newkb = (CamelPartitionKeyBlock *) &newblock->data;
newkb->used = 0;
newindex = index + 1;
ptb->partition[newindex].hashid = ptb->partition[index].hashid;
ptb->partition[newindex].blockid = newblock->id;
if (nblock)
camel_block_file_unref_block (cpi->blocks, nblock);
if (pblock)
camel_block_file_unref_block (cpi->blocks, pblock);
} else {
newkb = (CamelPartitionKeyBlock *) &newblock->data;
if (newblock == pblock) {
if (nblock)
camel_block_file_unref_block (cpi->blocks, nblock);
} else {
if (pblock)
camel_block_file_unref_block (cpi->blocks, pblock);
}
}
/* sort keys to find midpoint */
len = kb->used;
memcpy (keys, kb->keys, sizeof (kb->keys[0]) * len);
memcpy (keys + len, newkb->keys, sizeof (newkb->keys[0]) * newkb->used);
len += newkb->used;
keys[len].hashid = hashid;
keys[len].keyid = keyid;
len++;
qsort (keys, len, sizeof (keys[0]), keys_cmp);
/* Split keys, fix partition table */
half = len / 2;
partid = keys[half - 1].hashid;
if (index < newindex) {
memcpy (kb->keys, keys, sizeof (keys[0]) * half);
kb->used = half;
memcpy (newkb->keys, keys + half, sizeof (keys[0]) * (len - half));
newkb->used = len - half;
ptb->partition[index].hashid = partid;
} else {
memcpy (newkb->keys, keys, sizeof (keys[0]) * half);
newkb->used = half;
memcpy (kb->keys, keys + half, sizeof (keys[0]) * (len - half));
kb->used = len - half;
ptb->partition[newindex].hashid = partid;
}
camel_block_file_touch_block (cpi->blocks, ptblock);
#ifdef SYNC_UPDATES
camel_block_file_sync_block (cpi->blocks, ptblock);
#endif
camel_block_file_touch_block (cpi->blocks, newblock);
camel_block_file_unref_block (cpi->blocks, newblock);
}
camel_block_file_touch_block (cpi->blocks, block);
camel_block_file_unref_block (cpi->blocks, block);
ret = 0;
fail:
CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
return ret;
}
/* ********************************************************************** */
#define CAMEL_KEY_TABLE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_KEY_TABLE, CamelKeyTablePrivate))
#define CAMEL_KEY_TABLE_LOCK(kf, lock) \
(g_mutex_lock (&(kf)->priv->lock))
#define CAMEL_KEY_TABLE_UNLOCK(kf, lock) \
(g_mutex_unlock (&(kf)->priv->lock))
struct _CamelKeyTablePrivate {
GMutex lock; /* for locking key */
};
G_DEFINE_TYPE (CamelKeyTable, camel_key_table, G_TYPE_OBJECT)
static void
key_table_finalize (GObject *object)
{
CamelKeyTable *table = CAMEL_KEY_TABLE (object);
if (table->blocks) {
if (table->root_block) {
camel_block_file_sync_block (table->blocks, table->root_block);
camel_block_file_unref_block (table->blocks, table->root_block);
}
camel_block_file_sync (table->blocks);
g_object_unref (table->blocks);
}
g_mutex_clear (&table->priv->lock);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_key_table_parent_class)->finalize (object);
}
static void
camel_key_table_class_init (CamelKeyTableClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (CamelKeyTablePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = key_table_finalize;
}
static void
camel_key_table_init (CamelKeyTable *table)
{
table->priv = CAMEL_KEY_TABLE_GET_PRIVATE (table);
g_mutex_init (&table->priv->lock);
}
CamelKeyTable *
camel_key_table_new (CamelBlockFile *bs,
camel_block_t root)
{
CamelKeyTable *ki;
g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
ki = g_object_new (CAMEL_TYPE_KEY_TABLE, NULL);
ki->blocks = g_object_ref (bs);
ki->rootid = root;
ki->root_block = camel_block_file_get_block (bs, ki->rootid);
if (ki->root_block == NULL) {
g_object_unref (ki);
ki = NULL;
} else {
camel_block_file_detach_block (bs, ki->root_block);
ki->root = (CamelKeyRootBlock *) &ki->root_block->data;
k (printf ("Opening key index\n"));
k (printf (" first %u\n last %u\n free %u\n", ki->root->first, ki->root->last, ki->root->free));
}
return ki;
}
gint
camel_key_table_sync (CamelKeyTable *ki)
{
g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), -1);
#ifdef SYNC_UPDATES
return 0;
#else
return camel_block_file_sync_block (ki->blocks, ki->root_block);
#endif
}
camel_key_t
camel_key_table_add (CamelKeyTable *ki,
const gchar *key,
camel_block_t data,
guint flags)
{
CamelBlock *last, *next;
CamelKeyBlock *kblast, *kbnext;
gint len, left;
guint offset;
camel_key_t keyid = 0;
g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), 0);
g_return_val_if_fail (key != NULL, 0);
/* Maximum key size = 128 chars */
len = strlen (key);
if (len > CAMEL_KEY_TABLE_MAX_KEY)
len = 128;
CAMEL_KEY_TABLE_LOCK (ki, lock);
if (ki->root->last == 0) {
last = camel_block_file_new_block (ki->blocks);
if (last == NULL)
goto fail;
ki->root->last = ki->root->first = last->id;
camel_block_file_touch_block (ki->blocks, ki->root_block);
k (printf ("adding first block, first = %u\n", ki->root->first));
} else {
last = camel_block_file_get_block (
ki->blocks, ki->root->last);
if (last == NULL)
goto fail;
}
kblast = (CamelKeyBlock *) &last->data;
if (kblast->used >= 127)
goto fail;
if (kblast->used > 0) {
/*left = &kblast->u.keydata[kblast->u.keys[kblast->used-1].offset] - (gchar *)(&kblast->u.keys[kblast->used+1]);*/
left = kblast->u.keys[kblast->used - 1].offset - sizeof (kblast->u.keys[0]) * (kblast->used + 1);
d (printf (
"key '%s' used = %d (%d), filled = %d, left = %d len = %d?\n",
key, kblast->used, kblast->used * sizeof (kblast->u.keys[0]),
sizeof (kblast->u.keydata) - kblast->u.keys[kblast->used - 1].offset,
left, len));
if (left < len) {
next = camel_block_file_new_block (ki->blocks);
if (next == NULL) {
camel_block_file_unref_block (ki->blocks, last);
goto fail;
}
kbnext = (CamelKeyBlock *) &next->data;
kblast->next = next->id;
ki->root->last = next->id;
d (printf ("adding new block, first = %u, last = %u\n", ki->root->first, ki->root->last));
camel_block_file_touch_block (ki->blocks, ki->root_block);
camel_block_file_touch_block (ki->blocks, last);
camel_block_file_unref_block (ki->blocks, last);
kblast = kbnext;
last = next;
}
}
if (kblast->used > 0)
offset = kblast->u.keys[kblast->used - 1].offset - len;
else
offset = sizeof (kblast->u.keydata) - len;
kblast->u.keys[kblast->used].flags = flags;
kblast->u.keys[kblast->used].data = data;
kblast->u.keys[kblast->used].offset = offset;
memcpy (kblast->u.keydata + offset, key, len);
keyid = (last->id & (~(CAMEL_BLOCK_SIZE - 1))) | kblast->used;
kblast->used++;
if (kblast->used >=127) {
g_warning ("Invalid value for used %d\n", kblast->used);
return 0;
}
camel_block_file_touch_block (ki->blocks, last);
camel_block_file_unref_block (ki->blocks, last);
#ifdef SYNC_UPDATES
camel_block_file_sync_block (ki->blocks, ki->root_block);
#endif
fail:
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
return keyid;
}
gboolean
camel_key_table_set_data (CamelKeyTable *ki,
camel_key_t keyid,
camel_block_t data)
{
CamelBlock *bl;
camel_block_t blockid;
gint index;
CamelKeyBlock *kb;
g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), FALSE);
g_return_val_if_fail (keyid != 0, FALSE);
blockid = keyid & (~(CAMEL_BLOCK_SIZE - 1));
index = keyid & (CAMEL_BLOCK_SIZE - 1);
bl = camel_block_file_get_block (ki->blocks, blockid);
if (bl == NULL)
return FALSE;
kb = (CamelKeyBlock *) &bl->data;
CAMEL_KEY_TABLE_LOCK (ki, lock);
if (kb->u.keys[index].data != data) {
kb->u.keys[index].data = data;
camel_block_file_touch_block (ki->blocks, bl);
}
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
camel_block_file_unref_block (ki->blocks, bl);
return TRUE;
}
gboolean
camel_key_table_set_flags (CamelKeyTable *ki,
camel_key_t keyid,
guint flags,
guint set)
{
CamelBlock *bl;
camel_block_t blockid;
gint index;
CamelKeyBlock *kb;
guint old;
g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), FALSE);
g_return_val_if_fail (keyid != 0, FALSE);
blockid = keyid & (~(CAMEL_BLOCK_SIZE - 1));
index = keyid & (CAMEL_BLOCK_SIZE - 1);
bl = camel_block_file_get_block (ki->blocks, blockid);
if (bl == NULL)
return FALSE;
kb = (CamelKeyBlock *) &bl->data;
if (kb->used >=127 || index >= kb->used) {
g_warning ("Block %x: Invalid index or content: index %d used %d\n", blockid, index, kb->used);
return FALSE;
}
CAMEL_KEY_TABLE_LOCK (ki, lock);
old = kb->u.keys[index].flags;
if ((old & set) != (flags & set)) {
kb->u.keys[index].flags = (old & (~set)) | (flags & set);
camel_block_file_touch_block (ki->blocks, bl);
}
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
camel_block_file_unref_block (ki->blocks, bl);
return TRUE;
}
camel_block_t
camel_key_table_lookup (CamelKeyTable *ki,
camel_key_t keyid,
gchar **keyp,
guint *flags)
{
CamelBlock *bl;
camel_block_t blockid;
gint index, len, off;
gchar *key;
CamelKeyBlock *kb;
g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), 0);
g_return_val_if_fail (keyid != 0, 0);
if (keyp)
*keyp = NULL;
if (flags)
*flags = 0;
blockid = keyid & (~(CAMEL_BLOCK_SIZE - 1));
index = keyid & (CAMEL_BLOCK_SIZE - 1);
bl = camel_block_file_get_block (ki->blocks, blockid);
if (bl == NULL)
return 0;
kb = (CamelKeyBlock *) &bl->data;
if (kb->used >=127 || index >= kb->used) {
g_warning ("Block %x: Invalid index or content: index %d used %d\n", blockid, index, kb->used);
return 0;
}
CAMEL_KEY_TABLE_LOCK (ki, lock);
blockid = kb->u.keys[index].data;
if (flags)
*flags = kb->u.keys[index].flags;
if (keyp) {
off = kb->u.keys[index].offset;
if (index == 0)
len = sizeof (kb->u.keydata) - off;
else
len = kb->u.keys[index - 1].offset - off;
*keyp = key = g_malloc (len+1);
memcpy (key, kb->u.keydata + off, len);
key[len] = 0;
}
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
camel_block_file_unref_block (ki->blocks, bl);
return blockid;
}
/* iterate through all keys */
camel_key_t
camel_key_table_next (CamelKeyTable *ki,
camel_key_t next,
gchar **keyp,
guint *flagsp,
camel_block_t *datap)
{
CamelBlock *bl;
CamelKeyBlock *kb;
camel_block_t blockid;
gint index;
g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), 0);
if (keyp)
*keyp = NULL;
if (flagsp)
*flagsp = 0;
if (datap)
*datap = 0;
CAMEL_KEY_TABLE_LOCK (ki, lock);
if (next == 0) {
next = ki->root->first;
if (next == 0) {
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
return 0;
}
} else
next++;
do {
blockid = next & (~(CAMEL_BLOCK_SIZE - 1));
index = next & (CAMEL_BLOCK_SIZE - 1);
bl = camel_block_file_get_block (ki->blocks, blockid);
if (bl == NULL) {
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
return 0;
}
kb = (CamelKeyBlock *) &bl->data;
/* see if we need to goto the next block */
if (index >= kb->used) {
/* FIXME: check for loops */
next = kb->next;
camel_block_file_unref_block (ki->blocks, bl);
bl = NULL;
}
} while (bl == NULL);
/* invalid block data */
if ((kb->u.keys[index].offset >= sizeof (kb->u.keydata)
/*|| kb->u.keys[index].offset < kb->u.keydata - (gchar *)&kb->u.keys[kb->used])*/
|| kb->u.keys[index].offset < sizeof (kb->u.keys[0]) * kb->used
|| (index > 0 &&
(kb->u.keys[index - 1].offset >= sizeof (kb->u.keydata)
/*|| kb->u.keys[index-1].offset < kb->u.keydata - (gchar *)&kb->u.keys[kb->used]))) {*/
|| kb->u.keys[index - 1].offset < sizeof (kb->u.keys[0]) * kb->used)))) {
g_warning ("Block %u invalid scanning keys", bl->id);
camel_block_file_unref_block (ki->blocks, bl);
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
return 0;
}
if (datap)
*datap = kb->u.keys[index].data;
if (flagsp)
*flagsp = kb->u.keys[index].flags;
if (keyp) {
gint len, off = kb->u.keys[index].offset;
gchar *key;
if (index == 0)
len = sizeof (kb->u.keydata) - off;
else
len = kb->u.keys[index - 1].offset - off;
*keyp = key = g_malloc (len+1);
memcpy (key, kb->u.keydata + off, len);
key[len] = 0;
}
CAMEL_KEY_TABLE_UNLOCK (ki, lock);
camel_block_file_unref_block (ki->blocks, bl);
return next;
}
/* ********************************************************************** */