Exemplo n.º 1
0
/**
 * 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;
}
Exemplo n.º 2
0
/**
 * 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;
}
Exemplo n.º 3
0
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");
}
Exemplo n.º 4
0
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");
}
Exemplo n.º 5
0
/**
 * 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));
}
Exemplo n.º 6
0
/**
 * 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;
}
Exemplo n.º 7
0
/**
 * 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;
}
Exemplo n.º 8
0
/**
 * 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));
}
Exemplo n.º 9
0
/**
 * 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;
}
Exemplo n.º 10
0
/**
 * 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;
}
Exemplo n.º 11
0
/**
 * 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;
}
Exemplo n.º 12
0
/**
 * 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;
}
Exemplo n.º 13
0
/**
 * 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);
}
Exemplo n.º 14
0
/**
 * 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;
}
Exemplo n.º 15
0
/**
 * 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;
}
Exemplo n.º 16
0
/**
 * 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;
}
Exemplo n.º 17
0
/**
 * 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;
}
Exemplo n.º 18
0
/**
 * 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;
}