From: Jonathan Brassow This patch completes the clean break we wanted between snapshots and exception stores. Now it is possible to create exception store modules and be able to use them without having to alter the snapshot code. There are currently two types of exception stores, but more have been proposed - including a 'shared' exception store. Additionally, the constructor table has been enhanced to allow more flexibility in the arguments passed to the exception stores, as well as providing an easy path to further expand options in the future. This was done while maintaining backwards compatibility. [FIXME This needs splitting up!!!] Signed-off-by: Jonathan Brassow --- drivers/md/dm-exception-store.c | 312 ++++++++++++++++++++++++++++++++++++++ drivers/md/dm-exception-store.h | 61 +++++-- drivers/md/dm-snap-persistent.c | 189 +++++++++++++++-------- drivers/md/dm-snap-transient.c | 121 +++++++++++--- drivers/md/dm-snap.c | 324 +++++++++++++++++++++++----------------- 5 files changed, 763 insertions(+), 244 deletions(-) Index: linux-2.6.28-rc9/drivers/md/dm-exception-store.c =================================================================== --- linux-2.6.28-rc9.orig/drivers/md/dm-exception-store.c 2008-12-22 18:28:19.000000000 +0000 +++ linux-2.6.28-rc9/drivers/md/dm-exception-store.c 2008-12-22 18:29:07.000000000 +0000 @@ -14,6 +14,318 @@ #define DM_MSG_PREFIX "snapshot exception stores" +struct dm_exception_store_internal { + struct dm_exception_store_type *type; + + struct list_head list; + long use; +}; + +static LIST_HEAD(_exception_store_types); +static DEFINE_SPINLOCK(_lock); + +static struct dm_exception_store_internal * +__find_exception_store_type(const char *name) +{ + struct dm_exception_store_internal *internal_type; + + list_for_each_entry(internal_type, &_exception_store_types, list) + if (!strcmp(name, internal_type->type->name)) + return internal_type; + + return NULL; +} + +static struct dm_exception_store_internal * +_get_exception_store_type(const char *name) +{ + struct dm_exception_store_internal *internal_type; + + spin_lock(&_lock); + + internal_type = __find_exception_store_type(name); + if (internal_type) { + if (!internal_type->use && + !try_module_get(internal_type->type->module)) + internal_type = NULL; + else + internal_type->use++; + } + + spin_unlock(&_lock); + + return internal_type; +} + +/* + * get_type + * @type_name + * + * Attempt to retrieve the dm_exception_store_type by name. If not already + * available, attempt to load the appropriate module. + * + * Exstore modules are named "dm-exstore-" followed by the 'type_name'. + * Modules may contain multiple types. + * This function will first try the module "dm-exstore-", + * then truncate 'type_name' on the last '-' and try again. + * + * For example, if type_name was "clustered-shared", it would search + * 'dm-exstore-clustered-shared' then 'dm-exstore-clustered'. + * + * 'dm-exception-store-' is too long of a name in my + * opinion, which is why I've chosen to have the files + * containing exception store implementations be 'dm-exstore-'. + * If you want your module to be autoloaded, you will follow this + * naming convention. + * + * Returns: dm_exception_store_type* on success, NULL on failure + */ +static struct dm_exception_store_type *get_type(const char *type_name) +{ + char *p, *type_name_dup; + struct dm_exception_store_internal *internal_type; + + if (!type_name) + return NULL; + + internal_type = _get_exception_store_type(type_name); + if (internal_type) + return internal_type->type; + + type_name_dup = kstrdup(type_name, GFP_KERNEL); + if (!type_name_dup) { + DMWARN("No memory left to attempt load for \"%s\"", + type_name); + return NULL; + } + + while (request_module("dm-exstore-%s", type_name_dup) || + !(internal_type = _get_exception_store_type(type_name))) { + p = strrchr(type_name_dup, '-'); + if (!p) + break; + p[0] = '\0'; + } + + if (!internal_type) + DMWARN("Module for exstore type \"%s\" found.", type_name); + + kfree(type_name_dup); + + return internal_type ? internal_type->type : NULL; +} + +static void put_type(struct dm_exception_store_type *type) +{ + struct dm_exception_store_internal *internal_type; + + if (!type) + return; + + spin_lock(&_lock); + internal_type = __find_exception_store_type(type->name); + if (!internal_type) + goto out; + + if (!--internal_type->use) + module_put(type->module); + + BUG_ON(internal_type->use < 0); + +out: + spin_unlock(&_lock); +} + +static struct dm_exception_store_internal * +_alloc_exception_store_type(struct dm_exception_store_type *type) +{ + struct dm_exception_store_internal *internal_type; + + internal_type = kzalloc(sizeof(*internal_type), GFP_KERNEL); + + if (internal_type) + internal_type->type = type; + + return internal_type; +} + +int dm_exception_store_type_register(struct dm_exception_store_type *type) +{ + struct dm_exception_store_internal *internal_type; + int r = 0; + + internal_type = _alloc_exception_store_type(type); + + if (!internal_type) + return -ENOMEM; + + spin_lock(&_lock); + if (!__find_exception_store_type(type->name)) + list_add(&internal_type->list, &_exception_store_types); + else { + kfree(internal_type); + r = -EEXIST; + } + spin_unlock(&_lock); + + return r; +} +EXPORT_SYMBOL(dm_exception_store_type_register); + +int dm_exception_store_type_unregister(struct dm_exception_store_type *type) +{ + struct dm_exception_store_internal *internal_type; + + spin_lock(&_lock); + + internal_type = __find_exception_store_type(type->name); + if (!internal_type) { + spin_unlock(&_lock); + return -EINVAL; + } + + if (internal_type->use) { + spin_unlock(&_lock); + return -ETXTBSY; + } + + list_del(&internal_type->list); + + spin_unlock(&_lock); + kfree(internal_type); + + return 0; +} +EXPORT_SYMBOL(dm_exception_store_type_unregister); + +/* + * Round a number up to the nearest 'size' boundary. size must + * be a power of 2. + */ +static ulong round_up(ulong n, ulong size) +{ + size--; + return (n + size) & ~size; +} + +static int set_chunk_size(struct dm_exception_store *store, + const char *chunk_size_arg, char **error) +{ + unsigned long chunk_size; + char *value; + + chunk_size = simple_strtoul(chunk_size_arg, &value, 10); + if (*chunk_size_arg == '\0' || *value != '\0') { + *error = "Invalid chunk size"; + return -EINVAL; + } + + if (!chunk_size) { + store->chunk_size = store->chunk_mask = store->chunk_shift = 0; + return 0; + } + + /* + * Chunk size must be multiple of page size. Silently + * round up if it's not. + */ + chunk_size = round_up(chunk_size, PAGE_SIZE >> 9); + + /* Check chunk_size is a power of 2 */ + if (!is_power_of_2(chunk_size)) { + *error = "Chunk size is not a power of 2"; + return -EINVAL; + } + + /* Validate the chunk size against the device block size */ + if (chunk_size % (bdev_hardsect_size(store->cow->bdev) >> 9)) { + *error = "Chunk size is not a multiple of device blocksize"; + return -EINVAL; + } + + store->chunk_size = chunk_size; + store->chunk_mask = chunk_size - 1; + store->chunk_shift = ffs(chunk_size) - 1; + + return 0; +} + +int dm_exception_store_create(const char *type_name, struct dm_target *ti, + unsigned compatible_table, + unsigned int argc, char **argv, + struct dm_exception_store **store) +{ + int r = 0; + struct dm_exception_store_type *type; + struct dm_exception_store *tmp_store; + + if (argc < 2) { + ti->error = "Insufficient exception store arguments"; + return -EINVAL; + } + + tmp_store = kmalloc(sizeof(*tmp_store), GFP_KERNEL); + if (!tmp_store) + return -ENOMEM; + + type = get_type(type_name); + if (!type) { + kfree(tmp_store); + return -EINVAL; + } + + tmp_store->type = type; + tmp_store->ti = ti; + tmp_store->compatible_table = compatible_table; + + r = dm_get_device(ti, argv[0], 0, 0, + FMODE_READ | FMODE_WRITE, &tmp_store->cow); + if (r) { + ti->error = "Cannot get COW device"; + put_type(type); + kfree(tmp_store); + return r; + } + + r = set_chunk_size(tmp_store, argv[1], &ti->error); + if (r) { + dm_put_device(ti, tmp_store->cow); + put_type(type); + kfree(tmp_store); + return r; + } + + /* + * COW-dev and chunk_size are common to all types of + * exception stores and are stored directly in the + * dm_exception_store and not passed on to the + * constructor for the dm_exception_store_type + */ + argc -= 2; + argv += 2; + + r = type->ctr(tmp_store, argc, argv); + if (r) { + dm_put_device(ti, tmp_store->cow); + put_type(type); + kfree(tmp_store); + return r; + } + + *store = tmp_store; + return 0; +} +EXPORT_SYMBOL(dm_exception_store_create); + +void dm_exception_store_destroy(struct dm_exception_store *store) +{ + store->type->dtr(store); + dm_put_device(store->ti, store->cow); + put_type(store->type); + kfree(store); +} +EXPORT_SYMBOL(dm_exception_store_destroy); + int dm_exception_store_init(void) { int r; Index: linux-2.6.28-rc9/drivers/md/dm-exception-store.h =================================================================== --- linux-2.6.28-rc9.orig/drivers/md/dm-exception-store.h 2008-12-22 18:28:19.000000000 +0000 +++ linux-2.6.28-rc9/drivers/md/dm-exception-store.h 2008-12-22 18:29:07.000000000 +0000 @@ -33,15 +33,30 @@ struct dm_snap_exception { chunk_t new_chunk; }; -/* - * Abstraction to handle the meta/layout of exception stores (the - * COW device). - */ +struct dm_exception_store_type; struct dm_exception_store { - /* - * Destroys this object when you've finished with it. - */ - void (*destroy) (struct dm_exception_store *store); + struct dm_exception_store_type *type; + struct dm_target *ti; + + unsigned compatible_table; + + struct dm_dev *cow; + + /* Size of data blocks saved - must be a power of 2 */ + chunk_t chunk_size; + chunk_t chunk_mask; + chunk_t chunk_shift; + + void *context; +}; + +struct dm_exception_store_type { + const char *name; + struct module *module; + + int (*ctr) (struct dm_exception_store *store, + unsigned argc, char **argv); + void (*dtr) (struct dm_exception_store *store); /* * The target shouldn't read the COW device until this is @@ -81,9 +96,6 @@ struct dm_exception_store { void (*fraction_full) (struct dm_exception_store *store, sector_t *numerator, sector_t *denominator); - - struct dm_snapshot *snap; - void *context; }; /* @@ -129,6 +141,29 @@ static inline void dm_consecutive_chunk_ # endif +/* + * Return the number of sectors in the device. + */ +static inline sector_t get_dev_size(struct block_device *bdev) +{ + return bdev->bd_inode->i_size >> SECTOR_SHIFT; +} + +static inline chunk_t sector_to_chunk(struct dm_exception_store *store, + sector_t sector) +{ + return (sector & ~store->chunk_mask) >> store->chunk_shift; +} + +int dm_exception_store_type_register(struct dm_exception_store_type *type); +int dm_exception_store_type_unregister(struct dm_exception_store_type *type); + +int dm_exception_store_create(const char *type_name, struct dm_target *ti, + unsigned compatible_table, + unsigned argc, char **argv, + struct dm_exception_store **store); +void dm_exception_store_destroy(struct dm_exception_store *store); + int dm_exception_store_init(void); void dm_exception_store_exit(void); @@ -141,8 +176,4 @@ void dm_persistent_snapshot_exit(void); int dm_transient_snapshot_init(void); void dm_transient_snapshot_exit(void); -int dm_create_persistent(struct dm_exception_store *store); - -int dm_create_transient(struct dm_exception_store *store); - #endif /* _LINUX_DM_EXCEPTION_STORE */ Index: linux-2.6.28-rc9/drivers/md/dm-snap-persistent.c =================================================================== --- linux-2.6.28-rc9.orig/drivers/md/dm-snap-persistent.c 2008-12-22 18:28:19.000000000 +0000 +++ linux-2.6.28-rc9/drivers/md/dm-snap-persistent.c 2008-12-22 18:29:07.000000000 +0000 @@ -6,7 +6,6 @@ */ #include "dm-exception-store.h" -#include "dm-snap.h" #include #include @@ -15,6 +14,7 @@ #include #define DM_MSG_PREFIX "persistent snapshot" + #define DM_CHUNK_SIZE_DEFAULT_SECTORS 32 /* 16KB */ /*----------------------------------------------------------------- @@ -89,7 +89,7 @@ struct commit_callback { * The top level structure for a persistent exception store. */ struct pstore { - struct dm_snapshot *snap; /* up pointer to my snapshot */ + struct dm_exception_store *store; int version; int valid; uint32_t exceptions_per_area; @@ -141,7 +141,7 @@ static int alloc_area(struct pstore *ps) int r = -ENOMEM; size_t len; - len = ps->snap->chunk_size << SECTOR_SHIFT; + len = ps->store->chunk_size << SECTOR_SHIFT; /* * Allocate the chunk_size block of memory that will hold @@ -189,9 +189,9 @@ static void do_metadata(struct work_stru static int chunk_io(struct pstore *ps, chunk_t chunk, int rw, int metadata) { struct dm_io_region where = { - .bdev = ps->snap->cow->bdev, - .sector = ps->snap->chunk_size * chunk, - .count = ps->snap->chunk_size, + .bdev = ps->store->cow->bdev, + .sector = ps->store->chunk_size * chunk, + .count = ps->store->chunk_size, }; struct dm_io_request io_req = { .bi_rw = rw, @@ -247,15 +247,15 @@ static int area_io(struct pstore *ps, in static void zero_memory_area(struct pstore *ps) { - memset(ps->area, 0, ps->snap->chunk_size << SECTOR_SHIFT); + memset(ps->area, 0, ps->store->chunk_size << SECTOR_SHIFT); } static int zero_disk_area(struct pstore *ps, chunk_t area) { struct dm_io_region where = { - .bdev = ps->snap->cow->bdev, - .sector = ps->snap->chunk_size * area_location(ps, area), - .count = ps->snap->chunk_size, + .bdev = ps->store->cow->bdev, + .sector = ps->store->chunk_size * area_location(ps, area), + .count = ps->store->chunk_size, }; struct dm_io_request io_req = { .bi_rw = WRITE, @@ -278,15 +278,15 @@ static int read_header(struct pstore *ps /* * Use default chunk size (or hardsect_size, if larger) if none supplied */ - if (!ps->snap->chunk_size) { - ps->snap->chunk_size = max(DM_CHUNK_SIZE_DEFAULT_SECTORS, - bdev_hardsect_size(ps->snap->cow->bdev) >> 9); - ps->snap->chunk_mask = ps->snap->chunk_size - 1; - ps->snap->chunk_shift = ffs(ps->snap->chunk_size) - 1; + if (!ps->store->chunk_size) { + ps->store->chunk_size = max(DM_CHUNK_SIZE_DEFAULT_SECTORS, + bdev_hardsect_size(ps->store->cow->bdev) >> 9); + ps->store->chunk_mask = ps->store->chunk_size - 1; + ps->store->chunk_shift = ffs(ps->store->chunk_size) - 1; chunk_size_supplied = 0; } - ps->io_client = dm_io_client_create(sectors_to_pages(ps->snap-> + ps->io_client = dm_io_client_create(sectors_to_pages(ps->store-> chunk_size)); if (IS_ERR(ps->io_client)) return PTR_ERR(ps->io_client); @@ -317,22 +317,22 @@ static int read_header(struct pstore *ps ps->version = le32_to_cpu(dh->version); chunk_size = le32_to_cpu(dh->chunk_size); - if (!chunk_size_supplied || ps->snap->chunk_size == chunk_size) + if (!chunk_size_supplied || ps->store->chunk_size == chunk_size) return 0; DMWARN("chunk size %llu in device metadata overrides " "table chunk size of %llu.", (unsigned long long)chunk_size, - (unsigned long long)ps->snap->chunk_size); + (unsigned long long)ps->store->chunk_size); /* We had a bogus chunk_size. Fix stuff up. */ free_area(ps); - ps->snap->chunk_size = chunk_size; - ps->snap->chunk_mask = chunk_size - 1; - ps->snap->chunk_shift = ffs(chunk_size) - 1; + ps->store->chunk_size = chunk_size; + ps->store->chunk_mask = chunk_size - 1; + ps->store->chunk_shift = ffs(chunk_size) - 1; - r = dm_io_client_resize(sectors_to_pages(ps->snap->chunk_size), + r = dm_io_client_resize(sectors_to_pages(ps->store->chunk_size), ps->io_client); if (r) return r; @@ -349,13 +349,13 @@ static int write_header(struct pstore *p { struct disk_header *dh; - memset(ps->area, 0, ps->snap->chunk_size << SECTOR_SHIFT); + memset(ps->area, 0, ps->store->chunk_size << SECTOR_SHIFT); dh = (struct disk_header *) ps->area; dh->magic = cpu_to_le32(SNAP_MAGIC); dh->valid = cpu_to_le32(ps->valid); dh->version = cpu_to_le32(ps->version); - dh->chunk_size = cpu_to_le32(ps->snap->chunk_size); + dh->chunk_size = cpu_to_le32(ps->store->chunk_size); return chunk_io(ps, 0, WRITE, 1); } @@ -474,11 +474,44 @@ static struct pstore *get_info(struct dm static void persistent_fraction_full(struct dm_exception_store *store, sector_t *numerator, sector_t *denominator) { - *numerator = get_info(store)->next_free * store->snap->chunk_size; - *denominator = get_dev_size(store->snap->cow->bdev); + *numerator = get_info(store)->next_free * store->chunk_size; + *denominator = get_dev_size(store->cow->bdev); } -static void persistent_destroy(struct dm_exception_store *store) +static int persistent_ctr(struct dm_exception_store *store, + unsigned argc, char **argv) +{ + struct pstore *ps; + + /* allocate the pstore */ + ps = kmalloc(sizeof(*ps), GFP_KERNEL); + if (!ps) + return -ENOMEM; + + ps->store = store; + ps->valid = 1; + ps->version = SNAPSHOT_DISK_VERSION; + ps->area = NULL; + ps->next_free = 2; /* skipping the header and first area */ + ps->current_committed = 0; + + ps->callback_count = 0; + atomic_set(&ps->pending_count, 0); + ps->callbacks = NULL; + + ps->metadata_wq = create_singlethread_workqueue("ksnaphd"); + if (!ps->metadata_wq) { + kfree(ps); + DMERR("couldn't start header metadata update thread"); + return -ENOMEM; + } + + store->context = ps; + + return 0; +} + +static void persistent_dtr(struct dm_exception_store *store) { struct pstore *ps = get_info(store); @@ -507,7 +540,7 @@ static int persistent_read_metadata(stru /* * Now we know correct chunk_size, complete the initialisation. */ - ps->exceptions_per_area = (ps->snap->chunk_size << SECTOR_SHIFT) / + ps->exceptions_per_area = (ps->store->chunk_size << SECTOR_SHIFT) / sizeof(struct disk_exception); ps->callbacks = dm_vcalloc(ps->exceptions_per_area, sizeof(*ps->callbacks)); @@ -564,10 +597,10 @@ static int persistent_prepare_exception( struct pstore *ps = get_info(store); uint32_t stride; chunk_t next_free; - sector_t size = get_dev_size(store->snap->cow->bdev); + sector_t size = get_dev_size(store->cow->bdev); /* Is there enough room ? */ - if (size < ((ps->next_free + 1) * store->snap->chunk_size)) + if (size < ((ps->next_free + 1) * store->chunk_size)) return -ENOSPC; e->new_chunk = ps->next_free; @@ -656,49 +689,77 @@ static void persistent_drop_snapshot(str DMWARN("write header failed"); } -int dm_create_persistent(struct dm_exception_store *store) -{ - struct pstore *ps; +static int persistent_status(struct dm_exception_store *store, + status_type_t status, char *result, + unsigned int maxlen) +{ + int sz = 0; + + switch (status) { + case STATUSTYPE_INFO: + break; + case STATUSTYPE_TABLE: + if (store->compatible_table) + DMEMIT("%s %s %llu", store->cow->name, + store->type->name, + (unsigned long long)store->chunk_size); + else + DMEMIT("%s 2 %s %llu", store->type->name, + store->cow->name, + (unsigned long long)store->chunk_size); + } - /* allocate the pstore */ - ps = kmalloc(sizeof(*ps), GFP_KERNEL); - if (!ps) - return -ENOMEM; + return sz; +} - ps->snap = store->snap; - ps->valid = 1; - ps->version = SNAPSHOT_DISK_VERSION; - ps->area = NULL; - ps->next_free = 2; /* skipping the header and first area */ - ps->current_committed = 0; +static struct dm_exception_store_type _persistent_type = { + .name = "persistent", + .module = THIS_MODULE, + .ctr = persistent_ctr, + .dtr = persistent_dtr, + .read_metadata = persistent_read_metadata, + .prepare_exception = persistent_prepare_exception, + .commit_exception = persistent_commit_exception, + .drop_snapshot = persistent_drop_snapshot, + .fraction_full = persistent_fraction_full, + .status = persistent_status, +}; - ps->callback_count = 0; - atomic_set(&ps->pending_count, 0); - ps->callbacks = NULL; +static struct dm_exception_store_type _persistent_compat_type = { + .name = "p", + .module = THIS_MODULE, + .ctr = persistent_ctr, + .dtr = persistent_dtr, + .read_metadata = persistent_read_metadata, + .prepare_exception = persistent_prepare_exception, + .commit_exception = persistent_commit_exception, + .drop_snapshot = persistent_drop_snapshot, + .fraction_full = persistent_fraction_full, + .status = persistent_status, +}; - ps->metadata_wq = create_singlethread_workqueue("ksnaphd"); - if (!ps->metadata_wq) { - kfree(ps); - DMERR("couldn't start header metadata update thread"); - return -ENOMEM; - } +int dm_persistent_snapshot_init(void) +{ + int r; - store->destroy = persistent_destroy; - store->read_metadata = persistent_read_metadata; - store->prepare_exception = persistent_prepare_exception; - store->commit_exception = persistent_commit_exception; - store->drop_snapshot = persistent_drop_snapshot; - store->fraction_full = persistent_fraction_full; - store->context = ps; + r = dm_exception_store_type_register(&_persistent_type); + if (r) { + DMERR("Unable to register presistent exception store type"); + return r; + } - return 0; -} + r = dm_exception_store_type_register(&_persistent_compat_type); + if (r) { + DMERR("Unable to register old-style presistent exception store type"); + dm_exception_store_type_unregister(&_persistent_type); + return r; + } -int dm_persistent_snapshot_init(void) -{ - return 0; + return r; } void dm_persistent_snapshot_exit(void) { + dm_exception_store_type_unregister(&_persistent_type); + dm_exception_store_type_unregister(&_persistent_compat_type); } Index: linux-2.6.28-rc9/drivers/md/dm-snap-transient.c =================================================================== --- linux-2.6.28-rc9.orig/drivers/md/dm-snap-transient.c 2008-12-22 18:28:19.000000000 +0000 +++ linux-2.6.28-rc9/drivers/md/dm-snap-transient.c 2008-12-22 18:29:07.000000000 +0000 @@ -6,13 +6,11 @@ */ #include "dm-exception-store.h" -#include "dm-snap.h" #include #include #include #include -#include #define DM_MSG_PREFIX "transient snapshot" @@ -23,7 +21,31 @@ struct transient_c { sector_t next_free; }; -static void transient_destroy(struct dm_exception_store *store) +/* + * transient_ctr + * @store + * @argc + * @argv: Always + * + * Returns: 0 on success, -Exxx on error + */ +static int transient_ctr(struct dm_exception_store *store, + unsigned argc, char **argv) +{ + struct transient_c *tc; + + tc = kmalloc(sizeof(struct transient_c), GFP_KERNEL); + if (!tc) + return -ENOMEM; + + tc->next_free = 0; + store->context = tc; + + return 0; + +} + +static void transient_dtr(struct dm_exception_store *store) { kfree(store->context); } @@ -40,13 +62,13 @@ static int transient_prepare_exception(s struct dm_snap_exception *e) { struct transient_c *tc = (struct transient_c *) store->context; - sector_t size = get_dev_size(store->snap->cow->bdev); + sector_t size = get_dev_size(store->cow->bdev); - if (size < (tc->next_free + store->snap->chunk_size)) + if (size < (tc->next_free + store->chunk_size)) return -1; - e->new_chunk = sector_to_chunk(store->snap, tc->next_free); - tc->next_free += store->snap->chunk_size; + e->new_chunk = sector_to_chunk(store, tc->next_free); + tc->next_free += store->chunk_size; return 0; } @@ -64,35 +86,78 @@ static void transient_fraction_full(stru sector_t *numerator, sector_t *denominator) { *numerator = ((struct transient_c *) store->context)->next_free; - *denominator = get_dev_size(store->snap->cow->bdev); + *denominator = get_dev_size(store->cow->bdev); } -int dm_create_transient(struct dm_exception_store *store) -{ - struct transient_c *tc; - - store->destroy = transient_destroy; - store->read_metadata = transient_read_metadata; - store->prepare_exception = transient_prepare_exception; - store->commit_exception = transient_commit_exception; - store->drop_snapshot = NULL; - store->fraction_full = transient_fraction_full; - - tc = kmalloc(sizeof(struct transient_c), GFP_KERNEL); - if (!tc) - return -ENOMEM; - - tc->next_free = 0; - store->context = tc; +static int transient_status(struct dm_exception_store *store, + status_type_t status, char *result, + unsigned int maxlen) +{ + int sz = 0; + + switch (status) { + case STATUSTYPE_INFO: + break; + case STATUSTYPE_TABLE: + if (store->compatible_table) + DMEMIT("%s %s %llu", store->cow->name, + store->type->name, + (unsigned long long)store->chunk_size); + else + DMEMIT("%s 2 %s %llu", store->type->name, + store->cow->name, + (unsigned long long)store->chunk_size); + } + + return sz; +} + +static struct dm_exception_store_type _transient_type = { + .name = "transient", + .module = THIS_MODULE, + .ctr = transient_ctr, + .dtr = transient_dtr, + .read_metadata = transient_read_metadata, + .prepare_exception = transient_prepare_exception, + .commit_exception = transient_commit_exception, + .fraction_full = transient_fraction_full, + .status = transient_status, +}; - return 0; -} +static struct dm_exception_store_type _transient_compat_type = { + .name = "t", + .module = THIS_MODULE, + .ctr = transient_ctr, + .dtr = transient_dtr, + .read_metadata = transient_read_metadata, + .prepare_exception = transient_prepare_exception, + .commit_exception = transient_commit_exception, + .fraction_full = transient_fraction_full, + .status = transient_status, +}; int dm_transient_snapshot_init(void) { - return 0; + int r; + + r = dm_exception_store_type_register(&_transient_type); + if (r) { + DMWARN("Unable to register transient exception store type"); + return r; + } + + r = dm_exception_store_type_register(&_transient_compat_type); + if (r) { + DMWARN("Unable to register old-style transient exception store type"); + dm_exception_store_type_unregister(&_transient_type); + return r; + } + + return r; } void dm_transient_snapshot_exit(void) { + dm_exception_store_type_unregister(&_transient_type); + dm_exception_store_type_unregister(&_transient_compat_type); } Index: linux-2.6.28-rc9/drivers/md/dm-snap.c =================================================================== --- linux-2.6.28-rc9.orig/drivers/md/dm-snap.c 2008-12-22 18:28:19.000000000 +0000 +++ linux-2.6.28-rc9/drivers/md/dm-snap.c 2008-12-22 18:29:07.000000000 +0000 @@ -20,9 +20,9 @@ #include #include #include +#include #include "dm-exception-store.h" -#include "dm-snap.h" #include "dm-bio-list.h" #define DM_MSG_PREFIX "snapshots" @@ -47,9 +47,76 @@ */ #define MIN_IOS 256 +#define DM_TRACKED_CHUNK_HASH_SIZE 16 +#define DM_TRACKED_CHUNK_HASH(x) ((unsigned long)(x) & \ + (DM_TRACKED_CHUNK_HASH_SIZE - 1)) + +struct exception_table { + uint32_t hash_mask; + unsigned hash_shift; + struct list_head *table; +}; + +struct dm_snapshot { + struct rw_semaphore lock; + struct dm_target *ti; + + struct dm_dev *origin; + + /* List of snapshots per Origin */ + struct list_head list; + + /* You can't use a snapshot if this is 0 (e.g. if full) */ + int valid; + + /* Origin writes don't trigger exceptions until this is set */ + int active; + + mempool_t *pending_pool; + + atomic_t pending_exceptions_count; + + struct exception_table pending; + struct exception_table complete; + + /* + * pe_lock protects all pending_exception operations and access + * as well as the snapshot_bios list. + */ + spinlock_t pe_lock; + + /* The on disk metadata handler */ + struct dm_exception_store *store; + + struct dm_kcopyd_client *kcopyd_client; + + /* Queue of snapshot writes for ksnapd to flush */ + struct bio_list queued_bios; + struct work_struct queued_bios_work; + + /* Chunks with outstanding reads */ + mempool_t *tracked_chunk_pool; + spinlock_t tracked_chunk_lock; + struct hlist_head tracked_chunk_hash[DM_TRACKED_CHUNK_HASH_SIZE]; +}; + static struct workqueue_struct *ksnapd; static void flush_queued_bios(struct work_struct *work); +static inline sector_t chunk_to_sector(struct dm_snapshot *s, chunk_t chunk) +{ + return chunk << s->store->chunk_shift; +} + +static inline int bdev_equal(struct block_device *lhs, struct block_device *rhs) +{ + /* + * There is only ever one instance of a particular block + * device so we can compare pointers safely. + */ + return lhs == rhs; +} + struct dm_snap_pending_exception { struct dm_snap_exception e; @@ -431,7 +498,7 @@ out: } /* - * Callback used by the exception stores to load exceptions when + * Used by the exception stores to load exceptions when * initialising. */ static int dm_add_exception(void *context, chunk_t old, chunk_t new) @@ -476,11 +543,11 @@ static int init_hash_tables(struct dm_sn * Calculate based on the size of the original volume or * the COW volume... */ - cow_dev_size = get_dev_size(s->cow->bdev); + cow_dev_size = get_dev_size(s->store->cow->bdev); origin_dev_size = get_dev_size(s->origin->bdev); max_buckets = calc_max_buckets(); - hash_size = min(origin_dev_size, cow_dev_size) >> s->chunk_shift; + hash_size = min(origin_dev_size, cow_dev_size) >> s->store->chunk_shift; hash_size = min(hash_size, max_buckets); hash_size = rounddown_pow_of_two(hash_size); @@ -505,113 +572,115 @@ static int init_hash_tables(struct dm_sn } /* - * Round a number up to the nearest 'size' boundary. size must - * be a power of 2. + * create_exception_store + * @ti + * @argc + * @argv + * @args_used + * @store: contains newly allocated dm_exception_store + * + * Possible formats for argv:: + * Backwards compatibility mode: + * p/n + * Current format: + * [other args] + * + * Returns: 0 on success, -Exxx on error */ -static ulong round_up(ulong n, ulong size) +static int create_exception_store(struct dm_target *ti, unsigned argc, + char **argv, unsigned *args_used, + struct dm_exception_store **store) { - size--; - return (n + size) & ~size; -} - -static int set_chunk_size(struct dm_snapshot *s, const char *chunk_size_arg, - char **error) -{ - unsigned long chunk_size; - char *value; + unsigned param_count; + char *tmp_argv[2]; - chunk_size = simple_strtoul(chunk_size_arg, &value, 10); - if (*chunk_size_arg == '\0' || *value != '\0') { - *error = "Invalid chunk size"; - return -EINVAL; - } - - if (!chunk_size) { - s->chunk_size = s->chunk_mask = s->chunk_shift = 0; - return 0; - } + *store = NULL; /* - * Chunk size must be multiple of page size. Silently - * round up if it's not. + * Detect for old-style table line with type as second arg. */ - chunk_size = round_up(chunk_size, PAGE_SIZE >> 9); + if (isdigit(*argv[1])) { + if (argc < 3) { + ti->error = "Insufficient exception store arguments"; + return -EINVAL; + } - /* Check chunk_size is a power of 2 */ - if (!is_power_of_2(chunk_size)) { - *error = "Chunk size is not a power of 2"; - return -EINVAL; + tmp_argv[0] = argv[0]; /* COW dev */ + tmp_argv[1] = argv[2]; /* chunk size */ + + *args_used = 3; + + return dm_exception_store_create(argv[1], ti, 1, 2, tmp_argv, + store); } - /* Validate the chunk size against the device block size */ - if (chunk_size % (bdev_hardsect_size(s->cow->bdev) >> 9)) { - *error = "Chunk size is not a multiple of device blocksize"; + if (sscanf(argv[1], "%u", ¶m_count) != 1) { + ti->error = "Invalid exception store argument count"; return -EINVAL; } - s->chunk_size = chunk_size; - s->chunk_mask = chunk_size - 1; - s->chunk_shift = ffs(chunk_size) - 1; + *args_used = 2 + param_count; - return 0; + if (argc < *args_used) { + ti->error = "Insufficient exception store arguments"; + return -EINVAL; + } + + return dm_exception_store_create(argv[0], ti, 0, param_count, argv + 2, + store); } /* - * Construct a snapshot mapping:

