]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
btrfs: check squota parent usage on membership change
authorBoris Burkov <boris@bur.io>
Mon, 1 Dec 2025 23:33:49 +0000 (15:33 -0800)
committerDavid Sterba <dsterba@suse.com>
Tue, 3 Feb 2026 05:38:31 +0000 (06:38 +0100)
We could have detected the quick inherit bug more directly if we had
an extra warning about squota hierarchy consistency while modifying the
hierarchy. In squotas, the parent usage always simply adds up to the sum of
its children, so we can just check for that when changing membership and
detect more accounting bugs.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/qgroup.c

index 206587820fec097838a053404074608937ebde10..3a74759b59ee4f966edf734e6533ee0a2e7e2573 100644 (file)
@@ -346,6 +346,42 @@ int btrfs_verify_qgroup_counts(const struct btrfs_fs_info *fs_info, u64 qgroupid
 }
 #endif
 
+static bool squota_check_parent_usage(struct btrfs_fs_info *fs_info, struct btrfs_qgroup *parent)
+{
+       u64 excl_sum = 0;
+       u64 rfer_sum = 0;
+       u64 excl_cmpr_sum = 0;
+       u64 rfer_cmpr_sum = 0;
+       struct btrfs_qgroup_list *glist;
+       int nr_members = 0;
+       bool mismatch;
+
+       if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_SIMPLE)
+               return false;
+       if (btrfs_qgroup_level(parent->qgroupid) == 0)
+               return false;
+
+       /* Eligible parent qgroup. Squota; level > 0; empty members list. */
+       list_for_each_entry(glist, &parent->members, next_member) {
+               excl_sum += glist->member->excl;
+               rfer_sum += glist->member->rfer;
+               excl_cmpr_sum += glist->member->excl_cmpr;
+               rfer_cmpr_sum += glist->member->rfer_cmpr;
+               nr_members++;
+       }
+       mismatch = (parent->excl != excl_sum || parent->rfer != rfer_sum ||
+                   parent->excl_cmpr != excl_cmpr_sum || parent->rfer_cmpr != excl_cmpr_sum);
+
+       WARN(mismatch,
+            "parent squota qgroup %hu/%llu has mismatched usage from its %d members. "
+            "%llu %llu %llu %llu vs %llu %llu %llu %llu\n",
+            btrfs_qgroup_level(parent->qgroupid),
+            btrfs_qgroup_subvolid(parent->qgroupid), nr_members, parent->excl,
+            parent->rfer, parent->excl_cmpr, parent->rfer_cmpr, excl_sum,
+            rfer_sum, excl_cmpr_sum, rfer_cmpr_sum);
+       return mismatch;
+}
+
 __printf(2, 3)
 static void qgroup_mark_inconsistent(struct btrfs_fs_info *fs_info, const char *fmt, ...)
 {
@@ -1562,6 +1598,7 @@ int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst
                goto out;
        }
        ret = quick_update_accounting(fs_info, src, dst, 1);
+       squota_check_parent_usage(fs_info, parent);
        spin_unlock(&fs_info->qgroup_lock);
 out:
        kfree(prealloc);
@@ -1618,6 +1655,8 @@ delete_item:
                spin_lock(&fs_info->qgroup_lock);
                del_relation_rb(fs_info, src, dst);
                ret = quick_update_accounting(fs_info, src, dst, -1);
+               ASSERT(parent);
+               squota_check_parent_usage(fs_info, parent);
                spin_unlock(&fs_info->qgroup_lock);
        }
 out: