summaryrefslogtreecommitdiff
path: root/drivers/md/dm-snap.c
diff options
context:
space:
mode:
authorMikulas Patocka <mpatocka@redhat.com>2009-10-16 23:18:14 +0100
committerAlasdair G Kergon <agk@redhat.com>2009-10-16 23:18:14 +0100
commit6d45d93ead319423099b82a4efd775bc0f159121 (patch)
tree6af9a92a7e9ef38aac856233d7721e3dfaefbd3e /drivers/md/dm-snap.c
parent012abeea669ea49636cf952d13298bb68654146a (diff)
downloadkernel-common-6d45d93ead319423099b82a4efd775bc0f159121.tar.gz
kernel-common-6d45d93ead319423099b82a4efd775bc0f159121.tar.bz2
kernel-common-6d45d93ead319423099b82a4efd775bc0f159121.zip
dm snapshot: sort by chunk size to fix race
Avoid a race causing corruption when snapshots of the same origin have different chunk sizes by sorting the internal list of snapshots by chunk size, largest first. https://bugzilla.redhat.com/show_bug.cgi?id=182659 For example, let's have two snapshots with different chunk sizes. The first snapshot (1) has small chunk size and the second snapshot (2) has large chunk size. Let's have chunks A, B, C in these snapshots: snapshot1: ====A==== ====B==== snapshot2: ==========C========== (Chunk size is a power of 2. Chunks are aligned.) A write to the origin at a position within A and C comes along. It triggers reallocation of A, then reallocation of C and links them together using A as the 'primary' exception. Then another write to the origin comes along at a position within B and C. It creates pending exception for B. C already has a reallocation in progress and it already has a primary exception (A), so nothing is done to it: B and C are not linked. If the reallocation of B finishes before the reallocation of C, because there is no link with the pending exception for C it does not know to wait for it and, the second write is dispatched to the origin and causes data corruption in the chunk C in snapshot2. To avoid this situation, we maintain snapshots sorted in descending order of chunk size. This leads to a guaranteed ordering on the links between the pending exceptions and avoids the problem explained above - both A and B now get linked to C. Cc: stable@kernel.org Signed-off-by: Mikulas Patocka <mpatocka@redhat.com> Signed-off-by: Alasdair G Kergon <agk@redhat.com>
Diffstat (limited to 'drivers/md/dm-snap.c')
-rw-r--r--drivers/md/dm-snap.c7
1 files changed, 6 insertions, 1 deletions
diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c
index 57f1bf7f3b7a..3a53a5a9bec8 100644
--- a/drivers/md/dm-snap.c
+++ b/drivers/md/dm-snap.c
@@ -296,6 +296,7 @@ static void __insert_origin(struct origin *o)
*/
static int register_snapshot(struct dm_snapshot *snap)
{
+ struct dm_snapshot *l;
struct origin *o, *new_o;
struct block_device *bdev = snap->origin->bdev;
@@ -319,7 +320,11 @@ static int register_snapshot(struct dm_snapshot *snap)
__insert_origin(o);
}
- list_add_tail(&snap->list, &o->snapshots);
+ /* Sort the list according to chunk size, largest-first smallest-last */
+ list_for_each_entry(l, &o->snapshots, list)
+ if (l->store->chunk_size < snap->store->chunk_size)
+ break;
+ list_add_tail(&snap->list, &l->list);
up_write(&_origins_lock);
return 0;