+/*
+ * Exception table hash sizes for pending exceptions
+ * The snapshot pending exception table holds pending exceptions
+ * that affect all snapshots in the share group (due to origin write).
+ * The snapshare pending exception table holds pending exceptions
+ * that affect just one snapshot in the share group (due to a
+ * write to one of the snapshots).
+ */
+#define DM_SNAPSHARE_HASH_SIZE 16
+#define DM_SNAPSHOT_HASH_SIZE 64
+
struct dm_snapshot {
struct rw_semaphore lock;
+ uint64_t shared_uuid;
+ struct list_head shared_list;
+
/*
* pe_lock protects all pending_exception operations and access
* as well as the snapshot_bios list.
*/
spinlock_t pe_lock;
- /* The on disk metadata handler */
- struct dm_exception_store *store;
-
struct dm_kcopyd_client *kcopyd_client;
/* Queue of snapshot writes for ksnapd to flush */
@@ -98,6 +107,7 @@ struct dm_snapshare {
struct list_head shared_list;
- /* Pointer back to snapshot context */
+ /*
+ * Pointer back to snapshot or snapshare context
+ * Only one of 'ss' or 'snap' may be populated.
+ */
struct dm_snapshot *snap;
+ struct dm_snapshare *ss;
/*
* 1 indicates the exception has already been sent to
@@ -296,13 +323,21 @@ static void __insert_origin(struct origi
}
/*
+ * register_snapshare
+ * @ss: snapshare - initialized and populated with 's'
+ *
* Make a note of the snapshot and its origin so we can look it
* up when the origin has a write on it.
+ *
+ * Returns: 0 on success, -Exxx on failure
*/
-static int register_snapshot(struct dm_snapshot *snap)
+static void dealloc_snapshot(struct dm_snapshot *s);
+static int register_snapshare(struct dm_snapshare *ss)
{
+ int found = 0;
struct origin *o, *new_o;
- struct block_device *bdev = snap->origin->bdev;
+ struct dm_snapshot *s;
+ struct block_device *bdev = ss->snap->origin->bdev;
new_o = kmalloc(sizeof(*new_o), GFP_KERNEL);
if (!new_o)
@@ -324,20 +359,61 @@ static int register_snapshot(struct dm_s
__insert_origin(o);
}
- list_add_tail(&snap->list, &o->snapshots);
+ if (!ss->snap->shared_uuid)
+ goto new_snapshot;
+
+ list_for_each_entry(s, &o->snapshots, list) {
+ down_write(&s->lock);
+ if (s->shared_uuid == ss->snap->shared_uuid) {
+ DMERR("Putting origin because it is shared");
+ dm_put_device(ss->store->ti, ss->snap->origin);
+
+ DMERR("Adding share to existing snapshot");
+ list_add(&ss->shared_list, &s->shared_list);
+
+ DMERR("Deallocating duplicate snapshot");
+ dealloc_snapshot(ss->snap);
+
+ ss->snap = s;
+
+ up_write(&s->lock);
+ found = 1;
+ break;
+ }
+ up_write(&s->lock);
+ }
+
+new_snapshot:
+ if (!found)
+ list_add_tail(&ss->snap->list, &o->snapshots);
+ /*
+ * Always origin lock, then snapshot lock
+ */
down_write(&_origins_lock);
- o = __lookup_origin(s->origin->bdev);
+ o = __lookup_origin(ss->snap->origin->bdev);
+
+ down_write(&ss->snap->lock);
+
+ /*
+ * Remove the snapshare, then if there are no
+ * more snapshares left, remove the snapshot
+ * from the origin's list
+ */
+ list_del(&ss->shared_list);
+
+ if (list_empty(&ss->snap->shared_list))
+ list_del(&ss->snap->list);
+ up_write(&ss->snap->lock);
/* Allocate hash table for pending COW data */
- s->pending = dm_exception_table_create(hash_size, 0,
+ s->pending = dm_exception_table_create(DM_SNAPSHOT_HASH_SIZE, 0,
alloc_pending_exception, s,
free_pending_exception, NULL);
if (!s->pending) {
@@ -539,11 +636,9 @@ static void dealloc_snapshot(struct dm_s
*/
static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
- sector_t hash_size, cow_dev_size, origin_dev_size, max_buckets;
struct dm_dev *origin;
struct dm_snapshare *ss;
struct dm_snapshot *s;
- int i;
int r = -EINVAL;
char *origin_path;
struct dm_exception_store *store;
@@ -566,6 +661,14 @@ static int snapshot_ctr(struct dm_target
INIT_LIST_HEAD(&ss->shared_list);
atomic_set(&ss->pending_exceptions_count, 0);
+ ss->pending = dm_exception_table_create(DM_SNAPSHARE_HASH_SIZE, 0,
+ alloc_snapshare_pending_exception, ss,
+ free_snapshare_pending_exception, NULL);
+ if (!ss->pending) {
+ ti->error = "Failed to allocate exception hash table";
+ goto bad_hash_table;
+ }
+
r = create_exception_store(ti, argc, argv, &args_used, &store);
if (r) {
ti->error = "Failed to create snapshot exception store";
@@ -583,25 +686,9 @@ static int snapshot_ctr(struct dm_target
}
/*
- * Calculate based on the size of the original volume or
- * the COW volume...
- */
- cow_dev_size = get_dev_size(store->cow->bdev);
- origin_dev_size = get_dev_size(origin->bdev);
- max_buckets = calc_max_buckets();
-
- hash_size = min(origin_dev_size, cow_dev_size) >> store->chunk_shift;
- hash_size = min(hash_size, max_buckets);
-
- hash_size = rounddown_pow_of_two(hash_size);
- hash_size >>= 3;
- if (hash_size < 64)
- hash_size = 64;
-
- /*
* Allocate the snapshot
*/
- s = alloc_snapshot(hash_size);
+ s = alloc_snapshot();
if (!s) {
r = -ENOMEM;
ti->error = "Failed to create snapshot structure";
@@ -609,11 +696,12 @@ static int snapshot_ctr(struct dm_target
}
ss->snap = s;
s->origin = origin;
- s->store = ss->store;
+ s->shared_uuid = store->shared_uuid;
+ list_add(&ss->shared_list, &s->shared_list);
/* Add snapshot to the list of snapshots for this origin */
/* Exceptions aren't triggered till snapshot_resume() is called */
- if (register_snapshot(s)) {
+ if (register_snapshare(ss)) {
r = -EINVAL;
ti->error = "Cannot register snapshot with origin";
goto bad_load_and_register;
@@ -634,6 +722,9 @@ bad_origin:
dm_exception_store_destroy(store);
/* Prevent further origin writes from using this snapshot. */
/* After this returns there can be no new kcopyd jobs. */
- unregister_snapshot(s);
+ unregister_snapshare(ss);
- while (atomic_read(&s->pending_exceptions_count))
+ while (atomic_read(&ss->pending_exceptions_count))
msleep(1);
/*
* Ensure instructions in mempool_destroy aren't reordered
@@ -672,6 +763,8 @@ static void snapshot_dtr(struct dm_targe
/* Hand over to kcopyd */
@@ -873,14 +979,17 @@ static struct dm_snap_pending_exception
__find_pending_exception(struct dm_snapshot *s, struct bio *bio,
struct dm_snapshare *ss)
{
+ int r;
struct dm_exception *e, *tmp_e;
struct dm_snap_pending_exception *pe;
- chunk_t chunk = sector_to_chunk(s->store, bio->bi_sector);
+ struct dm_exception_store *store = ss ? ss->store : get_first_store(s);
+ struct dm_exception_table *table = ss ? ss->pending : s->pending;
+ chunk_t chunk = sector_to_chunk(store, bio->bi_sector);
/*
* Is there a pending exception for this already ?
*/
- e = dm_lookup_exception(s->pending, chunk);
+ e = dm_lookup_exception(table, chunk);
if (e) {
/* cast the exception to a pending exception */
pe = container_of(e, struct dm_snap_pending_exception, e);
@@ -892,18 +1001,18 @@ __find_pending_exception(struct dm_snaps
* to hold the lock while we do this.
*/
up_write(&s->lock);
- tmp_e = dm_alloc_exception(s->pending);
+ tmp_e = dm_alloc_exception(table);
pe = container_of(tmp_e, struct dm_snap_pending_exception, e);
down_write(&s->lock);
r = DM_MAPIO_SUBMITTED;
@@ -1112,13 +1222,38 @@ static int snapshot_message(struct dm_ta
return r;
}
+static int is_completely_remapped(struct dm_snapshot *s, chunk_t chunk)
+{
+ int r;
+ struct dm_snapshare *ss;
+
+ list_for_each_entry(ss, &s->shared_list, shared_list) {
+ r = ss->store->type->lookup_exception(ss->store, chunk,
+ NULL, 0);
+ switch (r) {
+ case 0:
+ continue;
+ case -ENOENT:
+ return 0;
+ case -EWOULDBLOCK:
+ DMERR("Unable to handle blocking exception stores");
+ BUG();
+ default:
+ DMERR("Invalid return from exception store lookup");
+ BUG();
+ }
+ }
+ return 1;
+}
+
/*-----------------------------------------------------------------
* Origin methods
*---------------------------------------------------------------*/
static int __origin_write(struct list_head *snapshots, struct bio *bio)
{
- int rtn, r = DM_MAPIO_REMAPPED, first = 0;
+ int r = DM_MAPIO_REMAPPED, first = 0;
struct dm_snapshot *snap;
+ struct dm_exception_store *store;
struct dm_snap_pending_exception *pe, *next_pe, *primary_pe = NULL;
chunk_t chunk;
LIST_HEAD(pe_queue);
@@ -1132,36 +1267,28 @@ static int __origin_write(struct list_he
if (!snap->valid || !snap->active)
goto next_snapshot;
+ store = get_first_store(snap);
+
/* Nothing to do if writing beyond end of snapshot */
- if (bio->bi_sector >= dm_table_get_size(snap->store->ti->table))
+ if (bio->bi_sector >= dm_table_get_size(store->ti->table))
goto next_snapshot;
/*
* Remember, different snapshots can have
* different chunk sizes.
*/
- chunk = sector_to_chunk(snap->store, bio->bi_sector);
+ chunk = sector_to_chunk(store, bio->bi_sector);
/*
- * Check exception table to see if block
- * is already remapped in this snapshot
- * and trigger an exception if not.
+ * Check exception table to see if block is already
+ * remapped in this snapshot and trigger an exception if not.
*
* ref_count is initialised to 1 so pending_complete()
* won't destroy the primary_pe while we're inside this loop.
*/
- rtn = snap->store->type->lookup_exception(snap->store, chunk,
- NULL, 0);
- if (!rtn)
+ if (is_completely_remapped(snap, chunk))
goto next_snapshot;
- /*
- * Could be -EWOULDBLOCK, but we don't handle that yet
- * and there are currently no exception store
- * implementations that would require us to.
- */
- BUG_ON(rtn != -ENOENT);
-
pe = __find_pending_exception(snap, bio, NULL);
if (!pe) {
__invalidate_snapshot(snap, -ENOMEM);
@@ -1299,15 +1426,18 @@ static void origin_resume(struct dm_targ
{
struct dm_dev *dev = ti->private;
struct dm_snapshot *snap;
+ struct dm_exception_store *store;
struct origin *o;
chunk_t chunk_size = 0;