
2001-08-10 Not Zed <NotZed@Ximian.com> * wordindexmem.c (sync_cache_entry): NOOP if writing to a failed file. (word_index_pre): NOOP if failed file. (ibex_create_word_index_mem): Setup blocks value. ** Added internal exception handling to libibex, in the case of errors with on-disk data, exceptions are returned. * block.c (ibex_block_cache_open): Detect fatal errors below us and clean up appropriately. (ibex_block_cache_fail): New function to handle the failure, and keep track of it. (ibex_block_cache_sync): Dont do anything if we've failed on this file. * disktail.c (tail_compress): Add blocks param so we can assert for exceptions. * hash.c, block.c disktail.c: g_assert->ibex_block_cache_assert where dealing with external data. * hash.c (hash_info): Add index param so we can assert for exceptions. * ibex_block.c (ibex_index_buffer): Setjmp before calling into internal routines. (ibex_save): " (ibex_unindex): " (ibex_find): " (ibex_find_name): " (ibex_contains_name): " (ibex_reset): Function to reset the index file if we have an error, call when we have an error. * block.h (ibex_block_cache_assert): Create assertion/exception macros, and include a setjmp buffer for returning it. 2001-08-09 Not Zed <NotZed@Ximian.com> * Makefile.am (libibex_la_SOURCES): Remove wordindex.c, wordindexmem is what's used. svn path=/trunk/; revision=11864
622 lines
14 KiB
C
622 lines
14 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2000 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 the GNU Library General Public License
|
|
* as published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with the Gnome Library; see the file COPYING.LIB. If not,
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
block file/cache/utility functions
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <glib.h>
|
|
|
|
#include "block.h"
|
|
|
|
/*#define MALLOC_CHECK*/
|
|
|
|
#ifdef MALLOC_CHECK
|
|
#include <mcheck.h>
|
|
#endif
|
|
|
|
#define d(x)
|
|
/*#define DEBUG*/
|
|
|
|
int block_log;
|
|
|
|
#ifdef IBEX_STATS
|
|
static void
|
|
init_stats(struct _memcache *index)
|
|
{
|
|
index->stats = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
}
|
|
|
|
static void
|
|
dump_1_stat(int id, struct _stat_info *info, struct _memcache *index)
|
|
{
|
|
printf("%d %d %d %d %d\n", id, info->read, info->write, info->cache_hit, info->cache_miss);
|
|
}
|
|
|
|
static void
|
|
dump_stats(struct _memcache *index)
|
|
{
|
|
printf("Block reads writes hits misses\n");
|
|
g_hash_table_foreach(index->stats, dump_1_stat, index);
|
|
}
|
|
|
|
static void
|
|
add_read(struct _memcache *index, int id)
|
|
{
|
|
struct _stat_info *info;
|
|
|
|
info = g_hash_table_lookup(index->stats, (void *)id);
|
|
if (info == NULL) {
|
|
info = g_malloc0(sizeof(*info));
|
|
g_hash_table_insert(index->stats, (void *)id, info);
|
|
}
|
|
info->read++;
|
|
}
|
|
|
|
static void
|
|
add_write(struct _memcache *index, int id)
|
|
{
|
|
struct _stat_info *info;
|
|
|
|
info = g_hash_table_lookup(index->stats, (void *)id);
|
|
if (info == NULL) {
|
|
info = g_malloc0(sizeof(*info));
|
|
g_hash_table_insert(index->stats, (void *)id, info);
|
|
}
|
|
info->write++;
|
|
}
|
|
|
|
static void
|
|
add_hit(struct _memcache *index, int id)
|
|
{
|
|
struct _stat_info *info;
|
|
|
|
info = g_hash_table_lookup(index->stats, (void *)id);
|
|
if (info == NULL) {
|
|
info = g_malloc0(sizeof(*info));
|
|
g_hash_table_insert(index->stats, (void *)id, info);
|
|
}
|
|
info->cache_hit++;
|
|
}
|
|
|
|
static void
|
|
add_miss(struct _memcache *index, int id)
|
|
{
|
|
struct _stat_info *info;
|
|
|
|
info = g_hash_table_lookup(index->stats, (void *)id);
|
|
if (info == NULL) {
|
|
info = g_malloc0(sizeof(*info));
|
|
g_hash_table_insert(index->stats, (void *)id, info);
|
|
}
|
|
info->cache_miss++;
|
|
}
|
|
#endif /* IBEX_STATS */
|
|
|
|
#ifdef MALLOC_CHECK
|
|
static void
|
|
checkmem(void *p)
|
|
{
|
|
if (p) {
|
|
int status = mprobe(p);
|
|
|
|
switch (status) {
|
|
case MCHECK_HEAD:
|
|
printf("Memory underrun at %p\n", p);
|
|
abort();
|
|
case MCHECK_TAIL:
|
|
printf("Memory overrun at %p\n", p);
|
|
abort();
|
|
case MCHECK_FREE:
|
|
printf("Double free %p\n", p);
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* simple list routines (for simplified memory management of cache/lists) */
|
|
|
|
/**
|
|
* ibex_list_new:
|
|
* @v:
|
|
*
|
|
* Initialise a list header. A list header must always be initialised
|
|
* before use.
|
|
**/
|
|
void ibex_list_new(struct _list *v)
|
|
{
|
|
v->head = (struct _listnode *)&v->tail;
|
|
v->tail = 0;
|
|
v->tailpred = (struct _listnode *)&v->head;
|
|
}
|
|
|
|
/**
|
|
* ibex_list_addhead:
|
|
* @l: List.
|
|
* @n: Node to append.
|
|
*
|
|
* Prepend a listnode to the head of the list @l.
|
|
*
|
|
* Return value: Always @n.
|
|
**/
|
|
struct _listnode *ibex_list_addhead(struct _list *l, struct _listnode *n)
|
|
{
|
|
n->next = l->head;
|
|
n->prev = (struct _listnode *)&l->head;
|
|
l->head->prev = n;
|
|
l->head = n;
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* ibex_list_addtail:
|
|
* @l:
|
|
* @n:
|
|
*
|
|
* Append a listnode to the end of the list @l.
|
|
*
|
|
* Return value: Always the same as @n.
|
|
**/
|
|
struct _listnode *ibex_list_addtail(struct _list *l, struct _listnode *n)
|
|
{
|
|
n->next = (struct _listnode *)&l->tail;
|
|
n->prev = l->tailpred;
|
|
l->tailpred->next = n;
|
|
l->tailpred = n;
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* ibex_list_remove:
|
|
* @n: The node to remove.
|
|
*
|
|
* Remove a listnode from a list.
|
|
*
|
|
* Return value: Always the same as @n.
|
|
**/
|
|
struct _listnode *ibex_list_remove(struct _listnode *n)
|
|
{
|
|
n->next->prev = n->prev;
|
|
n->prev->next = n->next;
|
|
return n;
|
|
}
|
|
|
|
static struct _memblock *
|
|
memblock_addr(struct _block *block)
|
|
{
|
|
return (struct _memblock *)(((char *)block) - G_STRUCT_OFFSET(struct _memblock, data));
|
|
}
|
|
|
|
/* read/sync the rootblock into the block_cache structure */
|
|
static int
|
|
ibex_block_read_root(struct _memcache *block_cache)
|
|
{
|
|
lseek(block_cache->fd, 0, SEEK_SET);
|
|
if (read(block_cache->fd, &block_cache->root, sizeof(block_cache->root)) != sizeof(block_cache->root)) {
|
|
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ibex_block_sync_root(struct _memcache *block_cache)
|
|
{
|
|
lseek(block_cache->fd, 0, SEEK_SET);
|
|
if (write(block_cache->fd, &block_cache->root, sizeof(block_cache->root)) != sizeof(block_cache->root)) {
|
|
return -1;
|
|
}
|
|
return fsync(block_cache->fd);
|
|
}
|
|
|
|
|
|
/**
|
|
* ibex_block_dirty:
|
|
* @block:
|
|
*
|
|
* Dirty a block. This will cause it to be written to disk on
|
|
* a cache sync, or when the block is flushed from the cache.
|
|
**/
|
|
void
|
|
ibex_block_dirty(struct _block *block)
|
|
{
|
|
memblock_addr(block)->flags |= BLOCK_DIRTY;
|
|
}
|
|
|
|
static void
|
|
sync_block(struct _memcache *block_cache, struct _memblock *memblock)
|
|
{
|
|
if (block_log)
|
|
printf("writing block %d\n", memblock->block);
|
|
|
|
lseek(block_cache->fd, memblock->block, SEEK_SET);
|
|
if (write(block_cache->fd, &memblock->data, sizeof(memblock->data)) != -1) {
|
|
memblock->flags &= ~BLOCK_DIRTY;
|
|
}
|
|
#ifdef IBEX_STATS
|
|
add_write(block_cache, memblock->block);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* ibex_block_cache_sync:
|
|
* @block_cache:
|
|
*
|
|
* Ensure the block cache is fully synced to disk.
|
|
**/
|
|
void
|
|
ibex_block_cache_sync(struct _memcache *block_cache)
|
|
{
|
|
struct _memblock *memblock;
|
|
|
|
if (block_cache->failed)
|
|
return;
|
|
|
|
memblock = (struct _memblock *)block_cache->nodes.head;
|
|
while (memblock->next) {
|
|
#ifdef MALLOC_CHECK
|
|
checkmem(memblock);
|
|
#endif
|
|
if (memblock->flags & BLOCK_DIRTY) {
|
|
sync_block(block_cache, memblock);
|
|
}
|
|
memblock = memblock->next;
|
|
}
|
|
|
|
block_cache->root.flags |= IBEX_ROOT_SYNCF;
|
|
if (ibex_block_sync_root(block_cache) != 0) {
|
|
block_cache->root.flags &= ~IBEX_ROOT_SYNCF;
|
|
}
|
|
|
|
#ifdef IBEX_STATS
|
|
dump_stats(block_cache);
|
|
#endif
|
|
|
|
}
|
|
|
|
#ifdef MALLOC_CHECK
|
|
static void
|
|
check_cache(struct _memcache *block_cache)
|
|
{
|
|
struct _memblock *mw, *mn;
|
|
|
|
checkmem(block_cache);
|
|
checkmem(block_cache->index);
|
|
|
|
mw = (struct _memblock *)block_cache->nodes.head;
|
|
mn = mw->next;
|
|
while (mn) {
|
|
checkmem(mw);
|
|
mw = mn;
|
|
mn = mn->next;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* ibex_block_cache_flush:
|
|
* @block_cache:
|
|
*
|
|
* Ensure the block cache is fully synced to disk, and then flush
|
|
* its contents from memory.
|
|
**/
|
|
void
|
|
ibex_block_cache_flush(struct _memcache *block_cache)
|
|
{
|
|
struct _memblock *mw, *mn;
|
|
|
|
ibex_block_cache_sync(block_cache);
|
|
|
|
mw = (struct _memblock *)block_cache->nodes.head;
|
|
mn = mw->next;
|
|
while (mn) {
|
|
g_hash_table_remove(block_cache->index, (void *)mw->block);
|
|
g_free(mw);
|
|
mw = mn;
|
|
mn = mn->next;
|
|
}
|
|
ibex_list_new(&block_cache->nodes);
|
|
}
|
|
|
|
/**
|
|
* ibex_block_read:
|
|
* @block_cache:
|
|
* @blockid:
|
|
*
|
|
* Read the data of a block by blockid. The data contents is backed by
|
|
* the block cache, and should be considered static.
|
|
*
|
|
* TODO; should this return a NULL block on error?
|
|
*
|
|
* Return value: The address of the block data (which may be cached).
|
|
**/
|
|
struct _block *
|
|
ibex_block_read(struct _memcache *block_cache, blockid_t blockid)
|
|
{
|
|
struct _memblock *memblock;
|
|
|
|
#ifdef MALLOC_CHECK
|
|
check_cache(block_cache);
|
|
#endif
|
|
|
|
/* nothing can read the root block directly */
|
|
ibex_block_cache_assert(block_cache, blockid != 0);
|
|
ibex_block_cache_assert(block_cache, blockid < block_cache->root.roof);
|
|
|
|
memblock = g_hash_table_lookup(block_cache->index, (void *)blockid);
|
|
|
|
#ifdef MALLOC_CHECK
|
|
check_cache(block_cache);
|
|
#endif
|
|
|
|
if (memblock) {
|
|
d(printf("foudn blockid in cache %d = %p\n", blockid, &memblock->data));
|
|
|
|
/* 'access' page */
|
|
ibex_list_remove((struct _listnode *)memblock);
|
|
ibex_list_addtail(&block_cache->nodes, (struct _listnode *)memblock);
|
|
|
|
#ifdef MALLOC_CHECK
|
|
check_cache(block_cache);
|
|
#endif
|
|
|
|
#ifdef IBEX_STATS
|
|
add_hit(block_cache, memblock->block);
|
|
#endif
|
|
|
|
#ifdef MALLOC_CHECK
|
|
check_cache(block_cache);
|
|
#endif
|
|
|
|
return &memblock->data;
|
|
}
|
|
#ifdef IBEX_STATS
|
|
add_miss(block_cache, blockid);
|
|
add_read(block_cache, blockid);
|
|
#endif
|
|
if (block_log)
|
|
printf("miss block %d\n", blockid);
|
|
|
|
d(printf("loading blockid from disk %d\n", blockid));
|
|
memblock = g_malloc(sizeof(*memblock));
|
|
memblock->block = blockid;
|
|
memblock->flags = 0;
|
|
lseek(block_cache->fd, blockid, SEEK_SET);
|
|
memset(&memblock->data, 0, sizeof(memblock->data));
|
|
read(block_cache->fd, &memblock->data, sizeof(memblock->data));
|
|
|
|
ibex_list_addtail(&block_cache->nodes, (struct _listnode *)memblock);
|
|
g_hash_table_insert(block_cache->index, (void *)blockid, memblock);
|
|
if (block_cache->count >= CACHE_SIZE) {
|
|
struct _memblock *old = (struct _memblock *)block_cache->nodes.head;
|
|
d(printf("discaring cache block %d\n", old->block));
|
|
g_hash_table_remove(block_cache->index, (void *)old->block);
|
|
ibex_list_remove((struct _listnode *)old);
|
|
if (old->flags & BLOCK_DIRTY) {
|
|
/* are we about to un-sync the file? update root and sync it */
|
|
if (block_cache->root.flags & IBEX_ROOT_SYNCF) {
|
|
d(printf("Unsyncing root block\n"));
|
|
|
|
block_cache->root.flags &= ~IBEX_ROOT_SYNCF;
|
|
if (ibex_block_sync_root(block_cache) != 0) {
|
|
/* what do we do? i dont know! */
|
|
g_warning("Could not sync root block of index: %s", strerror(errno));
|
|
}
|
|
}
|
|
sync_block(block_cache, old);
|
|
}
|
|
g_free(old);
|
|
} else {
|
|
block_cache->count++;
|
|
}
|
|
|
|
d(printf(" --- cached blocks : %d\n", block_cache->count));
|
|
|
|
#ifdef MALLOC_CHECK
|
|
check_cache(block_cache);
|
|
#endif
|
|
return &memblock->data;
|
|
}
|
|
|
|
void
|
|
ibex_block_cache_fail(struct _memcache *block_cache, char *where, int line, char *why)
|
|
{
|
|
block_cache->failed = TRUE;
|
|
block_cache->root.flags &= ~IBEX_ROOT_SYNCF;
|
|
/* and blow it away, we can do nothing better yet */
|
|
ftruncate(block_cache->fd, 0);
|
|
|
|
g_warning("%s(%d): Integrity assertion failed: '%s' on file '%s'", where, line, why, block_cache->name);
|
|
|
|
longjmp(block_cache->failenv, 1);
|
|
}
|
|
|
|
/**
|
|
* ibex_block_cache_open:
|
|
* @name:
|
|
* @flags: Flags as to open(2), should use O_RDWR and optionally O_CREAT.
|
|
* @mode: Mose as to open(2)
|
|
*
|
|
* Open a block file.
|
|
*
|
|
* FIXME; this currently also initialises the word and name indexes
|
|
* because their pointers are stored in the root block. Should be
|
|
* upto the caller to manage these pointers/data.
|
|
*
|
|
* Return value: NULL if the backing file could not be opened.
|
|
**/
|
|
struct _memcache *
|
|
ibex_block_cache_open(const char *name, int flags, int mode)
|
|
{
|
|
struct _memcache *block_cache = g_malloc0(sizeof(*block_cache));
|
|
|
|
d(printf("opening ibex file: %s", name));
|
|
|
|
/* setup cache */
|
|
ibex_list_new(&block_cache->nodes);
|
|
block_cache->count = 0;
|
|
block_cache->index = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
block_cache->fd = open(name, flags, mode);
|
|
block_cache->failed = FALSE;
|
|
block_cache->name = g_strdup(name);
|
|
|
|
if (block_cache->fd == -1) {
|
|
g_hash_table_destroy(block_cache->index);
|
|
g_free(block_cache);
|
|
return NULL;
|
|
}
|
|
|
|
if (ibex_block_cache_setjmp(block_cache) != 0) {
|
|
close(block_cache->fd);
|
|
g_hash_table_destroy(block_cache->index);
|
|
g_free(block_cache);
|
|
return NULL;
|
|
}
|
|
|
|
ibex_block_read_root(block_cache);
|
|
if (block_cache->root.roof == 0
|
|
|| memcmp(block_cache->root.version, IBEX_VERSION, 4)
|
|
|| ((block_cache->root.flags & IBEX_ROOT_SYNCF) == 0)) {
|
|
d(printf("Initialising superblock\n"));
|
|
/* reset root data */
|
|
memcpy(block_cache->root.version, IBEX_VERSION, 4);
|
|
block_cache->root.roof = 1024;
|
|
block_cache->root.free = 0;
|
|
block_cache->root.words = 0;
|
|
block_cache->root.names = 0;
|
|
block_cache->root.tail = 0; /* list of tail blocks */
|
|
block_cache->root.flags = 0;
|
|
ibex_block_sync_root(block_cache);
|
|
/* reset the file contents */
|
|
ftruncate(block_cache->fd, 1024);
|
|
} else {
|
|
d(printf("superblock already initialised:\n"
|
|
" roof = %d\n free = %d\n words = %d\n names = %d\n tail = %d\n",
|
|
block_cache->root.roof, block_cache->root.free,
|
|
block_cache->root.words, block_cache->root.names, block_cache->root.tail));
|
|
}
|
|
/* FIXME: this should be moved higher up in the object tree */
|
|
{
|
|
struct _IBEXWord *ibex_create_word_index_mem(struct _memcache *bc, blockid_t *wordroot, blockid_t *nameroot);
|
|
|
|
block_cache->words = ibex_create_word_index_mem(block_cache, &block_cache->root.words,&block_cache->root.names);
|
|
}
|
|
|
|
#ifdef IBEX_STATS
|
|
init_stats(block_cache);
|
|
#endif
|
|
|
|
return block_cache;
|
|
}
|
|
|
|
/**
|
|
* ibex_block_cache_close:
|
|
* @block_cache:
|
|
*
|
|
* Close the block file, sync any remaining cached data
|
|
* to disk, and free all resources.
|
|
**/
|
|
void
|
|
ibex_block_cache_close(struct _memcache *block_cache)
|
|
{
|
|
struct _memblock *mw, *mn;
|
|
|
|
ibex_block_cache_sync(block_cache);
|
|
close(block_cache->fd);
|
|
g_free(block_cache->name);
|
|
|
|
mw = (struct _memblock *)block_cache->nodes.head;
|
|
mn = mw->next;
|
|
while (mn) {
|
|
g_free(mw);
|
|
mw = mn;
|
|
mn = mw->next;
|
|
}
|
|
|
|
g_hash_table_destroy(block_cache->index);
|
|
|
|
g_free(block_cache);
|
|
}
|
|
|
|
/**
|
|
* ibex_block_free:
|
|
* @block_cache:
|
|
* @blockid:
|
|
*
|
|
* Return a block to the free pool.
|
|
**/
|
|
void
|
|
ibex_block_free(struct _memcache *block_cache, blockid_t blockid)
|
|
{
|
|
struct _block *block = ibex_block_read(block_cache, blockid);
|
|
|
|
block->next = block_number(block_cache->root.free);
|
|
block_cache->root.free = blockid;
|
|
ibex_block_dirty((struct _block *)block);
|
|
}
|
|
|
|
/**
|
|
* ibex_block_get:
|
|
* @block_cache:
|
|
*
|
|
* Allocate a new block, or access a previously freed block and return
|
|
* its block id. The block will have zeroed contents.
|
|
*
|
|
* Return value: 0 if there are no blocks left (disk full/read only
|
|
* file, etc).
|
|
**/
|
|
blockid_t
|
|
ibex_block_get(struct _memcache *block_cache)
|
|
{
|
|
struct _block *block;
|
|
blockid_t head;
|
|
|
|
if (block_cache->root.free) {
|
|
head = block_cache->root.free;
|
|
block = ibex_block_read(block_cache, head);
|
|
block_cache->root.free = block_location(block->next);
|
|
} else {
|
|
/* TODO: check the block will fit first */
|
|
/* TODO: no need to read this block, can allocate it manually (saves a syscall/read) */
|
|
head = block_cache->root.roof;
|
|
block_cache->root.roof += BLOCK_SIZE;
|
|
block = ibex_block_read(block_cache, head);
|
|
}
|
|
|
|
ibex_block_cache_assert(block_cache, head != 0);
|
|
|
|
d(printf("new block = %d\n", head));
|
|
block->next = 0;
|
|
block->used = 0;
|
|
ibex_block_dirty(block);
|
|
return head;
|
|
}
|