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);
+}