/** * Make sure vector of block numbers is ordered and points to allocated data, * but was not already flagged as being used by another key / value. * * @param what string describing what is being tested (key or value) * @param db the sdbm database * @param bvec vector where allocated block numbers are stored * @param bcnt amount of blocks in vector * * @return TRUE on success. */ static gboolean big_file_check(const char *what, DBM *db, const void *bvec, int bcnt) { size_t prev_bno = 0; /* 0 is invalid: it's the first bitmap */ const void *q; int n; if (!big_check_start(db)) return TRUE; /* Cannot validate, assume it's OK */ for (q = bvec, n = bcnt; n > 0; n--) { size_t bno = peek_be32(q); bit_field_t *map; long bmap; size_t bit; if (!big_block_is_allocated(db, bno)) { g_warning("sdbm: \"%s\": " "%s from .pag refers to unallocated block %lu in .dat", sdbm_name(db), what, (unsigned long) bno); return FALSE; } if (prev_bno != 0 && bno <= prev_bno) { g_warning("sdbm: \"%s\": " "%s from .pag lists unordered block list (corrupted file?)", sdbm_name(db), what); return FALSE; } q = const_ptr_add_offset(q, sizeof(guint32)); prev_bno = bno; /* * Make sure block is not used by someone else. * * Because we mark blocks as used in big keys and values only after * we validated both the key and the value for a given pair, we cannot * detect shared blocks between the key and value of a pair. */ bmap = bno / BIG_BITCOUNT; /* Bitmap handling this block */ bit = bno & (BIG_BITCOUNT - 1); /* Index within bitmap */ g_assert(bmap < db->big->bitmaps); map = ptr_add_offset(db->big->bitcheck, bmap * BIG_BLKSIZE); if (bit_field_get(map, bit)) { g_warning("sdbm: \"%s\": " "%s from .pag refers to already seen block %lu in .dat", sdbm_name(db), what, (unsigned long) bno); return FALSE; } } return TRUE; }
/** * Read n-th bitmap page. * * @return TRUE on success. */ static gboolean fetch_bitbuf(DBM *db, long num) { DBMBIG *dbg = db->big; long bno = num * BIG_BITCOUNT; /* address of n-th bitmap in file */ dbg->bitfetch++; if (bno != dbg->bitbno) { ssize_t got; if (dbg->bitbuf_dirty && !flush_bitbuf(db)) return FALSE; dbg->bitread++; got = compat_pread(dbg->fd, dbg->bitbuf, BIG_BLKSIZE, OFF_DAT(bno)); if (got < 0) { g_warning("sdbm: \"%s\": could not read bitmap block #%ld: %s", sdbm_name(db), num, g_strerror(errno)); ioerr(db, FALSE); return FALSE; } if (0 == got) { memset(dbg->bitbuf, 0, BIG_BLKSIZE); } dbg->bitbno = bno; dbg->bitbuf_dirty = FALSE; } else { dbg->bitbno_hit++; } return TRUE; }
static void log_lrustats(DBM *db) { struct lru_cache *cache = db->cache; unsigned long raccesses = cache->rhits + cache->rmisses; unsigned long waccesses = cache->whits + cache->wmisses; g_info("sdbm: \"%s\" LRU cache size = %ld page%s, %s writes, %s DB", sdbm_name(db), cache->pages, 1 == cache->pages ? "" : "s", cache->write_deferred ? "deferred" : "synchronous", db->is_volatile ? "volatile" : "persistent"); g_info("sdbm: \"%s\" LRU read cache hits = %.2f%% on %lu request%s", sdbm_name(db), cache->rhits * 100.0 / MAX(raccesses, 1), raccesses, 1 == raccesses ? "" : "s"); g_info("sdbm: \"%s\" LRU write cache hits = %.2f%% on %lu request%s", sdbm_name(db), cache->whits * 100.0 / MAX(waccesses, 1), waccesses, 1 == waccesses ? "" : "s"); }
static void log_bigstats(DBM *db) { DBMBIG *dbg = db->big; if (-1 == dbg->fd) return; /* The .dat file was never used */ g_info("sdbm: \"%s\" bitmap reads = %lu, bitmap writes = %lu " "(deferred %lu)", sdbm_name(db), dbg->bitread, dbg->bitwrite, dbg->bitwdelayed); g_info("sdbm: \"%s\" bitmap blocknum hits = %.2f%% on %lu request%s", sdbm_name(db), dbg->bitbno_hit * 100.0 / MAX(dbg->bitfetch, 1), dbg->bitfetch, 1 == dbg->bitfetch ? "" : "s"); g_info("sdbm: \"%s\" large key short matches = %.2f%% on %lu attempt%s", sdbm_name(db), dbg->key_short_match * 100.0 / MAX(dbg->key_matching, 1), dbg->key_matching, 1 == dbg->key_matching ? "" : "s"); g_info("sdbm: \"%s\" large key full matches = %.2f%% on %lu attempt%s", sdbm_name(db), dbg->key_full_match * 100.0 / MAX(dbg->key_short_match, 1), dbg->key_short_match, 1 == dbg->key_short_match ? "" : "s"); g_info("sdbm: \"%s\" big blocks read = %lu (%lu system call%s)", sdbm_name(db), dbg->bigread_blk, dbg->bigread, 1 == dbg->bigread ? "" : "s"); g_info("sdbm: \"%s\" big blocks written = %lu (%lu system call%s)", sdbm_name(db), dbg->bigwrite_blk, dbg->bigwrite, 1 == dbg->bigwrite ? "" : "s"); }
/** * Mark .dat blocks used to hold the value described in the .pag space as * being allocated in the bitmap checking array. * * @param db the sdbm database * @param bval start of big value in the page * @param blen length of big value in the page */ void bigval_mark_used(DBM *db, const char *bval, size_t blen) { size_t len = big_length(bval); if (bigval_length(len) != blen) { g_carp("sdbm: \"%s\": %s: inconsistent value length %lu in .pag", sdbm_name(db), G_STRFUNC, (unsigned long) len); return; } big_file_mark_used(db, bigval_blocks(bval), bigblocks(len)); }
/** * Shrink .dat file on disk to remove needlessly allocated blocks. * * @return TRUE if we were able to successfully shrink the file. */ gboolean big_shrink(DBM *db) { DBMBIG *dbg = db->big; long i; filesize_t offset = 0; if (-1 == dbg->fd) { /* * We do not want to call big_open() unless the .dat file already * exists because that would create it and it was not needed so far. */ if (-1 == big_open_lazy(dbg, TRUE)) return 0 == errno; } g_assert(dbg->fd != -1); /* * Loop through all the currently existing bitmaps, starting from the last * one, looking for the last set bit indicating the last used page. */ for (i = dbg->bitmaps - 1; i >= 0; i--) { size_t bno; if (!fetch_bitbuf(db, i)) return FALSE; bno = bit_field_last_set(dbg->bitbuf, 0, BIG_BITCOUNT - 1); if ((size_t) -1 == bno) { g_warning("sdbm: \"%s\": corrupted bitmap #%ld, considered empty", sdbm_name(db), i); } else if (bno != 0) { bno = size_saturate_add(bno, size_saturate_mult(BIG_BITCOUNT, i)); offset = OFF_DAT(bno + 1); break; } } if (-1 == ftruncate(dbg->fd, offset)) return FALSE; dbg->bitmaps = i + 1; /* Possibly reduced the amount of bitmaps */ 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; }
/** * Validate .dat blocks used to hold the value described in the .pag space. * * @param db the sdbm database * @param bval start of big value in the page * @param blen length of big value in the page * * @return TRUE on success. */ gboolean bigval_check(DBM *db, const char *bval, size_t blen) { size_t len = big_length(bval); if (bigval_length(len) != blen) { g_warning("sdbm: \"%s\": found inconsistent value length %lu, " "would span %lu bytes in .pag instead of the %lu present", sdbm_name(db), (unsigned long) len, (unsigned long) bigkey_length(len), (unsigned long) blen); return FALSE; } return big_file_check("value", db, bigval_blocks(bval), bigblocks(len)); }
/** * Free .dat blocks used to hold the value described in the .pag space. * * @param db the sdbm database * @param bval start of big value in the page * @param blen length of big value in the page * * @return TRUE on success. */ gboolean bigval_free(DBM *db, const char *bval, size_t blen) { size_t len = big_length(bval); if (bigval_length(len) != blen) { g_warning("sdbm: \"%s\": " "bigval_free: inconsistent key length %lu in .pag", sdbm_name(db), (unsigned long) len); return FALSE; } big_file_free(db, bigval_blocks(bval), bigblocks(len)); return TRUE; }
/** * Get value data from the block numbers held in the .pag value. * * @param db the sdbm database * @param bval start of big value in the page * @param blen length of big value in the page * * @return pointer to read value (scratch buffer) if OK, NULL on error. */ char * bigval_get(DBM *db, const char *bval, size_t blen) { size_t len = big_length(bval); DBMBIG *dbg = db->big; if (bigval_length(len) != blen) { g_warning("sdbm: \"%s\": " "bigval_get: inconsistent value length %lu in .pag", sdbm_name(db), (unsigned long) len); return NULL; } if (-1 == big_fetch(db, bigval_blocks(bval), len)) return NULL; return dbg->scratch; }
/** * End bitmap allocation checks that have been started by the usage of one * of the bigkey_mark_used() and bigval_mark_used() routines. * * @return the amount of corrections brought to the bitmap, 0 meaning * everything was consistent. */ size_t big_check_end(DBM *db) { DBMBIG *dbg = db->big; long i; size_t adjustments = 0; if (NULL == dbg->bitcheck) return 0; for (i = 0; i < dbg->bitmaps; i++) { if (!fetch_bitbuf(db, i)) { adjustments += BIG_BITCOUNT; /* Say, everything was wrong */ } else { guint8 *p = ptr_add_offset(dbg->bitcheck, i * BIG_BLKSIZE); guint8 *q = dbg->bitbuf; size_t j; size_t old_adjustments = adjustments; for (j = 0; j < BIG_BLKSIZE; j++, p++, q++) { guint8 mismatch = *p ^ *q; if (mismatch) { adjustments += bits_set(mismatch); *q = *p; } } if (old_adjustments != adjustments) { size_t adj = adjustments - old_adjustments; flush_bitbuf(db); g_warning("sdbm: \"%s\": adjusted %lu bit%s in bitmap #%ld", sdbm_name(db), (unsigned long) adj, 1 == adj ? "" : "s", i); } } } HFREE_NULL(dbg->bitcheck); return adjustments; }
/** * Flush bitmap to disk. * @return TRUE on sucess */ static gboolean flush_bitbuf(DBM *db) { DBMBIG *dbg = db->big; ssize_t w; dbg->bitwrite++; w = compat_pwrite(dbg->fd, dbg->bitbuf, BIG_BLKSIZE, OFF_DAT(dbg->bitbno)); if (BIG_BLKSIZE == w) { dbg->bitbuf_dirty = FALSE; return TRUE; } g_warning("sdbm: \"%s\": cannot flush bitmap #%ld: %s", sdbm_name(db), dbg->bitbno / BIG_BITCOUNT, -1 == w ? g_strerror(errno) : "partial write"); ioerr(db, TRUE); return FALSE; }
/** * Set the page cache size. * @return 0 if OK, -1 on failure with errno set. */ int setcache(DBM *db, long pages) { struct lru_cache *cache = db->cache; bool wdelay; sdbm_lru_check(cache); if (pages <= 0) { errno = EINVAL; return -1; } if (NULL == cache) return init_cache(db, pages, FALSE); /* * Easiest case: the size identical. */ if (pages == cache->pages) return 0; /* * Cache size is changed. * * This means the arena will be reallocated, so we must invalidate the * current db->pagbuf pointer, which lies within the old arena. It is * sufficient to reset db->pagbno, forcing a reload from the upper layers. * Note than when the cache size is enlarged, the old page is still cached * so reloading will be just a matter of recomputing db->pagbuf. We could * do so here, but cache size changes should only be infrequent. * * We also reset all the cache statistics, since a different cache size * will imply a different set of hit/miss ratio. */ db->pagbno = -1; /* Current page address will become invalid */ db->pagbuf = NULL; if (common_stats) { s_info("sdbm: \"%s\" LRU cache size %s from %ld page%s to %ld", sdbm_name(db), pages > cache->pages ? "increased" : "decreased", cache->pages, plural(cache->pages), pages); log_lrustats(db); } cache->rhits = cache->rmisses = 0; cache->whits = cache->wmisses = 0; /* * Straightforward: the size is increased. */ if (pages > cache->pages) { char *new_arena = vmm_alloc(pages * DBM_PBLKSIZ); if (NULL == new_arena) return -1; memmove(new_arena, cache->arena, cache->pages * DBM_PBLKSIZ); vmm_free(cache->arena, cache->pages * DBM_PBLKSIZ); cache->arena = new_arena; cache->dirty = wrealloc(cache->dirty, cache->pages, pages); cache->numpag = wrealloc(cache->numpag, cache->pages * sizeof(long), pages * sizeof(long)); cache->pages = pages; return 0; } /* * Difficult: the size is decreased. * * The current page buffer could point in a cache area that is going * to disappear, and the internal data structures must forget about * all the old indices that are greater than the new limit. * * We do not try to optimize anything here, as this call should happen * only infrequently: we flush the current cache (in case there are * deferred writes), destroy the LRU cache data structures, recreate a * new one and invalidate the current DB page. */ wdelay = cache->write_deferred; flush_dirtypag(db); free_cache(cache); return setup_cache(cache, pages, wdelay); }
/** * Store big value in the .dat file, writing to the supplied block numbers. * * @param db the sdbm database * @param bvec start of block vector, containing block numbers * @param data start of data to write * @param len length of data to write * * @return -1 on error with errno set, 0 if OK. */ static int big_store(DBM *db, const void *bvec, const void *data, size_t len) { DBMBIG *dbg = db->big; int bcnt = bigbcnt(len); int n; const void *p; const char *q; size_t remain; g_return_val_if_fail(NULL == dbg->bitcheck, -1); if (-1 == dbg->fd && -1 == big_open(dbg)) return -1; /* * Look at the amount of consecutive block numbers we have to be able * to write into them via a single system call. */ n = bcnt; p = bvec; q = data; remain = len; while (n > 0) { size_t towrite = MIN(remain, BIG_BLKSIZE); guint32 bno = peek_be32(p); guint32 prev_bno = bno; p = const_ptr_add_offset(p, sizeof(guint32)); n--; remain = size_saturate_sub(remain, towrite); while (n > 0) { guint32 next_bno = peek_be32(p); size_t amount; if (next_bno <= prev_bno) /* Block numbers are sorted */ goto corrupted_page; if (next_bno - prev_bno != 1) break; /* Not consecutive */ prev_bno = next_bno; p = const_ptr_add_offset(p, sizeof(guint32)); amount = MIN(remain, BIG_BLKSIZE); towrite += amount; n--; remain = size_saturate_sub(remain, amount); } dbg->bigwrite++; if (-1 == compat_pwrite(dbg->fd, q, towrite, OFF_DAT(bno))) { g_warning("sdbm: \"%s\": " "could not write %lu bytes starting at data block #%u: %s", sdbm_name(db), (unsigned long) towrite, bno, g_strerror(errno)); ioerr(db, TRUE); return -1; } q += towrite; dbg->bigwrite_blk += bigblocks(towrite); g_assert(ptr_diff(q, data) <= len); } g_assert(ptr_diff(q, data) == len); return 0; corrupted_page: g_warning("sdbm: \"%s\": corrupted page: %d big data block%s not sorted", sdbm_name(db), bcnt, 1 == bcnt ? "" : "s"); ioerr(db, FALSE); errno = EFAULT; /* Data corrupted somehow (.pag file) */ return -1; }
/** * Is a big key stored at bkey in an SDBM .pag file equal to a siz-byte key? * * @param db the sdbm database * @param bkey start of big key in the page * @param blen length of big key in the page * @param key the key we're trying to match against * @param siz length of the key * * @return TRUE if the key matches. */ gboolean bigkey_eq(DBM *db, const char *bkey, size_t blen, const char *key, size_t siz) { size_t len = big_length(bkey); DBMBIG *dbg = db->big; g_assert(bigkey_length(len) == blen); /* * Comparing a key in memory with a big key on disk is potentially a * costly operation because it requires some I/O to fetch the key from * the .dat file, which may involve several system calls, simply to find * out that the key is not matching. * * To avoid useless reads as much as possible, we store the length of * the big key at the beginning of the .pag key indirection data. If the * size does not match, there's no need to go further. * * Then we keep the first and last BIG_KEYSAVED bytes of the key as part * of the .pag data so that we may quickly filter out keys that are * obviously not matching. * * Only when things look like it could be a match do we engage in the * process of fetching the big key data to perform the actual comparison. * * Nonetheless this means fetching data indexed by large keys requires * extra I/Os and therefore large keys should be avoided if possible. * In practice, keys are shorthand to the actual data and therefore are * likely to be kept short enough so that they are always expanded in the * .pag data and never stored as big keys. */ if (siz != len) return FALSE; dbg->key_matching++; if (0 != memcmp(key, bigkey_head(bkey), BIG_KEYSAVED)) return FALSE; if (0 != memcmp(key + (siz-BIG_KEYSAVED), bigkey_tail(bkey), BIG_KEYSAVED)) return FALSE; dbg->key_short_match++; /* * Need to read the key to make sure it's an exact match. * * There is a high probability as the head and the tail already match, * and the length is the same. */ if (-1 == big_fetch(db, bigkey_blocks(bkey), siz)) return FALSE; /* * Data stored in the .dat file must match what the .pag had for the key */ if ( 0 != memcmp(db->big->scratch, bigkey_head(bkey), BIG_KEYSAVED) || 0 != memcmp(db->big->scratch + (siz-BIG_KEYSAVED), bigkey_tail(bkey), BIG_KEYSAVED) ) { g_warning("sdbm: \"%s\": found %lu-byte key page/data inconsistency", sdbm_name(db), (unsigned long) siz); return FALSE; } if (0 == memcmp(db->big->scratch, key, siz)) { dbg->key_full_match++; return TRUE; } return FALSE; }
/** * Fetch data block from the .dat file, reading from the supplied block numbers. * * @param db the sdbm database * @param bvec start of block vector, containing block numbers * @param len length of the data to be read * * @return -1 on error with errno set, 0 if OK. Read data is left in the * scratch buffer. */ static int big_fetch(DBM *db, const void *bvec, size_t len) { int bcnt = bigbcnt(len); DBMBIG *dbg = db->big; int n; const void *p; char *q; size_t remain; guint32 prev_bno; if (-1 == dbg->fd && -1 == big_open(dbg)) return -1; if (dbg->scratch_len < len) big_scratch_grow(dbg, len); /* * Read consecutive blocks in one single system call. */ n = bcnt; p = bvec; q = dbg->scratch; remain = len; while (n > 0) { size_t toread = MIN(remain, BIG_BLKSIZE); guint32 bno = peek_be32(p); prev_bno = bno; if (!big_block_is_allocated(db, prev_bno)) goto corrupted_database; p = const_ptr_add_offset(p, sizeof(guint32)); n--; remain = size_saturate_sub(remain, toread); while (n > 0) { guint32 next_bno = peek_be32(p); size_t amount; if (next_bno <= prev_bno) /* Block numbers are sorted */ goto corrupted_page; if (next_bno - prev_bno != 1) break; /* Not consecutive */ prev_bno = next_bno; if (!big_block_is_allocated(db, prev_bno)) goto corrupted_database; p = const_ptr_add_offset(p, sizeof(guint32)); amount = MIN(remain, BIG_BLKSIZE); toread += amount; n--; remain = size_saturate_sub(remain, amount); } dbg->bigread++; if (-1 == compat_pread(dbg->fd, q, toread, OFF_DAT(bno))) { g_warning("sdbm: \"%s\": " "could not read %lu bytes starting at data block #%u: %s", sdbm_name(db), (unsigned long) toread, bno, g_strerror(errno)); ioerr(db, FALSE); return -1; } q += toread; dbg->bigread_blk += bigblocks(toread); g_assert(UNSIGNED(q - dbg->scratch) <= dbg->scratch_len); } g_assert(UNSIGNED(q - dbg->scratch) == len); return 0; corrupted_database: g_warning("sdbm: \"%s\": cannot read unallocated data block #%u", sdbm_name(db), prev_bno); goto fault; corrupted_page: g_warning("sdbm: \"%s\": corrupted page: %d big data block%s not sorted", sdbm_name(db), bcnt, 1 == bcnt ? "" : "s"); /* FALL THROUGH */ fault: ioerr(db, FALSE); errno = EFAULT; /* Data corrupted somehow (.pag or .dat file) */ return -1; }
/** * Free a block from file. */ static void big_ffree(DBM *db, size_t bno) { DBMBIG *dbg = db->big; long bmap; size_t i; STATIC_ASSERT(IS_POWER_OF_2(BIG_BITCOUNT)); if (-1 == dbg->fd && -1 == big_open(dbg)) { g_warning("sdbm: \"%s\": cannot free block #%ld", sdbm_name(db), (long) bno); return; } /* * Block number must be positive, and we cannot free a bitmap block. * If we end-up doing it, then it means data in the .pag was corrupted, * so we do not assert but fail gracefully. */ if (!size_is_positive(bno) || 0 == (bno & (BIG_BITCOUNT - 1))) { g_warning("sdbm: \"%s\": attempt to free invalid block #%ld", sdbm_name(db), (long) bno); return; } g_assert(size_is_positive(bno)); /* Can never free block 0 (bitmap!) */ g_assert(bno & (BIG_BITCOUNT - 1)); /* Cannot be a bitmap block */ bmap = bno / BIG_BITCOUNT; /* Bitmap handling this block */ i = bno & (BIG_BITCOUNT - 1); /* Index within bitmap */ /* * Likewise, if the block falls in a bitmap we do not know about yet, * the .pag was corrupted. */ if (bmap >= dbg->bitmaps) { g_warning("sdbm: \"%s\": " "freed block #%ld falls within invalid bitmap #%ld (max %ld)", sdbm_name(db), (long) bno, bmap, dbg->bitmaps - 1); return; } if (!fetch_bitbuf(db, bmap)) return; /* * Again, freeing a block that is already marked as being freed is * a severe error but can happen if the bitmap cannot be flushed to disk * at some point, hence it cannot be an assertion. */ if (!bit_field_get(dbg->bitbuf, i)) { g_warning("sdbm: \"%s\": freed block #%ld was already marked as free", sdbm_name(db), (long) bno); return; } bit_field_clear(dbg->bitbuf, i); dbg->bitbuf_dirty = TRUE; }
/** * Get a new index in the cache, and update LRU data structures. * * @param db the database * @param num page number in the DB for which we want a cache index * * * @return -1 on error, or the allocated cache index. */ static int getidx(DBM *db, long num) { struct lru_cache *cache = db->cache; long n; /* Cache index */ /* * If we invalidated pages, reuse their indices. * If we have not used all the pages yet, get the next one. * Otherwise, use the least-recently requested page. */ if (slist_length(cache->available)) { void *v = slist_shift(cache->available); n = pointer_to_int(v); g_assert(n >= 0 && n < cache->pages); g_assert(!cache->dirty[n]); g_assert(-1 == cache->numpag[n]); hash_list_prepend(cache->used, int_to_pointer(n)); } else if (cache->next < cache->pages) { n = cache->next++; cache->dirty[n] = FALSE; hash_list_prepend(cache->used, int_to_pointer(n)); } else { void *last = hash_list_tail(cache->used); long oldnum; gboolean had_ioerr = booleanize(db->flags & DBM_IOERR_W); hash_list_moveto_head(cache->used, last); n = pointer_to_int(last); /* * This page is no longer cached as its cache index is being reused * Flush it to disk if dirty before discarding it. */ g_assert(n >= 0 && n < cache->pages); oldnum = cache->numpag[n]; if (cache->dirty[n] && !writebuf(db, oldnum, n)) { hash_list_iter_t *iter; void *item; gboolean found = FALSE; /* * Cannot flush dirty page now, probably because we ran out of * disk space. Look through the cache whether we can reuse a * non-dirty page instead, which would let us keep the dirty * page a little longer in the cache, in the hope it can then * be properly flushed later. */ iter = hash_list_iterator_tail(cache->used); while (NULL != (item = hash_list_iter_previous(iter))) { long i = pointer_to_int(item); g_assert(i >= 0 && i < cache->pages); if (!cache->dirty[i]) { found = TRUE; /* OK, reuse cache slot #i then */ n = i; oldnum = cache->numpag[i]; break; } } hash_list_iter_release(&iter); if (found) { g_assert(item != NULL); hash_list_moveto_head(cache->used, item); /* * Clear error condition if we had none prior to the flush * attempt, since we can do without it for now. */ if (!had_ioerr) db->flags &= ~DBM_IOERR_W; g_warning("sdbm: \"%s\": " "reusing cache slot used by clean page #%ld instead", sdbm_name(db), oldnum); } else { g_warning("sdbm: \"%s\": cannot discard dirty page #%ld", sdbm_name(db), oldnum); return -1; } } g_hash_table_remove(cache->pagnum, ulong_to_pointer(oldnum)); cache->dirty[n] = FALSE; } /* * Record the association between the cache index and the page number. */ g_assert(n >= 0 && n < cache->pages); cache->numpag[n] = num; g_hash_table_insert(cache->pagnum, ulong_to_pointer(num), int_to_pointer(n)); return n; }