New variables merge_write_interlock and merge_write_interlock_n determine the chunk number (on the origin device) and number of chunks that are being merged. Writes to this area are held on the queue merge_write_list. Signed-off-by: Mikulas Patocka --- drivers/md/dm-snap.c | 76 ++++++++++++++++++++++++++++++++++++++------------- drivers/md/dm-snap.h | 7 ++++ 2 files changed, 64 insertions(+), 19 deletions(-) Index: linux-2.6.26-rc8/drivers/md/dm-snap.c =================================================================== --- linux-2.6.26-rc8.orig/drivers/md/dm-snap.c 2008-07-01 20:12:06.000000000 +0200 +++ linux-2.6.26-rc8/drivers/md/dm-snap.c 2008-07-01 20:13:22.000000000 +0200 @@ -563,13 +563,15 @@ static int set_chunk_size(struct dm_snap return 0; } +static void flush_bios(struct bio *bio); +static void error_bios(struct bio *bio); + static void merge_callback(int read_err, unsigned long write_err, void *context); static void snapshot_merge_process(struct dm_snapshot *s) { int r; chunk_t old_chunk, new_chunk; - struct dm_snap_exception *e; struct dm_io_region src, dest; BUG_ON(!s->merge_running); @@ -594,22 +596,6 @@ static void snapshot_merge_process(struc /* TODO: use larger I/O size once we verify that kcopyd handles it */ - /* !!! FIXME: intelock writes to this chunk */ - down_write(&s->lock); - e = lookup_exception(&s->complete, old_chunk); - if (!e) { - DMERR("exception for block %Lu is on disk but not in memory", (unsigned long long)old_chunk); - up_write(&s->lock); - goto shut; - } - if (dm_consecutive_chunk_count(e)) { - dm_consecutive_chunk_count_dec(e); - } else { - remove_exception(e); - free_exception(e); - } - up_write(&s->lock); - dest.bdev = s->origin->bdev; dest.sector = chunk_to_sector(s, old_chunk); dest.count = min(s->chunk_size, get_dev_size(dest.bdev) - dest.sector); @@ -618,6 +604,13 @@ static void snapshot_merge_process(struc src.sector = chunk_to_sector(s, new_chunk); src.count = dest.count; + down_write(&s->lock); + s->merge_write_interlock = old_chunk; + s->merge_write_interlock_n = 1; + up_write(&s->lock); + + /* !!! FIXME: wait until writes to this chunk drain */ + dm_kcopyd_copy(s->kcopyd_client, &src, 1, &dest, 0, merge_callback, s); return; @@ -625,10 +618,25 @@ static void snapshot_merge_process(struc s->merge_running = 0; } +/* This function drops s->lock */ +static inline void release_write_interlock(struct dm_snapshot *s, int err) +{ + struct bio *b; + s->merge_write_interlock = 0; + s->merge_write_interlock_n = 0; + b = bio_list_get(&s->merge_write_list); + up_write(&s->lock); + if (!err) + flush_bios(b); + else + error_bios(b); +} + static void merge_callback(int read_err, unsigned long write_err, void *context) { int r; struct dm_snapshot *s = context; + struct dm_snap_exception *e; if (read_err || write_err) { if (read_err) @@ -644,10 +652,27 @@ static void merge_callback(int read_err, goto shut; } + down_write(&s->lock); + e = lookup_exception(&s->complete, s->merge_write_interlock); + if (!e) { + DMERR("exception for block %Lu is on disk but not in memory", (unsigned long long)s->merge_write_interlock); + up_write(&s->lock); + goto shut; + } + if (dm_consecutive_chunk_count(e)) { + dm_consecutive_chunk_count_dec(e); + } else { + remove_exception(e); + free_exception(e); + } + release_write_interlock(s, 0); + snapshot_merge_process(s); return; shut: + down_write(&s->lock); + release_write_interlock(s, 1); s->merge_running = 0; } @@ -741,6 +766,9 @@ static int snapshot_ctr(struct dm_target s->merge_shutdown = 0; init_rwsem(&s->lock); s->ti = ti; + s->merge_write_interlock = 0; + s->merge_write_interlock_n = 0; + bio_list_init(&s->merge_write_list); /* Allocate hash table for COW data */ if (init_hash_tables(s)) { @@ -1245,7 +1273,7 @@ static int snapshot_merge_map(struct dm_ chunk = sector_to_chunk(s, bio->bi_sector); - down_read(&s->lock); + down_write(&s->lock); /* Full snapshots are not usable */ if (!s->valid) { @@ -1256,6 +1284,16 @@ static int snapshot_merge_map(struct dm_ /* If the block is already remapped - use that */ e = lookup_exception(&s->complete, chunk); if (e) { + /* We are copying this area --- so don't write to it */ + if (bio_rw(bio) == WRITE && + chunk >= s->merge_write_interlock && + chunk < s->merge_write_interlock + s->merge_write_interlock_n) { + bio->bi_bdev = s->origin->bdev; + bio_list_add(&s->merge_write_list, bio); + + r = DM_MAPIO_SUBMITTED; + goto out_unlock; + } remap_exception(s, e, bio, chunk); goto out_unlock; } @@ -1263,7 +1301,7 @@ static int snapshot_merge_map(struct dm_ bio->bi_bdev = s->origin->bdev; out_unlock: - up_read(&s->lock); + up_write(&s->lock); return r; } Index: linux-2.6.26-rc8/drivers/md/dm-snap.h =================================================================== --- linux-2.6.26-rc8.orig/drivers/md/dm-snap.h 2008-07-01 20:12:06.000000000 +0200 +++ linux-2.6.26-rc8/drivers/md/dm-snap.h 2008-07-01 20:13:22.000000000 +0200 @@ -216,6 +216,13 @@ struct dm_snapshot { /* It is requested to shut down merging */ /* Cleared back to 0 when the merging is stopped */ int merge_shutdown; + + /* Merging this area --- block any writes */ + chunk_t merge_write_interlock; + int merge_write_interlock_n; + + /* A list of requests that were delayed because of racing with merge */ + struct bio_list merge_write_list; }; /*