Permit in-use snapshot exception data to be 'handed over' from one snapshot instance to another. This is a pre-requisite for patches that allow the changes made in a snapshot device to be merged back into its origin device and also allows device resizing. The basic call sequence is: dmsetup load new_snapshot (referencing the existing in-use cow device) - the ctr code detects that the cow is already in use and links the two snapshot target instances together dmsetup suspend original_snapshot dmsetup resume new_snapshot - the new_snapshot becomes live, and if anything now tries to access the original one it will receive EIO dmsetup remove original_snapshot (There can only be two snapshot targets referencing the same cow device simultaneously.) Snapshot locking is such that: 0) snapshot that is passed to find_snapshot_using_cow() is not locked 1) only need handover-source lock to determine if handover is needed - handover-source lock is primary lock used in handover code paths - only need handover-destination lock before handover_exceptions() 2) handover-source lock is taken before handover-destination lock - but this is only ever needed before calling handover_exceptions() Signed-off-by: Mike Snitzer <snitzer@xxxxxxxxxx> --- drivers/md/dm-snap.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 203 insertions(+), 16 deletions(-) Index: linux-2.6/drivers/md/dm-snap.c =================================================================== --- linux-2.6.orig/drivers/md/dm-snap.c +++ linux-2.6/drivers/md/dm-snap.c @@ -72,6 +72,22 @@ struct dm_snapshot { /* Origin writes don't trigger exceptions until this is set */ int active; + /* + * 'is_handover_source' denotes this snapshot will handover its + * exception store to another snapshot. This handover-source + * snapshot is using the same cow block device that the + * handover-destination will use. + * + * The handover-destination snapshot identifies the + * handover-source using find_snapshot_using_cow. + * + * 'is_handover_source' is set in snapshot_ctr if an existing + * snapshot has the same cow device. The handover is performed, + * and 'is_handover_source' is cleared, when the + * handover-destination snapshot is resumed. + */ + int is_handover_source; + mempool_t *pending_pool; atomic_t pending_exceptions_count; @@ -303,15 +319,18 @@ static void __insert_origin(struct origi * Make a note of the snapshot and its origin so we can look it * up when the origin has a write on it. */ -static int register_snapshot(struct dm_snapshot *snap) +static int register_snapshot(struct dm_snapshot *snap, + int origin_exists) { struct dm_snapshot *l; - struct origin *o, *new_o; + struct origin *o, *new_o = NULL; struct block_device *bdev = snap->origin->bdev; - new_o = kmalloc(sizeof(*new_o), GFP_KERNEL); - if (!new_o) - return -ENOMEM; + if (!origin_exists) { + new_o = kmalloc(sizeof(*new_o), GFP_KERNEL); + if (!new_o) + return -ENOMEM; + } down_write(&_origins_lock); o = __lookup_origin(bdev); @@ -319,6 +338,12 @@ static int register_snapshot(struct dm_s if (o) kfree(new_o); else { + if (origin_exists) { + up_write(&_origins_lock); + DMERR("Origin does not exist but it should."); + return -EINVAL; + } + /* New origin */ o = new_o; @@ -347,7 +372,7 @@ static void unregister_snapshot(struct d o = __lookup_origin(s->origin->bdev); list_del(&s->list); - if (list_empty(&o->snapshots)) { + if (o && list_empty(&o->snapshots)) { list_del(&o->hash_list); kfree(o); } @@ -525,6 +550,31 @@ static int dm_add_exception(void *contex return 0; } +static struct dm_snapshot *find_snapshot_using_cow(struct dm_snapshot *snap) +{ + struct dm_snapshot *s, *handover_snap = NULL; + struct origin *o; + + down_read(&_origins_lock); + + o = __lookup_origin(snap->origin->bdev); + if (!o) + goto out; + + list_for_each_entry(s, &o->snapshots, list) { + if (!s->active || !bdev_equal(s->cow->bdev, snap->cow->bdev)) + continue; + + handover_snap = s; + break; + } + +out: + up_read(&_origins_lock); + + return handover_snap; +} + #define min_not_zero(l, r) (((l) == 0) ? (r) : (((r) == 0) ? (l) : min(l, r))) /* @@ -600,7 +650,7 @@ static int init_hash_tables(struct dm_sn */ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv) { - struct dm_snapshot *s; + struct dm_snapshot *s, *handover_snap; int i; int r = -EINVAL; char *origin_path, *cow_path; @@ -655,7 +705,9 @@ static int snapshot_ctr(struct dm_target s->valid = 1; s->active = 0; atomic_set(&s->pending_exceptions_count, 0); + s->is_handover_source = 0; init_rwsem(&s->lock); + INIT_LIST_HEAD(&s->list); spin_lock_init(&s->pe_lock); /* Allocate hash table for COW data */ @@ -690,6 +742,30 @@ static int snapshot_ctr(struct dm_target spin_lock_init(&s->tracked_chunk_lock); + /* Does snapshot need exceptions handed over to it? */ + handover_snap = find_snapshot_using_cow(s); + if (handover_snap) { + down_write(&handover_snap->lock); + if (handover_snap->is_handover_source) { + up_write(&handover_snap->lock); + ti->error = "Unable to handover snapshot to " + "two devices at once."; + goto bad_load_and_register; + } + handover_snap->is_handover_source = 1; + up_write(&handover_snap->lock); + } + + bio_list_init(&s->queued_bios); + INIT_WORK(&s->queued_bios_work, flush_queued_bios); + + ti->private = s; + ti->num_flush_requests = 1; + + if (handover_snap) + /* defer register_snapshot until after handover_exceptions */ + return 0; + /* Metadata must only be loaded into one table at once */ r = s->store->type->read_metadata(s->store, dm_add_exception, (void *)s); @@ -701,26 +777,20 @@ static int snapshot_ctr(struct dm_target DMWARN("Snapshot is marked invalid."); } - bio_list_init(&s->queued_bios); - INIT_WORK(&s->queued_bios_work, flush_queued_bios); - if (!s->store->chunk_size) { ti->error = "Chunk size not set"; goto bad_load_and_register; } + ti->split_io = s->store->chunk_size; /* Add snapshot to the list of snapshots for this origin */ /* Exceptions aren't triggered till snapshot_resume() is called */ - if (register_snapshot(s)) { + if (register_snapshot(s, 0)) { r = -EINVAL; ti->error = "Cannot register snapshot origin"; goto bad_load_and_register; } - ti->private = s; - ti->split_io = s->store->chunk_size; - ti->num_flush_requests = 1; - return 0; bad_load_and_register: @@ -761,15 +831,71 @@ static void __free_exceptions(struct dm_ dm_exception_table_exit(&s->complete, exception_cache); } +static void handover_exceptions(struct dm_snapshot *snap_src, + struct dm_snapshot *snap_dest) +{ + union { + struct dm_exception_table table_swap; + struct dm_exception_store *store_swap; + } u; + + BUG_ON((snap_src->is_handover_source != 1) || + (snap_dest->is_handover_source != 0)); + + /* swap exceptions tables and stores */ + u.table_swap = snap_dest->complete; + snap_dest->complete = snap_src->complete; + snap_src->complete = u.table_swap; + u.store_swap = snap_dest->store; + snap_dest->store = snap_src->store; + snap_src->store = u.store_swap; + + snap_dest->store->snap = snap_dest; + snap_src->store->snap = snap_src; + + /* reset split_io to store's chunk_size */ + if (snap_dest->ti->split_io != snap_dest->store->chunk_size) + snap_dest->ti->split_io = snap_dest->store->chunk_size; + + /* transfer 'valid' state, mark snap_src snapshot invalid */ + snap_dest->valid = snap_src->valid; + snap_src->valid = 0; + + /* mark snap_src as inactive */ + snap_src->active = 0; + + snap_src->is_handover_source = 0; +} + static void snapshot_dtr(struct dm_target *ti) { #ifdef CONFIG_DM_DEBUG int i; #endif struct dm_snapshot *s = ti->private; + struct dm_snapshot *snap_src = NULL; flush_workqueue(ksnapd); + /* Check if exception handover must be cancelled */ + snap_src = find_snapshot_using_cow(s); + if (snap_src) { + down_write(&snap_src->lock); + if (!snap_src->is_handover_source) { + up_write(&snap_src->lock); + goto normal_snapshot; + } + if (s == snap_src) { + DMERR("Unable to handover exceptions to another " + "snapshot from dtr, cancelling handover."); + snap_src->valid = 0; + } + /* allow table_clear to cancel handover */ + snap_src->is_handover_source = 0; + up_write(&snap_src->lock); + } + +normal_snapshot: /* Prevent further origin writes from using this snapshot. */ /* After this returns there can be no new kcopyd jobs. */ unregister_snapshot(s); @@ -1173,9 +1299,69 @@ static int snapshot_end_io(struct dm_tar return 0; } +static int snapshot_preresume(struct dm_target *ti) +{ + int r = 0; + struct dm_snapshot *s = ti->private; + struct dm_snapshot *snap_src = NULL; + + snap_src = find_snapshot_using_cow(s); + if (snap_src) { + down_write(&snap_src->lock); + if (!snap_src->is_handover_source) { + up_write(&snap_src->lock); + goto normal_snapshot; + } + up_write(&snap_src->lock); + if (s == snap_src) { + /* handover source must not resume before destination */ + DMERR("Unable to handover exceptions on " + "resume of source snapshot.\n" + "Deferring handover until destination " + "snapshot is resumed."); + r = -EINVAL; + } else { + /* if handover-destination, source must be suspended */ + if (!dm_table_md_suspended(snap_src->ti->table)) { + DMERR("Unable to accept exceptions from a " + "snapshot that is not suspended."); + r = -EINVAL; + } + } + } + +normal_snapshot: + return r; +} + static void snapshot_resume(struct dm_target *ti) { struct dm_snapshot *s = ti->private; + struct dm_snapshot *snap_src = NULL; + struct dm_snapshot *snap_dest = NULL; + + /* Check if snapshot needs exceptions handed over to it */ + snap_src = find_snapshot_using_cow(s); + if (snap_src) { + down_write_nested(&snap_src->lock, SINGLE_DEPTH_NESTING); + if (snap_src->is_handover_source) { + BUG_ON(s == snap_src); + snap_dest = s; + down_write(&snap_dest->lock); + /* Get exception store from another snapshot */ + handover_exceptions(snap_src, snap_dest); + up_write(&snap_dest->lock); + } + up_write(&snap_src->lock); + + if (snap_dest && register_snapshot(snap_dest, 1)) { + DMERR("Unable to register snapshot " + "after exception handover."); + down_write(&snap_dest->lock); + snap_dest->valid = 0; + up_write(&snap_dest->lock); + } + } down_write(&s->lock); s->active = 1; @@ -1485,12 +1671,13 @@ static struct target_type origin_target static struct target_type snapshot_target = { .name = "snapshot", - .version = {1, 8, 0}, + .version = {1, 9, 0}, .module = THIS_MODULE, .ctr = snapshot_ctr, .dtr = snapshot_dtr, .map = snapshot_map, .end_io = snapshot_end_io, + .preresume = snapshot_preresume, .resume = snapshot_resume, .status = snapshot_status, .iterate_devices = snapshot_iterate_devices, -- dm-devel mailing list dm-devel@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/dm-devel