Common operations with blocks.

Management of tmp_remap array and some helper functions.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

---
 drivers/md/dm-multisnap-blocks.c |  198 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 198 insertions(+)

Index: linux-2.6.32/drivers/md/dm-multisnap-blocks.c
===================================================================
--- /dev/null
+++ linux-2.6.32/drivers/md/dm-multisnap-blocks.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2009 Red Hat Czech, s.r.o.
+ *
+ * Mikulas Patocka <mpatocka@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))) {
+		DMERR("check_invalid: access to invalid part of the device: %llx, size %llx", (unsigned long long)block, (unsigned long long)s->dev_size);
+		dm_multisnap_set_error(s->dm, -EFSERROR);
+		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;
+}
+
+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;
+}
+
+void *dm_multisnap_read_block(struct dm_exception_store *s, chunk_t block, struct dm_buffer **bp)
+{
+	void *buf;
+	cond_resched();
+
+	if (check_invalid(s, block))
+		return NULL;
+
+	block = dm_multisnap_remap_block(s, block);
+
+	if (check_invalid(s, block))
+		return NULL;
+
+	buf = dm_bufio_read(s->bufio, block, bp);
+	if (unlikely(IS_ERR(buf))) {
+		DMERR("dm_multisnap_read_block: error read chunk %llx", (unsigned long long)block);
+		dm_multisnap_set_error(s->dm, PTR_ERR(buf));
+		return NULL;
+	}
+	return buf;
+}
+
+int dm_multisnap_block_is_uncommitted(struct dm_exception_store *s, chunk_t chunk)
+{
+	struct tmp_remap *t;
+	check_invalid(s, chunk);
+	t = find_tmp_remap(s, chunk);
+	return t && t->uncommitted;
+}
+
+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)) {
+			DMERR("dm_multisnap_duplicate_block: bitmap_idx doesn't match, %X != %X", t->bitmap_idx, bitmap_idx);
+			dm_multisnap_set_error(s->dm, -EFSERROR);
+			return NULL;
+		}
+		*to_free_ptr = t->new;
+		t->new = new_chunk;
+	} else {
+		if (unlikely(list_empty(&s->free_tmp_remaps))) {
+			DMERR("dm_multisnap_duplicate_block: all remap blocks used");
+			dm_multisnap_set_error(s->dm, -EFSERROR);
+			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)) {
+		DMERR("dm_multisnap_duplicate_block: error reading chunk %llx", (unsigned long long)new_chunk);
+		dm_multisnap_set_error(s->dm, PTR_ERR(buf));
+		return NULL;
+	}
+	return buf;
+}
+
+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);
+}
+
+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;
+
+	/* !!! TODO: add it to the list of recently allocated blocks */
+
+	buf = dm_bufio_new(s->bufio, new_chunk, bp);
+	if (unlikely(IS_ERR(buf))) {
+		DMERR("dm_multisnap_make_block: error creating new block at chunk %llx", (unsigned long long)new_chunk);
+		dm_multisnap_set_error(s->dm, PTR_ERR(buf));
+		return NULL;
+	}
+	return buf;
+}
+
+void dm_multisnap_free_block_and_duplicates(struct dm_exception_store *s, chunk_t chunk)
+{
+	struct tmp_remap *t;
+
+	if (unlikely(check_invalid(s, chunk)))
+		return;
+
+	t = find_tmp_remap(s, chunk);
+	if (t) {
+		dm_multisnap_free_block(s, t->new, 0);
+		dm_multisnap_free_tmp_remap(s, t);
+	}
+	dm_multisnap_free_block(s, chunk, 0);
+}
+
+int dm_multisnap_is_commit_block(struct dm_exception_store *s, chunk_t block)
+{
+	if (unlikely(block < FIRST_CB_BLOCK))
+		return 0;
+	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;
+}
+
+void dm_multisnap_init_stop_cycles(stop_cycles_t *cy)
+{
+	(*cy)[1] = 0;
+}
+
+int dm_multisnap_stop_cycles(struct dm_exception_store *s, stop_cycles_t *cy, chunk_t key)
+{
+	if (unlikely((*cy)[0] == key) && unlikely((*cy)[1] != 0)) {
+		DMERR("dm_multisnap_stop_cycles: cycle detected at chunk %llx", (unsigned long long)key);
+		dm_multisnap_set_error(s->dm, -EFSERROR);
+		return -1;
+	}
+	return 0;
+}