Commit 99aacd19 authored by Boris Burkov's avatar Boris Burkov Committed by David Sterba
Browse files

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: default avatarQu Wenruo <wqu@suse.com>
Signed-off-by: default avatarBoris Burkov <boris@bur.io>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent d7c60055
Loading
Loading
Loading
Loading
+13 −2
Original line number Diff line number Diff line
@@ -4967,8 +4967,19 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info,
	list_for_each_entry(qg, &qgroup_list, iterator) {
		struct btrfs_qgroup_list *glist;

		ASSERT(qg->excl == qg->rfer);
		if (WARN_ON_ONCE(sign < 0 && qg->excl < num_bytes)) {
			btrfs_warn(fs_info,
				   "squota underflow qg %hu/%llu excl %llu num_bytes %llu",
				   btrfs_qgroup_level(qg->qgroupid),
				   btrfs_qgroup_subvolid(qg->qgroupid),
				   qg->excl, num_bytes);
			qg->excl = 0;
			qg->rfer = 0;
		} else {
			qg->excl += num_bytes * sign;
			qg->rfer += num_bytes * sign;
		}
		qgroup_dirty(fs_info, qg);

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