shithub: purgatorio

ref: db1eb844461b07a25ca49117851fa874fd88e065
dir: /appl/lib/dbm.b/

View raw version
implement Dbm;

# Copyright © Caldera International Inc.  2001-2002.  All rights reserved.
# Limbo transliteration (with amendment) Copyright © 2004 Vita Nuova Holdings Limited.

include "sys.m";
	sys: Sys;
	OREAD, OWRITE, ORDWR: import Sys;

include "dbm.m";

BYTESIZ: con 8;	# bits
SHORTSIZ: con 2;	# bytes

PBLKSIZ: con 512;
DBLKSIZ: con 8192;	# was 4096

init()
{
	sys = load Sys Sys->PATH;
}

Dbf.create(file: string, mode: int): ref Dbf
{
	pf := sys->create(file+".pag", ORDWR, mode);
	if(pf == nil)
		return nil;
	df := sys->create(file+".dir", ORDWR, mode);
	if(df == nil)
		return nil;
	return alloc(pf, df, ORDWR);
}

Dbf.open(file: string, flags: int): ref Dbf
{
	if((flags & 3) == OWRITE)
		flags = (flags & ~3) | ORDWR;
	pf := sys->open(file+".pag", flags);
	if(pf == nil)
		return nil;
	df := sys->open(file+".dir", flags);
	if(df == nil)
		return nil;
	return alloc(pf, df, flags);
}

alloc(pf: ref Sys->FD, df: ref Sys->FD, flags: int): ref Dbf
{
	db := ref Dbf;
	db.pagf = pf;
	db.dirf = df;
	db.flags = flags & 3;
	db.maxbno = 0;
	db.bitno = 0;
	db.hmask = 0;
	db.blkno = 0;
	db.pagbno = -1;
	db.pagbuf = array[PBLKSIZ] of byte;
	db.dirbno = -1;
	db.dirbuf = array[DBLKSIZ] of byte;
	(ok, d) := sys->fstat(db.dirf);
	if(ok < 0)
		d.length = big 0;
	db.maxbno = int (d.length*big BYTESIZ - big 1);
	return db;
}

Dbf.flush(db: self ref Dbf)
{
	db.pagbno = db.dirbno = -1;
}

Dbf.isrdonly(db: self ref Dbf): int
{
	return db.flags == OREAD;
}

Dbf.fetch(db: self ref Dbf, key: Datum): Datum
{
	access(db, calchash(key));
	for(i:=0;; i+=2){
		item := makdatum(db.pagbuf, i);
		if(item == nil)
			return item;
		if(cmpdatum(key, item) == 0){
			item = makdatum(db.pagbuf, i+1);
			if(item == nil){
				sys->fprint(sys->fildes(2), "dbm: items not in pairs\n");
				raise "dbm: items not in pairs";
			}
			return item;
		}
	}
}

Dbf.delete(db: self ref Dbf, key: Datum): int
{
	if(db.isrdonly())
		return -1;
	access(db, calchash(key));
	for(i:=0;; i+=2){
		item := makdatum(db.pagbuf, i);
		if(item == nil)
			return -1;
		if(cmpdatum(key, item) == 0){
			delitem(db.pagbuf, i);
			delitem(db.pagbuf, i);
			break;
		}
	}
	sys->seek(db.pagf, big db.blkno*big PBLKSIZ, 0);
	write(db.pagf, db.pagbuf, PBLKSIZ);
	db.pagbno = db.blkno;
	return 0;
}

Dbf.store(db: self ref Dbf, key: Datum, dat: Datum, replace: int): int
{
	if(db.isrdonly())
		return -1;
	for(;;){
		access(db, calchash(key));
		for(i:=0;; i+=2){
			item := makdatum(db.pagbuf, i);
			if(item == nil)
				break;
			if(cmpdatum(key, item) == 0){
				if(!replace)
					return 1;
				delitem(db.pagbuf, i);
				delitem(db.pagbuf, i);
				break;
			}
		}
		i = additem(db.pagbuf, key);
		if(i >= 0){
			if(additem(db.pagbuf, dat) >= 0)
				break;
			delitem(db.pagbuf, i);
		}
		if(!split(db, key, dat))
			return -1;
	}
	sys->seek(db.pagf, big db.blkno*big PBLKSIZ, 0);
	write(db.pagf, db.pagbuf, PBLKSIZ);
	db.pagbno = db.blkno;
	return 0;
}

split(db: ref Dbf, key: Datum, dat: Datum): int
{
	if(len key+len dat+3*SHORTSIZ >= PBLKSIZ)
		return 0;
	ovfbuf := array[PBLKSIZ] of {* => byte 0};
	for(i:=0;;){
		item := makdatum(db.pagbuf, i);
		if(item == nil)
			break;
		if(calchash(item) & (db.hmask+1)){
			additem(ovfbuf, item);
			delitem(db.pagbuf, i);
			item = makdatum(db.pagbuf, i);
			if(item == nil){
				sys->fprint(sys->fildes(2), "dbm: split not paired\n");
				raise "dbm: split not paired";
				#break;
			}
			additem(ovfbuf, item);
			delitem(db.pagbuf, i);
			continue;
		}
		i += 2;
	}
	sys->seek(db.pagf, big db.blkno*big PBLKSIZ, 0);
	write(db.pagf, db.pagbuf, PBLKSIZ);
	db.pagbno = db.blkno;
	sys->seek(db.pagf, (big db.blkno+big db.hmask+big 1)*big PBLKSIZ, 0);
	write(db.pagf, ovfbuf, PBLKSIZ);
	setbit(db);
	return 1;
}

Dbf.firstkey(db: self ref Dbf): Datum
{
	return copy(firsthash(db, 0));
}

Dbf.nextkey(db: self ref Dbf, key: Datum): Datum
{
	hash := calchash(key);
	access(db, hash);
	item, bitem: Datum;
	for(i:=0;; i+=2){
		item = makdatum(db.pagbuf, i);
		if(item == nil)
			break;
		if(cmpdatum(key, item) <= 0)
			continue;
		if(bitem == nil || cmpdatum(bitem, item) < 0)
			bitem = item;
	}
	if(bitem != nil)
		return copy(bitem);
	hash = hashinc(db, hash);
	if(hash == 0)
		return copy(item);
	return copy(firsthash(db, hash));
}

firsthash(db: ref Dbf, hash: int): Datum
{
	for(;;){
		access(db, hash);
		bitem := makdatum(db.pagbuf, 0);
		item: Datum;
		for(i:=2;; i+=2){
			item = makdatum(db.pagbuf, i);
			if(item == nil)
				break;
			if(cmpdatum(bitem, item) < 0)
				bitem = item;
		}
		if(bitem != nil)
			return bitem;
		hash = hashinc(db, hash);
		if(hash == 0)
			return item;
	}
}

access(db: ref Dbf, hash: int)
{
	for(db.hmask=0;; db.hmask=(db.hmask<<1)+1){
		db.blkno = hash & db.hmask;
		db.bitno = db.blkno + db.hmask;
		if(getbit(db) == 0)
			break;
	}
	if(db.blkno != db.pagbno){
		sys->seek(db.pagf, big db.blkno * big PBLKSIZ, 0);
		read(db.pagf, db.pagbuf, PBLKSIZ);
		chkblk(db.pagbuf);
		db.pagbno = db.blkno;
	}
}

getbit(db: ref Dbf): int
{
	if(db.bitno > db.maxbno)
		return 0;
	n := db.bitno % BYTESIZ;
	bn := db.bitno / BYTESIZ;
	i := bn % DBLKSIZ;
	b := bn / DBLKSIZ;
	if(b != db.dirbno){
		sys->seek(db.dirf, big b * big DBLKSIZ, 0);
		read(db.dirf, db.dirbuf, DBLKSIZ);
		db.dirbno = b;
	}
	if(int db.dirbuf[i] & (1<<n))
		return 1;
	return 0;
}

setbit(db: ref Dbf)
{
	if(db.bitno > db.maxbno){
		db.maxbno = db.bitno;
		getbit(db);
	}
	n := db.bitno % BYTESIZ;
	bn := db.bitno / BYTESIZ;
	i := bn % DBLKSIZ;
	b := bn / DBLKSIZ;
	db.dirbuf[i] |= byte (1<<n);
	sys->seek(db.dirf, big b * big DBLKSIZ, 0);
	write(db.dirf, db.dirbuf, DBLKSIZ);
	db.dirbno = b;
}

makdatum(buf: array of byte, n: int): Datum
{
	ne := GETS(buf, 0);
	if(n < 0 || n >= ne)
		return nil;
	t := PBLKSIZ;
	if(n > 0)
		t = GETS(buf, n+1-1);
	v := GETS(buf, n+1);
	return buf[v: t];	# size is t-v
}

cmpdatum(d1: Datum, d2: Datum): int
{
	n := len d1;
	if(n != len d2)
		return n - len d2;
	if(n == 0)
		return 0;
	for(i := 0; i < len d1; i++)
		if(d1[i] != d2[i])
			return int d1[i] - int d2[i];
	return 0;
}

copy(d: Datum): Datum
{
	if(d == nil)
		return nil;
	a := array[len d] of byte;
	a[0:] = d;
	return a;
}

# ken's
#
#	055,043,036,054,063,014,004,005,
#	010,064,077,000,035,027,025,071,
#

