Skip to content

Commit 99aacd1

Browse files
boryaskdave
authored andcommitted
btrfs: clamp to avoid squota underflow
Simple quota accounting can undercount metadata tree block allocations in certain scenarios. When an undercounted subvolume is deleted and its tree blocks freed, the free deltas decrement rfer/excl past zero, wrapping the u64 to a value near U64_MAX. Once wrapped, can_delete_squota_qgroup() sees non-zero rfer and refuses to delete the qgroup. The qgroup becomes permanently orphaned in the quota tree, since there is no subvolume left to generate frees that would bring the counter back to zero. While we ultimately want to fix any mis-accounting at the source, it is also helpful and worthwhile to mitigate the damage by clamping rfer and excl to zero on underflow rather than allowing the u64 to wrap. This at least allows us to clean up the messed up qgroups on subvol deletion. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Boris Burkov <boris@bur.io> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent d7c6005 commit 99aacd1

1 file changed

Lines changed: 13 additions & 2 deletions

File tree

fs/btrfs/qgroup.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4967,8 +4967,19 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info,
49674967
list_for_each_entry(qg, &qgroup_list, iterator) {
49684968
struct btrfs_qgroup_list *glist;
49694969

4970-
qg->excl += num_bytes * sign;
4971-
qg->rfer += num_bytes * sign;
4970+
ASSERT(qg->excl == qg->rfer);
4971+
if (WARN_ON_ONCE(sign < 0 && qg->excl < num_bytes)) {
4972+
btrfs_warn(fs_info,
4973+
"squota underflow qg %hu/%llu excl %llu num_bytes %llu",
4974+
btrfs_qgroup_level(qg->qgroupid),
4975+
btrfs_qgroup_subvolid(qg->qgroupid),
4976+
qg->excl, num_bytes);
4977+
qg->excl = 0;
4978+
qg->rfer = 0;
4979+
} else {
4980+
qg->excl += num_bytes * sign;
4981+
qg->rfer += num_bytes * sign;
4982+
}
49724983
qgroup_dirty(fs_info, qg);
49734984

49744985
list_for_each_entry(glist, &qg->groups, next_group)

0 commit comments

Comments
 (0)