/* * getnext - get the next key in the page, and if done with * the page, try the next page in sequence */ static datum getnext(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), SEEK_SET) < 0) break; db->pagbno = db->blkptr; if (read(db->pagf, db->pagbuf, PBLKSIZ) <= 0) break; if (!chkpage(db->pagbuf)) break; } return ioerr(db), nullitem; }
int sdbm_store(register DBM *db, datum key, datum val, int flags) { int need; register long hash; if (db == NULL || bad(key)) return errno = EINVAL, -1; if (sdbm_rdonly(db)) return errno = EPERM, -1; need = key.dsize + val.dsize; /* * is the pair too big (or too small) for this database ?? */ if (need < 0 || need > PAIRMAX) return errno = EINVAL, -1; if (getpage(db, (hash = exhash(key)))) { /* * 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 SEEDUPS 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 ioerr(db), -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), SEEK_SET) < 0 || write(db->pagf, db->pagbuf, PBLKSIZ) < 0) return ioerr(db), -1; /* * success */ return 0; } return ioerr(db), -1; }
/** * Write back cached page to disk. * @return TRUE on success. */ static bool writebuf(DBM *db, long oldnum, long idx) { struct lru_cache *cache = db->cache; char *pag = cache->arena + OFF_PAG(idx); g_assert(idx >= 0 && idx < cache->pages); if (!flushpag(db, pag, oldnum)) return FALSE; cache->dirty[idx] = FALSE; return TRUE; }
/* * the following two routines will break if * deletions aren't taken into account. (ndbm bug) */ datum sdbm_firstkey(register DBM *db) { if (db == NULL) return errno = EINVAL, nullitem; /* * start at page 0 */ if (lseek(db->pagf, OFF_PAG(0), SEEK_SET) < 0 || read(db->pagf, db->pagbuf, PBLKSIZ) < 0) return ioerr(db), nullitem; db->pagbno = 0; db->blkptr = 0; db->keyptr = 0; return getnext(db); }
/** * Discard any pending data for cached pages whose block number is greater * or equal than the given base block number. */ void lru_discard(DBM *db, long bno) { struct lru_cache *cache = db->cache; int n; long pages = MIN(cache->pages, cache->next); for (n = 0; n < pages; n++) { long num = cache->numpag[n]; if (num >= bno) { void *base = cache->arena + OFF_PAG(n); cache->dirty[n] = FALSE; memset(base, 0, DBM_PBLKSIZ); } } }
/* * all important binary trie traversal */ static int getpage(register DBM *db, register long int hash) { register int hbit; register long dbit; register long pagb; dbit = 0; hbit = 0; while (dbit < db->maxbno && getdbit(db, dbit)) dbit = 2 * dbit + ((hash & (1 << hbit++)) ? 2 : 1); debug(("dbit: %d...", dbit)); db->curbit = dbit; db->hmask = masks[hbit]; pagb = hash & db->hmask; /* * see if the block we need is already in memory. * note: this lookaside cache has about 10% hit rate. */ if (pagb != db->pagbno) { /* * note: here, we assume a "hole" is read as 0s. * if not, must zero pagbuf first. */ if (lseek(db->pagf, OFF_PAG(pagb), SEEK_SET) < 0 || read(db->pagf, db->pagbuf, PBLKSIZ) < 0) return 0; if (!chkpage(db->pagbuf)) return 0; db->pagbno = pagb; debug(("pag read: %d\n", pagb)); } return 1; }
int sdbm_delete(register DBM *db, datum key) { if (db == NULL || bad(key)) return errno = EINVAL, -1; if (sdbm_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), SEEK_SET) < 0 || write(db->pagf, db->pagbuf, PBLKSIZ) < 0) return ioerr(db), -1; return 0; } return ioerr(db), -1; }
/** * Get the address in the cache of a given page number. * * @param db the database * @param num the page number in the DB * * @return page address if found, NULL if not cached. */ char * lru_cached_page(DBM *db, long num) { struct lru_cache *cache = db->cache; void *value; g_assert(num >= 0); if ( cache != NULL && g_hash_table_lookup_extended(cache->pagnum, ulong_to_pointer(num), NULL, &value) ) { long idx = pointer_to_int(value); g_assert(idx >= 0 && idx < cache->pages); g_assert(cache->numpag[idx] == num); return cache->arena + OFF_PAG(idx); } return NULL; }
/** * Get a suitable buffer in the cache to read a page and set db->pagbuf * accordingly. * * The '`loaded'' parameter, if non-NULL, is set to TRUE if page was already * held in the cache, FALSE when it needs to be loaded. * * @return TRUE if OK, FALSE if we could not allocate a suitable buffer, leaving * the old db->pagbuf intact. */ gboolean readbuf(DBM *db, long num, gboolean *loaded) { struct lru_cache *cache = db->cache; void *value; long idx; gboolean good_page; g_assert(num >= 0); if ( g_hash_table_lookup_extended(cache->pagnum, ulong_to_pointer(num), NULL, &value) ) { hash_list_moveto_head(cache->used, value); idx = pointer_to_int(value); g_assert(idx >= 0 && idx < cache->pages); g_assert(cache->numpag[idx] == num); good_page = TRUE; cache->rhits++; } else { idx = getidx(db, num); if (-1 == idx) return FALSE; /* Do not update db->pagbuf */ good_page = FALSE; cache->rmisses++; } db->pagbuf = cache->arena + OFF_PAG(idx); if (loaded != NULL) *loaded = good_page; return TRUE; }
/** * Flush page to disk. * @return TRUE on success */ gboolean flushpag(DBM *db, char *pag, long num) { ssize_t w; g_assert(num >= 0); db->pagwrite++; w = compat_pwrite(db->pagf, pag, DBM_PBLKSIZ, OFF_PAG(num)); if (w < 0 || w != DBM_PBLKSIZ) { if (w < 0) g_warning("sdbm: \"%s\": cannot flush page #%ld: %s", sdbm_name(db), num, g_strerror(errno)); else g_warning("sdbm: \"%s\": could only flush %u bytes from page #%ld", sdbm_name(db), (unsigned) w, num); ioerr(db, TRUE); db->flush_errors++; return FALSE; } return TRUE; }
/* * makroom - make room by splitting the overfull page * this routine will attempt to make room for SPLTMAX times before * giving up. */ static int makroom(register DBM *db, long int hash, int need) { long newp; char twin[PBLKSIZ]; #if defined(DOSISH) || defined(WIN32) char zer[PBLKSIZ]; long oldtail; #endif 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 avoidance/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 sdbm_store will do so, after it inserts the incoming pair. */ #if defined(DOSISH) || defined(WIN32) /* * Fill hole with 0 if made it. * (hole is NOT read as 0) */ oldtail = lseek(db->pagf, 0L, SEEK_END); memset(zer, 0, PBLKSIZ); while (OFF_PAG(newp) > oldtail) { if (lseek(db->pagf, 0L, SEEK_END) < 0 || write(db->pagf, zer, PBLKSIZ) < 0) { return 0; } oldtail += PBLKSIZ; } #endif if (hash & (db->hmask + 1)) { if (lseek(db->pagf, OFF_PAG(db->pagbno), SEEK_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), SEEK_SET) < 0 || write(db->pagf, New, PBLKSIZ) < 0) return 0; if (!setdbit(db, db->curbit)) return 0; /* * see if we have enough room now */ 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 + ((hash & (db->hmask + 1)) ? 2 : 1); db->hmask |= db->hmask + 1; if (lseek(db->pagf, OFF_PAG(db->pagbno), SEEK_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 BADMESS (void) write(2, "sdbm: cannot insert after SPLTMAX attempts.\n", 44); #endif return 0; }
/** * Cache new page held in memory if there are deferred writes configured. * @return TRUE on success. */ gboolean cachepag(DBM *db, char *pag, long num) { struct lru_cache *cache = db->cache; void *value; g_assert(num >= 0); /* * Coming from makroom() where we allocated a new page, starting at "pag". * * Normally the page should not be cached, but it is possible we iterated * over the hash table and traversed the page on disk as a hole, and cached * it during the process. If present, it must be clean and should hold * no data (or the bitmap forest in the .dir file is corrupted). * * Otherwise, we cache the new page and hold it there if we we can defer * writes, or flush it to disk immediately (without caching it). */ if ( g_hash_table_lookup_extended(cache->pagnum, ulong_to_pointer(num), NULL, &value) ) { long idx; unsigned short *ino; unsigned weird = 0; char *cpag; /* * Do not move the page to the head of the cache list. * * This page should not have been cached (it was supposed to be a * hole up to now) and its being cached now does not constitute usage. */ idx = pointer_to_int(value); g_assert(idx >= 0 && idx < cache->pages); g_assert(cache->numpag[idx] == num); /* * Not a read hit since we're about to supersede the data */ cpag = cache->arena + OFF_PAG(idx); ino = (unsigned short *) cpag; if (ino[0] != 0) { weird++; g_warning("sdbm: \"%s\": new page #%ld was cached but not empty", db->name, num); } if (cache->dirty[idx]) { weird++; g_warning("sdbm: \"%s\": new page #%ld was cached and not clean", db->name, num); } if (weird > 0) { g_warning("sdbm: \"%s\": previous warning%s indicate possible " "corruption in the bitmap forest", db->name, 1 == weird ? "" : "s"); } /* * Supersede cached page with new page created by makroom(). */ memmove(cpag, pag, DBM_PBLKSIZ); if (cache->write_deferred) { cache->dirty[idx] = TRUE; } else { cache->dirty[idx] = !flushpag(db, pag, num); } return TRUE; } else if (cache->write_deferred) { long idx; char *cpag; idx = getidx(db, num); if (-1 == idx) return FALSE; cpag = cache->arena + OFF_PAG(idx); memmove(cpag, pag, DBM_PBLKSIZ); cache->dirty[idx] = TRUE; return TRUE; } else { return flushpag(db, pag, num); } }