Skip to content

Commit 74e9795

Browse files
boryaskdave
authored andcommitted
btrfs: qgroup: fix qgroup prealloc rsv leak in subvolume operations
Create subvolume, create snapshot and delete subvolume all use btrfs_subvolume_reserve_metadata() to reserve metadata for the changes done to the parent subvolume's fs tree, which cannot be mediated in the normal way via start_transaction. When quota groups (squota or qgroups) are enabled, this reserves qgroup metadata of type PREALLOC. Once the operation is associated to a transaction, we convert PREALLOC to PERTRANS, which gets cleared in bulk at the end of the transaction. However, the error paths of these three operations were not implementing this lifecycle correctly. They unconditionally converted the PREALLOC to PERTRANS in a generic cleanup step regardless of errors or whether the operation was fully associated to a transaction or not. This resulted in error paths occasionally converting this rsv to PERTRANS without calling record_root_in_trans successfully, which meant that unless that root got recorded in the transaction by some other thread, the end of the transaction would not free that root's PERTRANS, leaking it. Ultimately, this resulted in hitting a WARN in CONFIG_BTRFS_DEBUG builds at unmount for the leaked reservation. The fix is to ensure that every qgroup PREALLOC reservation observes the following properties: 1. any failure before record_root_in_trans is called successfully results in freeing the PREALLOC reservation. 2. after record_root_in_trans, we convert to PERTRANS, and now the transaction owns freeing the reservation. This patch enforces those properties on the three operations. Without it, generic/269 with squotas enabled at mkfs time would fail in ~5-10 runs on my system. With this patch, it ran successfully 1000 times in a row. Fixes: e85fde5 ("btrfs: qgroup: fix qgroup meta rsv leak for subvolume operations") CC: [email protected] # 6.1+ Reviewed-by: Qu Wenruo <[email protected]> Signed-off-by: Boris Burkov <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 141fb8c commit 74e9795

File tree

4 files changed

+40
-22
lines changed

4 files changed

+40
-22
lines changed

fs/btrfs/inode.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4503,6 +4503,7 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
45034503
struct btrfs_trans_handle *trans;
45044504
struct btrfs_block_rsv block_rsv;
45054505
u64 root_flags;
4506+
u64 qgroup_reserved = 0;
45064507
int ret;
45074508

45084509
down_write(&fs_info->subvol_sem);
@@ -4547,12 +4548,20 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
45474548
ret = btrfs_subvolume_reserve_metadata(root, &block_rsv, 5, true);
45484549
if (ret)
45494550
goto out_undead;
4551+
qgroup_reserved = block_rsv.qgroup_rsv_reserved;
45504552

45514553
trans = btrfs_start_transaction(root, 0);
45524554
if (IS_ERR(trans)) {
45534555
ret = PTR_ERR(trans);
45544556
goto out_release;
45554557
}
4558+
ret = btrfs_record_root_in_trans(trans, root);
4559+
if (ret) {
4560+
btrfs_abort_transaction(trans, ret);
4561+
goto out_end_trans;
4562+
}
4563+
btrfs_qgroup_convert_reserved_meta(root, qgroup_reserved);
4564+
qgroup_reserved = 0;
45564565
trans->block_rsv = &block_rsv;
45574566
trans->bytes_reserved = block_rsv.size;
45584567

