[dm-devel] [PATCH 05/14] dm-multisnap-mikulas-blocks

Mike Snitzer snitzer at redhat.com
Tue Mar 2 00:23:49 UTC 2010


From: Mikulas Patocka <mpatocka at redhat.com>

Common operations with blocks.

Management of tmp_remap array and some helper functions.

Signed-off-by: Mikulas Patocka <mpatocka at redhat.com>
---
 drivers/md/dm-multisnap-blocks.c |  333 ++++++++++++++++++++++++++++++++++++++
 1 files changed, 333 insertions(+), 0 deletions(-)
 create mode 100644 drivers/md/dm-multisnap-blocks.c

diff --git a/drivers/md/dm-multisnap-blocks.c b/drivers/md/dm-multisnap-blocks.c
new file mode 100644
index 0000000..8715ed9
--- /dev/null
+++ b/drivers/md/dm-multisnap-blocks.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2009 Red Hat Czech, s.r.o.
+ *
+ * Mikulas Patocka <mpatocka at redhat.com>
+ *
+ * This file is released under the GPL.
+ */
+
+#include "dm-multisnap-mikulas.h"
+
+/*
+ * Check that the block is valid.
+ */
+static int check_invalid(struct dm_exception_store *s, chunk_t block)
+{
+	if (unlikely(block >= s->dev_size) ||
+	    unlikely(block == SB_BLOCK) ||
+	    unlikely(dm_multisnap_is_commit_block(s, block))) {
+		DM_MULTISNAP_SET_ERROR(s->dm, -EFSERROR,
+				       ("check_invalid: access to invalid part of the device: %llx, size %llx",
+					(unsigned long long)block, (unsigned long long)s->dev_size));
+		return 1;
+	}
+	return 0;
+}
+
+static struct tmp_remap *find_tmp_remap(struct dm_exception_store *s, chunk_t block)
+{
+	struct tmp_remap *t;
+	struct hlist_node *hn;
+	unsigned hash = TMP_REMAP_HASH(block);
+	hlist_for_each_entry(t, hn, &s->tmp_remap[hash], hash_list) {
+		if (t->old == block)
+			return t;
+		cond_resched();
+	}
+	return NULL;
+}
+
+/*
+ * Remap a block number according to tmp_remap table.
+ */
+chunk_t dm_multisnap_remap_block(struct dm_exception_store *s, chunk_t block)
+{
+	struct tmp_remap *t;
+	t = find_tmp_remap(s, block);
+	if (t)
+		return t->new;
+	return block;
+}
+
+/*
+ * Read a metadata block, return pointer to the dataand hold a buffer for that
+ * block.
+ *
+ * Do a possible block remapping according to tmp_remap table.
+ */
+void *dm_multisnap_read_block(struct dm_exception_store *s, chunk_t block,
+			      struct dm_buffer **bp)
+{
+	void *buf;
+	cond_resched();
+
+	if (unlikely(check_invalid(s, block)))
+		return NULL;
+
+	block = dm_multisnap_remap_block(s, block);
+
+	if (unlikely(check_invalid(s, block)))
+		return NULL;
+
+	buf = dm_bufio_read(s->bufio, block, bp);
+	if (unlikely(IS_ERR(buf))) {
+		DM_MULTISNAP_SET_ERROR(s->dm, PTR_ERR(buf),
+				       ("dm_multisnap_read_block: error read chunk %llx",
+					(unsigned long long)block));
+		return NULL;
+	}
+	return buf;
+}
+
+struct uncommitted_record {
+	struct hlist_node hash;
+	chunk_t block;
+};
+
+/*
+ * Check if the block is not yet committed.
+ *
+ * If this function returns 1, the block is surely uncommitted.
+ * If it returns 0, the block may be committed or may be uncommitted.
+ * This function is used for optimizations, if it returns 0
+ * it doesn't break correctness, it only degrades performance.
+ */
+int dm_multisnap_block_is_uncommitted(struct dm_exception_store *s, chunk_t block)
+{
+	struct tmp_remap *t;
+
+	struct uncommitted_record *ur;
+	struct hlist_node *hn;
+
+	check_invalid(s, block);
+	t = find_tmp_remap(s, block);
+	if (t) {
+		if (t->uncommitted)
+			return 1;
+		block = t->new;
+	}
+	hlist_for_each_entry(ur, hn, &s->uncommitted_blocks[UNCOMMITTED_BLOCK_HASH(block)], hash)
+		if (ur->block == block)
+			return 1;
+	return 0;
+}
+
+/*
+ * Set the given block as uncommitted.
+ *
+ * The allocation may fail, in this case we see only a performance degradation
+ * (the block will be copied again), there is no functionality loss.
+ *
+ * We can't use non-failing allocation because it could deadlock (wait for some
+ * pages being written and that write could be directed through this driver).
+ */
+void dm_multisnap_block_set_uncommitted(struct dm_exception_store *s, chunk_t block)
+{
+	struct uncommitted_record *ur;
+	/*
+	 * GFP_ATOMIC allows to exhaust reserves. We don't want it (we can
+	 * afford failure), so we use GFP_NOWAIT.
+	 * __GFP_NOWARN supresses the log message on failure.
+	 * __GFP_NOMEMALLOC makes it less aggressive if the allocator recurses
+	 * into itself.
+	 */
+	ur = kmalloc(sizeof(struct uncommitted_record),
+		     GFP_NOWAIT | __GFP_NOWARN | __GFP_NOMEMALLOC);
+	if (!ur)
+		return;
+	ur->block = block;
+	hlist_add_head(&ur->hash, &s->uncommitted_blocks[UNCOMMITTED_BLOCK_HASH(block)]);
+}
+
+/*
+ * Clear the register of uncommitted blocks. This is called on commit and
+ * on unload.
+ */
+void dm_multisnap_clear_uncommitted(struct dm_exception_store *s)
+{
+	int i;
+	for (i = 0; i < UNCOMMITTED_BLOCK_HASH_SIZE; i++) {
+		struct hlist_head *h = &s->uncommitted_blocks[i];
+		while (!hlist_empty(h)) {
+			struct uncommitted_record *ur =
+				hlist_entry(h->first, struct uncommitted_record, hash);
+			hlist_del(&ur->hash);
+			kfree(ur);
+		}
+	}
+}
+
+/*
+ * This function is called by an allocation code when needing to modify a
+ * committed block.
+ *
+ * It will create new remap for old_chunk->new_chunk.
+ * bitmap_idx is the index of bitmap if we are remapping bitmap, otherwise
+ * CB_BITMAP_IDX_NONE.
+ *
+ * *bp must be open buffer for old_chunk. New buffer for new_chunk is returned
+ * there.
+ *
+ * A block that needs to be freed is returned in to_free. If to_free is NULL,
+ * that block is freed immediatelly.
+ */
+void *dm_multisnap_duplicate_block(struct dm_exception_store *s, chunk_t old_chunk,
+				   chunk_t new_chunk, bitmap_t bitmap_idx,
+				   struct dm_buffer **bp, chunk_t *to_free_ptr)
+{
+	chunk_t to_free_val;
+	void *buf;
+	struct tmp_remap *t;
+
+	if (unlikely(check_invalid(s, old_chunk)) ||
+	    unlikely(check_invalid(s, new_chunk)))
+		return NULL;
+
+	if (!to_free_ptr)
+		to_free_ptr = &to_free_val;
+	*to_free_ptr = 0;
+
+	t = find_tmp_remap(s, old_chunk);
+	if (t) {
+		if (unlikely(t->bitmap_idx != bitmap_idx)) {
+			DM_MULTISNAP_SET_ERROR(s->dm, -EFSERROR,
+					       ("dm_multisnap_duplicate_block: bitmap_idx doesn't match, %X != %X",
+						t->bitmap_idx, bitmap_idx));
+			return NULL;
+		}
+		*to_free_ptr = t->new;
+		t->new = new_chunk;
+	} else {
+		if (unlikely(list_empty(&s->free_tmp_remaps))) {
+			DM_MULTISNAP_SET_ERROR(s->dm, -EFSERROR,
+					       ("dm_multisnap_duplicate_block: all remap blocks used"));
+			return NULL;
+		}
+		t = list_first_entry(&s->free_tmp_remaps, struct tmp_remap, list);
+		t->new = new_chunk;
+		t->old = old_chunk;
+		t->bitmap_idx = bitmap_idx;
+		hlist_add_head(&t->hash_list, &s->tmp_remap[TMP_REMAP_HASH(old_chunk)]);
+		s->n_used_tmp_remaps++;
+	}
+	list_del(&t->list);
+	if (bitmap_idx == CB_BITMAP_IDX_NONE)
+		list_add_tail(&t->list, &s->used_bt_tmp_remaps);
+	else
+		list_add_tail(&t->list, &s->used_bitmap_tmp_remaps);
+	t->uncommitted = 1;
+	dm_bufio_release_move(*bp, new_chunk);
+
+	if (to_free_ptr == &to_free_val && to_free_val)
+		dm_multisnap_free_block(s, to_free_val, 0);
+
+	buf = dm_bufio_read(s->bufio, new_chunk, bp);
+	if (IS_ERR(buf)) {
+		DM_MULTISNAP_SET_ERROR(s->dm, PTR_ERR(buf),
+				       ("dm_multisnap_duplicate_block: error reading chunk %llx",
+					(unsigned long long)new_chunk));
+		return NULL;
+	}
+	return buf;
+}
+
+/*
+ * Remove an entry from tmp_remap table.
+ */
+void dm_multisnap_free_tmp_remap(struct dm_exception_store *s, struct tmp_remap *t)
+{
+	list_del(&t->list);
+	hlist_del(&t->hash_list);
+	s->n_used_tmp_remaps--;
+	list_add(&t->list, &s->free_tmp_remaps);
+}
+
+/*
+ * Get a new block. Just a wrapper around dm_bufio_new.
+ * It is expected that the caller fills all the data in the block, calls
+ * dm_bufio_mark_buffer_dirty and releases the buffer.
+ */
+void *dm_multisnap_make_block(struct dm_exception_store *s, chunk_t new_chunk,
+			      struct dm_buffer **bp)
+{
+	void *buf;
+
+	if (unlikely(check_invalid(s, new_chunk)))
+		return NULL;
+
+	dm_multisnap_block_set_uncommitted(s, new_chunk);
+
+	buf = dm_bufio_new(s->bufio, new_chunk, bp);
+	if (unlikely(IS_ERR(buf))) {
+		DM_MULTISNAP_SET_ERROR(s->dm, PTR_ERR(buf),
+				       ("dm_multisnap_make_block: error creating new block at chunk %llx",
+					(unsigned long long)new_chunk));
+		return NULL;
+	}
+	return buf;
+}
+
+/*
+ * Free the given block and a possible tmp_remap shadow of it.
+ */
+void dm_multisnap_free_block_and_duplicates(struct dm_exception_store *s, chunk_t block)
+{
+	struct tmp_remap *t;
+
+	if (unlikely(check_invalid(s, block)))
+		return;
+
+	t = find_tmp_remap(s, block);
+	if (t) {
+		dm_multisnap_free_block(s, t->new, 0);
+		dm_multisnap_free_tmp_remap(s, t);
+	}
+	dm_multisnap_free_block(s, block, 0);
+}
+
+/*
+ * Return true if the block is a commit block.
+ */
+int dm_multisnap_is_commit_block(struct dm_exception_store *s, chunk_t block)
+{
+	if (unlikely(block < FIRST_CB_BLOCK))
+		return 0;
+	/*
+	 * Division is very slow, thus we optimize the most common case
+	 * if cb_stride is the power of 2.
+	 */
+	if (likely(!(s->cb_stride & (s->cb_stride - 1))))
+		return (block & (s->cb_stride - 1)) == (FIRST_CB_BLOCK & (s->cb_stride - 1));
+	else
+		return sector_div(block, s->cb_stride) == FIRST_CB_BLOCK % s->cb_stride;
+}
+
+/*
+ * These two functions are used to avoid cycling on a corrupted device.
+ *
+ * If the data on the device is corrupted, we mark the device as errorneous,
+ * but we don't want to lockup the whole system. These functions help to achieve
+ * this goal.
+ *
+ * cy->count is the number of processed blocks.
+ * cy->key is the recorded block at last power-of-two count.
+ */
+void dm_multisnap_init_stop_cycles(struct stop_cycles *cy)
+{
+	cy->key = 0;
+	cy->count = 0;
+}
+
+int dm_multisnap_stop_cycles(struct dm_exception_store *s, struct stop_cycles *cy, chunk_t key)
+{
+	if (unlikely(cy->key == key) && unlikely(cy->count != 0)) {
+		DM_MULTISNAP_SET_ERROR(s->dm, -EFSERROR,
+				       ("dm_multisnap_stop_cycles: cycle detected at chunk %llx",
+					(unsigned long long)key));
+		return -1;
+	}
+	cy->count++;
+	if (!((cy->count - 1) & cy->count))
+		cy->key = key;
+	return 0;
+}
-- 
1.6.5.2




More information about the dm-devel mailing list