]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
btrfs: relax squota parent qgroup deletion rule
authorBoris Burkov <boris@bur.io>
Mon, 1 Dec 2025 23:35:02 +0000 (15:35 -0800)
committerDavid Sterba <dsterba@suse.com>
Tue, 3 Feb 2026 05:38:31 +0000 (06:38 +0100)
Currently, with squotas, we do not allow removing a parent qgroup with
no members if it still has usage accounted to it. This makes it really
difficult to recover from accounting bugs, as we have no good way of
getting back to 0 usage.

Instead, allow deletion (it's safe at 0 members..) while still warning
about the inconsistency by adding a squota parent check.

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 3a74759b59ee4f966edf734e6533ee0a2e7e2573..ae4a1b76646c5ccfec0f41635017482be1f304c8 100644 (file)
@@ -1718,6 +1718,36 @@ out:
        return ret;
 }
 
+static bool can_delete_parent_qgroup(struct btrfs_qgroup *qgroup)
+
+{
+       ASSERT(btrfs_qgroup_level(qgroup->qgroupid));
+       return list_empty(&qgroup->members);
+}
+
+/*
+ * Return true if we can delete the squota qgroup and false otherwise.
+ *
+ * Rules for whether we can delete:
+ *
+ * A subvolume qgroup can be removed iff the subvolume is fully deleted, which
+ * is iff there is 0 usage in the qgroup.
+ *
+ * A higher level qgroup can be removed iff it has no members.
+ * Note: We audit its usage to warn on inconsitencies without blocking deletion.
+ */
+static bool can_delete_squota_qgroup(struct btrfs_fs_info *fs_info, struct btrfs_qgroup *qgroup)
+{
+       ASSERT(btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE);
+
+       if (btrfs_qgroup_level(qgroup->qgroupid) > 0) {
+               squota_check_parent_usage(fs_info, qgroup);
+               return can_delete_parent_qgroup(qgroup);
+       }
+
+       return !(qgroup->rfer || qgroup->excl || qgroup->rfer_cmpr || qgroup->excl_cmpr);
+}
+
 /*
  * Return 0 if we can not delete the qgroup (not empty or has children etc).
  * Return >0 if we can delete the qgroup.
@@ -1728,23 +1758,13 @@ static int can_delete_qgroup(struct btrfs_fs_info *fs_info, struct btrfs_qgroup
        struct btrfs_key key;
        BTRFS_PATH_AUTO_FREE(path);
 
-       /*
-        * Squota would never be inconsistent, but there can still be case
-        * where a dropped subvolume still has qgroup numbers, and squota
-        * relies on such qgroup for future accounting.
-        *
-        * So for squota, do not allow dropping any non-zero qgroup.
-        */
-       if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE &&
-           (qgroup->rfer || qgroup->excl || qgroup->excl_cmpr || qgroup->rfer_cmpr))
-               return 0;
+       /* Since squotas cannot be inconsistent, they have special rules for deletion. */
+       if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE)
+               return can_delete_squota_qgroup(fs_info, qgroup);
 
        /* For higher level qgroup, we can only delete it if it has no child. */
-       if (btrfs_qgroup_level(qgroup->qgroupid)) {
-               if (!list_empty(&qgroup->members))
-                       return 0;
-               return 1;
-       }
+       if (btrfs_qgroup_level(qgroup->qgroupid))
+               return can_delete_parent_qgroup(qgroup);
 
        /*
         * For level-0 qgroups, we can only delete it if it has no subvolume