aboutsummaryrefslogblamecommitdiffstats
path: root/libibex/block.c
blob: 3d6f908c725d541ec703b812509f1f3cdf79a0fa (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

                                                                        
                                  
  
                                              

                                                                



                                                                   

                                                                    
                                           
  


                                                               












                                    

                   



                  





                        


                 
              









































































                                                                                                   




















                                                             









































































                                                                                               






















                                                                                                                 















                                                                     


                                                              

















                                                                                    
                                   
 


                                

                                                               




                                                          


                                          
 


                                                            
         






                                


















                                                         
























                                                                           
















                                                                       




                                                      

                                                                               

                                                                            




                                 

                                                                                        
 


                                                                                     




                                         


                                                      




                                         





                                       


                                                   






                                                                       
 







                                                                                    
                                                                                        
                                                                        
                                                                    
 




                                                                                                             
                         








                                                                    


                                 


                               












                                                                                                                
















                                                                         








                                                                             

                                           






                                                         






                                                         

                                          
                                                                 
                                                                    
                                                       
                                     
                                                                   






                                                                         

                                                 
                



                                                                                                    
         
                                                                      
         
                                                                                                                             
 
                                                                                                                                






















                                                       
                                  























                                                                 

                                                                     

                                                           















                                                                      


                             

                                              
                                                           
                                                                     


                                                                                                       

                                                     


                                                           
                                                        




                                            

                    
/* -*- 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 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.
 */

/*
  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;
}