diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-18 16:32:17 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-18 16:32:17 -0400 |
commit | ef161ea1ff96337cbe2253afb72636474d90598e (patch) | |
tree | 58fa11ed86de6069d81a5f5b09310ae2a9bd0c70 /fs | |
parent | 8ccf863f09bbff209b124cbd90644c0b75b8fefd (diff) | |
parent | d37973082b453ba6b89ec07eb7b84305895d35e1 (diff) |
Merge branch 'for-linus-update' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux-btrfs
Pull btrfs data corruption fix from Chris Mason:
"I'm testing a pull with more fixes, but wanted to get this one out so
Greg can pick it up.
The corruption isn't easy to hit, you have to do a readonly snapshot
and have orphans in the snapshot. But my review and testing missed
the bug. Filipe has added a better xfstest to cover it"
* 'for-linus-update' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux-btrfs:
Revert "Btrfs: race free update of commit root for ro snapshots"
Diffstat (limited to 'fs')
-rw-r--r-- | fs/btrfs/inode.c | 36 | ||||
-rw-r--r-- | fs/btrfs/ioctl.c | 33 |
2 files changed, 33 insertions, 36 deletions
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index fc9c0439caa3..d23362f4464e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c | |||
@@ -5261,42 +5261,6 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry) | |||
5261 | iput(inode); | 5261 | iput(inode); |
5262 | inode = ERR_PTR(ret); | 5262 | inode = ERR_PTR(ret); |
5263 | } | 5263 | } |
5264 | /* | ||
5265 | * If orphan cleanup did remove any orphans, it means the tree | ||
5266 | * was modified and therefore the commit root is not the same as | ||
5267 | * the current root anymore. This is a problem, because send | ||
5268 | * uses the commit root and therefore can see inode items that | ||
5269 | * don't exist in the current root anymore, and for example make | ||
5270 | * calls to btrfs_iget, which will do tree lookups based on the | ||
5271 | * current root and not on the commit root. Those lookups will | ||
5272 | * fail, returning a -ESTALE error, and making send fail with | ||
5273 | * that error. So make sure a send does not see any orphans we | ||
5274 | * have just removed, and that it will see the same inodes | ||
5275 | * regardless of whether a transaction commit happened before | ||
5276 | * it started (meaning that the commit root will be the same as | ||
5277 | * the current root) or not. | ||
5278 | */ | ||
5279 | if (sub_root->node != sub_root->commit_root) { | ||
5280 | u64 sub_flags = btrfs_root_flags(&sub_root->root_item); | ||
5281 | |||
5282 | if (sub_flags & BTRFS_ROOT_SUBVOL_RDONLY) { | ||
5283 | struct extent_buffer *eb; | ||
5284 | |||
5285 | /* | ||
5286 | * Assert we can't have races between dentry | ||
5287 | * lookup called through the snapshot creation | ||
5288 | * ioctl and the VFS. | ||
5289 | */ | ||
5290 | ASSERT(mutex_is_locked(&dir->i_mutex)); | ||
5291 | |||
5292 | down_write(&root->fs_info->commit_root_sem); | ||
5293 | eb = sub_root->commit_root; | ||
5294 | sub_root->commit_root = | ||
5295 | btrfs_root_node(sub_root); | ||
5296 | up_write(&root->fs_info->commit_root_sem); | ||
5297 | free_extent_buffer(eb); | ||
5298 | } | ||
5299 | } | ||
5300 | } | 5264 | } |
5301 | 5265 | ||
5302 | return inode; | 5266 | return inode; |
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 0fe1aa047f15..8d2b76e29d3b 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c | |||
@@ -713,6 +713,39 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir, | |||
713 | if (ret) | 713 | if (ret) |
714 | goto fail; | 714 | goto fail; |
715 | 715 | ||
716 | ret = btrfs_orphan_cleanup(pending_snapshot->snap); | ||
717 | if (ret) | ||
718 | goto fail; | ||
719 | |||
720 | /* | ||
721 | * If orphan cleanup did remove any orphans, it means the tree was | ||
722 | * modified and therefore the commit root is not the same as the | ||
723 | * current root anymore. This is a problem, because send uses the | ||
724 | * commit root and therefore can see inode items that don't exist | ||
725 | * in the current root anymore, and for example make calls to | ||
726 | * btrfs_iget, which will do tree lookups based on the current root | ||
727 | * and not on the commit root. Those lookups will fail, returning a | ||
728 | * -ESTALE error, and making send fail with that error. So make sure | ||
729 | * a send does not see any orphans we have just removed, and that it | ||
730 | * will see the same inodes regardless of whether a transaction | ||
731 | * commit happened before it started (meaning that the commit root | ||
732 | * will be the same as the current root) or not. | ||
733 | */ | ||
734 | if (readonly && pending_snapshot->snap->node != | ||
735 | pending_snapshot->snap->commit_root) { | ||
736 | trans = btrfs_join_transaction(pending_snapshot->snap); | ||
737 | if (IS_ERR(trans) && PTR_ERR(trans) != -ENOENT) { | ||
738 | ret = PTR_ERR(trans); | ||
739 | goto fail; | ||
740 | } | ||
741 | if (!IS_ERR(trans)) { | ||
742 | ret = btrfs_commit_transaction(trans, | ||
743 | pending_snapshot->snap); | ||
744 | if (ret) | ||
745 | goto fail; | ||
746 | } | ||
747 | } | ||
748 | |||
716 | inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry); | 749 | inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry); |
717 | if (IS_ERR(inode)) { | 750 | if (IS_ERR(inode)) { |
718 | ret = PTR_ERR(inode); | 751 | ret = PTR_ERR(inode); |