mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-13 03:39:36 +00:00
btrfs: fix invalid removal of root ref
If we have the following sequence of events btrfs sub create A btrfs sub create A/B btrfs sub snap A C mkdir C/foo mv A/B C/foo rm -rf * We will end up with a transaction abort. The reason for this is because we create a root ref for B pointing to A. When we create a snapshot of C we still have B in our tree, but because the root ref points to A and not C we will make it appear to be empty. The problem happens when we move B into C. This removes the root ref for B pointing to A and adds a ref of B pointing to C. When we rmdir C we'll see that we have a ref to our root and remove the root ref, despite not actually matching our reference name. Now btrfs_del_root_ref() allowing this to work is a bug as well, however we know that this inode does not actually point to a root ref in the first place, so we shouldn't be calling btrfs_del_root_ref() in the first place and instead simply look up our dir index for this item and do the rest of the removal. CC: stable@vger.kernel.org # 4.4+ Signed-off-by: Josef Bacik <josef@toxicpanda.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
045d3967b6
commit
d49d3287e7
@ -4283,13 +4283,16 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
|
||||
}
|
||||
btrfs_release_path(path);
|
||||
|
||||
ret = btrfs_del_root_ref(trans, objectid, root->root_key.objectid,
|
||||
dir_ino, &index, name, name_len);
|
||||
if (ret < 0) {
|
||||
if (ret != -ENOENT) {
|
||||
btrfs_abort_transaction(trans, ret);
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* This is a placeholder inode for a subvolume we didn't have a
|
||||
* reference to at the time of the snapshot creation. In the meantime
|
||||
* we could have renamed the real subvol link into our snapshot, so
|
||||
* depending on btrfs_del_root_ref to return -ENOENT here is incorret.
|
||||
* Instead simply lookup the dir_index_item for this entry so we can
|
||||
* remove it. Otherwise we know we have a ref to the root and we can
|
||||
* call btrfs_del_root_ref, and it _shouldn't_ fail.
|
||||
*/
|
||||
if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
|
||||
di = btrfs_search_dir_index_item(root, path, dir_ino,
|
||||
name, name_len);
|
||||
if (IS_ERR_OR_NULL(di)) {
|
||||
@ -4304,8 +4307,16 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
|
||||
index = key.offset;
|
||||
btrfs_release_path(path);
|
||||
} else {
|
||||
ret = btrfs_del_root_ref(trans, objectid,
|
||||
root->root_key.objectid, dir_ino,
|
||||
&index, name, name_len);
|
||||
if (ret) {
|
||||
btrfs_abort_transaction(trans, ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
btrfs_release_path(path);
|
||||
|
||||
ret = btrfs_delete_delayed_dir_index(trans, BTRFS_I(dir), index);
|
||||
if (ret) {
|
||||
|
Loading…
Reference in New Issue
Block a user