+ * snapshot_ctr + * @ti + * @argc + * @argv + * + * Construct a snapshot mapping. Possible mapping tables include: + * + * See 'create_exception_store' for format of . + * + * Returns: 0 on success, -XXX on error */ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv) { struct dm_snapshot *s; - int i; + int i = 0; int r = -EINVAL; - char persistent; char *origin_path; - char *cow_path; + unsigned args_used; + struct dm_exception_store *store; - if (argc != 4) { - ti->error = "requires exactly 4 arguments"; - r = -EINVAL; - goto bad1; + if (argc < 4) { + ti->error = "too few arguments"; + return -EINVAL; } origin_path = argv[0]; - cow_path = argv[1]; - persistent = toupper(*argv[2]); + argv++; + argc--; + r = create_exception_store(ti, argc, argv, &args_used, &store); + if (r) + return r; - if (persistent != 'P' && persistent != 'N') { - ti->error = "Persistent flag is not P or N"; - r = -EINVAL; - goto bad1; - } + argc -= args_used; + argv += args_used; s = kmalloc(sizeof(*s), GFP_KERNEL); if (s == NULL) { - ti->error = "Cannot allocate snapshot context private " - "structure"; + ti->error = "Cannot allocate snapshot private structure"; r = -ENOMEM; goto bad1; } + s->store = store; + r = dm_get_device(ti, origin_path, 0, ti->len, FMODE_READ, &s->origin); if (r) { ti->error = "Cannot get origin device"; goto bad2; } - r = dm_get_device(ti, cow_path, 0, 0, - FMODE_READ | FMODE_WRITE, &s->cow); - if (r) { - dm_put_device(ti, s->origin); - ti->error = "Cannot get COW device"; - goto bad2; - } - - r = set_chunk_size(s, argv[3], &ti->error); - if (r) - goto bad3; - - s->type = persistent; - s->valid = 1; s->active = 0; atomic_set(&s->pending_exceptions_count, 0); @@ -626,29 +695,16 @@ static int snapshot_ctr(struct dm_target goto bad3; } - s->store.snap = s; - - if (persistent == 'P') - r = dm_create_persistent(&s->store); - else - r = dm_create_transient(&s->store); - - if (r) { - ti->error = "Couldn't create exception store"; - r = -EINVAL; - goto bad4; - } - r = dm_kcopyd_client_create(SNAPSHOT_PAGES, &s->kcopyd_client); if (r) { ti->error = "Could not create kcopyd client"; - goto bad5; + goto bad4; } s->pending_pool = mempool_create_slab_pool(MIN_IOS, pending_cache); if (!s->pending_pool) { ti->error = "Could not allocate mempool for pending exceptions"; - goto bad6; + goto bad5; } s->tracked_chunk_pool = mempool_create_slab_pool(MIN_IOS, @@ -656,7 +712,7 @@ static int snapshot_ctr(struct dm_target if (!s->tracked_chunk_pool) { ti->error = "Could not allocate tracked_chunk mempool for " "tracking reads"; - goto bad_tracked_chunk_pool; + goto bad6; } for (i = 0; i < DM_TRACKED_CHUNK_HASH_SIZE; i++) @@ -665,10 +721,11 @@ static int snapshot_ctr(struct dm_target spin_lock_init(&s->tracked_chunk_lock); /* Metadata must only be loaded into one table at once */ - r = s->store.read_metadata(&s->store, dm_add_exception, (void *)s); + r = s->store->type->read_metadata(s->store, dm_add_exception, + (void *)s); if (r < 0) { ti->error = "Failed to read snapshot metadata"; - goto bad_load_and_register; + goto bad7; } else if (r > 0) { s->valid = 0; DMWARN("Snapshot is marked invalid."); @@ -682,38 +739,29 @@ static int snapshot_ctr(struct dm_target if (register_snapshot(s)) { r = -EINVAL; ti->error = "Cannot register snapshot origin"; - goto bad_load_and_register; + goto bad7; } ti->private = s; - ti->split_io = s->chunk_size; + ti->split_io = s->store->chunk_size; return 0; - bad_load_and_register: +bad7: mempool_destroy(s->tracked_chunk_pool); - - bad_tracked_chunk_pool: +bad6: mempool_destroy(s->pending_pool); - - bad6: +bad5: dm_kcopyd_client_destroy(s->kcopyd_client); - - bad5: - s->store.destroy(&s->store); - - bad4: +bad4: exit_exception_table(&s->pending, pending_cache); exit_exception_table(&s->complete, exception_cache); - - bad3: - dm_put_device(ti, s->cow); +bad3: dm_put_device(ti, s->origin); - - bad2: +bad2: kfree(s); - - bad1: +bad1: + dm_exception_store_destroy(s->store); return r; } @@ -725,7 +773,7 @@ static void __free_exceptions(struct dm_ exit_exception_table(&s->pending, pending_cache); exit_exception_table(&s->complete, exception_cache); - s->store.destroy(&s->store); + s->store->type->dtr(s->store); } static void snapshot_dtr(struct dm_target *ti) @@ -761,7 +809,6 @@ static void snapshot_dtr(struct dm_targe mempool_destroy(s->pending_pool); dm_put_device(ti, s->origin); - dm_put_device(ti, s->cow); kfree(s); } @@ -820,8 +867,8 @@ static void __invalidate_snapshot(struct else if (err == -ENOMEM) DMERR("Invalidating snapshot: Unable to allocate exception."); - if (s->store.drop_snapshot) - s->store.drop_snapshot(&s->store); + if (s->store->type->drop_snapshot) + s->store->type->drop_snapshot(s->store); s->valid = 0; @@ -943,8 +990,8 @@ static void copy_callback(int read_err, else /* Update the metadata if we are persistent */ - s->store.commit_exception(&s->store, &pe->e, commit_callback, - pe); + s->store->type->commit_exception(s->store, &pe->e, + commit_callback, pe); } /* @@ -961,9 +1008,9 @@ static void start_copy(struct dm_snap_pe src.bdev = bdev; src.sector = chunk_to_sector(s, pe->e.old_chunk); - src.count = min(s->chunk_size, dev_size - src.sector); + src.count = min(s->store->chunk_size, dev_size - src.sector); - dest.bdev = s->cow->bdev; + dest.bdev = s->store->cow->bdev; dest.sector = chunk_to_sector(s, pe->e.new_chunk); dest.count = src.count; @@ -985,7 +1032,7 @@ __find_pending_exception(struct dm_snaps { struct dm_snap_exception *e; struct dm_snap_pending_exception *pe; - chunk_t chunk = sector_to_chunk(s, bio->bi_sector); + chunk_t chunk = sector_to_chunk(s->store, bio->bi_sector); /* * Is there a pending exception for this already ? @@ -1024,7 +1071,7 @@ __find_pending_exception(struct dm_snaps atomic_set(&pe->ref_count, 0); pe->started = 0; - if (s->store.prepare_exception(&s->store, &pe->e)) { + if (s->store->type->prepare_exception(s->store, &pe->e)) { free_pending_exception(pe); return NULL; } @@ -1039,10 +1086,10 @@ __find_pending_exception(struct dm_snaps static void remap_exception(struct dm_snapshot *s, struct dm_snap_exception *e, struct bio *bio, chunk_t chunk) { - bio->bi_bdev = s->cow->bdev; + bio->bi_bdev = s->store->cow->bdev; bio->bi_sector = chunk_to_sector(s, dm_chunk_number(e->new_chunk) + (chunk - e->old_chunk)) + - (bio->bi_sector & s->chunk_mask); + (bio->bi_sector & s->store->chunk_mask); } static int snapshot_map(struct dm_target *ti, struct bio *bio, @@ -1054,7 +1101,7 @@ static int snapshot_map(struct dm_target chunk_t chunk; struct dm_snap_pending_exception *pe = NULL; - chunk = sector_to_chunk(s, bio->bi_sector); + chunk = sector_to_chunk(s->store, bio->bi_sector); /* Full snapshots are not usable */ /* To get here the table must be live so s->active is always set. */ @@ -1137,24 +1184,25 @@ static void snapshot_resume(struct dm_ta static int snapshot_status(struct dm_target *ti, status_type_t type, char *result, unsigned int maxlen) { + int sz = 0; struct dm_snapshot *snap = ti->private; switch (type) { case STATUSTYPE_INFO: if (!snap->valid) - snprintf(result, maxlen, "Invalid"); + DMEMIT("Invalid"); else { - if (snap->store.fraction_full) { + if (snap->store->type->fraction_full) { sector_t numerator, denominator; - snap->store.fraction_full(&snap->store, - &numerator, - &denominator); - snprintf(result, maxlen, "%llu/%llu", - (unsigned long long)numerator, - (unsigned long long)denominator); + snap->store->type->fraction_full(snap->store, + &numerator, + &denominator); + DMEMIT("%llu/%llu", + (unsigned long long)numerator, + (unsigned long long)denominator); } else - snprintf(result, maxlen, "Unknown"); + DMEMIT("Unknown"); } break; @@ -1164,10 +1212,9 @@ static int snapshot_status(struct dm_tar * to make private copies if the output is to * make sense. */ - snprintf(result, maxlen, "%s %s %c %llu", - snap->origin->name, snap->cow->name, - snap->type, - (unsigned long long)snap->chunk_size); + DMEMIT("%s", snap->origin->name); + snap->store->type->status(snap->store, type, result, maxlen); + break; } @@ -1203,7 +1250,7 @@ static int __origin_write(struct list_he * Remember, different snapshots can have * different chunk sizes. */ - chunk = sector_to_chunk(snap, bio->bi_sector); + chunk = sector_to_chunk(snap->store, bio->bi_sector); /* * Check exception table to see if block @@ -1360,7 +1407,8 @@ static void origin_resume(struct dm_targ o = __lookup_origin(dev->bdev); if (o) list_for_each_entry (snap, &o->snapshots, list) - chunk_size = min_not_zero(chunk_size, snap->chunk_size); + chunk_size = min_not_zero(chunk_size, + snap->store->chunk_size); up_read(&_origins_lock); ti->split_io = chunk_size; @@ -1420,7 +1468,7 @@ static int __init dm_snapshot_init(void) r = dm_register_target(&snapshot_target); if (r) { DMERR("snapshot target register failed %d", r); - return r; + goto bad0; } r = dm_register_target(&origin_target); @@ -1477,6 +1525,8 @@ bad2: dm_unregister_target(&origin_target); bad1: dm_unregister_target(&snapshot_target); +bad0: + dm_exception_store_exit(); return r; }