Patchset to fix possible corruption with dm-thin et al.
cover Patchset to fix possible corruption with dm-thin et al.
The persistent-data library used by dm-thin, dm-cache and dm-era is transactional. If anything goes wrong, such as an io error when writing new metadata or a power failure, then we roll back to the last transaction.
Atomicity when committing a transaction is achieved by:
a) Never overwriting data from the previous transaction.
b) Writing the superblock last, after all other metadata has hit the disk.
This patchset fixes a bug associated with (b). When committing it was possible for the superblock to still be written in spite of an io error occurring during the preceding flush. With these patches we're careful not to take the write lock out on the superblock until after the flush has completed.
This issue was discovered by forcing io errors at the crucial time using dm-flakey.
Joe Thornber (4):
[perstent-data] Simplify dm_bm_flush_and_unlock() -> dm_bm_flush() [persistent-data] change the semantics of dm_tm_commit(). [dm-cache] Switch to new dm_bm_flush() interface. [dm-thin, dm-cache, dm-era] Take care to copy the space map roots
before locking the superblock.
drivers/md/dm-cache-metadata.c | 63 +++++++++------ drivers/md/dm-era-target.c | 68 ++++++++++------- drivers/md/dm-thin-metadata.c | 80 ++++++++++++-------- drivers/md/persistent-data/dm-block-manager.c | 15 +--- drivers/md/persistent-data/dm-block-manager.h | 3 +- .../md/persistent-data/dm-transaction-manager.c | 5 +- .../md/persistent-data/dm-transaction-manager.h | 17 ++--- 7 files changed, 143 insertions(+), 108 deletions(-)
--
1.7.10.4
PATCH 1/4 Simplify dm_bm_flush_and_unlock() -> dm_bm_flush()
Now you have to do the unlock yourself.
--- drivers/md/persistent-data/dm-block-manager.c | 15 ++------------- drivers/md/persistent-data/dm-block-manager.h | 3 +-- 2 files changed, 3 insertions(+), 15 deletions(-)
drivers/md/persistent-data/dm-block-manager.c +2 -13
@@ -609,25 +609,14 @@ int dm_bm_unlock(struct dm_block *b) }EXPORT_SYMBOL_GPL (dm_bm_unlock); -int dm_bm_flush_and_unlock (struct dm_block_manager *bm, -struct dm_block *superblock) +int dm_bm_flush (struct dm_block_manager *bm) { -int r; -if (bm->read_only)return -EPERM ; - r =dm_bufio_write_dirty_buffers (bm->bufio); -if (unlikely (r)) { -dm_bm_unlock (superblock); -return r; - } - -dm_bm_unlock (superblock); -return dm_bufio_write_dirty_buffers (bm->bufio); } -EXPORT_SYMBOL_GPL (dm_bm_flush_and_unlock); +EXPORT_SYMBOL_GPL (dm_bm_flush);void dm_bm_prefetch (struct dm_block_manager *bm,dm_block_t b) {
drivers/md/persistent-data/dm-block-manager.h +1 -2
@@ -105,8 +105,7 @@ int dm_bm_unlock(struct dm_block *b);* * This method always blocks. */ -int dm_bm_flush_and_unlock (struct dm_block_manager *bm, -struct dm_block *superblock); +int dm_bm_flush (struct dm_block_manager *bm);/* * Request data is prefetched into the cache.
Signature · 1 line
1.7.10.4
PATCH 2/4 change the semantics of dm_tm_commit().
In reply to Joe ThornberNow it assumes all data has been flushed apart from the single superblock passed in.
--- drivers/md/persistent-data/dm-transaction-manager.c | 5 +++-- drivers/md/persistent-data/dm-transaction-manager.h | 17 ++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-)
drivers/md/persistent-data/dm-transaction-manager.c +3 -2
@@ -154,7 +154,7 @@ int dm_tm_pre_commit(struct dm_transaction_manager *tm)if (r <0 )return r; -return 0 ; +return dm_bm_flush (tm->bm); }EXPORT_SYMBOL_GPL (dm_tm_pre_commit);
@@ -164,8 +164,9 @@ int dm_tm_commit(struct dm_transaction_manager *tm, struct dm_block *root)return -EWOULDBLOCK ;wipe_shadow_table (tm); +dm_bm_unlock (root); -return dm_bm_flush_and_unlock (tm->bm, root); +return dm_bm_flush (tm->bm); }EXPORT_SYMBOL_GPL (dm_tm_commit);
drivers/md/persistent-data/dm-transaction-manager.h +8 -9
@@ -38,18 +38,17 @@ struct dm_transaction_manager *dm_tm_create_non_blocking_clone(struct dm_transac/* * We use a 2-phase commit here. * -* i) In the first phase the block manager is told to start flushing, and -* the changes to the space map are written to disk. You should interrogate -* your particular space map to get detail of its root node etc. to be -* included in your superblock. +* i) Make all changes for the transaction *except* for the superblock. +* Then call dm_tm_pre_commit() to flush them to disk. * -* ii) @root will be committed last. You shouldn't use more than the -* first 512 bytes of @root if you wish the transaction to survive a power -* failure. You *must* have a write lock held on @root for both stage (i) -* and (ii). The commit will drop the write lock. +* ii) Lock your superblock. Update. Then call dm_tm_commit() which will +* unlock the superblock and flush it. No other blocks should be updated +* during this period. Care should be taken to never unlock a partially +* updated superblock; perform any operations that could fail *before* you +* take the superblock lock. */ int dm_tm_pre_commit (struct dm_transaction_manager *tm); -int dm_tm_commit (struct dm_transaction_manager *tm,struct dm_block *root); +int dm_tm_commit (struct dm_transaction_manager *tm,struct dm_block *superblock);/* * These methods are the only way to get hold of a writeable block.
Signature · 1 line
1.7.10.4
PATCH 3/4 Switch to new dm_bm_flush() interface.
In reply to Joe Thornber--- drivers/md/dm-cache-metadata.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/md/dm-cache-metadata.c +2 -1
@@ -530,8 +530,9 @@ static int __begin_transaction_flags(struct dm_cache_metadata *cmd, disk_super =dm_block_data (sblock);update_flags (disk_super, mutator);read_superblock_fields (cmd, disk_super); +dm_bm_unlock (sblock); -return dm_bm_flush_and_unlock (cmd->bm, sblock); +return dm_bm_flush (cmd->bm); }static int __begin_transaction (struct dm_cache_metadata *cmd)
Signature · 1 line
1.7.10.4
PATCH 4/4 Take care to copy the space map roots before locking the superblock.
In reply to Joe ThornberParanoia patch. In theory this operation can fail, but in practise it never does because we're careful to check what size buffer is needed.
--- drivers/md/dm-cache-metadata.c | 60 +++++++++++++++++++----------- drivers/md/dm-era-target.c | 68 +++++++++++++++++++++------------- drivers/md/dm-thin-metadata.c | 80 +++++++++++++++++++++++----------------- 3 files changed, 127 insertions(+), 81 deletions(-)
drivers/md/dm-cache-metadata.c +38 -22
@@ -120,6 +120,12 @@ struct dm_cache_metadata {unsigned policy_version[CACHE_POLICY_VERSION_SIZE ];size_t policy_hint_size;struct dm_cache_statistics stats; + +/* +* Reading the space map root can fail, so we read it into this +* buffer before the superblock is locked and updated. +*/ +__u8 metadata_space_map_root[SPACE_MAP_ROOT_SIZE ]; };/*-------------------------------------------------------------------
@@ -260,11 +266,31 @@ static void __setup_mapping_info(struct dm_cache_metadata *cmd) } } +static int __save_sm_root (struct dm_cache_metadata *cmd) +{ +int r; +size_t metadata_len; + + r =dm_sm_root_size (cmd->metadata_sm, &metadata_len); +if (r <0 ) +return r; + +return dm_sm_copy_root (cmd->metadata_sm, &cmd->metadata_space_map_root, + metadata_len); +} + +static void __copy_sm_root (struct dm_cache_metadata *cmd, +struct cache_disk_superblock *disk_super) +{ +memcpy (&disk_super->metadata_space_map_root, + &cmd->metadata_space_map_root, +sizeof (cmd->metadata_space_map_root)); +} +static int __write_initial_superblock (struct dm_cache_metadata *cmd) {int r;struct dm_block *sblock; -size_t metadata_len;struct cache_disk_superblock *disk_super;sector_t bdev_size =i_size_read (cmd->bdev->bd_inode) >>SECTOR_SHIFT ;
@@ -272,12 +298,16 @@ static int __write_initial_superblock(struct dm_cache_metadata *cmd)if (bdev_size >DM_CACHE_METADATA_MAX_SECTORS ) bdev_size =DM_CACHE_METADATA_MAX_SECTORS ; - r =dm_sm_root_size (cmd->metadata_sm, &metadata_len); + r =dm_tm_pre_commit (cmd->tm);if (r <0 )return r; - r =dm_tm_pre_commit (cmd->tm); -if (r <0 ) +/* +* dm_sm_copy_root() can fail. So we need to do it before we start +* updating the superblock. +*/ + r =__save_sm_root (cmd); +if (r)return r; r =superblock_lock_zero (cmd, &sblock);
@@ -293,10 +323,7 @@ static int __write_initial_superblock(struct dm_cache_metadata *cmd)memset (disk_super->policy_version,0 ,sizeof (disk_super->policy_version)); disk_super->policy_hint_size =0 ; - r =dm_sm_copy_root (cmd->metadata_sm, &disk_super->metadata_space_map_root, - metadata_len); -if (r <0 ) - goto bad_locked; +__copy_sm_root (cmd, disk_super); disk_super->mapping_root =cpu_to_le64 (cmd->root); disk_super->hint_root =cpu_to_le64 (cmd->hint_root);
@@ -313,10 +340,6 @@ static int __write_initial_superblock(struct dm_cache_metadata *cmd) disk_super->write_misses =cpu_to_le32 (0 );return dm_tm_commit (cmd->tm, sblock); - -bad_locked: -dm_bm_unlock (sblock); -return r; }static int __format_metadata (struct dm_cache_metadata *cmd)
@@ -560,7 +583,6 @@ static int __commit_transaction(struct dm_cache_metadata *cmd, flags_mutator mutator) {int r; -size_t metadata_len;struct cache_disk_superblock *disk_super;struct dm_block *sblock;
@@ -578,8 +600,8 @@ static int __commit_transaction(struct dm_cache_metadata *cmd,if (r <0 )return r; - r =dm_sm_root_size (cmd->metadata_sm, &metadata_len); -if (r <0 ) + r =__save_sm_root (cmd); +if (r)return r; r =superblock_lock (cmd, &sblock);
@@ -606,13 +628,7 @@ static int __commit_transaction(struct dm_cache_metadata *cmd, disk_super->read_misses =cpu_to_le32 (cmd->stats.read_misses); disk_super->write_hits =cpu_to_le32 (cmd->stats.write_hits); disk_super->write_misses =cpu_to_le32 (cmd->stats.write_misses); - - r =dm_sm_copy_root (cmd->metadata_sm, &disk_super->metadata_space_map_root, - metadata_len); -if (r <0 ) { -dm_bm_unlock (sblock); -return r; - } +__copy_sm_root (cmd, disk_super);return dm_tm_commit (cmd->tm, sblock); }
drivers/md/dm-era-target.c +42 -26
121 lines not on this page · raw message
drivers/md/dm-thin-metadata.c +47 -33
131 lines not on this page · raw message
Signature · 1 line
1.7.10.4