From: Mikulas Patocka 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.) Signed-off-by: Mikulas Patocka Signed-off-by: Mike Snitzer --- drivers/md/dm-snap.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 1 deletion(-) Index: linux-2.6.32-rc6/drivers/md/dm-snap.c =================================================================== --- linux-2.6.32-rc6.orig/drivers/md/dm-snap.c +++ linux-2.6.32-rc6/drivers/md/dm-snap.c @@ -75,6 +75,27 @@ struct dm_snapshot { /* Whether or not owning mapped_device is suspended */ int suspended; + /* + * 'handover' denotes exception store will be handed over from + * another snapshot with the same cow block device (as identified + * with __find_snapshot_using_cow). + * + * 'handover' is set during a new snapshot's constructor if it finds + * a single old snapshot is using the same cow block device as it. + * Handover operation is performed, and 'handover' is cleared, + * when one of the following occurs: + * - old snapshot, that is handing over, is destructed + * - old snapshot, that is handing over, is resumed + * - new snapshot, that is accepting the handover, is resumed + */ + int is_handover_destination; + + /* + * reference to the other snapshot that will participate in the + * exception store handover; new references old, old references new + */ + struct dm_snapshot *handover_snap; + mempool_t *pending_pool; atomic_t pending_exceptions_count; @@ -528,6 +549,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 == snap || !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))) /* @@ -599,11 +645,37 @@ static int init_hash_tables(struct dm_sn } /* + * Reserve snap_source for handover to snap_dest. + */ +static int link_snapshots_for_handover(struct dm_snapshot *snap_src, + struct dm_snapshot *snap_dest) +{ + int r = -EINVAL; + + down_write(&snap_src->lock); + + /* Another handover already set? */ + if (snap_src->handover_snap) + goto out; + + snap_src->handover_snap = snap_dest; + + snap_dest->handover_snap = snap_src; + snap_dest->is_handover_destination = 1; + + r = 0; + +out: + up_write(&snap_src->lock); + return r; +} + +/* * Construct a snapshot mapping:

*/ 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; @@ -659,6 +731,8 @@ static int snapshot_ctr(struct dm_target s->active = 0; s->suspended = 0; atomic_set(&s->pending_exceptions_count, 0); + s->is_handover_destination = 0; + s->handover_snap = NULL; init_rwsem(&s->lock); spin_lock_init(&s->pe_lock); @@ -694,6 +768,17 @@ static int snapshot_ctr(struct dm_target spin_lock_init(&s->tracked_chunk_lock); + /* Does snapshot need exceptions handing over to it? */ + handover_snap = find_snapshot_using_cow(s); + if (handover_snap) { + r = link_snapshots_for_handover(handover_snap, s); + if (r) { + ti->error = "Unable to handover snapshot to " + "two devices at once."; + goto bad_load_and_register; + } + } + /* Metadata must only be loaded into one table at once */ r = s->store->type->read_metadata(s->store, dm_add_exception, (void *)s); @@ -765,15 +850,62 @@ static void __free_exceptions(struct dm_ dm_exception_table_exit(&s->complete, exception_cache); } +static void handover_exceptions(struct dm_snapshot *old, + struct dm_snapshot *new) +{ + union { + struct dm_exception_table table_swap; + struct dm_exception_store *store_swap; + } u; + + BUG_ON((old->handover_snap != new) || + (new->handover_snap != old)); + BUG_ON((old->is_handover_destination != 0) || + (new->is_handover_destination != 1)); + BUG_ON(!old->suspended); + + u.table_swap = new->complete; + new->complete = old->complete; + old->complete = u.table_swap; + u.store_swap = new->store; + new->store = old->store; + old->store = u.store_swap; + + new->store->snap = new; + old->store->snap = old; + + /* Mark old snapshot invalid and inactive */ + old->valid = 0; + old->active = 0; + + /* Reset handover_snap references */ + old->handover_snap = NULL; + new->handover_snap = NULL; + + new->is_handover_destination = 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 *new_snap; flush_workqueue(ksnapd); + /* This snapshot may need to handover its exception store */ + down_write(&s->lock); + if (s->handover_snap) { + new_snap = s->handover_snap; + + down_write_nested(&new_snap->lock, SINGLE_DEPTH_NESTING); + handover_exceptions(s, new_snap); + up_write(&new_snap->lock); + } + up_write(&s->lock); + /* Prevent further origin writes from using this snapshot. */ /* After this returns there can be no new kcopyd jobs. */ unregister_snapshot(s); @@ -1189,8 +1321,31 @@ static void snapshot_presuspend(struct d static void snapshot_resume(struct dm_target *ti) { struct dm_snapshot *s = ti->private; + struct dm_snapshot *old_snap, *new_snap, *lock_snap; down_write(&s->lock); + if (s->handover_snap) { + /* + * Initially assumes this snapshot will get + * exception store from another snapshot + */ + old_snap = s->handover_snap; + new_snap = s; + lock_snap = old_snap; + + if (!s->is_handover_destination) { + /* Handover exceptions to another snapshot */ + old_snap = s; + new_snap = s->handover_snap; + lock_snap = new_snap; + } + down_write_nested(&lock_snap->lock, + SINGLE_DEPTH_NESTING); + handover_exceptions(old_snap, new_snap); + up_write(&lock_snap->lock); + } + /* An incomplete exception handover is not allowed */ + BUG_ON(s->handover_snap); s->active = 1; s->suspended = 0; up_write(&s->lock);