/* -*- 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.
*/
/* a disk based array storage class that stores the tails of data lists
in common blocks */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <glib.h>
#include "block.h"
#include "index.h"
#define d(x)
/*#define DEBUG*/
/* marker to define which root keys indicate a single length key */
#define BLOCK_ONE (1<<BLOCK_BITS)
/* tail blocks only contain tail data ... */
/* and we pack it in, similar to the key data, only more compact */
struct _tailblock {
unsigned int next:32-BLOCK_BITS; /* only needs to point to block numbers */
unsigned int used:BLOCK_BITS; /* how many entries are used */
union {
unsigned char offset[BLOCK_SIZE-4]; /* works upto blocksize of 1024 bytes */
nameid_t data[(BLOCK_SIZE-4)/4];
} tailblock_u;
};
#define tb_offset tailblock_u.offset
#define tb_data tailblock_u.data
/* map a tail index to a block index */
#define TAIL_INDEX(b) ((b) & (BLOCK_SIZE-1))
/* map a tail index to a block number */
#define TAIL_BLOCK(b) ((b) & ~(BLOCK_SIZE-1))
/* map a block + index to a tailid */
#define TAIL_KEY(b, i) (((b) & ~(BLOCK_SIZE-1)) | ((i) & (BLOCK_SIZE-1)))
#define TAIL_THRESHOLD ((BLOCK_SIZE-8)/6)
static struct _IBEXStore *disk_create(struct _memcache *bc);
static int disk_sync(struct _IBEXStore *store);
static int disk_close(struct _IBEXStore *store);
static blockid_t disk_add(struct _IBEXStore *store, blockid_t *head, blockid_t *tail, nameid_t data);
static blockid_t disk_add_list(struct _IBEXStore *store, blockid_t *head, blockid_t *tail, GArray *data);
static blockid_t disk_remove(struct _IBEXStore *store, blockid_t *head, blockid_t *tail, nameid_t data);
static void disk_free(struct _IBEXStore *store, blockid_t head, blockid_t tail);
static gboolean disk_find(struct _IBEXStore *store, blockid_t head, blockid_t tail, nameid_t data);
static GArray *disk_get(struct _IBEXStore *store, blockid_t head, blockid_t tail);
struct _IBEXStoreClass ibex_diskarray_class = {
disk_create, disk_sync, disk_close,
disk_add, disk_add_list,
disk_remove, disk_free,
disk_find, disk_get
};
static int
tail_info(struct _tailblock *bucket, nameid_t tailid, blockid_t **startptr)
{
blockid_t *start, *end;
int index;
/* get start/end of area to zap */
index = TAIL_INDEX(tailid);
start = &bucket->tb_data[bucket->tb_offset[index]];
if (index == 0) {
end = &bucket->tb_data[sizeof(bucket->tb_data)/sizeof(bucket->tb_data[0])];
} else {
end = &bucket->tb_data[bucket->tb_offset[index-1]];
}
if (startptr)
*startptr = start;
return end-start;
}
/* compresses (or expand) the bucket entry, to the new size */
static void
tail_compress(struct _memcache *blocks, struct _tailblock *bucket, int index, int newsize)
{
int i;
blockid_t *start, *end, *newstart;
/* get start/end of area to zap */
start = &bucket->tb_data[bucket->tb_offset[index]];
if (index == 0) {
end = &bucket->tb_data[sizeof(bucket->tb_data)/sizeof(bucket->tb_data[0])];
} else {
end = &bucket->tb_data[bucket->tb_offset[index-1]];
}
if (end-start == newsize)
return;
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXyyyyy
0 20 25
newsize = 0
end = 25
newstart = 0
start = 20
newstart+(end-start)-newsize = 5
i = start-newstart
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXyyyyy
0 20 25
newsize = 2
end = 25
newstart = 0
start = 20
newstart+(end-start)-newsize = 3
i = start-newstart + MIN(end-start, newsize)) = 22
XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
5 25
newsize = 5
end = 25
start = 25
newstart = 5
newstart+(end-start)-newsize = 0
i = start-newstart = 20
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXyy
3 23 25
newsize = 5
end = 25
start = 23
newstart = 3
newstart+(end-start)-newsize = 0
i = start-newstart + MIN(end-start, newsize) = 22
*/
/* fixup data */
newstart = &bucket->tb_data[bucket->tb_offset[bucket->used-1]];
ibex_block_cache_assert(blocks, newstart+(end-start)-newsize <= &bucket->tb_data[sizeof(bucket->tb_data)/sizeof(bucket->tb_data[0])]);
ibex_block_cache_assert(blocks, newstart + (start-newstart) + MIN(end-start, newsize) <= &bucket->tb_data[sizeof(bucket->tb_data)/sizeof(bucket->tb_data[0])]);
ibex_block_cache_assert(blocks, newstart+(end-start)-newsize >= (blockid_t *) &bucket->tb_offset[bucket->used]);
ibex_block_cache_assert(blocks, newstart + (start-newstart) + MIN(end-start, newsize) >= (blockid_t *) &bucket->tb_offset[bucket->used]);
memmove(newstart+(end-start)-newsize, newstart, ((start-newstart)+MIN(end-start, newsize)) * sizeof(blockid_t));
/* fixup key pointers */
for (i=index;i<bucket->used;i++) {
bucket->tb_offset[i] += (end-start)-newsize;
}
ibex_block_dirty((struct _block *)bucket);
}
/*
returns the number of blockid's free
*/
static int
tail_space(struct _tailblock *tail)
{
if (tail->used == 0)
return sizeof(tail->tb_data)/sizeof(tail->tb_data[0])-1;
return &tail->tb_data[tail->tb_offset[tail->used-1]]
- (blockid_t *)&tail->tb_offset[tail->used];
}
#if 0
static void
tail_dump(struct _memcache *blocks, blockid_t tailid)
{
int i;
struct _tailblock *tail = (struct _tailblock *)ibex_block_read(blocks, TAIL_BLOCK(tailid));
printf("Block %d, used %d\n", tailid, tail->used);
for (i=0;i<sizeof(struct _tailblock)/sizeof(unsigned int);i++) {
printf(" %08x", ((unsigned int *)tail)[i]);
}
printf("\n");
}
#endif
static blockid_t
tail_get(struct _memcache *blocks, int size)
{
blockid_t tailid;
struct _tailblock *tail;
int freeindex;
blockid_t *end;
int i, count = 0;
d(printf("looking for a tail node with %d items in it\n", size));
/* look for a node with enough space, if we dont find it fairly
quickly, just quit. needs a better free algorithm i think ... */
tailid = blocks->root.tail;
while (tailid && count<5) {
int space;
d(printf(" checking tail node %d\n", tailid));
tail = (struct _tailblock *)ibex_block_read(blocks, tailid);
if (tail->used == 0) {
/* assume its big enough ... */
tail->used = 1;
tail->tb_offset[0] = sizeof(tail->tb_data)/sizeof(tail->tb_data[0]) - size;
d(printf("allocated %d (%d), used %d\n", tailid, tailid, tail->used));
ibex_block_dirty((struct _block *)tail);
ibex_block_cache_assert(blocks, &tail->tb_offset[tail->used-1]
< (unsigned char *) &tail->tb_data[tail->tb_offset[tail->used-1]]);
return tailid;
}
ibex_block_cache_assert(blocks, &tail->tb_offset[tail->used-1]
< (unsigned char *) &tail->tb_data[tail->tb_offset[tail->used-1]]);
/* see if we have a free slot first */
freeindex = -1;
end = &tail->tb_data[sizeof(tail->tb_data)/sizeof(tail->tb_data[0])];
for (i=0;i<tail->used;i++) {
if (end == &tail->tb_data[tail->tb_offset[i]]) {
freeindex = i;
break;
}
end = &tail->tb_data[tail->tb_offset[i]];
}
/* determine how much space we have available - incl any extra header we might need */
space = ((char *)&tail->tb_data[tail->tb_offset[tail->used-1]])
- ((char *)&tail->tb_offset[tail->used])
/* FIXMEL work out why this is out a little bit */
- 8;
if (freeindex == -1)
space -= sizeof(tail->tb_offset[0]);
/* if we have enough, set it up, creating a new entry if necessary */
/* for some really odd reason the compiler promotes this expression to unsigned, hence
the requirement for the space>0 check ... */
if (space>0 && space > size*sizeof(blockid_t)) {
d(printf("space = %d, size = %d size*sizeof() = %d truth = %d\n", space, size, size*sizeof(blockid_t), space>size*sizeof(blockid_t)));
if (freeindex == -1) {
freeindex = tail->used;
tail->tb_offset[tail->used] = tail->tb_offset[tail->used-1];
tail->used++;
}
tail_compress(blocks, tail, freeindex, size);
ibex_block_dirty((struct _block *)tail);
d(printf("allocated %d (%d), used %d\n", tailid, TAIL_KEY(tailid, freeindex), tail->used));
return TAIL_KEY(tailid, freeindex);
}
count++;
tailid = block_location(tail->next);
}
d(printf("allocating new data node for tail data\n"));
tailid = ibex_block_get(blocks);
tail = (struct _tailblock *)ibex_block_read(blocks, tailid);
tail->next = block_number(blocks->root.tail);
blocks->root.tail = tailid;
tail->used = 1;
tail->tb_offset[0] = sizeof(tail->tb_data)/sizeof(tail->tb_data[0]) - size;
ibex_block_dirty((struct _block *)tail);
d(printf("allocated %d (%d), used %d\n", tailid, TAIL_KEY(tailid, 0), tail->used));
g_assert(&tail->tb_offset[tail->used-1]
< (unsigned char *) &tail->tb_data[tail->tb_offset[tail->used-1]]);
return TAIL_KEY(tailid, 0);
}
static void
tail_free(struct _memcache *blocks, blockid_t tailid)
{
struct _tailblock *tail;
d(printf("freeing tail id %d\n", tailid));
if (tailid == 0)
return;
tail = (struct _tailblock *)ibex_block_read(blocks, TAIL_BLOCK(tailid));
d(printf(" tail %d used %d\n", tailid, tail->used));
ibex_block_cache_assert(blocks, tail->used);
if (TAIL_INDEX(tailid) == tail->used - 1) {
tail->used --;
} else {
tail_compress(blocks, tail, TAIL_INDEX(tailid), 0);
}
ibex_block_dirty((struct _block *)tail);
}
static struct _IBEXStore *disk_create(struct _memcache *bc)
{
struct _IBEXStore *store;
store = g_malloc0(sizeof(*store));
store->klass = &ibex_diskarray_class;
store->blocks = bc;
return store;
}
static int disk_sync(struct _IBEXStore *store)
{
/* no cache, nop */
return 0;
}
static int disk_close(struct _IBEXStore *store)
{
g_free(store);
return 0;
}
static blockid_t
disk_add(struct _IBEXStore *store, blockid_t *headptr, blockid_t *tailptr, nameid_t data)
{
struct _block *block;
struct _block *newblock;
blockid_t new, head = *headptr /*, tail = *tailptr*/;
g_error("Inbimplemented");
if (head == 0) {
head = ibex_block_get(store->blocks);
}
block = ibex_block_read(store->blocks, head);
d(printf("adding record %d to block %d (next = %d)\n", data, head, block->next));
if (block->used < sizeof(block->bl_data)/sizeof(block->bl_data[0])) {
(printf("adding record into block %d %d\n", head, data));
block->bl_data[block->used] = data;
block->used++;
ibex_block_dirty(block);
return head;
} else {
new = ibex_block_get(store->blocks);
newblock = ibex_block_read(store->blocks, new);
newblock->next = block_number(head);
newblock->bl_data[0] = data;
newblock->used = 1;
d(printf("adding record into new %d %d, next =%d\n", new, data, newblock->next));
ibex_block_dirty(newblock);
return new;
}
}
static blockid_t
disk_add_blocks_internal(struct _IBEXStore *store, blockid_t *headptr, blockid_t *tailptr, GArray *data)
{
blockid_t head = *headptr, tail = *tailptr, new;
int tocopy;
struct _tailblock *tailblock;
struct _block *block, *newblock;
int space, copied = 0, left;
/* assumes this funciton is in control of any tail creation */
ibex_block_cache_assert(store->blocks, tail == 0);
d(printf("Adding %d items to block list\n", data->len));
if (head == 0) {
head = ibex_block_get(store->blocks);
d(printf("allocating new head %d\n", head));
}
block = ibex_block_read(store->blocks, head);
/* ensure the first block is full before proceeding */
space = sizeof(block->bl_data)/sizeof(block->bl_data[0]) - block->used;
if (space) {
tocopy = MIN(data->len, space);
memcpy(block->bl_data+block->used, &g_array_index(data, blockid_t, copied), tocopy*sizeof(blockid_t));
block->used += tocopy;
ibex_block_dirty(block);
copied = tocopy;
d(printf("copied %d items to left over last node\n", tocopy));
}
while (copied < data->len) {
left = data->len - copied;
/* do we drop the rest in a tail? */
if (left < TAIL_THRESHOLD) {
d(printf("Storing remaining %d items in tail\n", left));
tocopy = left;
new = tail_get(store->blocks, tocopy);
tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(new));
memcpy(&tailblock->tb_data[tailblock->tb_offset[TAIL_INDEX(new)]],
&g_array_index(data, blockid_t, copied), tocopy*sizeof(blockid_t));
*tailptr = new;
} else {
new = ibex_block_get(store->blocks);
newblock = (struct _block *)ibex_block_read(store->blocks, new);
newblock->next = block_number(head);
tocopy = MIN(left, sizeof(block->bl_data)/sizeof(block->bl_data[0]));
d(printf("storing %d items in own block\n", tocopy));
memcpy(newblock->bl_data, &g_array_index(data, blockid_t, copied), tocopy*sizeof(blockid_t));
newblock->used = tocopy;
block = newblock;
head = new;
ibex_block_dirty(newblock);
}
copied += tocopy;
}
*headptr = head;
return head;
}
/*
case 1:
no head, no tail, adding a lot of data
add blocks until the last, which goes in a tail node
case 2:
no head, no tail, adding a little data
just add a tail node
case 3:
no head, tail, total exceeds a block
merge as much as possible into new full blocks, then the remainder in the tail
case 4:
no head, tail, room in existing tail for data
add new data to tail node
case 5:
no head, tail, no room in existing tail for data
add a new tail node, copy data across, free old tail node
*/
static blockid_t
disk_add_list(struct _IBEXStore *store, blockid_t *headptr, blockid_t *tailptr, GArray *data)
{
blockid_t new, head = *headptr, tail = *tailptr, *start;
struct _tailblock *tailblock, *tailnew;
int len;
GArray *tmpdata = NULL;
d(printf("adding %d items head=%d tail=%d\n", data->len, head, tail));
if (data->len == 0)
return head;
/* store length=1 data in the tail pointer */
if (head == 0 && tail == 0 && data->len == 1) {
*headptr = BLOCK_ONE;
*tailptr = g_array_index(data, blockid_t, 0);
return BLOCK_ONE;
}
/* if we got length=1 data to append to, upgrade it to a real block list */
if (head == BLOCK_ONE) {
tmpdata = g_array_new(0, 0, sizeof(blockid_t));
g_array_append_vals(tmpdata, data->data, data->len);
g_array_append_val(tmpdata, tail);
head = *headptr = 0;
tail = *tailptr = 0;
}
/* if we have no head, then check the tail */
if (head == 0) {
if (tail == 0) {
if (data->len >= TAIL_THRESHOLD) {
/* add normally */
head = disk_add_blocks_internal(store, headptr, tailptr, data);
} else {
/* else add to a tail block */
new = tail_get(store->blocks, data->len);
d(printf("adding %d items to a tail block %d\n", data->len, new));
tailnew = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(new));
memcpy(&tailnew->tb_data[tailnew->tb_offset[TAIL_INDEX(new)]],
data->data, data->len*sizeof(blockid_t));
*tailptr = new;
ibex_block_dirty((struct _block *)tailnew);
}
} else {
tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(tail));
len = tail_info(tailblock, tail, &start);
/* case 3 */
if (len + data->len >= TAIL_THRESHOLD) {
/* this is suboptimal, but should work - merge the tail data with
our new data, and write it out */
if (tmpdata == NULL) {
tmpdata = g_array_new(0, 0, sizeof(blockid_t));
g_array_append_vals(tmpdata, data->data, data->len);
}
g_array_append_vals(tmpdata, start, len);
*tailptr = 0;
tail_free(store->blocks, tail);
head = disk_add_blocks_internal(store, headptr, tailptr, tmpdata);
} else if (tail_space(tailblock) >= data->len) {
/* can we just expand this in our node, or do we need a new one? */
tail_compress(store->blocks, tailblock, TAIL_INDEX(tail), data->len + len);
memcpy(&tailblock->tb_data[tailblock->tb_offset[TAIL_INDEX(tail)] + len],
data->data, data->len * sizeof(blockid_t));
ibex_block_dirty((struct _block *)tailblock);
} else {
/* we need to allocate a new tail node */
new = tail_get(store->blocks, data->len + len);
/* and copy the data across */
tailnew = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(new));
memcpy(&tailnew->tb_data[tailnew->tb_offset[TAIL_INDEX(new)]],
start, len*sizeof(blockid_t));
memcpy(&tailnew->tb_data[tailnew->tb_offset[TAIL_INDEX(new)]+len],
data->data, data->len*sizeof(blockid_t));
tail_free(store->blocks, tail);
ibex_block_dirty((struct _block *)tailnew);
*tailptr = new;
}
}
} else {
if (tail) {
/* read/merge the tail with the new data, rewrite out.
suboptimal, but it should be 'ok' ? */
tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(tail));
len = tail_info(tailblock, tail, &start);
tmpdata = g_array_new(0, 0, sizeof(blockid_t));
g_array_append_vals(tmpdata, start, len);
g_array_append_vals(tmpdata, data->data, data->len);
*tailptr = 0;
tail_free(store->blocks, tail);
head = disk_add_blocks_internal(store, headptr, tailptr, tmpdata);
} else {
head = disk_add_blocks_internal(store, headptr, tailptr, data);
}
}
if (tmpdata)
g_array_free(tmpdata, TRUE);
return head;
}
static blockid_t
disk_remove(struct _IBEXStore *store, blockid_t *headptr, blockid_t *tailptr, nameid_t data)
{
blockid_t head = *headptr, node = *headptr, tail = *tailptr;
int i;
/* special case for 1-item nodes */
if (head == BLOCK_ONE) {
if (tail == data) {
*tailptr = 0;
*headptr = 0;
head = 0;
}
return head;
}
d(printf("removing %d from %d\n", data, head));
while (node) {
struct _block *block = ibex_block_read(store->blocks, node);
for (i=0;i<block->used;i++) {
if (block->bl_data[i] == data) {
struct _block *start = ibex_block_read(store->blocks, head);
d(printf("removing data from block %d\n ", head));
start->used--;
block->bl_data[i] = start->bl_data[start->used];
if (start->used == 0) {
blockid_t new;
d(printf("dropping block %d, new head = %d\n", head, start->next));
new = block_location(start->next);
start->next = block_number(store->blocks->root.free);
store->blocks->root.free = head;
head = new;
}
ibex_block_dirty(block);
ibex_block_dirty(start);
*headptr = head;
return head;
}
}
node = block_location(block->next);
}
if (tail) {
struct _tailblock *tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(tail));
int len;
blockid_t *start;
len = tail_info(tailblock, tail, &start);
for (i=0;i<len;i++) {
if (start[i] == data) {
for (;i<len-1;i++)
start[i] = start[i+1];
if (len == 1)
*tailptr = 0;
if (len == 1 && (tailblock->used-1) == TAIL_INDEX(tail)) {
d(printf("dropping/unlinking tailblock %d (%d) used = %d\n",
TAIL_BLOCK(tail), tail, tailblock->used));
tailblock->used--;
/* drop/unlink block? */
ibex_block_dirty((struct _block *)tailblock);
*tailptr = 0;
} else {
tail_compress(store->blocks, tailblock, TAIL_INDEX(tail), len-1);
ibex_block_dirty((struct _block *)tailblock);
}
}
}
}
return head;
}
static void disk_free(struct _IBEXStore *store, blockid_t head, blockid_t tail)
{
blockid_t next;
struct _block *block;
if (head == BLOCK_ONE)
return;
while (head) {
d(printf("freeing block %d\n", head));
block = ibex_block_read(store->blocks, head);
next = block_location(block->next);
ibex_block_free(store->blocks, head);
head = next;
}
if (tail) {
struct _tailblock *tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(tail));
d(printf("freeing tail block %d (%d)\n", TAIL_BLOCK(tail), tail));
tail_compress(store->blocks, tailblock, TAIL_INDEX(tail), 0);
ibex_block_dirty((struct _block *)tailblock);
}
}
static gboolean
disk_find(struct _IBEXStore *store, blockid_t head, blockid_t tail, nameid_t data)
{
blockid_t node = head;
int i;
d(printf("finding %d from %d\n", data, head));
if (head == BLOCK_ONE)
return data == tail;
/* first check full blocks */
while (node) {
struct _block *block = ibex_block_read(store->blocks, node);
for (i=0;i<block->used;i++) {
if (block->bl_data[i] == data) {
return TRUE;
}
}
node = block_location(block->next);
}
/* then check tail */
if (tail) {
struct _tailblock *tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(tail));
int len;
blockid_t *start;
len = tail_info(tailblock, tail, &start);
for (i=0;i<len;i++) {
if (start[i] == data)
return TRUE;
}
}
return FALSE;
}
static GArray *
disk_get(struct _IBEXStore *store, blockid_t head, blockid_t tail)
{
GArray *result = g_array_new(0, 0, sizeof(nameid_t));
if (head == BLOCK_ONE) {
g_array_append_val(result, tail);
return result;
}
while (head) {
struct _block *block = ibex_block_read(store->blocks, head);
d(printf("getting data from block %d\n", head));
g_array_append_vals(result, block->bl_data, block->used);
head = block_location(block->next);
d(printf("next = %d\n", head));
}
/* chuck on any tail data as well */
if (tail) {
struct _tailblock *tailblock;
int len;
blockid_t *start;
tailblock = (struct _tailblock *)ibex_block_read(store->blocks, TAIL_BLOCK(tail));
len = tail_info(tailblock, tail, &start);
g_array_append_vals(result, start, len);
}
return result;
}
void ibex_diskarray_dump(struct _memcache *blocks, blockid_t head, blockid_t tail);
void
ibex_diskarray_dump(struct _memcache *blocks, blockid_t head, blockid_t tail)
{
blockid_t node = head;
printf("dumping list %d tail %d\n", node, tail);
if (head == BLOCK_ONE) {
printf(" 1 length index: %d\n", tail);
return;
}
while (node) {
struct _block *block = ibex_block_read(blocks, node);
int i;
printf(" block %d used %d\n ", node, block->used);
for (i=0;i<block->used;i++) {
printf(" %d", block->bl_data[i]);
}
printf("\n");
node = block_location(block->next);
}
printf("tail: ");
if (tail) {
struct _tailblock *tailblock;
int len;
blockid_t *start;
int i;
tailblock = (struct _tailblock *)ibex_block_read(blocks, TAIL_BLOCK(tail));
len = tail_info(tailblock, tail, &start);
for (i=0;i<len;i++)
printf(" %d", start[i]);
}
printf("\n");
}
#if 0
int main(int argc, char **argv)
{
struct _memcache *bc;
struct _IBEXStore *disk;
int i;
blockid_t data[100];
GArray adata;
blockid_t head=0, tail=0;
for (i=0;i<100;i++) {
data[i] = i;
}
bc = ibex_block_cache_open("index.db", O_CREAT|O_RDWR, 0600);
disk = ibex_diskarray_class.create(bc);
head = 0;
tail = 0;
adata.data = data;
adata.len = 70;
for (i=0;i<100;i++) {
printf("Loop %d\n", i);
ibex_diskarray_class.add_list(disk, &head, &tail, &adata);
ibex_diskarray_dump(bc, head, tail);
}
#if 0
for (i=1;i<100;i++) {
printf("inserting %d items\n", i);
adata.data = data;
adata.len = i;
head=0;
tail=0;
ibex_diskarray_class.add_list(disk, &head, &tail, &adata);
ibex_diskarray_dump(bc, head, tail);
}
#endif
ibex_diskarray_class.close(disk);
ibex_block_cache_close(bc);
return 0;
}
#endif