/* * sdbm - ndbm work-alike hashed database library * based on Per-Ake Larson's Dynamic Hashing algorithms. BIT 18 (1978). * author: oz@nexus.yorku.ca * * Copyright 1989, Ozan S. Yigit * When it is officially released, this code will be in Public Domain. * Until that time, only those who have received this code directly * from the author have the right to modify and use it in a form suitable * to a beta-test release. Please especially note the following: * * - The author is not responsible for the consequences of use of * this software, no matter how awful, even if they arise from defects * in it. * * - This code must NOT be distributed without prior permission from * the author. */ #ifndef lint static char rcsid[] = "$Id: sdbm.c,v 1.3 90/10/05 23:52:55 lee Exp $"; #endif #include "sdbm.h" #include "port.h" /* this may need SVID to be defined... */ #include "pair.h" #define SPLTMAX 8 /* maximum number of split attempts */ #include #include #ifdef SYSV # include #else # include #endif #include #include #ifndef BSD42 #include #endif #ifdef __STDC__ #include #endif #ifndef NULL #define NULL 0 #endif /* * externals */ #ifndef sun extern int errno; #endif extern char *malloc proto((unsigned int)); extern void free proto((void *)); extern long lseek(); /* * forward */ static int getdbit proto((DBM *, long)); static int setdbit proto((DBM *, long)); static int getpage proto((DBM *, long)); static datum getnext proto((DBM *)); static int makroom proto((DBM *, long, int)); /* * useful macros */ #define bad(x) ((x).dptr == NULL || (x).dsize <= 0) #define exhash(item) dbm_hash((item).dptr, (item).dsize) #define OFF_PAG(off) (long) (off) * PBLKSIZ #define OFF_DIR(off) (long) (off) * DBLKSIZ static long masks[] = { 000000000000, 000000000001, 000000000003, 000000000007, 000000000017, 000000000037, 000000000077, 000000000177, 000000000377, 000000000777, 000000001777, 000000003777, 000000007777, 000000017777, 000000037777, 000000077777, 000000177777, 000000377777, 000000777777, 000001777777, 000003777777, 000007777777, 000017777777, 000037777777, 000077777777, 000177777777, 000377777777, 000777777777, 001777777777, 003777777777, 007777777777, 017777777777 }; datum nullitem = {NULL, 0}; DBM * dbm_open(file, flags, mode) register char *file; register int flags; register int mode; { register DBM *db; register char *dirname; register char *pagname; register int n; if (file == NULL || !*file) return (errno = EINVAL, (DBM *) NULL); /* * need space for two seperate filenames */ n = strlen(file) * 2 + strlen(DIRFEXT) + strlen(PAGFEXT) + 2; if ((dirname = malloc((unsigned) n)) == NULL) return (errno = ENOMEM, (DBM *) NULL); /* * build the file names */ dirname = strcat(strcpy(dirname, file), DIRFEXT); pagname = strcpy(dirname + strlen(dirname) + 1, file); pagname = strcat(pagname, PAGFEXT); db = dbm_prep(dirname, pagname, flags, mode); free((char *) dirname); return (db); } DBM * dbm_prep(dirname, pagname, flags, mode) char *dirname; char *pagname; int flags; int mode; { register DBM *db; struct stat dstat; if ((db = (DBM *) malloc(sizeof(DBM))) == NULL) return (errno = ENOMEM, (DBM *) NULL); /* Liam Quin (lee@sq.com): initialise the new structure: */ db->dirf = db->pagf = -1; /* paranoia on this one! */ db->flags = 0; /* but this is very important! */ db->curbit = 0; db->hmask = 0; db->blkptr = 0; db->keyptr = 0; db->blkno = 0L; /* I think this is unused actually. */ db->pagbno = -1; db->dirbno = -1; /* End of Liam's change */ /* * adjust user flags so that WRONLY becomes RDWR, * as required by this package. Also set our internal * flag for RDONLY. */ if (flags & O_WRONLY) flags = (flags & ~O_WRONLY) | O_RDWR; if (flags & O_RDONLY) db->flags = DBM_RDONLY; /* * open the files in sequence, and stat the dirfile. * If we fail anywhere, undo everything, return NULL. */ if ((db->pagf = open(pagname, flags, mode)) > -1) { if ((db->dirf = open(dirname, flags, mode)) > -1) { /* * need the dirfile size to establish max bit number. */ if (fstat(db->dirf, &dstat) == 0) { /* * zero size: either a fresh database, or one with a single, * unsplit data page: dirpage is all zeros. */ db->dirbno = (!dstat.st_size) ? 0 : -1; db->pagbno = -1; db->maxbno = dstat.st_size * BYTESIZ; (void) memset(db->pagbuf, 0, PBLKSIZ); (void) memset(db->dirbuf, 0, DBLKSIZ); /* * success */ return (db); } (void) close(db->dirf); } (void) close(db->pagf); } free((char *) db); return ((DBM *) NULL); } void dbm_close(db) register DBM *db; { if (db == NULL) errno = EINVAL; else { (void) close(db->dirf); (void) close(db->pagf); free((char *) db); } } datum dbm_fetch(db, key) register DBM *db; datum key; { if (db == NULL || bad(key)) return (errno = EINVAL, nullitem); if (getpage(db, exhash(key))) return (getpair(db->pagbuf, key)); return (nullitem); } int dbm_delete(db, key) register DBM *db; datum key; { if (db == NULL || bad(key)) return (errno = EINVAL, -1); if (dbm_rdonly(db)) return (errno = EPERM, -1); if (getpage(db, exhash(key))) { if (!delpair(db->pagbuf, key)) return (-1); /* * update the page file */ if (lseek(db->pagf, OFF_PAG(db->pagbno), L_SET) < 0 || write(db->pagf, db->pagbuf, PBLKSIZ) < 0) return (-1); return (0); } return (-1); } int dbm_store(db, key, val, flags) register DBM *db; datum key; datum val; int flags; { int need; register long hash; errno = 0; /* Liam, to assist debugging */ if (db == NULL || bad(key)) return (errno = EINVAL, -1); if (dbm_rdonly(db)) return (errno = EPERM, -1); /* * is the pair too big for this database ?? */ if ((need = key.dsize + val.dsize) > PAIRMAX) return (-1); if (!getpage(db, (hash = exhash(key)))) return (-1); /* * if we need to replace, delete the key/data pair * first. If it is not there, ignore. */ if (flags == DBM_REPLACE) (void) delpair(db->pagbuf, key); #ifdef DUPERROR else if (duppair(db->pagbuf, key)) return (-1); #endif /* * if we do not have enough room, we have to split. */ if (!fitpair(db->pagbuf, need)) if (!makroom(db, hash, need)) return (-1); /* * we have enough room or split is successful. insert the key, * and update the page file. */ (void) putpair(db->pagbuf, key, val); if (lseek(db->pagf, OFF_PAG(db->pagbno), L_SET) < 0 || write(db->pagf, db->pagbuf, PBLKSIZ) < 0) return (-1); return (0); } /* * makroom - make room by splitting the overfull page * this routine will attempt to split for SPLTMAX times before * giving up. */ static int makroom(db, hash, need) register DBM *db; long hash; int need; { long newp; char twin[PBLKSIZ]; char *pag = db->pagbuf; char *new = twin; register int smax = SPLTMAX; do { /* * split the current page */ (void) splpage(pag, new, db->hmask + 1); /* * address of the new page */ newp = (hash & db->hmask) | (db->hmask + 1); /* * write delay, read avoidence/cache shuffle: * select the page for incoming pair: if key is to go to the new page, * write out the previous one, and copy the new one over, thus making * it the current page. If not, simply write the new page, and we are * still looking at the page of interest. current page is not updated * here, as dbm_store will do so, after it inserts the incoming pair. */ if (hash & (db->hmask + 1)) { if (lseek(db->pagf, OFF_PAG(db->pagbno), L_SET) < 0 || write(db->pagf, db->pagbuf, PBLKSIZ) < 0) return (0); db->pagbno = newp; (void) memcpy(pag, new, PBLKSIZ); } else if (lseek(db->pagf, OFF_PAG(newp), L_SET) < 0 || write(db->pagf, new, PBLKSIZ) < 0) return (0); if (!setdbit(db, db->curbit)) return (0); if (fitpair(pag, need)) return (1); /* * try again... update curbit and hmask as getpage would have * done. because of our update of the current page, we do not * need to read in anything. BUT we have to write the current * [deferred] page out, as the window of failure is too great. */ db->curbit = 2 * db->curbit + 1 + ((hash & (db->hmask + 1)) ? 1 : 0); db->hmask |= (db->hmask + 1); if (lseek(db->pagf, OFF_PAG(db->pagbno), L_SET) < 0 || write(db->pagf, db->pagbuf, PBLKSIZ) < 0) return (0); } while (--smax); /* * if we are here, this is real bad news. After SPLTMAX splits, * we still cannot fit the key. say goodnight. */ #ifdef SPLITERROR printf("sdbm: cannot make room after %d splits.\n", SPLTMAX); #endif return (0); } /* * the following two routines will break if * deletions aren't taken into account. (ndbm bug) */ datum dbm_firstkey(db) register DBM *db; { if (db == NULL) return (errno = EINVAL, nullitem); /* * start at page 0 */ if (lseek(db->pagf, OFF_PAG(0), L_SET) < 0 || read(db->pagf, db->pagbuf, PBLKSIZ) < 0) return (nullitem); db->pagbno = 0; db->blkptr = 0; db->keyptr = 0; return (getnext(db)); } datum dbm_nextkey(db) register DBM *db; { if (db == NULL) return (errno = EINVAL, nullitem); return (getnext(db)); } static int getpage(db, hash) register DBM *db; register long hash; { register int hbit; register long dbit; register long pagb; dbit = 0; hbit = 0; while (dbit < db->maxbno && getdbit(db, dbit)) dbit = 2 * dbit + 1 + ((hash & (1 << hbit++)) ? 1 : 0); #ifdef DEBUG printf("dbit: %d...", dbit); #endif db->curbit = dbit; pagb = hash & (db->hmask = masks[hbit]); /* * note: here, we assume a "hole" is read as 0s. * if not, must zero pagbuf first. */ if (pagb != db->pagbno) { if (lseek(db->pagf, OFF_PAG(pagb), L_SET) < 0 || read(db->pagf, db->pagbuf, PBLKSIZ) < 0) return (0); if (!chkpage(db->pagbuf)) return (0); db->pagbno = pagb; #ifdef DEBUG printf("pag read: %d\n", pagb); #endif } return (1); } static int getdbit(db, dbit) register DBM *db; register long dbit; { register long c; register long dirb; c = dbit / BYTESIZ; dirb = c / DBLKSIZ; if (dirb != db->dirbno) { if (lseek(db->dirf, OFF_DIR(dirb), L_SET) < 0 || read(db->dirf, db->dirbuf, DBLKSIZ) < 0) return (0); db->dirbno = dirb; #ifdef DEBUG printf("dir read: %d\n", dirb); #endif } return (db->dirbuf[c % DBLKSIZ] & (1 << dbit % BYTESIZ)); } static int setdbit(db, dbit) register DBM *db; register long dbit; { register long c; register long dirb; c = dbit / BYTESIZ; dirb = c / DBLKSIZ; if (dirb != db->dirbno) { if (lseek(db->dirf, OFF_DIR(dirb), L_SET) < 0 || read(db->dirf, db->dirbuf, DBLKSIZ) < 0) return (0); db->dirbno = dirb; #ifdef DEBUG printf("dir read: %d\n", dirb); #endif } db->dirbuf[c % DBLKSIZ] |= (1 << dbit % BYTESIZ); if (dbit >= db->maxbno) db->maxbno += DBLKSIZ * BYTESIZ; if (lseek(db->dirf, OFF_DIR(dirb), L_SET) < 0 || write(db->dirf, db->dirbuf, DBLKSIZ) < 0) return (0); return (1); } /* * getnext - get the next key in the page, and if done with * the page, try the next page in sequence */ static datum getnext(db) register DBM *db; { datum key; for (;;) { db->keyptr++; key = getnkey(db->pagbuf, db->keyptr); if (key.dptr != NULL) return (key); /* * we either run out, or there is nothing on this page.. * try the next one... If we lost our position on the * file, we will have to seek. */ db->keyptr = 0; if (db->pagbno != db->blkptr++) if (lseek(db->pagf, OFF_PAG(db->blkptr), L_SET) < 0) break; db->pagbno = db->blkptr; if (read(db->pagf, db->pagbuf, PBLKSIZ) <= 0) break; if (!chkpage(db->pagbuf)) break; } return (nullitem); }