@@ -4611,7 +4620,9 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
46114620
ret = btrfs_end_transaction(trans);
46124621
inode->i_flags |= S_DEAD;
46134622
out_release:
4614-
btrfs_subvolume_release_metadata(root, &block_rsv);
4623+
btrfs_block_rsv_release(fs_info, &block_rsv, (u64)-1, NULL);
4624+
if (qgroup_reserved)
4625+
btrfs_qgroup_free_meta_prealloc(root, qgroup_reserved);
46154626
out_undead:
46164627
if (ret) {
46174628
spin_lock(&dest->root_item_lock);

fs/btrfs/ioctl.c

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ static noinline int create_subvol(struct mnt_idmap *idmap,
613613
int ret;
614614
dev_t anon_dev;
615615
u64 objectid;
616+
u64 qgroup_reserved = 0;
616617

617618
root_item = kzalloc(sizeof(*root_item), GFP_KERNEL);
618619
if (!root_item)
@@ -650,13 +651,18 @@ static noinline int create_subvol(struct mnt_idmap *idmap,
650651
trans_num_items, false);
651652
if (ret)
652653
goto out_new_inode_args;
654+
qgroup_reserved = block_rsv.qgroup_rsv_reserved;
653655

654656
trans = btrfs_start_transaction(root, 0);
655657
if (IS_ERR(trans)) {
656658
ret = PTR_ERR(trans);
657-
btrfs_subvolume_release_metadata(root, &block_rsv);
658-
goto out_new_inode_args;
659+
goto out_release_rsv;
659660
}
661+
ret = btrfs_record_root_in_trans(trans, BTRFS_I(dir)->root);
662+
if (ret)
663+
goto out;
664+
btrfs_qgroup_convert_reserved_meta(root, qgroup_reserved);
665+
qgroup_reserved = 0;
660666
trans->block_rsv = &block_rsv;
661667
trans->bytes_reserved = block_rsv.size;
662668
/* Tree log can't currently deal with an inode which is a new root. */
@@ -767,9 +773,11 @@ static noinline int create_subvol(struct mnt_idmap *idmap,
767773
out:
768774
trans->block_rsv = NULL;
769775
trans->bytes_reserved = 0;
770-
btrfs_subvolume_release_metadata(root, &block_rsv);
771-
772776
btrfs_end_transaction(trans);
777+
out_release_rsv:
778+
btrfs_block_rsv_release(fs_info, &block_rsv, (u64)-1, NULL);
779+
if (qgroup_reserved)
780+
btrfs_qgroup_free_meta_prealloc(root, qgroup_reserved);
773781
out_new_inode_args:
774782
btrfs_new_inode_args_destroy(&new_inode_args);
775783
out_inode:
@@ -791,6 +799,8 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
791799
struct btrfs_pending_snapshot *pending_snapshot;
792800
unsigned int trans_num_items;
793801
struct btrfs_trans_handle *trans;
802+
struct btrfs_block_rsv *block_rsv;
803+
u64 qgroup_reserved = 0;
794804
int ret;
795805

796806
/* We do not support snapshotting right now. */
@@ -827,19 +837,19 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
827837
goto free_pending;
828838
}
829839

830-
btrfs_init_block_rsv(&pending_snapshot->block_rsv,
831-
BTRFS_BLOCK_RSV_TEMP);
840+
block_rsv = &pending_snapshot->block_rsv;
841+
btrfs_init_block_rsv(block_rsv, BTRFS_BLOCK_RSV_TEMP);
832842
/*
833843
* 1 to add dir item
834844
* 1 to add dir index
835845
* 1 to update parent inode item
836846
*/
837847
trans_num_items = create_subvol_num_items(inherit) + 3;
838-
ret = btrfs_subvolume_reserve_metadata(BTRFS_I(dir)->root,
839-
&pending_snapshot->block_rsv,
848+
ret = btrfs_subvolume_reserve_metadata(BTRFS_I(dir)->root, block_rsv,
840849
trans_num_items, false);
841850
if (ret)
842851
goto free_pending;
852+
qgroup_reserved = block_rsv->qgroup_rsv_reserved;
843853

844854
pending_snapshot->dentry = dentry;
845855
pending_snapshot->root = root;
@@ -852,6 +862,13 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
852862
ret = PTR_ERR(trans);
853863
goto fail;
854864
}
865+
ret = btrfs_record_root_in_trans(trans, BTRFS_I(dir)->root);
866+
if (ret) {
867+
btrfs_end_transaction(trans);
868+
goto fail;
869+
}
870+
btrfs_qgroup_convert_reserved_meta(root, qgroup_reserved);
871+
qgroup_reserved = 0;
855872

856873
trans->pending_snapshot = pending_snapshot;
857874

@@ -881,7 +898,9 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
881898
if (ret && pending_snapshot->snap)
882899
pending_snapshot->snap->anon_dev = 0;
883900
btrfs_put_root(pending_snapshot->snap);
884-
btrfs_subvolume_release_metadata(root, &pending_snapshot->block_rsv);
901+
btrfs_block_rsv_release(fs_info, block_rsv, (u64)-1, NULL);
902+
if (qgroup_reserved)
903+
btrfs_qgroup_free_meta_prealloc(root, qgroup_reserved);
885904
free_pending:
886905
if (pending_snapshot->anon_dev)
887906
free_anon_bdev(pending_snapshot->anon_dev);

fs/btrfs/root-tree.c

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -548,13 +548,3 @@ int btrfs_subvolume_reserve_metadata(struct btrfs_root *root,
548548
}
549549
return ret;
550550
}
551-
552-
void btrfs_subvolume_release_metadata(struct btrfs_root *root,
553-
struct btrfs_block_rsv *rsv)
554-
{
555-
struct btrfs_fs_info *fs_info = root->fs_info;
556-
u64 qgroup_to_release;
557-
558-
btrfs_block_rsv_release(fs_info, rsv, (u64)-1, &qgroup_to_release);
559-
btrfs_qgroup_convert_reserved_meta(root, qgroup_to_release);
560-
}

fs/btrfs/root-tree.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ struct btrfs_trans_handle;
1818
int btrfs_subvolume_reserve_metadata(struct btrfs_root *root,
1919
struct btrfs_block_rsv *rsv,
2020
int nitems, bool use_global_rsv);
21-
void btrfs_subvolume_release_metadata(struct btrfs_root *root,
22-
struct btrfs_block_rsv *rsv);
2321
int btrfs_add_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
2422
u64 ref_id, u64 dirid, u64 sequence,
2523
const struct fscrypt_str *name);

0 commit comments

Comments
 (0)