/* A simple database manager,
* Goals: crashproof, small, simple and not GPL, the idea is that
* only the last writen entries can disappear in a crash.
* Possible future goals: speed, locking and shrinking
* Written by Fredrik Hubinette.
*/
#include "global.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include "dbase.h"
#ifndef SEEK_SET
#define SEEK_SET 0
#endif
#define BLOCK0_NEEDS_SAVING 1
#define LINKTABLE_NEEDS_SAVING 2
#define HASHTABLE_NEEDS_SAVING 4
#define LINKS_ALLOCATED_BUT_NOT_SAVED 8
#define FAILED_TO_WRITE_HASHTABLE 16
#define FAILED_TO_SAVE_BLOCK0 32
#define SET(X,Y) (X)=(int)htonl((unsigned long)(Y))
#define GET(X) ((int)ntohl((unsigned long)(X)))
/* We can't use the normal hashmem, because it's machine dependant. */
unsigned int hashkey(const DBT t)
{
unsigned int ret;
int mlen=t.len;
char *a=t.data;
ret=9248339*mlen;
switch(mlen&7)
{
case 7: ret^=*(a++);
case 6: ret^=(ret<<4)+*(a++);
case 5: ret^=(ret<<7)+*(a++);
case 4: ret^=(ret<<6)+*(a++);
case 3: ret^=(ret<<3)+*(a++);
case 2: ret^=(ret<<7)+*(a++);
case 1: ret^=(ret<<5)+*(a++);
}
for(mlen>>=3;--mlen>=0;)
{
ret^=(ret<<7)+((((((*(a++)<<3)+*(a++))<<4)+*(a++))<<5)+*(a++));
ret^=(ret>>6)+((((((*(a++)<<3)+*(a++))<<4)+*(a++))<<5)+*(a++));
}
return ret;
}
static block *read_block(dbase *d,int blockno,int ignore,block *whereto)
{
block *tmp=NULL;
int pos;
pos=blockno*BLOCK_SIZE;
if(pos!=lseek(d->fd,pos,SEEK_SET)) return NULL;
if(!whereto)
{
tmp=whereto=(block *)calloc(1,sizeof(block));
if(!whereto) return NULL;
}
if(BLOCK_SIZE!=read(d->fd,(char *)whereto,BLOCK_SIZE) && !ignore)
{
if(tmp) free((char *)tmp);
return NULL;
}
return whereto;
}
static int write_block(dbase *d,int blockno,block *b)
{
int pos;
pos=blockno*BLOCK_SIZE;
if(pos!=lseek(d->fd,pos,SEEK_SET)) return 0;
if(BLOCK_SIZE!=write(d->fd,(char *)b,BLOCK_SIZE)) return 0;
return 1;
}
static int more_links(dbase *d)
{
int *q,tmp;
q=(int *)realloc((char *)d->links,2*d->no_links*sizeof(int));
if(!q) return 0;
for(tmp=d->no_links;tmp<d->no_links*2;tmp++) SET(q[tmp],0);
d->no_links*=2;
d->links=q;
return 1;
}
static void free_file(dbase *d,int ptr,int immidiate)
{
if(immidiate)
{
int *last;
while(ptr!=-1)
{
last=d->links+ptr;
ptr=GET(*last);
SET(*last,0);
}
}else{
d->files_to_free[d->no_files_to_free++]=ptr;
}
}
static int save_linktable2(dbase *d)
{
int pos,len;
d->flags|=LINKS_ALLOCATED_BUT_NOT_SAVED;
pos=GET(d->block0.links.ptr)*BLOCK_SIZE;
if(lseek(d->fd,pos,SEEK_SET)!=pos)
return 0;
len=d->no_links*sizeof(int);
if(write(d->fd,(char *)d->links,len)!=len)
return 0;
d->flags|=BLOCK0_NEEDS_SAVING;
d->flags&=~LINKS_ALLOCATED_BUT_NOT_SAVED;
d->flags&=~LINKTABLE_NEEDS_SAVING;
return 1;
}
static int save_linktable(dbase *d)
{
int e,q,needed_links;
if(d->flags & LINKS_ALLOCATED_BUT_NOT_SAVED)
return save_linktable2(d);
free_file(d,GET(d->block0.links.ptr),0);
needed_links=d->no_links*sizeof(int)/BLOCK_SIZE;
while(1)
{
for(e=0;e<d->no_links-needed_links;e++)
{
for(q=0;q<needed_links;q++)
if(GET(d->links[e+q]))
break;
if(q==needed_links)
break;
}
if(e==d->no_links-needed_links)
{
if(!more_links(d))
return 0;
}else{
break;
}
}
/* allocate links */
for(q=0;q<needed_links-1;q++)
SET(d->links[e+q],e+q+1);
SET(d->links[e+q],-1);
/* gc */
for(q=0;q<d->no_files_to_free;q++)
free_file(d,d->files_to_free[q],1);
d->no_files_to_free=0;
SET(d->block0.links.ptr,e);
SET(d->block0.links.len,d->no_links*sizeof(int));
return save_linktable2(d);
}
static int *read_linktable(dbase *d)
{
int pos;
if(d->links) return d->links;
pos=GET(d->block0.links.ptr)*BLOCK_SIZE;
if(lseek(d->fd,pos,SEEK_SET)!=pos)
return 0;
d->links=(int *)malloc(MAX(GET(d->block0.links.len),1));
if(!d->links) return NULL;
if(GET(d->block0.links.len)!=
read(d->fd,(char *)d->links,GET(d->block0.links.len)))
{
free((char *)d->links);
return d->links=NULL;
}
d->no_links=GET(d->block0.links.len)/sizeof(int);
return d->links;
}
static char *read_file(dbase *d,file f)
{
block b;
int blockno,tmp;
char *ret,*p;
if(!read_linktable(d))
return NULL;
blockno=GET(f.ptr);
ret=p=(char *)malloc(MAX(GET(f.len),1));
if(!ret) return NULL;
while(p<ret+GET(f.len))
{
tmp=MIN(ret+GET(f.len)-p,BLOCK_SIZE);
if(tmp==BLOCK_SIZE)
{
if(!read_block(d,blockno,0,(block *)p))
{
free(ret);
return NULL;
}
}else{
if(!read_block(d,blockno,0,&b))
{
free(ret);
return NULL;
}
MEMCPY(p,(char *)&b,tmp);
}
p+=tmp;
blockno=GET(d->links[blockno]);
}
return ret;
}
/* don't forget to save the linktable afterwards */
static int write_file(dbase *d,DBT a)
{
block b;
int tmp,e,blockno;
int needed_links,first_link,*last;
char *p;
needed_links=(a.len+BLOCK_SIZE-1)/BLOCK_SIZE;
while(1)
{
for(tmp=e=0;e<d->no_links && tmp<needed_links;e++)
if(!GET(d->links[e]))
tmp++;
if(tmp<needed_links)
more_links(d);
else
break;
}
last=&first_link;
for(e=0;e<d->no_links && needed_links;e++)
{
if(GET(d->links[e])) continue;
SET(*last,e);
last=d->links+e;
needed_links--;
}
SET(*last,-1); /* used */
MEMSET((char *)&b,0,sizeof(b));
/* blocks allocated, let's try to save into them */
blockno=GET(first_link);
p=a.data;
while(p<a.data+a.len)
{
tmp=MIN(a.data+a.len-p,BLOCK_SIZE);
if(tmp==BLOCK_SIZE)
{
if(!write_block(d,blockno,(block *)p))
{
free_file(d,first_link,1);
return 0;
}
}else{
MEMCPY((char *)&b,p,tmp);
if(!write_block(d,blockno,&b))
{
free_file(d,first_link,1);
return 0;
}
}
blockno=GET(d->links[blockno]);
p+=tmp;
}
d->flags|=LINKTABLE_NEEDS_SAVING;
return GET(first_link);
}
static int write_hashtable(dbase *d)
{
DBT htable;
int f;
d->flags|=FAILED_TO_WRITE_HASHTABLE;
htable.len=d->hashsize*sizeof(hash_entry);
htable.data=(char *)d->hashtable;
f=write_file(d,htable);
if(!f) return 0;
free_file(d,GET(d->block0.hashtable.ptr),0);
SET(d->block0.hashtable.ptr,f);
SET(d->block0.hashtable.len,htable.len);
d->flags&=~FAILED_TO_WRITE_HASHTABLE;
d->flags&=~HASHTABLE_NEEDS_SAVING;
d->flags|=BLOCK0_NEEDS_SAVING;
return 1;
}
static int save_block0(dbase *d)
{
d->flags|=FAILED_TO_SAVE_BLOCK0;
if(lseek(d->fd,0,SEEK_SET))
return 0;
if(sizeof(struct block0)!=write(d->fd,(char *)&(d->block0),sizeof(struct block0)))
return 0;
d->flags&=~BLOCK0_NEEDS_SAVING;
d->flags&=~FAILED_TO_SAVE_BLOCK0;
return 1;
}
static int read_block0(dbase *d)
{
if(lseek(d->fd,0,SEEK_SET))
return 0;
if(sizeof(struct block0)!=read(d->fd,(char *)&(d->block0),sizeof(struct block0)))
return 0;
if(GET(d->block0.magic1)!=MAGIC1 || GET(d->block0.magic2)!=MAGIC2)
return 0;
return 1;
}
int sync_database(dbase *d)
{
if(d->flags & HASHTABLE_NEEDS_SAVING)
if(!write_hashtable(d))
return 0;
if(d->flags & LINKTABLE_NEEDS_SAVING)
if(!save_linktable(d))
return 0;
if(d->flags & BLOCK0_NEEDS_SAVING)
if(!save_block0(d))
return 0;
d->counter=0;
return 1;
}
static int spurious_sync(dbase *d)
{
d->counter++;
if(d->no_files_to_free>BUFFER || d->counter>BUFFER)
return sync_database(d);
return 1;
}
static hash_entry *read_hashtable(dbase *d)
{
if(d->flags &
(FAILED_TO_WRITE_HASHTABLE |
LINKS_ALLOCATED_BUT_NOT_SAVED |
FAILED_TO_SAVE_BLOCK0))
{
/* no further operations can be done when syncing fails */
if(!sync_database(d))
return NULL;
}
if(d->hashtable) return d->hashtable;
d->hashsize=GET(d->block0.hashtable.len)/sizeof(hash_entry);
d->hashtable=(hash_entry *)read_file(d,d->block0.hashtable);
if(d->hashtable) return d->hashtable;
return 0;
}
static int find_key(dbase *d,DBT key,unsigned int hashval)
{
int p,q,tmp;
q=p=hashval%(d->hashsize);
do
{
if(GET(d->hashtable[p].hashval)==hashval
&& GET(d->hashtable[p].key.len)==key.len)
{
char *k;
k=read_file(d,d->hashtable[p].key);
if(!k) return -2;
tmp=MEMCMP(k,key.data,key.len);
free(k);
if(!tmp) return p;
}
p++;
if(p>=d->hashsize) p=0;
}while(p!=q);
return -1;
}
static int add_key(dbase *d,DBT key,unsigned int hashval)
{
int p,q,f;
hash_entry *h;
q=p=hashval % (d->hashsize);
do
{
if(GET(d->hashtable[p].key.len)==-1)
{
f=write_file(d,key);
if(!f) return -1;
SET(d->hashtable[p].key.ptr,f);
SET(d->hashtable[p].key.len,key.len);
SET(d->hashtable[p].hashval,hashval);
return p;
}
p++;
if(p>=d->hashsize) p=0;
}while(p!=q);
/* rehash !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
#ifdef MALLOC_DEBUG
check_sfltable();
#endif
h=(hash_entry *)malloc(sizeof(hash_entry)*d->hashsize*2);
if(!h) return -1;
MEMSET((char *)h,-1,d->hashsize*2*sizeof(hash_entry));
for(f=0;f<d->hashsize;f++)
{
p=q=GET(d->hashtable[f].hashval) % (d->hashsize*2);
do
{
if(GET(h[p].key.len)==-1)
{
h[p]=d->hashtable[f];
break;
}
p++;
if(p>=d->hashsize*2) p=0;
}while(p!=q);
}
#ifdef MALLOC_DEBUG
check_sfltable();
#endif
free((char *)d->hashtable);
d->hashsize*=2;
d->hashtable=h;
return add_key(d,key,hashval);
}
int set_database_entry(dbase *d,DBT key,DBT data)
{
file dataf;
int keyindex;
unsigned int hashval;
if(!read_hashtable(d)) return 0;
SET(dataf.len,data.len);
SET(dataf.ptr,write_file(d,data));
if(!dataf.ptr) return 0;
hashval=hashkey(key);
keyindex=find_key(d,key,hashval);
if(keyindex==-2) return 0;
if(keyindex<0)
{
keyindex=add_key(d,key,hashval);
if(keyindex<0)
{
free_file(d,GET(dataf.ptr),1);
return 0;
}
}else{
free_file(d,GET(d->hashtable[keyindex].data.ptr),0);
}
d->hashtable[keyindex].data=dataf;
d->flags|=HASHTABLE_NEEDS_SAVING;
if(!spurious_sync(d)) return 0;
return 1;
}
int remove_database_entry(dbase *d,DBT key)
{
unsigned int hashval;
int keyindex;
if(!read_hashtable(d)) return 0;
hashval=hashkey(key);
keyindex=find_key(d,key,hashval);
if(keyindex==-2) return 0;
if(keyindex<0)
{
return 1;
}else{
free_file(d,GET(d->hashtable[keyindex].data.ptr),0);
free_file(d,GET(d->hashtable[keyindex].key.ptr),0);
SET(d->hashtable[keyindex].key.ptr,-1);
SET(d->hashtable[keyindex].key.len,-1);
d->flags|=HASHTABLE_NEEDS_SAVING;
if(!spurious_sync(d)) return 0;
return 1;
}
}
DBT get_database_entry(dbase *d,DBT key)
{
DBT ret;
unsigned int hashval;
int keyindex;
ret.data=NULL;
ret.len=-1;
if(!read_hashtable(d)) return ret;
hashval=hashkey(key);
keyindex=find_key(d,key,hashval);
if(keyindex>=0)
{
ret.data=read_file(d,d->hashtable[keyindex].data);
ret.len=GET(d->hashtable[keyindex].data.len);
}
return ret;
}
int count_database_entries(dbase *d)
{
int ret, e;
ret=0;
if(!read_hashtable(d)) return -1;
for(e=0;e<d->hashsize;e++)
if(GET(d->hashtable[e].key.len)>=0)
ret++;
return ret;
}
int map_database_keys(dbase *d,void (*fun)(DBT))
{
int e;
DBT tmp;
if(!read_hashtable(d)) return 0;
for(e=0;e<d->hashsize;e++)
{
if(GET(d->hashtable[e].key.len)>=0)
{
tmp.data=read_file(d,d->hashtable[e].key);
if(!tmp.data) return 0;
tmp.len=GET(d->hashtable[e].key.len);
fun(tmp);
}
}
return 1;
}
dbase *open_database(char *name)
{
int fd;
dbase *d;
#if 0
fd=open(name,O_RDWR | O_SYNC);
#else
fd=open(name,O_RDWR);
#endif
if(fd<0) return NULL;
d=(dbase *)malloc(sizeof(dbase));
d->fd=fd;
d->hashtable=NULL;
d->links=NULL;
d->counter=0;
d->no_files_to_free=0;
d->flags=0;
if(!read_block0(d))
{
close(fd);
free((char *)d);
return NULL;
}
if(!read_linktable(d))
{
close(fd);
free((char *)d);
return NULL;
}
return d;
}
dbase *create_database(char *name)
{
char bb[BLOCK_SIZE];
int fd,e;
struct block0 b;
fd=open(name,O_WRONLY | O_CREAT | O_EXCL,(6<<6)|(6<<3)|6);
SET(b.magic1,MAGIC1);
SET(b.magic2,MAGIC2);
SET(b.links.ptr,1);
SET(b.links.len, BLOCK_SIZE-BLOCK_SIZE%sizeof(int) );
SET(b.hashtable.ptr,2);
SET(b.hashtable.len, BLOCK_SIZE-BLOCK_SIZE%sizeof(hash_entry) );
for(e=0;e<BLOCK_SIZE;e++) bb[e]=0;
/* header */
if(write(fd,(char *)&b,sizeof(b))!=sizeof(b))
{
close(fd);
return NULL;
}
/* fill block0 */
if(write(fd,bb,BLOCK_SIZE-sizeof(b))!=BLOCK_SIZE-sizeof(b))
{
close(fd);
return NULL;
}
/* phony links */
((int *)(&bb[0]))[0]=-1;
((int *)(&bb[0]))[1]=-1;
((int *)(&bb[0]))[2]=-1;
if(write(fd,bb,BLOCK_SIZE)!=BLOCK_SIZE)
{
close(fd);
return NULL;
}
/* hashtable */
for(e=0;e<BLOCK_SIZE;e++) bb[e]=-1;
if(write(fd,bb,BLOCK_SIZE)!=BLOCK_SIZE)
{
close(fd);
return NULL;
}
close(fd);
return open_database(name);
}
int close_database(dbase *d)
{
if(!sync_database(d))
return 0;
if(d->hashtable) free((char *)d->hashtable);
if(d->links) free((char *)d->links);
close(d->fd);
free((char *)d);
return 1;
}