hitab := array[16] of {
         61, 57, 53, 49, 45, 41, 37, 33,
	29, 25, 21, 17, 13,  9,  5,  1,
};

hltab := array[64] of {
	8r6100151277,8r6106161736,8r6452611562,8r5001724107,
	8r2614772546,8r4120731531,8r4665262210,8r7347467531,
	8r6735253126,8r6042345173,8r3072226605,8r1464164730,
	8r3247435524,8r7652510057,8r1546775256,8r5714532133,
	8r6173260402,8r7517101630,8r2431460343,8r1743245566,
	8r0261675137,8r2433103631,8r3421772437,8r4447707466,
	8r4435620103,8r3757017115,8r3641531772,8r6767633246,
	8r2673230344,8r0260612216,8r4133454451,8r0615531516,
	8r6137717526,8r2574116560,8r2304023373,8r7061702261,
	8r5153031405,8r5322056705,8r7401116734,8r6552375715,
	8r6165233473,8r5311063631,8r1212221723,8r1052267235,
	8r6000615237,8r1075222665,8r6330216006,8r4402355630,
	8r1451177262,8r2000133436,8r6025467062,8r7121076461,
	8r3123433522,8r1010635225,8r1716177066,8r5161746527,
	8r1736635071,8r6243505026,8r3637211610,8r1756474365,
	8r4723077174,8r3642763134,8r5750130273,8r3655541561,
};

hashinc(db: ref Dbf, hash: int): int
{
	hash &= db.hmask;
	bit := db.hmask+1;
	for(;;){
		bit >>= 1;
		if(bit == 0)
			return 0;
		if((hash&bit) == 0)
			return hash|bit;
		hash &= ~bit;
	}
}

calchash(item: Datum): int
{
	hashl := 0;
	hashi := 0;
	for(i:=0; i<len item; i++){
		f := int item[i];
		for(j:=0; j<BYTESIZ; j+=4){
			hashi += hitab[f&16rF];
			hashl += hltab[hashi&16r3F];
			f >>= 4;
		}
	}
	return hashl;
}

delitem(buf: array of byte, n: int)
{
	ne := GETS(buf, 0);
	if(n < 0 || n >= ne){
		sys->fprint(sys->fildes(2), "dbm: bad delitem\n");
		raise "dbm: bad delitem";
	}
	i1 := GETS(buf, n+1);
	i2 := PBLKSIZ;
	if(n > 0)
		i2 = GETS(buf, n+1-1);
	i3 := GETS(buf, ne+1-1);
	if(i2 > i1)
		while(i1 > i3){
			i1--;
			i2--;
			buf[i2] = buf[i1];
			buf[i1] = byte 0;
		}
	i2 -= i1;
	for(i1=n+1; i1<ne; i1++)
		PUTS(buf, i1+1-1, GETS(buf, i1+1) + i2);
	PUTS(buf, 0, ne-1);
	PUTS(buf, ne, 0);
}

additem(buf: array of byte, item: Datum): int
{
	i1 := PBLKSIZ;
	ne := GETS(buf, 0);
	if(ne > 0)
		i1 = GETS(buf, ne+1-1);
	i1 -= len item;
	i2 := (ne+2) * SHORTSIZ;
	if(i1 <= i2)
		return -1;
	PUTS(buf, ne+1, i1);
	buf[i1:] = item;
	PUTS(buf, 0, ne+1);
	return ne;
}

chkblk(buf: array of byte)
{
	t := PBLKSIZ;
	ne := GETS(buf, 0);
	for(i:=0; i<ne; i++){
		v := GETS(buf, i+1);
		if(v > t)
			badblk();
		t = v;
	}
	if(t < (ne+1)*SHORTSIZ)
		badblk();
}

read(fd: ref Sys->FD, buf: array of byte, n: int)
{
	nr := sys->read(fd, buf, n);
	if(nr == 0){
		for(i := 0; i < len buf; i++)
			buf[i] = byte 0;
	}else if(nr != n)
		raise "dbm: read error: "+sys->sprint("%r");
}

write(fd: ref Sys->FD, buf: array of byte, n: int)
{
	if(sys->write(fd, buf, n) != n)
		raise "dbm: write error: "+sys->sprint("%r");
}

badblk()
{
	sys->fprint(sys->fildes(2), "dbm: bad block\n");
	raise "dbm: bad block";
}

GETS(buf: array of byte, sh: int): int
{
	sh *= SHORTSIZ;
	return (int buf[sh]<<8) | int buf[sh+1];
}

PUTS(buf: array of byte, sh: int, v: int)
{
	sh *= SHORTSIZ;
	buf[sh] = byte (v>>8);
	buf[sh+1] = byte v;
}