diff options
Diffstat (limited to 'drivers/md/dm-snap.c')
-rw-r--r-- | drivers/md/dm-snap.c | 239 |
1 files changed, 233 insertions, 6 deletions
diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index 7ddee7c0c518..dc2412e6c5cf 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c | |||
@@ -106,8 +106,20 @@ struct dm_snapshot { | |||
106 | mempool_t *tracked_chunk_pool; | 106 | mempool_t *tracked_chunk_pool; |
107 | spinlock_t tracked_chunk_lock; | 107 | spinlock_t tracked_chunk_lock; |
108 | struct hlist_head tracked_chunk_hash[DM_TRACKED_CHUNK_HASH_SIZE]; | 108 | struct hlist_head tracked_chunk_hash[DM_TRACKED_CHUNK_HASH_SIZE]; |
109 | |||
110 | /* Wait for events based on state_bits */ | ||
111 | unsigned long state_bits; | ||
109 | }; | 112 | }; |
110 | 113 | ||
114 | /* | ||
115 | * state_bits: | ||
116 | * RUNNING_MERGE - Merge operation is in progress. | ||
117 | * SHUTDOWN_MERGE - Set to signal that merge needs to be stopped; | ||
118 | * cleared afterwards. | ||
119 | */ | ||
120 | #define RUNNING_MERGE 0 | ||
121 | #define SHUTDOWN_MERGE 1 | ||
122 | |||
111 | struct dm_dev *dm_snap_cow(struct dm_snapshot *s) | 123 | struct dm_dev *dm_snap_cow(struct dm_snapshot *s) |
112 | { | 124 | { |
113 | return s->cow; | 125 | return s->cow; |
@@ -386,6 +398,13 @@ static int __validate_exception_handover(struct dm_snapshot *snap) | |||
386 | return -EINVAL; | 398 | return -EINVAL; |
387 | } | 399 | } |
388 | 400 | ||
401 | if (!snap_src->store->type->prepare_merge || | ||
402 | !snap_src->store->type->commit_merge) { | ||
403 | snap->ti->error = "Snapshot exception store does not " | ||
404 | "support snapshot-merge."; | ||
405 | return -EINVAL; | ||
406 | } | ||
407 | |||
389 | return 1; | 408 | return 1; |
390 | } | 409 | } |
391 | 410 | ||
@@ -721,6 +740,178 @@ static int init_hash_tables(struct dm_snapshot *s) | |||
721 | return 0; | 740 | return 0; |
722 | } | 741 | } |
723 | 742 | ||
743 | static void merge_shutdown(struct dm_snapshot *s) | ||
744 | { | ||
745 | clear_bit_unlock(RUNNING_MERGE, &s->state_bits); | ||
746 | smp_mb__after_clear_bit(); | ||
747 | wake_up_bit(&s->state_bits, RUNNING_MERGE); | ||
748 | } | ||
749 | |||
750 | /* | ||
751 | * Remove one chunk from the index of completed exceptions. | ||
752 | */ | ||
753 | static int __remove_single_exception_chunk(struct dm_snapshot *s, | ||
754 | chunk_t old_chunk) | ||
755 | { | ||
756 | struct dm_exception *e; | ||
757 | |||
758 | /* FIXME: interlock writes to this chunk */ | ||
759 | |||
760 | e = dm_lookup_exception(&s->complete, old_chunk); | ||
761 | if (!e) { | ||
762 | DMERR("Corruption detected: exception for block %llu is " | ||
763 | "on disk but not in memory", | ||
764 | (unsigned long long)old_chunk); | ||
765 | return -EINVAL; | ||
766 | } | ||
767 | |||
768 | /* | ||
769 | * If this is the only chunk using this exception, remove exception. | ||
770 | */ | ||
771 | if (!dm_consecutive_chunk_count(e)) { | ||
772 | dm_remove_exception(e); | ||
773 | free_completed_exception(e); | ||
774 | return 0; | ||
775 | } | ||
776 | |||
777 | /* | ||
778 | * The chunk may be either at the beginning or the end of a | ||
779 | * group of consecutive chunks - never in the middle. We are | ||
780 | * removing chunks in the opposite order to that in which they | ||
781 | * were added, so this should always be true. | ||
782 | * Decrement the consecutive chunk counter and adjust the | ||
783 | * starting point if necessary. | ||
784 | */ | ||
785 | if (old_chunk == e->old_chunk) { | ||
786 | e->old_chunk++; | ||
787 | e->new_chunk++; | ||
788 | } else if (old_chunk != e->old_chunk + | ||
789 | dm_consecutive_chunk_count(e)) { | ||
790 | DMERR("Attempt to merge block %llu from the " | ||
791 | "middle of a chunk range [%llu - %llu]", | ||
792 | (unsigned long long)old_chunk, | ||
793 | (unsigned long long)e->old_chunk, | ||
794 | (unsigned long long) | ||
795 | e->old_chunk + dm_consecutive_chunk_count(e)); | ||
796 | return -EINVAL; | ||
797 | } | ||
798 | |||
799 | dm_consecutive_chunk_count_dec(e); | ||
800 | |||
801 | return 0; | ||
802 | } | ||
803 | |||
804 | static int remove_single_exception_chunk(struct dm_snapshot *s, | ||
805 | chunk_t old_chunk) | ||
806 | { | ||
807 | int r = 0; | ||
808 | |||
809 | down_write(&s->lock); | ||
810 | r = __remove_single_exception_chunk(s, old_chunk); | ||
811 | up_write(&s->lock); | ||
812 | |||
813 | return r; | ||
814 | } | ||
815 | |||
816 | static void merge_callback(int read_err, unsigned long write_err, | ||
817 | void *context); | ||
818 | |||
819 | static void snapshot_merge_next_chunks(struct dm_snapshot *s) | ||
820 | { | ||
821 | int r; | ||
822 | chunk_t old_chunk, new_chunk; | ||
823 | struct dm_io_region src, dest; | ||
824 | |||
825 | BUG_ON(!test_bit(RUNNING_MERGE, &s->state_bits)); | ||
826 | if (unlikely(test_bit(SHUTDOWN_MERGE, &s->state_bits))) | ||
827 | goto shut; | ||
828 | |||
829 | /* | ||
830 | * valid flag never changes during merge, so no lock required. | ||
831 | */ | ||
832 | if (!s->valid) { | ||
833 | DMERR("Snapshot is invalid: can't merge"); | ||
834 | goto shut; | ||
835 | } | ||
836 | |||
837 | r = s->store->type->prepare_merge(s->store, &old_chunk, &new_chunk); | ||
838 | if (r <= 0) { | ||
839 | if (r < 0) | ||
840 | DMERR("Read error in exception store: " | ||
841 | "shutting down merge"); | ||
842 | goto shut; | ||
843 | } | ||
844 | |||
845 | /* TODO: use larger I/O size once we verify that kcopyd handles it */ | ||
846 | |||
847 | if (remove_single_exception_chunk(s, old_chunk) < 0) | ||
848 | goto shut; | ||
849 | |||
850 | dest.bdev = s->origin->bdev; | ||
851 | dest.sector = chunk_to_sector(s->store, old_chunk); | ||
852 | dest.count = min((sector_t)s->store->chunk_size, | ||
853 | get_dev_size(dest.bdev) - dest.sector); | ||
854 | |||
855 | src.bdev = s->cow->bdev; | ||
856 | src.sector = chunk_to_sector(s->store, new_chunk); | ||
857 | src.count = dest.count; | ||
858 | |||
859 | dm_kcopyd_copy(s->kcopyd_client, &src, 1, &dest, 0, merge_callback, s); | ||
860 | return; | ||
861 | |||
862 | shut: | ||
863 | merge_shutdown(s); | ||
864 | } | ||
865 | |||
866 | static void merge_callback(int read_err, unsigned long write_err, void *context) | ||
867 | { | ||
868 | struct dm_snapshot *s = context; | ||
869 | |||
870 | if (read_err || write_err) { | ||
871 | if (read_err) | ||
872 | DMERR("Read error: shutting down merge."); | ||
873 | else | ||
874 | DMERR("Write error: shutting down merge."); | ||
875 | goto shut; | ||
876 | } | ||
877 | |||
878 | if (s->store->type->commit_merge(s->store, 1) < 0) { | ||
879 | DMERR("Write error in exception store: shutting down merge"); | ||
880 | goto shut; | ||
881 | } | ||
882 | |||
883 | snapshot_merge_next_chunks(s); | ||
884 | |||
885 | return; | ||
886 | |||
887 | shut: | ||
888 | merge_shutdown(s); | ||
889 | } | ||
890 | |||
891 | static void start_merge(struct dm_snapshot *s) | ||
892 | { | ||
893 | if (!test_and_set_bit(RUNNING_MERGE, &s->state_bits)) | ||
894 | snapshot_merge_next_chunks(s); | ||
895 | } | ||
896 | |||
897 | static int wait_schedule(void *ptr) | ||
898 | { | ||
899 | schedule(); | ||
900 | |||
901 | return 0; | ||
902 | } | ||
903 | |||
904 | /* | ||
905 | * Stop the merging process and wait until it finishes. | ||
906 | */ | ||
907 | static void stop_merge(struct dm_snapshot *s) | ||
908 | { | ||
909 | set_bit(SHUTDOWN_MERGE, &s->state_bits); | ||
910 | wait_on_bit(&s->state_bits, RUNNING_MERGE, wait_schedule, | ||
911 | TASK_UNINTERRUPTIBLE); | ||
912 | clear_bit(SHUTDOWN_MERGE, &s->state_bits); | ||
913 | } | ||
914 | |||
724 | /* | 915 | /* |
725 | * Construct a snapshot mapping: <origin_dev> <COW-dev> <p/n> <chunk-size> | 916 | * Construct a snapshot mapping: <origin_dev> <COW-dev> <p/n> <chunk-size> |
726 | */ | 917 | */ |
@@ -791,6 +982,7 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv) | |||
791 | init_rwsem(&s->lock); | 982 | init_rwsem(&s->lock); |
792 | INIT_LIST_HEAD(&s->list); | 983 | INIT_LIST_HEAD(&s->list); |
793 | spin_lock_init(&s->pe_lock); | 984 | spin_lock_init(&s->pe_lock); |
985 | s->state_bits = 0; | ||
794 | 986 | ||
795 | /* Allocate hash table for COW data */ | 987 | /* Allocate hash table for COW data */ |
796 | if (init_hash_tables(s)) { | 988 | if (init_hash_tables(s)) { |
@@ -963,6 +1155,9 @@ static void snapshot_dtr(struct dm_target *ti) | |||
963 | } | 1155 | } |
964 | up_read(&_origins_lock); | 1156 | up_read(&_origins_lock); |
965 | 1157 | ||
1158 | if (dm_target_is_snapshot_merge(ti)) | ||
1159 | stop_merge(s); | ||
1160 | |||
966 | /* Prevent further origin writes from using this snapshot. */ | 1161 | /* Prevent further origin writes from using this snapshot. */ |
967 | /* After this returns there can be no new kcopyd jobs. */ | 1162 | /* After this returns there can be no new kcopyd jobs. */ |
968 | unregister_snapshot(s); | 1163 | unregister_snapshot(s); |
@@ -1404,6 +1599,13 @@ static int snapshot_end_io(struct dm_target *ti, struct bio *bio, | |||
1404 | return 0; | 1599 | return 0; |
1405 | } | 1600 | } |
1406 | 1601 | ||
1602 | static void snapshot_merge_presuspend(struct dm_target *ti) | ||
1603 | { | ||
1604 | struct dm_snapshot *s = ti->private; | ||
1605 | |||
1606 | stop_merge(s); | ||
1607 | } | ||
1608 | |||
1407 | static void snapshot_postsuspend(struct dm_target *ti) | 1609 | static void snapshot_postsuspend(struct dm_target *ti) |
1408 | { | 1610 | { |
1409 | struct dm_snapshot *s = ti->private; | 1611 | struct dm_snapshot *s = ti->private; |
@@ -1464,6 +1666,34 @@ static void snapshot_resume(struct dm_target *ti) | |||
1464 | up_write(&s->lock); | 1666 | up_write(&s->lock); |
1465 | } | 1667 | } |
1466 | 1668 | ||
1669 | static sector_t get_origin_minimum_chunksize(struct block_device *bdev) | ||
1670 | { | ||
1671 | sector_t min_chunksize; | ||
1672 | |||
1673 | down_read(&_origins_lock); | ||
1674 | min_chunksize = __minimum_chunk_size(__lookup_origin(bdev)); | ||
1675 | up_read(&_origins_lock); | ||
1676 | |||
1677 | return min_chunksize; | ||
1678 | } | ||
1679 | |||
1680 | static void snapshot_merge_resume(struct dm_target *ti) | ||
1681 | { | ||
1682 | struct dm_snapshot *s = ti->private; | ||
1683 | |||
1684 | /* | ||
1685 | * Handover exceptions from existing snapshot. | ||
1686 | */ | ||
1687 | snapshot_resume(ti); | ||
1688 | |||
1689 | /* | ||
1690 | * snapshot-merge acts as an origin, so set ti->split_io | ||
1691 | */ | ||
1692 | ti->split_io = get_origin_minimum_chunksize(s->origin->bdev); | ||
1693 | |||
1694 | start_merge(s); | ||
1695 | } | ||
1696 | |||
1467 | static int snapshot_status(struct dm_target *ti, status_type_t type, | 1697 | static int snapshot_status(struct dm_target *ti, status_type_t type, |
1468 | char *result, unsigned int maxlen) | 1698 | char *result, unsigned int maxlen) |
1469 | { | 1699 | { |
@@ -1722,11 +1952,7 @@ static void origin_resume(struct dm_target *ti) | |||
1722 | { | 1952 | { |
1723 | struct dm_dev *dev = ti->private; | 1953 | struct dm_dev *dev = ti->private; |
1724 | 1954 | ||
1725 | down_read(&_origins_lock); | 1955 | ti->split_io = get_origin_minimum_chunksize(dev->bdev); |
1726 | |||
1727 | ti->split_io = __minimum_chunk_size(__lookup_origin(dev->bdev)); | ||
1728 | |||
1729 | up_read(&_origins_lock); | ||
1730 | } | 1956 | } |
1731 | 1957 | ||
1732 | static int origin_status(struct dm_target *ti, status_type_t type, char *result, | 1958 | static int origin_status(struct dm_target *ti, status_type_t type, char *result, |
@@ -1790,9 +2016,10 @@ static struct target_type merge_target = { | |||
1790 | .dtr = snapshot_dtr, | 2016 | .dtr = snapshot_dtr, |
1791 | .map = snapshot_merge_map, | 2017 | .map = snapshot_merge_map, |
1792 | .end_io = snapshot_end_io, | 2018 | .end_io = snapshot_end_io, |
2019 | .presuspend = snapshot_merge_presuspend, | ||
1793 | .postsuspend = snapshot_postsuspend, | 2020 | .postsuspend = snapshot_postsuspend, |
1794 | .preresume = snapshot_preresume, | 2021 | .preresume = snapshot_preresume, |
1795 | .resume = snapshot_resume, | 2022 | .resume = snapshot_merge_resume, |
1796 | .status = snapshot_status, | 2023 | .status = snapshot_status, |
1797 | .iterate_devices = snapshot_iterate_devices, | 2024 | .iterate_devices = snapshot_iterate_devices, |
1798 | }; | 2025 | }; |