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"
This commit is contained in:
commit
ef161ea1ff
@ -5261,42 +5261,6 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
|
||||
iput(inode);
|
||||
inode = ERR_PTR(ret);
|
||||
}
|
||||
/*
|
||||
* If orphan cleanup did remove any orphans, it means the tree
|
||||
* was modified and therefore the commit root is not the same as
|
||||
* the current root anymore. This is a problem, because send
|
||||
* uses the commit root and therefore can see inode items that
|
||||
* don't exist in the current root anymore, and for example make
|
||||
* calls to btrfs_iget, which will do tree lookups based on the
|
||||
* current root and not on the commit root. Those lookups will
|
||||
* fail, returning a -ESTALE error, and making send fail with
|
||||
* that error. So make sure a send does not see any orphans we
|
||||
* have just removed, and that it will see the same inodes
|
||||
* regardless of whether a transaction commit happened before
|
||||
* it started (meaning that the commit root will be the same as
|
||||
* the current root) or not.
|
||||
*/
|
||||
if (sub_root->node != sub_root->commit_root) {
|
||||
u64 sub_flags = btrfs_root_flags(&sub_root->root_item);
|
||||
|
||||
if (sub_flags & BTRFS_ROOT_SUBVOL_RDONLY) {
|
||||
struct extent_buffer *eb;
|
||||
|
||||
/*
|
||||
* Assert we can't have races between dentry
|
||||
* lookup called through the snapshot creation
|
||||
* ioctl and the VFS.
|
||||
*/
|
||||
ASSERT(mutex_is_locked(&dir->i_mutex));
|
||||
|
||||
down_write(&root->fs_info->commit_root_sem);
|
||||
eb = sub_root->commit_root;
|
||||
sub_root->commit_root =
|
||||
btrfs_root_node(sub_root);
|
||||
up_write(&root->fs_info->commit_root_sem);
|
||||
free_extent_buffer(eb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inode;
|
||||
|
@ -713,6 +713,39 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = btrfs_orphan_cleanup(pending_snapshot->snap);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* If orphan cleanup did remove any orphans, it means the tree was
|
||||
* modified and therefore the commit root is not the same as the
|
||||
* current root anymore. This is a problem, because send uses the
|
||||
* commit root and therefore can see inode items that don't exist
|
||||
* in the current root anymore, and for example make calls to
|
||||
* btrfs_iget, which will do tree lookups based on the current root
|
||||
* and not on the commit root. Those lookups will fail, returning a
|
||||
* -ESTALE error, and making send fail with that error. So make sure
|
||||
* a send does not see any orphans we have just removed, and that it
|
||||
* will see the same inodes regardless of whether a transaction
|
||||
* commit happened before it started (meaning that the commit root
|
||||
* will be the same as the current root) or not.
|
||||
*/
|
||||
if (readonly && pending_snapshot->snap->node !=
|
||||
pending_snapshot->snap->commit_root) {
|
||||
trans = btrfs_join_transaction(pending_snapshot->snap);
|
||||
if (IS_ERR(trans) && PTR_ERR(trans) != -ENOENT) {
|
||||
ret = PTR_ERR(trans);
|
||||
goto fail;
|
||||
}
|
||||
if (!IS_ERR(trans)) {
|
||||
ret = btrfs_commit_transaction(trans,
|
||||
pending_snapshot->snap);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry);
|
||||
if (IS_ERR(inode)) {
|
||||
ret = PTR_ERR(inode);
|
||||
|
Loading…
Reference in New Issue
Block a user