dm snapshot: delay merging a chunk until writes to it complete
[safe/jmp/linux-2.6] / drivers / md / dm-snap.c
index 59d9ef6..bc52776 100644 (file)
@@ -106,8 +106,30 @@ struct dm_snapshot {
        mempool_t *tracked_chunk_pool;
        spinlock_t tracked_chunk_lock;
        struct hlist_head tracked_chunk_hash[DM_TRACKED_CHUNK_HASH_SIZE];
+
+       /* Wait for events based on state_bits */
+       unsigned long state_bits;
+
+       /* Range of chunks currently being merged. */
+       chunk_t first_merging_chunk;
+       int num_merging_chunks;
+
+       /*
+        * Incoming bios that overlap with chunks being merged must wait
+        * for them to be committed.
+        */
+       struct bio_list bios_queued_during_merge;
 };
 
+/*
+ * state_bits:
+ *   RUNNING_MERGE  - Merge operation is in progress.
+ *   SHUTDOWN_MERGE - Set to signal that merge needs to be stopped;
+ *                    cleared afterwards.
+ */
+#define RUNNING_MERGE          0
+#define SHUTDOWN_MERGE         1
+
 struct dm_dev *dm_snap_cow(struct dm_snapshot *s)
 {
        return s->cow;
@@ -300,8 +322,10 @@ static void __insert_origin(struct origin *o)
  * Returns number of snapshots registered using the supplied cow device, plus:
  * snap_src - a snapshot suitable for use as a source of exception handover
  * snap_dest - a snapshot capable of receiving exception handover.
+ * snap_merge - an existing snapshot-merge target linked to the same origin.
+ *   There can be at most one snapshot-merge target. The parameter is optional.
  *
- * Possible return values and states:
+ * Possible return values and states of snap_src and snap_dest.
  *   0: NULL, NULL  - first new snapshot
  *   1: snap_src, NULL - normal snapshot
  *   2: snap_src, snap_dest  - waiting for handover
@@ -310,7 +334,8 @@ static void __insert_origin(struct origin *o)
  */
 static int __find_snapshots_sharing_cow(struct dm_snapshot *snap,
                                        struct dm_snapshot **snap_src,
-                                       struct dm_snapshot **snap_dest)
+                                       struct dm_snapshot **snap_dest,
+                                       struct dm_snapshot **snap_merge)
 {
        struct dm_snapshot *s;
        struct origin *o;
@@ -322,6 +347,8 @@ static int __find_snapshots_sharing_cow(struct dm_snapshot *snap,
                goto out;
 
        list_for_each_entry(s, &o->snapshots, list) {
+               if (dm_target_is_snapshot_merge(s->ti) && snap_merge)
+                       *snap_merge = s;
                if (!bdev_equal(s->cow->bdev, snap->cow->bdev))
                        continue;
 
@@ -349,9 +376,11 @@ out:
 static int __validate_exception_handover(struct dm_snapshot *snap)
 {
        struct dm_snapshot *snap_src = NULL, *snap_dest = NULL;
+       struct dm_snapshot *snap_merge = NULL;
 
        /* Does snapshot need exceptions handed over to it? */
-       if ((__find_snapshots_sharing_cow(snap, &snap_src, &snap_dest) == 2) ||
+       if ((__find_snapshots_sharing_cow(snap, &snap_src, &snap_dest,
+                                         &snap_merge) == 2) ||
            snap_dest) {
                snap->ti->error = "Snapshot cow pairing for exception "
                                  "table handover failed";
@@ -365,6 +394,27 @@ static int __validate_exception_handover(struct dm_snapshot *snap)
        if (!snap_src)
                return 0;
 
+       /*
+        * Non-snapshot-merge handover?
+        */
+       if (!dm_target_is_snapshot_merge(snap->ti))
+               return 1;
+
+       /*
+        * Do not allow more than one merging snapshot.
+        */
+       if (snap_merge) {
+               snap->ti->error = "A snapshot is already merging.";
+               return -EINVAL;
+       }
+
+       if (!snap_src->store->type->prepare_merge ||
+           !snap_src->store->type->commit_merge) {
+               snap->ti->error = "Snapshot exception store does not "
+                                 "support snapshot-merge.";
+               return -EINVAL;
+       }
+
        return 1;
 }
 
@@ -700,6 +750,218 @@ static int init_hash_tables(struct dm_snapshot *s)
        return 0;
 }
 
+static void merge_shutdown(struct dm_snapshot *s)
+{
+       clear_bit_unlock(RUNNING_MERGE, &s->state_bits);
+       smp_mb__after_clear_bit();
+       wake_up_bit(&s->state_bits, RUNNING_MERGE);
+}
+
+static struct bio *__release_queued_bios_after_merge(struct dm_snapshot *s)
+{
+       s->first_merging_chunk = 0;
+       s->num_merging_chunks = 0;
+
+       return bio_list_get(&s->bios_queued_during_merge);
+}
+
+/*
+ * Remove one chunk from the index of completed exceptions.
+ */
+static int __remove_single_exception_chunk(struct dm_snapshot *s,
+                                          chunk_t old_chunk)
+{
+       struct dm_exception *e;
+
+       e = dm_lookup_exception(&s->complete, old_chunk);
+       if (!e) {
+               DMERR("Corruption detected: exception for block %llu is "
+                     "on disk but not in memory",
+                     (unsigned long long)old_chunk);
+               return -EINVAL;
+       }
+
+       /*
+        * If this is the only chunk using this exception, remove exception.
+        */
+       if (!dm_consecutive_chunk_count(e)) {
+               dm_remove_exception(e);
+               free_completed_exception(e);
+               return 0;
+       }
+
+       /*
+        * The chunk may be either at the beginning or the end of a
+        * group of consecutive chunks - never in the middle.  We are
+        * removing chunks in the opposite order to that in which they
+        * were added, so this should always be true.
+        * Decrement the consecutive chunk counter and adjust the
+        * starting point if necessary.
+        */
+       if (old_chunk == e->old_chunk) {
+               e->old_chunk++;
+               e->new_chunk++;
+       } else if (old_chunk != e->old_chunk +
+                  dm_consecutive_chunk_count(e)) {
+               DMERR("Attempt to merge block %llu from the "
+                     "middle of a chunk range [%llu - %llu]",
+                     (unsigned long long)old_chunk,
+                     (unsigned long long)e->old_chunk,
+                     (unsigned long long)
+                     e->old_chunk + dm_consecutive_chunk_count(e));
+               return -EINVAL;
+       }
+
+       dm_consecutive_chunk_count_dec(e);
+
+       return 0;
+}
+
+static void flush_bios(struct bio *bio);
+
+static int remove_single_exception_chunk(struct dm_snapshot *s)
+{
+       struct bio *b = NULL;
+       int r;
+       chunk_t old_chunk = s->first_merging_chunk + s->num_merging_chunks - 1;
+
+       down_write(&s->lock);
+
+       /*
+        * Process chunks (and associated exceptions) in reverse order
+        * so that dm_consecutive_chunk_count_dec() accounting works.
+        */
+       do {
+               r = __remove_single_exception_chunk(s, old_chunk);
+               if (r)
+                       goto out;
+       } while (old_chunk-- > s->first_merging_chunk);
+
+       b = __release_queued_bios_after_merge(s);
+
+out:
+       up_write(&s->lock);
+       if (b)
+               flush_bios(b);
+
+       return r;
+}
+
+static void merge_callback(int read_err, unsigned long write_err,
+                          void *context);
+
+static void snapshot_merge_next_chunks(struct dm_snapshot *s)
+{
+       int r;
+       chunk_t old_chunk, new_chunk;
+       struct dm_io_region src, dest;
+
+       BUG_ON(!test_bit(RUNNING_MERGE, &s->state_bits));
+       if (unlikely(test_bit(SHUTDOWN_MERGE, &s->state_bits)))
+               goto shut;
+
+       /*
+        * valid flag never changes during merge, so no lock required.
+        */
+       if (!s->valid) {
+               DMERR("Snapshot is invalid: can't merge");
+               goto shut;
+       }
+
+       r = s->store->type->prepare_merge(s->store, &old_chunk, &new_chunk);
+       if (r <= 0) {
+               if (r < 0)
+                       DMERR("Read error in exception store: "
+                             "shutting down merge");
+               goto shut;
+       }
+
+       /* TODO: use larger I/O size once we verify that kcopyd handles it */
+
+       dest.bdev = s->origin->bdev;
+       dest.sector = chunk_to_sector(s->store, old_chunk);
+       dest.count = min((sector_t)s->store->chunk_size,
+                        get_dev_size(dest.bdev) - dest.sector);
+
+       src.bdev = s->cow->bdev;
+       src.sector = chunk_to_sector(s->store, new_chunk);
+       src.count = dest.count;
+
+       down_write(&s->lock);
+       s->first_merging_chunk = old_chunk;
+       s->num_merging_chunks = 1;
+       up_write(&s->lock);
+
+       __check_for_conflicting_io(s, old_chunk);
+
+       dm_kcopyd_copy(s->kcopyd_client, &src, 1, &dest, 0, merge_callback, s);
+       return;
+
+shut:
+       merge_shutdown(s);
+}
+
+static void error_bios(struct bio *bio);
+
+static void merge_callback(int read_err, unsigned long write_err, void *context)
+{
+       struct dm_snapshot *s = context;
+       struct bio *b = NULL;
+
+       if (read_err || write_err) {
+               if (read_err)
+                       DMERR("Read error: shutting down merge.");
+               else
+                       DMERR("Write error: shutting down merge.");
+               goto shut;
+       }
+
+       if (s->store->type->commit_merge(s->store,
+                                        s->num_merging_chunks) < 0) {
+               DMERR("Write error in exception store: shutting down merge");
+               goto shut;
+       }
+
+       if (remove_single_exception_chunk(s) < 0)
+               goto shut;
+
+       snapshot_merge_next_chunks(s);
+
+       return;
+
+shut:
+       down_write(&s->lock);
+       b = __release_queued_bios_after_merge(s);
+       up_write(&s->lock);
+       error_bios(b);
+
+       merge_shutdown(s);
+}
+
+static void start_merge(struct dm_snapshot *s)
+{
+       if (!test_and_set_bit(RUNNING_MERGE, &s->state_bits))
+               snapshot_merge_next_chunks(s);
+}
+
+static int wait_schedule(void *ptr)
+{
+       schedule();
+
+       return 0;
+}
+
+/*
+ * Stop the merging process and wait until it finishes.
+ */
+static void stop_merge(struct dm_snapshot *s)
+{
+       set_bit(SHUTDOWN_MERGE, &s->state_bits);
+       wait_on_bit(&s->state_bits, RUNNING_MERGE, wait_schedule,
+                   TASK_UNINTERRUPTIBLE);
+       clear_bit(SHUTDOWN_MERGE, &s->state_bits);
+}
+
 /*
  * Construct a snapshot mapping: <origin_dev> <COW-dev> <p/n> <chunk-size>
  */
@@ -709,7 +971,8 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
        int i;
        int r = -EINVAL;
        char *origin_path, *cow_path;
-       unsigned args_used;
+       unsigned args_used, num_flush_requests = 1;
+       fmode_t origin_mode = FMODE_READ;
 
        if (argc != 4) {
                ti->error = "requires exactly 4 arguments";
@@ -717,6 +980,11 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
                goto bad;
        }
 
+       if (dm_target_is_snapshot_merge(ti)) {
+               num_flush_requests = 2;
+               origin_mode = FMODE_WRITE;
+       }
+
        origin_path = argv[0];
        argv++;
        argc--;
@@ -750,7 +1018,7 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
        argv += args_used;
        argc -= args_used;
 
-       r = dm_get_device(ti, origin_path, 0, ti->len, FMODE_READ, &s->origin);
+       r = dm_get_device(ti, origin_path, 0, ti->len, origin_mode, &s->origin);
        if (r) {
                ti->error = "Cannot get origin device";
                goto bad_origin;
@@ -764,6 +1032,10 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
        init_rwsem(&s->lock);
        INIT_LIST_HEAD(&s->list);
        spin_lock_init(&s->pe_lock);
+       s->state_bits = 0;
+       s->first_merging_chunk = 0;
+       s->num_merging_chunks = 0;
+       bio_list_init(&s->bios_queued_during_merge);
 
        /* Allocate hash table for COW data */
        if (init_hash_tables(s)) {
@@ -801,7 +1073,7 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
        INIT_WORK(&s->queued_bios_work, flush_queued_bios);
 
        ti->private = s;
-       ti->num_flush_requests = 1;
+       ti->num_flush_requests = num_flush_requests;
 
        /* Add snapshot to the list of snapshots for this origin */
        /* Exceptions aren't triggered till snapshot_resume() is called */
@@ -927,7 +1199,7 @@ static void snapshot_dtr(struct dm_target *ti)
 
        down_read(&_origins_lock);
        /* Check whether exception handover must be cancelled */
-       (void) __find_snapshots_sharing_cow(s, &snap_src, &snap_dest);
+       (void) __find_snapshots_sharing_cow(s, &snap_src, &snap_dest, NULL);
        if (snap_src && snap_dest && (s == snap_src)) {
                down_write(&snap_dest->lock);
                snap_dest->valid = 0;
@@ -936,6 +1208,9 @@ static void snapshot_dtr(struct dm_target *ti)
        }
        up_read(&_origins_lock);
 
+       if (dm_target_is_snapshot_merge(ti))
+               stop_merge(s);
+
        /* Prevent further origin writes from using this snapshot. */
        /* After this returns there can be no new kcopyd jobs. */
        unregister_snapshot(s);
@@ -1317,6 +1592,8 @@ static int snapshot_map(struct dm_target *ti, struct bio *bio,
  * For each chunk, if there is an existing exception, it is used to
  * redirect I/O to the cow device.  Otherwise I/O is sent to the origin,
  * which in turn might generate exceptions in other snapshots.
+ * If merging is currently taking place on the chunk in question, the
+ * I/O is deferred by adding it to s->bios_queued_during_merge.
  */
 static int snapshot_merge_map(struct dm_target *ti, struct bio *bio,
                              union map_info *map_context)
@@ -1326,9 +1603,18 @@ static int snapshot_merge_map(struct dm_target *ti, struct bio *bio,
        int r = DM_MAPIO_REMAPPED;
        chunk_t chunk;
 
+       if (unlikely(bio_empty_barrier(bio))) {
+               if (!map_context->flush_request)
+                       bio->bi_bdev = s->origin->bdev;
+               else
+                       bio->bi_bdev = s->cow->bdev;
+               map_context->ptr = NULL;
+               return DM_MAPIO_REMAPPED;
+       }
+
        chunk = sector_to_chunk(s->store, bio->bi_sector);
 
-       down_read(&s->lock);
+       down_write(&s->lock);
 
        /* Full snapshots are not usable */
        if (!s->valid) {
@@ -1339,19 +1625,33 @@ static int snapshot_merge_map(struct dm_target *ti, struct bio *bio,
        /* If the block is already remapped - use that */
        e = dm_lookup_exception(&s->complete, chunk);
        if (e) {
+               /* Queue writes overlapping with chunks being merged */
+               if (bio_rw(bio) == WRITE &&
+                   chunk >= s->first_merging_chunk &&
+                   chunk < (s->first_merging_chunk +
+                            s->num_merging_chunks)) {
+                       bio->bi_bdev = s->origin->bdev;
+                       bio_list_add(&s->bios_queued_during_merge, bio);
+                       r = DM_MAPIO_SUBMITTED;
+                       goto out_unlock;
+               }
+
                remap_exception(s, e, bio, chunk);
+
+               if (bio_rw(bio) == WRITE)
+                       map_context->ptr = track_chunk(s, chunk);
                goto out_unlock;
        }
 
        bio->bi_bdev = s->origin->bdev;
 
        if (bio_rw(bio) == WRITE) {
-               up_read(&s->lock);
+               up_write(&s->lock);
                return do_origin(s->origin, bio);
        }
 
 out_unlock:
-       up_read(&s->lock);
+       up_write(&s->lock);
 
        return r;
 }
@@ -1368,6 +1668,13 @@ static int snapshot_end_io(struct dm_target *ti, struct bio *bio,
        return 0;
 }
 
+static void snapshot_merge_presuspend(struct dm_target *ti)
+{
+       struct dm_snapshot *s = ti->private;
+
+       stop_merge(s);
+}
+
 static void snapshot_postsuspend(struct dm_target *ti)
 {
        struct dm_snapshot *s = ti->private;
@@ -1384,7 +1691,7 @@ static int snapshot_preresume(struct dm_target *ti)
        struct dm_snapshot *snap_src = NULL, *snap_dest = NULL;
 
        down_read(&_origins_lock);
-       (void) __find_snapshots_sharing_cow(s, &snap_src, &snap_dest);
+       (void) __find_snapshots_sharing_cow(s, &snap_src, &snap_dest, NULL);
        if (snap_src && snap_dest) {
                down_read(&snap_src->lock);
                if (s == snap_src) {
@@ -1409,7 +1716,7 @@ static void snapshot_resume(struct dm_target *ti)
        struct dm_snapshot *snap_src = NULL, *snap_dest = NULL;
 
        down_read(&_origins_lock);
-       (void) __find_snapshots_sharing_cow(s, &snap_src, &snap_dest);
+       (void) __find_snapshots_sharing_cow(s, &snap_src, &snap_dest, NULL);
        if (snap_src && snap_dest) {
                down_write(&snap_src->lock);
                down_write_nested(&snap_dest->lock, SINGLE_DEPTH_NESTING);
@@ -1428,6 +1735,34 @@ static void snapshot_resume(struct dm_target *ti)
        up_write(&s->lock);
 }
 
+static sector_t get_origin_minimum_chunksize(struct block_device *bdev)
+{
+       sector_t min_chunksize;
+
+       down_read(&_origins_lock);
+       min_chunksize = __minimum_chunk_size(__lookup_origin(bdev));
+       up_read(&_origins_lock);
+
+       return min_chunksize;
+}
+
+static void snapshot_merge_resume(struct dm_target *ti)
+{
+       struct dm_snapshot *s = ti->private;
+
+       /*
+        * Handover exceptions from existing snapshot.
+        */
+       snapshot_resume(ti);
+
+       /*
+        * snapshot-merge acts as an origin, so set ti->split_io
+        */
+       ti->split_io = get_origin_minimum_chunksize(s->origin->bdev);
+
+       start_merge(s);
+}
+
 static int snapshot_status(struct dm_target *ti, status_type_t type,
                           char *result, unsigned int maxlen)
 {
@@ -1686,11 +2021,7 @@ static void origin_resume(struct dm_target *ti)
 {
        struct dm_dev *dev = ti->private;
 
-       down_read(&_origins_lock);
-
-       ti->split_io = __minimum_chunk_size(__lookup_origin(dev->bdev));
-
-       up_read(&_origins_lock);
+       ti->split_io = get_origin_minimum_chunksize(dev->bdev);
 }
 
 static int origin_status(struct dm_target *ti, status_type_t type, char *result,
@@ -1754,9 +2085,10 @@ static struct target_type merge_target = {
        .dtr     = snapshot_dtr,
        .map     = snapshot_merge_map,
        .end_io  = snapshot_end_io,
+       .presuspend = snapshot_merge_presuspend,
        .postsuspend = snapshot_postsuspend,
        .preresume  = snapshot_preresume,
-       .resume  = snapshot_resume,
+       .resume  = snapshot_merge_resume,
        .status  = snapshot_status,
        .iterate_devices = snapshot_iterate_devices,
 };