Merge tag 'asoc-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound...
[cascardo/linux.git] / drivers / md / dm-thin-metadata.c
index 3e2907f..693e149 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011-2012 Red Hat, Inc.
  *
  * This file is released under the GPL.
  */
 #define THIN_METADATA_CACHE_SIZE 64
 #define SECTOR_TO_BLOCK_SHIFT 3
 
+/*
+ *  3 for btree insert +
+ *  2 for btree lookup used within space map
+ */
+#define THIN_MAX_CONCURRENT_LOCKS 5
+
 /* This should be plenty */
 #define SPACE_MAP_ROOT_SIZE 128
 
@@ -172,13 +178,20 @@ struct dm_pool_metadata {
 
        struct rw_semaphore root_lock;
        uint32_t time;
-       int need_commit;
        dm_block_t root;
        dm_block_t details_root;
        struct list_head thin_devices;
        uint64_t trans_id;
        unsigned long flags;
        sector_t data_block_size;
+       bool read_only:1;
+
+       /*
+        * Set if a transaction has to be aborted but the attempt to roll back
+        * to the previous (good) transaction failed.  The only pool metadata
+        * operation possible in this state is the closing of the device.
+        */
+       bool fail_io:1;
 };
 
 struct dm_thin_device {
@@ -187,7 +200,8 @@ struct dm_thin_device {
        dm_thin_id id;
 
        int open_count;
-       int changed;
+       bool changed:1;
+       bool aborted_with_changes:1;
        uint64_t mapped_blocks;
        uint64_t transaction_id;
        uint32_t creation_time;
@@ -338,7 +352,21 @@ static int subtree_equal(void *context, void *value1_le, void *value2_le)
 
 /*----------------------------------------------------------------*/
 
-static int superblock_all_zeroes(struct dm_block_manager *bm, int *result)
+static int superblock_lock_zero(struct dm_pool_metadata *pmd,
+                               struct dm_block **sblock)
+{
+       return dm_bm_write_lock_zero(pmd->bm, THIN_SUPERBLOCK_LOCATION,
+                                    &sb_validator, sblock);
+}
+
+static int superblock_lock(struct dm_pool_metadata *pmd,
+                          struct dm_block **sblock)
+{
+       return dm_bm_write_lock(pmd->bm, THIN_SUPERBLOCK_LOCATION,
+                               &sb_validator, sblock);
+}
+
+static int __superblock_all_zeroes(struct dm_block_manager *bm, int *result)
 {
        int r;
        unsigned i;
@@ -365,72 +393,9 @@ static int superblock_all_zeroes(struct dm_block_manager *bm, int *result)
        return dm_bm_unlock(b);
 }
 
-static int init_pmd(struct dm_pool_metadata *pmd,
-                   struct dm_block_manager *bm,
-                   dm_block_t nr_blocks, int create)
+static void __setup_btree_details(struct dm_pool_metadata *pmd)
 {
-       int r;
-       struct dm_space_map *sm, *data_sm;
-       struct dm_transaction_manager *tm;
-       struct dm_block *sblock;
-
-       if (create) {
-               r = dm_tm_create_with_sm(bm, THIN_SUPERBLOCK_LOCATION,
-                                        &sb_validator, &tm, &sm, &sblock);
-               if (r < 0) {
-                       DMERR("tm_create_with_sm failed");
-                       return r;
-               }
-
-               data_sm = dm_sm_disk_create(tm, nr_blocks);
-               if (IS_ERR(data_sm)) {
-                       DMERR("sm_disk_create failed");
-                       dm_tm_unlock(tm, sblock);
-                       r = PTR_ERR(data_sm);
-                       goto bad;
-               }
-       } else {
-               struct thin_disk_superblock *disk_super = NULL;
-               size_t space_map_root_offset =
-                       offsetof(struct thin_disk_superblock, metadata_space_map_root);
-
-               r = dm_tm_open_with_sm(bm, THIN_SUPERBLOCK_LOCATION,
-                                      &sb_validator, space_map_root_offset,
-                                      SPACE_MAP_ROOT_SIZE, &tm, &sm, &sblock);
-               if (r < 0) {
-                       DMERR("tm_open_with_sm failed");
-                       return r;
-               }
-
-               disk_super = dm_block_data(sblock);
-               data_sm = dm_sm_disk_open(tm, disk_super->data_space_map_root,
-                                         sizeof(disk_super->data_space_map_root));
-               if (IS_ERR(data_sm)) {
-                       DMERR("sm_disk_open failed");
-                       r = PTR_ERR(data_sm);
-                       goto bad;
-               }
-       }
-
-
-       r = dm_tm_unlock(tm, sblock);
-       if (r < 0) {
-               DMERR("couldn't unlock superblock");
-               goto bad_data_sm;
-       }
-
-       pmd->bm = bm;
-       pmd->metadata_sm = sm;
-       pmd->data_sm = data_sm;
-       pmd->tm = tm;
-       pmd->nb_tm = dm_tm_create_non_blocking_clone(tm);
-       if (!pmd->nb_tm) {
-               DMERR("could not create clone tm");
-               r = -ENOMEM;
-               goto bad_data_sm;
-       }
-
-       pmd->info.tm = tm;
+       pmd->info.tm = pmd->tm;
        pmd->info.levels = 2;
        pmd->info.value_type.context = pmd->data_sm;
        pmd->info.value_type.size = sizeof(__le64);
@@ -441,7 +406,7 @@ static int init_pmd(struct dm_pool_metadata *pmd,
        memcpy(&pmd->nb_info, &pmd->info, sizeof(pmd->nb_info));
        pmd->nb_info.tm = pmd->nb_tm;
 
-       pmd->tl_info.tm = tm;
+       pmd->tl_info.tm = pmd->tm;
        pmd->tl_info.levels = 1;
        pmd->tl_info.value_type.context = &pmd->info;
        pmd->tl_info.value_type.size = sizeof(__le64);
@@ -449,7 +414,7 @@ static int init_pmd(struct dm_pool_metadata *pmd,
        pmd->tl_info.value_type.dec = subtree_dec;
        pmd->tl_info.value_type.equal = subtree_equal;
 
-       pmd->bl_info.tm = tm;
+       pmd->bl_info.tm = pmd->tm;
        pmd->bl_info.levels = 1;
        pmd->bl_info.value_type.context = pmd->data_sm;
        pmd->bl_info.value_type.size = sizeof(__le64);
@@ -457,47 +422,265 @@ static int init_pmd(struct dm_pool_metadata *pmd,
        pmd->bl_info.value_type.dec = data_block_dec;
        pmd->bl_info.value_type.equal = data_block_equal;
 
-       pmd->details_info.tm = tm;
+       pmd->details_info.tm = pmd->tm;
        pmd->details_info.levels = 1;
        pmd->details_info.value_type.context = NULL;
        pmd->details_info.value_type.size = sizeof(struct disk_device_details);
        pmd->details_info.value_type.inc = NULL;
        pmd->details_info.value_type.dec = NULL;
        pmd->details_info.value_type.equal = NULL;
+}
 
-       pmd->root = 0;
+static int __write_initial_superblock(struct dm_pool_metadata *pmd)
+{
+       int r;
+       struct dm_block *sblock;
+       size_t metadata_len, data_len;
+       struct thin_disk_superblock *disk_super;
+       sector_t bdev_size = i_size_read(pmd->bdev->bd_inode) >> SECTOR_SHIFT;
 
-       init_rwsem(&pmd->root_lock);
-       pmd->time = 0;
-       pmd->need_commit = 0;
-       pmd->details_root = 0;
-       pmd->trans_id = 0;
-       pmd->flags = 0;
-       INIT_LIST_HEAD(&pmd->thin_devices);
+       if (bdev_size > THIN_METADATA_MAX_SECTORS)
+               bdev_size = THIN_METADATA_MAX_SECTORS;
+
+       r = dm_sm_root_size(pmd->metadata_sm, &metadata_len);
+       if (r < 0)
+               return r;
+
+       r = dm_sm_root_size(pmd->data_sm, &data_len);
+       if (r < 0)
+               return r;
+
+       r = dm_sm_commit(pmd->data_sm);
+       if (r < 0)
+               return r;
+
+       r = dm_tm_pre_commit(pmd->tm);
+       if (r < 0)
+               return r;
+
+       r = superblock_lock_zero(pmd, &sblock);
+       if (r)
+               return r;
+
+       disk_super = dm_block_data(sblock);
+       disk_super->flags = 0;
+       memset(disk_super->uuid, 0, sizeof(disk_super->uuid));
+       disk_super->magic = cpu_to_le64(THIN_SUPERBLOCK_MAGIC);
+       disk_super->version = cpu_to_le32(THIN_VERSION);
+       disk_super->time = 0;
+       disk_super->trans_id = 0;
+       disk_super->held_root = 0;
+
+       r = dm_sm_copy_root(pmd->metadata_sm, &disk_super->metadata_space_map_root,
+                           metadata_len);
+       if (r < 0)
+               goto bad_locked;
+
+       r = dm_sm_copy_root(pmd->data_sm, &disk_super->data_space_map_root,
+                           data_len);
+       if (r < 0)
+               goto bad_locked;
+
+       disk_super->data_mapping_root = cpu_to_le64(pmd->root);
+       disk_super->device_details_root = cpu_to_le64(pmd->details_root);
+       disk_super->metadata_block_size = cpu_to_le32(THIN_METADATA_BLOCK_SIZE >> SECTOR_SHIFT);
+       disk_super->metadata_nr_blocks = cpu_to_le64(bdev_size >> SECTOR_TO_BLOCK_SHIFT);
+       disk_super->data_block_size = cpu_to_le32(pmd->data_block_size);
+
+       return dm_tm_commit(pmd->tm, sblock);
+
+bad_locked:
+       dm_bm_unlock(sblock);
+       return r;
+}
+
+static int __format_metadata(struct dm_pool_metadata *pmd)
+{
+       int r;
+
+       r = dm_tm_create_with_sm(pmd->bm, THIN_SUPERBLOCK_LOCATION,
+                                &pmd->tm, &pmd->metadata_sm);
+       if (r < 0) {
+               DMERR("tm_create_with_sm failed");
+               return r;
+       }
+
+       pmd->data_sm = dm_sm_disk_create(pmd->tm, 0);
+       if (IS_ERR(pmd->data_sm)) {
+               DMERR("sm_disk_create failed");
+               r = PTR_ERR(pmd->data_sm);
+               goto bad_cleanup_tm;
+       }
+
+       pmd->nb_tm = dm_tm_create_non_blocking_clone(pmd->tm);
+       if (!pmd->nb_tm) {
+               DMERR("could not create non-blocking clone tm");
+               r = -ENOMEM;
+               goto bad_cleanup_data_sm;
+       }
+
+       __setup_btree_details(pmd);
+
+       r = dm_btree_empty(&pmd->info, &pmd->root);
+       if (r < 0)
+               goto bad_cleanup_nb_tm;
+
+       r = dm_btree_empty(&pmd->details_info, &pmd->details_root);
+       if (r < 0) {
+               DMERR("couldn't create devices root");
+               goto bad_cleanup_nb_tm;
+       }
+
+       r = __write_initial_superblock(pmd);
+       if (r)
+               goto bad_cleanup_nb_tm;
 
        return 0;
 
-bad_data_sm:
-       dm_sm_destroy(data_sm);
-bad:
-       dm_tm_destroy(tm);
-       dm_sm_destroy(sm);
+bad_cleanup_nb_tm:
+       dm_tm_destroy(pmd->nb_tm);
+bad_cleanup_data_sm:
+       dm_sm_destroy(pmd->data_sm);
+bad_cleanup_tm:
+       dm_tm_destroy(pmd->tm);
+       dm_sm_destroy(pmd->metadata_sm);
+
+       return r;
+}
+
+static int __check_incompat_features(struct thin_disk_superblock *disk_super,
+                                    struct dm_pool_metadata *pmd)
+{
+       uint32_t features;
+
+       features = le32_to_cpu(disk_super->incompat_flags) & ~THIN_FEATURE_INCOMPAT_SUPP;
+       if (features) {
+               DMERR("could not access metadata due to unsupported optional features (%lx).",
+                     (unsigned long)features);
+               return -EINVAL;
+       }
+
+       /*
+        * Check for read-only metadata to skip the following RDWR checks.
+        */
+       if (get_disk_ro(pmd->bdev->bd_disk))
+               return 0;
+
+       features = le32_to_cpu(disk_super->compat_ro_flags) & ~THIN_FEATURE_COMPAT_RO_SUPP;
+       if (features) {
+               DMERR("could not access metadata RDWR due to unsupported optional features (%lx).",
+                     (unsigned long)features);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int __open_metadata(struct dm_pool_metadata *pmd)
+{
+       int r;
+       struct dm_block *sblock;
+       struct thin_disk_superblock *disk_super;
+
+       r = dm_bm_read_lock(pmd->bm, THIN_SUPERBLOCK_LOCATION,
+                           &sb_validator, &sblock);
+       if (r < 0) {
+               DMERR("couldn't read superblock");
+               return r;
+       }
+
+       disk_super = dm_block_data(sblock);
+
+       r = __check_incompat_features(disk_super, pmd);
+       if (r < 0)
+               goto bad_unlock_sblock;
+
+       r = dm_tm_open_with_sm(pmd->bm, THIN_SUPERBLOCK_LOCATION,
+                              disk_super->metadata_space_map_root,
+                              sizeof(disk_super->metadata_space_map_root),
+                              &pmd->tm, &pmd->metadata_sm);
+       if (r < 0) {
+               DMERR("tm_open_with_sm failed");
+               goto bad_unlock_sblock;
+       }
+
+       pmd->data_sm = dm_sm_disk_open(pmd->tm, disk_super->data_space_map_root,
+                                      sizeof(disk_super->data_space_map_root));
+       if (IS_ERR(pmd->data_sm)) {
+               DMERR("sm_disk_open failed");
+               r = PTR_ERR(pmd->data_sm);
+               goto bad_cleanup_tm;
+       }
+
+       pmd->nb_tm = dm_tm_create_non_blocking_clone(pmd->tm);
+       if (!pmd->nb_tm) {
+               DMERR("could not create non-blocking clone tm");
+               r = -ENOMEM;
+               goto bad_cleanup_data_sm;
+       }
+
+       __setup_btree_details(pmd);
+       return dm_bm_unlock(sblock);
+
+bad_cleanup_data_sm:
+       dm_sm_destroy(pmd->data_sm);
+bad_cleanup_tm:
+       dm_tm_destroy(pmd->tm);
+       dm_sm_destroy(pmd->metadata_sm);
+bad_unlock_sblock:
+       dm_bm_unlock(sblock);
+
+       return r;
+}
+
+static int __open_or_format_metadata(struct dm_pool_metadata *pmd, bool format_device)
+{
+       int r, unformatted;
+
+       r = __superblock_all_zeroes(pmd->bm, &unformatted);
+       if (r)
+               return r;
+
+       if (unformatted)
+               return format_device ? __format_metadata(pmd) : -EPERM;
+
+       return __open_metadata(pmd);
+}
+
+static int __create_persistent_data_objects(struct dm_pool_metadata *pmd, bool format_device)
+{
+       int r;
+
+       pmd->bm = dm_block_manager_create(pmd->bdev, THIN_METADATA_BLOCK_SIZE,
+                                         THIN_METADATA_CACHE_SIZE,
+                                         THIN_MAX_CONCURRENT_LOCKS);
+       if (IS_ERR(pmd->bm)) {
+               DMERR("could not create block manager");
+               return PTR_ERR(pmd->bm);
+       }
+
+       r = __open_or_format_metadata(pmd, format_device);
+       if (r)
+               dm_block_manager_destroy(pmd->bm);
 
        return r;
 }
 
+static void __destroy_persistent_data_objects(struct dm_pool_metadata *pmd)
+{
+       dm_sm_destroy(pmd->data_sm);
+       dm_sm_destroy(pmd->metadata_sm);
+       dm_tm_destroy(pmd->nb_tm);
+       dm_tm_destroy(pmd->tm);
+       dm_block_manager_destroy(pmd->bm);
+}
+
 static int __begin_transaction(struct dm_pool_metadata *pmd)
 {
        int r;
-       u32 features;
        struct thin_disk_superblock *disk_super;
        struct dm_block *sblock;
 
-       /*
-        * __maybe_commit_transaction() resets these
-        */
-       WARN_ON(pmd->need_commit);
-
        /*
         * We re-read the superblock every time.  Shouldn't need to do this
         * really.
@@ -515,32 +698,8 @@ static int __begin_transaction(struct dm_pool_metadata *pmd)
        pmd->flags = le32_to_cpu(disk_super->flags);
        pmd->data_block_size = le32_to_cpu(disk_super->data_block_size);
 
-       features = le32_to_cpu(disk_super->incompat_flags) & ~THIN_FEATURE_INCOMPAT_SUPP;
-       if (features) {
-               DMERR("could not access metadata due to "
-                     "unsupported optional features (%lx).",
-                     (unsigned long)features);
-               r = -EINVAL;
-               goto out;
-       }
-
-       /*
-        * Check for read-only metadata to skip the following RDWR checks.
-        */
-       if (get_disk_ro(pmd->bdev->bd_disk))
-               goto out;
-
-       features = le32_to_cpu(disk_super->compat_ro_flags) & ~THIN_FEATURE_COMPAT_RO_SUPP;
-       if (features) {
-               DMERR("could not access metadata RDWR due to "
-                     "unsupported optional features (%lx).",
-                     (unsigned long)features);
-               r = -EINVAL;
-       }
-
-out:
        dm_bm_unlock(sblock);
-       return r;
+       return 0;
 }
 
 static int __write_changed_details(struct dm_pool_metadata *pmd)
@@ -573,8 +732,6 @@ static int __write_changed_details(struct dm_pool_metadata *pmd)
                        list_del(&td->list);
                        kfree(td);
                }
-
-               pmd->need_commit = 1;
        }
 
        return 0;
@@ -582,9 +739,6 @@ static int __write_changed_details(struct dm_pool_metadata *pmd)
 
 static int __commit_transaction(struct dm_pool_metadata *pmd)
 {
-       /*
-        * FIXME: Associated pool should be made read-only on failure.
-        */
        int r;
        size_t metadata_len, data_len;
        struct thin_disk_superblock *disk_super;
@@ -597,31 +751,27 @@ static int __commit_transaction(struct dm_pool_metadata *pmd)
 
        r = __write_changed_details(pmd);
        if (r < 0)
-               goto out;
-
-       if (!pmd->need_commit)
-               goto out;
+               return r;
 
        r = dm_sm_commit(pmd->data_sm);
        if (r < 0)
-               goto out;
+               return r;
 
        r = dm_tm_pre_commit(pmd->tm);
        if (r < 0)
-               goto out;
+               return r;
 
        r = dm_sm_root_size(pmd->metadata_sm, &metadata_len);
        if (r < 0)
-               goto out;
+               return r;
 
        r = dm_sm_root_size(pmd->data_sm, &data_len);
        if (r < 0)
-               goto out;
+               return r;
 
-       r = dm_bm_write_lock(pmd->bm, THIN_SUPERBLOCK_LOCATION,
-                            &sb_validator, &sblock);
+       r = superblock_lock(pmd, &sblock);
        if (r)
-               goto out;
+               return r;
 
        disk_super = dm_block_data(sblock);
        disk_super->time = cpu_to_le32(pmd->time);
@@ -640,12 +790,7 @@ static int __commit_transaction(struct dm_pool_metadata *pmd)
        if (r < 0)
                goto out_locked;
 
-       r = dm_tm_commit(pmd->tm, sblock);
-       if (!r)
-               pmd->need_commit = 0;
-
-out:
-       return r;
+       return dm_tm_commit(pmd->tm, sblock);
 
 out_locked:
        dm_bm_unlock(sblock);
@@ -653,15 +798,11 @@ out_locked:
 }
 
 struct dm_pool_metadata *dm_pool_metadata_open(struct block_device *bdev,
-                                              sector_t data_block_size)
+                                              sector_t data_block_size,
+                                              bool format_device)
 {
        int r;
-       struct thin_disk_superblock *disk_super;
        struct dm_pool_metadata *pmd;
-       sector_t bdev_size = i_size_read(bdev->bd_inode) >> SECTOR_SHIFT;
-       struct dm_block_manager *bm;
-       int create;
-       struct dm_block *sblock;
 
        pmd = kmalloc(sizeof(*pmd), GFP_KERNEL);
        if (!pmd) {
@@ -669,90 +810,28 @@ struct dm_pool_metadata *dm_pool_metadata_open(struct block_device *bdev,
                return ERR_PTR(-ENOMEM);
        }
 
-       /*
-        * Max hex locks:
-        *  3 for btree insert +
-        *  2 for btree lookup used within space map
-        */
-       bm = dm_block_manager_create(bdev, THIN_METADATA_BLOCK_SIZE,
-                                    THIN_METADATA_CACHE_SIZE, 5);
-       if (!bm) {
-               DMERR("could not create block manager");
-               kfree(pmd);
-               return ERR_PTR(-ENOMEM);
-       }
-
-       r = superblock_all_zeroes(bm, &create);
-       if (r) {
-               dm_block_manager_destroy(bm);
-               kfree(pmd);
-               return ERR_PTR(r);
-       }
-
+       init_rwsem(&pmd->root_lock);
+       pmd->time = 0;
+       INIT_LIST_HEAD(&pmd->thin_devices);
+       pmd->read_only = false;
+       pmd->fail_io = false;
+       pmd->bdev = bdev;
+       pmd->data_block_size = data_block_size;
 
-       r = init_pmd(pmd, bm, 0, create);
+       r = __create_persistent_data_objects(pmd, format_device);
        if (r) {
-               dm_block_manager_destroy(bm);
                kfree(pmd);
                return ERR_PTR(r);
        }
-       pmd->bdev = bdev;
-
-       if (!create) {
-               r = __begin_transaction(pmd);
-               if (r < 0)
-                       goto bad;
-               return pmd;
-       }
-
-       /*
-        * Create.
-        */
-       r = dm_bm_write_lock(pmd->bm, THIN_SUPERBLOCK_LOCATION,
-                            &sb_validator, &sblock);
-       if (r)
-               goto bad;
-
-       if (bdev_size > THIN_METADATA_MAX_SECTORS)
-               bdev_size = THIN_METADATA_MAX_SECTORS;
-
-       disk_super = dm_block_data(sblock);
-       disk_super->magic = cpu_to_le64(THIN_SUPERBLOCK_MAGIC);
-       disk_super->version = cpu_to_le32(THIN_VERSION);
-       disk_super->time = 0;
-       disk_super->metadata_block_size = cpu_to_le32(THIN_METADATA_BLOCK_SIZE >> SECTOR_SHIFT);
-       disk_super->metadata_nr_blocks = cpu_to_le64(bdev_size >> SECTOR_TO_BLOCK_SHIFT);
-       disk_super->data_block_size = cpu_to_le32(data_block_size);
-
-       r = dm_bm_unlock(sblock);
-       if (r < 0)
-               goto bad;
-
-       r = dm_btree_empty(&pmd->info, &pmd->root);
-       if (r < 0)
-               goto bad;
-
-       r = dm_btree_empty(&pmd->details_info, &pmd->details_root);
-       if (r < 0) {
-               DMERR("couldn't create devices root");
-               goto bad;
-       }
 
-       pmd->flags = 0;
-       pmd->need_commit = 1;
-       r = dm_pool_commit_metadata(pmd);
+       r = __begin_transaction(pmd);
        if (r < 0) {
-               DMERR("%s: dm_pool_commit_metadata() failed, error = %d",
-                     __func__, r);
-               goto bad;
+               if (dm_pool_metadata_close(pmd) < 0)
+                       DMWARN("%s: dm_pool_metadata_close() failed.", __func__);
+               return ERR_PTR(r);
        }
 
        return pmd;
-
-bad:
-       if (dm_pool_metadata_close(pmd) < 0)
-               DMWARN("%s: dm_pool_metadata_close() failed.", __func__);
-       return ERR_PTR(r);
 }
 
 int dm_pool_metadata_close(struct dm_pool_metadata *pmd)
@@ -778,18 +857,17 @@ int dm_pool_metadata_close(struct dm_pool_metadata *pmd)
                return -EBUSY;
        }
 
-       r = __commit_transaction(pmd);
-       if (r < 0)
-               DMWARN("%s: __commit_transaction() failed, error = %d",
-                      __func__, r);
+       if (!pmd->read_only && !pmd->fail_io) {
+               r = __commit_transaction(pmd);
+               if (r < 0)
+                       DMWARN("%s: __commit_transaction() failed, error = %d",
+                              __func__, r);
+       }
 
-       dm_tm_destroy(pmd->tm);
-       dm_tm_destroy(pmd->nb_tm);
-       dm_block_manager_destroy(pmd->bm);
-       dm_sm_destroy(pmd->metadata_sm);
-       dm_sm_destroy(pmd->data_sm);
-       kfree(pmd);
+       if (!pmd->fail_io)
+               __destroy_persistent_data_objects(pmd);
 
+       kfree(pmd);
        return 0;
 }
 
@@ -850,6 +928,7 @@ static int __open_device(struct dm_pool_metadata *pmd,
        (*td)->id = dev;
        (*td)->open_count = 1;
        (*td)->changed = changed;
+       (*td)->aborted_with_changes = false;
        (*td)->mapped_blocks = le64_to_cpu(details_le.mapped_blocks);
        (*td)->transaction_id = le64_to_cpu(details_le.transaction_id);
        (*td)->creation_time = le32_to_cpu(details_le.creation_time);
@@ -911,10 +990,11 @@ static int __create_thin(struct dm_pool_metadata *pmd,
 
 int dm_pool_create_thin(struct dm_pool_metadata *pmd, dm_thin_id dev)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __create_thin(pmd, dev);
+       if (!pmd->fail_io)
+               r = __create_thin(pmd, dev);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1001,10 +1081,11 @@ int dm_pool_create_snap(struct dm_pool_metadata *pmd,
                                 dm_thin_id dev,
                                 dm_thin_id origin)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __create_snap(pmd, dev, origin);
+       if (!pmd->fail_io)
+               r = __create_snap(pmd, dev, origin);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1037,18 +1118,17 @@ static int __delete_device(struct dm_pool_metadata *pmd, dm_thin_id dev)
        if (r)
                return r;
 
-       pmd->need_commit = 1;
-
        return 0;
 }
 
 int dm_pool_delete_thin_device(struct dm_pool_metadata *pmd,
                               dm_thin_id dev)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __delete_device(pmd, dev);
+       if (!pmd->fail_io)
+               r = __delete_device(pmd, dev);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1058,28 +1138,40 @@ int dm_pool_set_metadata_transaction_id(struct dm_pool_metadata *pmd,
                                        uint64_t current_id,
                                        uint64_t new_id)
 {
+       int r = -EINVAL;
+
        down_write(&pmd->root_lock);
+
+       if (pmd->fail_io)
+               goto out;
+
        if (pmd->trans_id != current_id) {
-               up_write(&pmd->root_lock);
                DMERR("mismatched transaction id");
-               return -EINVAL;
+               goto out;
        }
 
        pmd->trans_id = new_id;
-       pmd->need_commit = 1;
+       r = 0;
+
+out:
        up_write(&pmd->root_lock);
 
-       return 0;
+       return r;
 }
 
 int dm_pool_get_metadata_transaction_id(struct dm_pool_metadata *pmd,
                                        uint64_t *result)
 {
+       int r = -EINVAL;
+
        down_read(&pmd->root_lock);
-       *result = pmd->trans_id;
+       if (!pmd->fail_io) {
+               *result = pmd->trans_id;
+               r = 0;
+       }
        up_read(&pmd->root_lock);
 
-       return 0;
+       return r;
 }
 
 static int __reserve_metadata_snap(struct dm_pool_metadata *pmd)
@@ -1108,8 +1200,6 @@ static int __reserve_metadata_snap(struct dm_pool_metadata *pmd)
 
                dm_tm_dec(pmd->tm, held_root);
                dm_tm_unlock(pmd->tm, copy);
-               pmd->need_commit = 1;
-
                return -EBUSY;
        }
 
@@ -1131,29 +1221,25 @@ static int __reserve_metadata_snap(struct dm_pool_metadata *pmd)
        /*
         * Write the held root into the superblock.
         */
-       r = dm_bm_write_lock(pmd->bm, THIN_SUPERBLOCK_LOCATION,
-                            &sb_validator, &sblock);
+       r = superblock_lock(pmd, &sblock);
        if (r) {
                dm_tm_dec(pmd->tm, held_root);
-               pmd->need_commit = 1;
                return r;
        }
 
        disk_super = dm_block_data(sblock);
        disk_super->held_root = cpu_to_le64(held_root);
        dm_bm_unlock(sblock);
-
-       pmd->need_commit = 1;
-
        return 0;
 }
 
 int dm_pool_reserve_metadata_snap(struct dm_pool_metadata *pmd)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __reserve_metadata_snap(pmd);
+       if (!pmd->fail_io)
+               r = __reserve_metadata_snap(pmd);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1166,15 +1252,13 @@ static int __release_metadata_snap(struct dm_pool_metadata *pmd)
        struct dm_block *sblock, *copy;
        dm_block_t held_root;
 
-       r = dm_bm_write_lock(pmd->bm, THIN_SUPERBLOCK_LOCATION,
-                            &sb_validator, &sblock);
+       r = superblock_lock(pmd, &sblock);
        if (r)
                return r;
 
        disk_super = dm_block_data(sblock);
        held_root = le64_to_cpu(disk_super->held_root);
        disk_super->held_root = cpu_to_le64(0);
-       pmd->need_commit = 1;
 
        dm_bm_unlock(sblock);
 
@@ -1197,10 +1281,11 @@ static int __release_metadata_snap(struct dm_pool_metadata *pmd)
 
 int dm_pool_release_metadata_snap(struct dm_pool_metadata *pmd)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __release_metadata_snap(pmd);
+       if (!pmd->fail_io)
+               r = __release_metadata_snap(pmd);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1227,10 +1312,11 @@ static int __get_metadata_snap(struct dm_pool_metadata *pmd,
 int dm_pool_get_metadata_snap(struct dm_pool_metadata *pmd,
                              dm_block_t *result)
 {
-       int r;
+       int r = -EINVAL;
 
        down_read(&pmd->root_lock);
-       r = __get_metadata_snap(pmd, result);
+       if (!pmd->fail_io)
+               r = __get_metadata_snap(pmd, result);
        up_read(&pmd->root_lock);
 
        return r;
@@ -1239,10 +1325,11 @@ int dm_pool_get_metadata_snap(struct dm_pool_metadata *pmd,
 int dm_pool_open_thin_device(struct dm_pool_metadata *pmd, dm_thin_id dev,
                             struct dm_thin_device **td)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __open_device(pmd, dev, 0, td);
+       if (!pmd->fail_io)
+               r = __open_device(pmd, dev, 0, td);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1262,7 +1349,7 @@ dm_thin_id dm_thin_dev_id(struct dm_thin_device *td)
        return td->id;
 }
 
-static int __snapshotted_since(struct dm_thin_device *td, uint32_t time)
+static bool __snapshotted_since(struct dm_thin_device *td, uint32_t time)
 {
        return td->snapshotted_time > time;
 }
@@ -1270,28 +1357,31 @@ static int __snapshotted_since(struct dm_thin_device *td, uint32_t time)
 int dm_thin_find_block(struct dm_thin_device *td, dm_block_t block,
                       int can_block, struct dm_thin_lookup_result *result)
 {
-       int r;
+       int r = -EINVAL;
        uint64_t block_time = 0;
        __le64 value;
        struct dm_pool_metadata *pmd = td->pmd;
        dm_block_t keys[2] = { td->id, block };
+       struct dm_btree_info *info;
 
        if (can_block) {
                down_read(&pmd->root_lock);
-               r = dm_btree_lookup(&pmd->info, pmd->root, keys, &value);
-               if (!r)
-                       block_time = le64_to_cpu(value);
-               up_read(&pmd->root_lock);
-
-       } else if (down_read_trylock(&pmd->root_lock)) {
-               r = dm_btree_lookup(&pmd->nb_info, pmd->root, keys, &value);
-               if (!r)
-                       block_time = le64_to_cpu(value);
-               up_read(&pmd->root_lock);
-
-       } else
+               info = &pmd->info;
+       } else if (down_read_trylock(&pmd->root_lock))
+               info = &pmd->nb_info;
+       else
                return -EWOULDBLOCK;
 
+       if (pmd->fail_io)
+               goto out;
+
+       r = dm_btree_lookup(info, pmd->root, keys, &value);
+       if (!r)
+               block_time = le64_to_cpu(value);
+
+out:
+       up_read(&pmd->root_lock);
+
        if (!r) {
                dm_block_t exception_block;
                uint32_t exception_time;
@@ -1312,7 +1402,6 @@ static int __insert(struct dm_thin_device *td, dm_block_t block,
        struct dm_pool_metadata *pmd = td->pmd;
        dm_block_t keys[2] = { td->id, block };
 
-       pmd->need_commit = 1;
        value = cpu_to_le64(pack_block_time(data_block, pmd->time));
        __dm_bless_for_disk(&value);
 
@@ -1321,10 +1410,9 @@ static int __insert(struct dm_thin_device *td, dm_block_t block,
        if (r)
                return r;
 
-       if (inserted) {
+       td->changed = 1;
+       if (inserted)
                td->mapped_blocks++;
-               td->changed = 1;
-       }
 
        return 0;
 }
@@ -1332,10 +1420,11 @@ static int __insert(struct dm_thin_device *td, dm_block_t block,
 int dm_thin_insert_block(struct dm_thin_device *td, dm_block_t block,
                         dm_block_t data_block)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&td->pmd->root_lock);
-       r = __insert(td, block, data_block);
+       if (!td->pmd->fail_io)
+               r = __insert(td, block, data_block);
        up_write(&td->pmd->root_lock);
 
        return r;
@@ -1353,31 +1442,51 @@ static int __remove(struct dm_thin_device *td, dm_block_t block)
 
        td->mapped_blocks--;
        td->changed = 1;
-       pmd->need_commit = 1;
 
        return 0;
 }
 
 int dm_thin_remove_block(struct dm_thin_device *td, dm_block_t block)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&td->pmd->root_lock);
-       r = __remove(td, block);
+       if (!td->pmd->fail_io)
+               r = __remove(td, block);
        up_write(&td->pmd->root_lock);
 
        return r;
 }
 
-int dm_pool_alloc_data_block(struct dm_pool_metadata *pmd, dm_block_t *result)
+bool dm_thin_changed_this_transaction(struct dm_thin_device *td)
 {
        int r;
 
-       down_write(&pmd->root_lock);
+       down_read(&td->pmd->root_lock);
+       r = td->changed;
+       up_read(&td->pmd->root_lock);
 
-       r = dm_sm_new_block(pmd->data_sm, result);
-       pmd->need_commit = 1;
+       return r;
+}
+
+bool dm_thin_aborted_changes(struct dm_thin_device *td)
+{
+       bool r;
 
+       down_read(&td->pmd->root_lock);
+       r = td->aborted_with_changes;
+       up_read(&td->pmd->root_lock);
+
+       return r;
+}
+
+int dm_pool_alloc_data_block(struct dm_pool_metadata *pmd, dm_block_t *result)
+{
+       int r = -EINVAL;
+
+       down_write(&pmd->root_lock);
+       if (!pmd->fail_io)
+               r = dm_sm_new_block(pmd->data_sm, result);
        up_write(&pmd->root_lock);
 
        return r;
@@ -1385,9 +1494,11 @@ int dm_pool_alloc_data_block(struct dm_pool_metadata *pmd, dm_block_t *result)
 
 int dm_pool_commit_metadata(struct dm_pool_metadata *pmd)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
+       if (pmd->fail_io)
+               goto out;
 
        r = __commit_transaction(pmd);
        if (r <= 0)
@@ -1402,12 +1513,41 @@ out:
        return r;
 }
 
+static void __set_abort_with_changes_flags(struct dm_pool_metadata *pmd)
+{
+       struct dm_thin_device *td;
+
+       list_for_each_entry(td, &pmd->thin_devices, list)
+               td->aborted_with_changes = td->changed;
+}
+
+int dm_pool_abort_metadata(struct dm_pool_metadata *pmd)
+{
+       int r = -EINVAL;
+
+       down_write(&pmd->root_lock);
+       if (pmd->fail_io)
+               goto out;
+
+       __set_abort_with_changes_flags(pmd);
+       __destroy_persistent_data_objects(pmd);
+       r = __create_persistent_data_objects(pmd, false);
+       if (r)
+               pmd->fail_io = true;
+
+out:
+       up_write(&pmd->root_lock);
+
+       return r;
+}
+
 int dm_pool_get_free_block_count(struct dm_pool_metadata *pmd, dm_block_t *result)
 {
-       int r;
+       int r = -EINVAL;
 
        down_read(&pmd->root_lock);
-       r = dm_sm_get_nr_free(pmd->data_sm, result);
+       if (!pmd->fail_io)
+               r = dm_sm_get_nr_free(pmd->data_sm, result);
        up_read(&pmd->root_lock);
 
        return r;
@@ -1416,10 +1556,11 @@ int dm_pool_get_free_block_count(struct dm_pool_metadata *pmd, dm_block_t *resul
 int dm_pool_get_free_metadata_block_count(struct dm_pool_metadata *pmd,
                                          dm_block_t *result)
 {
-       int r;
+       int r = -EINVAL;
 
        down_read(&pmd->root_lock);
-       r = dm_sm_get_nr_free(pmd->metadata_sm, result);
+       if (!pmd->fail_io)
+               r = dm_sm_get_nr_free(pmd->metadata_sm, result);
        up_read(&pmd->root_lock);
 
        return r;
@@ -1428,10 +1569,11 @@ int dm_pool_get_free_metadata_block_count(struct dm_pool_metadata *pmd,
 int dm_pool_get_metadata_dev_size(struct dm_pool_metadata *pmd,
                                  dm_block_t *result)
 {
-       int r;
+       int r = -EINVAL;
 
        down_read(&pmd->root_lock);
-       r = dm_sm_get_nr_blocks(pmd->metadata_sm, result);
+       if (!pmd->fail_io)
+               r = dm_sm_get_nr_blocks(pmd->metadata_sm, result);
        up_read(&pmd->root_lock);
 
        return r;
@@ -1448,10 +1590,11 @@ int dm_pool_get_data_block_size(struct dm_pool_metadata *pmd, sector_t *result)
 
 int dm_pool_get_data_dev_size(struct dm_pool_metadata *pmd, dm_block_t *result)
 {
-       int r;
+       int r = -EINVAL;
 
        down_read(&pmd->root_lock);
-       r = dm_sm_get_nr_blocks(pmd->data_sm, result);
+       if (!pmd->fail_io)
+               r = dm_sm_get_nr_blocks(pmd->data_sm, result);
        up_read(&pmd->root_lock);
 
        return r;
@@ -1459,13 +1602,17 @@ int dm_pool_get_data_dev_size(struct dm_pool_metadata *pmd, dm_block_t *result)
 
 int dm_thin_get_mapped_count(struct dm_thin_device *td, dm_block_t *result)
 {
+       int r = -EINVAL;
        struct dm_pool_metadata *pmd = td->pmd;
 
        down_read(&pmd->root_lock);
-       *result = td->mapped_blocks;
+       if (!pmd->fail_io) {
+               *result = td->mapped_blocks;
+               r = 0;
+       }
        up_read(&pmd->root_lock);
 
-       return 0;
+       return r;
 }
 
 static int __highest_block(struct dm_thin_device *td, dm_block_t *result)
@@ -1487,11 +1634,12 @@ static int __highest_block(struct dm_thin_device *td, dm_block_t *result)
 int dm_thin_get_highest_mapped_block(struct dm_thin_device *td,
                                     dm_block_t *result)
 {
-       int r;
+       int r = -EINVAL;
        struct dm_pool_metadata *pmd = td->pmd;
 
        down_read(&pmd->root_lock);
-       r = __highest_block(td, result);
+       if (!pmd->fail_io)
+               r = __highest_block(td, result);
        up_read(&pmd->root_lock);
 
        return r;
@@ -1514,20 +1662,25 @@ static int __resize_data_dev(struct dm_pool_metadata *pmd, dm_block_t new_count)
                return -EINVAL;
        }
 
-       r = dm_sm_extend(pmd->data_sm, new_count - old_count);
-       if (!r)
-               pmd->need_commit = 1;
-
-       return r;
+       return dm_sm_extend(pmd->data_sm, new_count - old_count);
 }
 
 int dm_pool_resize_data_dev(struct dm_pool_metadata *pmd, dm_block_t new_count)
 {
-       int r;
+       int r = -EINVAL;
 
        down_write(&pmd->root_lock);
-       r = __resize_data_dev(pmd, new_count);
+       if (!pmd->fail_io)
+               r = __resize_data_dev(pmd, new_count);
        up_write(&pmd->root_lock);
 
        return r;
 }
+
+void dm_pool_metadata_read_only(struct dm_pool_metadata *pmd)
+{
+       down_write(&pmd->root_lock);
+       pmd->read_only = true;
+       dm_bm_set_read_only(pmd->bm);
+       up_write(&pmd->root_lock);
+}