Freelist management.

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

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

Index: linux-2.6.32/drivers/md/dm-multisnap-freelist.c
===================================================================
--- /dev/null
+++ linux-2.6.32/drivers/md/dm-multisnap-freelist.c
@@ -0,0 +1,236 @@
+/*
+ * 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"
+
+void dm_multisnap_init_freelist(struct dm_multisnap_freelist *fl, unsigned chunk_size)
+{
+	cond_resched();
+	memset(fl, 0, chunk_size);
+	cond_resched();
+	fl->signature = FL_SIGNATURE;
+	write_48(fl, backlink, 0);
+	fl->n_entries = cpu_to_le32(0);
+}
+
+static int add_to_freelist(struct dm_exception_store *s, chunk_t block, unsigned flags)
+{
+	int i;
+	struct dm_multisnap_freelist *fl = s->freelist;
+	for (i = le32_to_cpu(fl->n_entries) - 1; i >= 0; i--) {
+		chunk_t x = read_48(&fl->entries[i], block);
+		unsigned r = le16_to_cpu(fl->entries[i].run_length) & FREELIST_RL_MASK;
+		unsigned f = le16_to_cpu(fl->entries[i].run_length) & FREELIST_DATA_FLAG;
+		if (block >= x && block < x + r) {
+			DMERR("add_to_freelist: freeing already free block %llx (%llx - %x)", (unsigned long long)block, (unsigned long long)x, r);
+			dm_multisnap_set_error(s->dm, -EFSERROR);
+			return -1;
+		}
+		if (likely(r < FREELIST_RL_MASK) && likely(f == flags)) {
+			if (block == x - 1) {
+				write_48(&fl->entries[i], block, x - 1);
+				goto inc_length;
+			}
+			if (block == x + r) {
+inc_length:
+				fl->entries[i].run_length = cpu_to_le16((r + 1) | f);
+				return 1;
+			}
+		}
+		cond_resched();
+	}
+	i = le32_to_cpu(fl->n_entries);
+	if (i < dm_multisnap_freelist_entries(s->chunk_size)) {
+		fl->n_entries = cpu_to_le32(i + 1);
+		write_48(&fl->entries[i], block, block);
+		fl->entries[i].run_length = cpu_to_le16(1 | flags);
+		return 1;
+	}
+	return 0;
+}
+
+static struct dm_multisnap_freelist *read_freelist(struct dm_exception_store *s, chunk_t block, struct dm_buffer **bp)
+{
+	struct dm_multisnap_freelist *fl;
+	fl = dm_bufio_read(s->bufio, block, bp);
+	if (IS_ERR(fl)) {
+		DMERR("read_freelist: can't read freelist block %llx", (unsigned long long)block);
+		dm_multisnap_set_error(s->dm, PTR_ERR(fl));
+		return NULL;
+	}
+	if (fl->signature != FL_SIGNATURE) {
+		dm_bufio_release(*bp);
+		DMERR("read_freelist: bad signature freelist block %llx", (unsigned long long)block);
+		dm_multisnap_set_error(s->dm, -EFSERROR);
+		return NULL;
+	}
+	if (le32_to_cpu(fl->n_entries) > dm_multisnap_freelist_entries(s->chunk_size)) {
+		dm_bufio_release(*bp);
+		DMERR("read_freelist: bad number of entries in freelist block %llx", (unsigned long long)block);
+		dm_multisnap_set_error(s->dm, -EFSERROR);
+		return NULL;
+	}
+	return fl;
+}
+
+static void alloc_write_freelist(struct dm_exception_store *s)
+{
+	chunk_t new_block;
+	struct dm_multisnap_freelist *fl;
+	struct dm_buffer *bp;
+
+	if (dm_multisnap_alloc_blocks(s, &new_block, 1, ALLOC_DRY))
+		return;
+
+	fl = dm_bufio_new(s->bufio, new_block, &bp);
+	if (IS_ERR(fl)) {
+		DMERR("alloc_write_freelist: can't make new freelist block %llx", (unsigned long long)new_block);
+		dm_multisnap_set_error(s->dm, PTR_ERR(fl));
+		return;
+	}
+
+	memcpy(fl, s->freelist, s->chunk_size);
+
+	dm_bufio_mark_buffer_dirty(bp);
+	dm_bufio_release(bp);
+
+	dm_multisnap_init_freelist(s->freelist, s->chunk_size);
+	write_48(s->freelist, backlink, new_block);
+}
+
+void dm_multisnap_free_block(struct dm_exception_store *s, chunk_t block, unsigned flags)
+{
+	if (likely(add_to_freelist(s, block, flags)))
+		return;
+
+	alloc_write_freelist(s);
+	if (dm_multisnap_has_error(s->dm))
+		return;
+
+	if (add_to_freelist(s, block, flags))
+		return;
+
+	BUG();
+}
+
+static int check_against_freelist(struct dm_multisnap_freelist *fl, chunk_t block)
+{
+	int i;
+	for (i = le32_to_cpu(fl->n_entries) - 1; i >= 0; i--) {
+		chunk_t x = read_48(&fl->entries[i], block);
+		unsigned r = le16_to_cpu(fl->entries[i].run_length) & FREELIST_RL_MASK;
+		if (block - x >= 0 && unlikely(block - x < r))
+			return 1;
+		cond_resched();
+	}
+	return 0;
+}
+
+static int check_against_freelist_chain(struct dm_exception_store *s, chunk_t fl_block, chunk_t block)
+{
+	stop_cycles_t cy;
+	dm_multisnap_init_stop_cycles(&cy);
+
+	while (unlikely(fl_block != 0)) {
+		int c;
+		struct dm_buffer *bp;
+		struct dm_multisnap_freelist *fl;
+
+		if (dm_multisnap_stop_cycles(s, &cy, fl_block))
+			return -1;
+
+		if (unlikely(block == fl_block))
+			return 1;
+
+		fl = read_freelist(s, fl_block, &bp);
+		if (unlikely(!fl))
+			return -1;
+		c = check_against_freelist(fl, block);
+		fl_block = read_48(fl, backlink);
+		dm_bufio_release(bp);
+		if (unlikely(c))
+			return c;
+	}
+	return 0;
+}
+
+int dm_multisnap_check_allocated_block(struct dm_exception_store *s, chunk_t block)
+{
+	int c;
+
+	c = check_against_freelist(s->freelist, block);
+	if (unlikely(c))
+		return c;
+
+	c = check_against_freelist_chain(s, read_48(s->freelist, backlink), block);
+	if (unlikely(c))
+		return c;
+
+	c = check_against_freelist_chain(s, s->freelist_ptr, block);
+	if (unlikely(c))
+		return c;
+
+	return 0;
+}
+
+void dm_multisnap_flush_freelist_before_commit(struct dm_exception_store *s)
+{
+	alloc_write_freelist(s);
+
+	if (dm_multisnap_has_error(s->dm))
+		return;
+
+	s->freelist_ptr = read_48(s->freelist, backlink);
+}
+
+static void free_blocks_in_freelist(struct dm_exception_store *s, struct dm_multisnap_freelist *fl)
+{
+	int i;
+	for (i = le32_to_cpu(fl->n_entries) - 1; i >= 0; i--) {
+		chunk_t x = read_48(&fl->entries[i], block);
+		unsigned r = le16_to_cpu(fl->entries[i].run_length) & FREELIST_RL_MASK;
+		unsigned f = le16_to_cpu(fl->entries[i].run_length) & FREELIST_DATA_FLAG;
+		dm_multisnap_free_blocks_immediate(s, x, r);
+		if (likely(f & FREELIST_DATA_FLAG)) {
+			dm_multisnap_status_lock(s->dm);
+			s->data_allocated -= r;
+			dm_multisnap_status_unlock(s->dm);
+		}
+		cond_resched();
+	}
+}
+
+void dm_multisnap_load_freelist(struct dm_exception_store *s)
+{
+	chunk_t fl_block = s->freelist_ptr;
+
+	stop_cycles_t cy;
+	dm_multisnap_init_stop_cycles(&cy);
+
+	while (fl_block) {
+		struct dm_buffer *bp;
+		struct dm_multisnap_freelist *fl;
+
+		if (dm_multisnap_stop_cycles(s, &cy, fl_block))
+			break;
+
+		if (dm_multisnap_has_error(s->dm))
+			break;
+
+		fl = read_freelist(s, fl_block, &bp);
+		if (!fl)
+			break;
+		memcpy(s->freelist, fl, s->chunk_size);
+		dm_bufio_release(bp);
+
+		free_blocks_in_freelist(s, s->freelist);
+		fl_block = read_48(s->freelist, backlink);
+	}
+
+	dm_multisnap_init_freelist(s->freelist, s->chunk_size);
+}