btrfs: qgroup: Fix reserved data space leak if we have multiple reserve calls
[sfrench/cifs-2.6.git] / fs / btrfs / qgroup.c
index f8a3c1b0a15a81d5cb3a9ea5cdd9c6205fe32d96..c4bb69941c77c721a7b6c69bb11d83938bfb69d2 100644 (file)
@@ -21,7 +21,7 @@
 #include "backref.h"
 #include "extent_io.h"
 #include "qgroup.h"
-
+#include "block-group.h"
 
 /* TODO XXX FIXME
  *  - subvol delete -> delete when ref goes to 0? delete limits also?
@@ -1312,8 +1312,9 @@ static int __del_qgroup_relation(struct btrfs_trans_handle *trans, u64 src,
        struct btrfs_qgroup *member;
        struct btrfs_qgroup_list *list;
        struct ulist *tmp;
+       bool found = false;
        int ret = 0;
-       int err;
+       int ret2;
 
        tmp = ulist_alloc(GFP_KERNEL);
        if (!tmp)
@@ -1327,28 +1328,39 @@ static int __del_qgroup_relation(struct btrfs_trans_handle *trans, u64 src,
 
        member = find_qgroup_rb(fs_info, src);
        parent = find_qgroup_rb(fs_info, dst);
-       if (!member || !parent) {
-               ret = -EINVAL;
-               goto out;
-       }
+       /*
+        * The parent/member pair doesn't exist, then try to delete the dead
+        * relation items only.
+        */
+       if (!member || !parent)
+               goto delete_item;
 
        /* check if such qgroup relation exist firstly */
        list_for_each_entry(list, &member->groups, next_group) {
-               if (list->group == parent)
-                       goto exist;
+               if (list->group == parent) {
+                       found = true;
+                       break;
+               }
        }
-       ret = -ENOENT;
-       goto out;
-exist:
+
+delete_item:
        ret = del_qgroup_relation_item(trans, src, dst);
-       err = del_qgroup_relation_item(trans, dst, src);
-       if (err && !ret)
-               ret = err;
+       if (ret < 0 && ret != -ENOENT)
+               goto out;
+       ret2 = del_qgroup_relation_item(trans, dst, src);
+       if (ret2 < 0 && ret2 != -ENOENT)
+               goto out;
 
-       spin_lock(&fs_info->qgroup_lock);
-       del_relation_rb(fs_info, src, dst);
-       ret = quick_update_accounting(fs_info, tmp, src, dst, -1);
-       spin_unlock(&fs_info->qgroup_lock);
+       /* At least one deletion succeeded, return 0 */
+       if (!ret || !ret2)
+               ret = 0;
+
+       if (found) {
+               spin_lock(&fs_info->qgroup_lock);
+               del_relation_rb(fs_info, src, dst);
+               ret = quick_update_accounting(fs_info, tmp, src, dst, -1);
+               spin_unlock(&fs_info->qgroup_lock);
+       }
 out:
        ulist_free(tmp);
        return ret;
@@ -3154,9 +3166,6 @@ out:
        btrfs_free_path(path);
 
        mutex_lock(&fs_info->qgroup_rescan_lock);
-       if (!btrfs_fs_closing(fs_info))
-               fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_RESCAN;
-
        if (err > 0 &&
            fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT) {
                fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;
@@ -3172,16 +3181,30 @@ out:
        trans = btrfs_start_transaction(fs_info->quota_root, 1);
        if (IS_ERR(trans)) {
                err = PTR_ERR(trans);
+               trans = NULL;
                btrfs_err(fs_info,
                          "fail to start transaction for status update: %d",
                          err);
-               goto done;
        }
-       ret = update_qgroup_status_item(trans);
-       if (ret < 0) {
-               err = ret;
-               btrfs_err(fs_info, "fail to update qgroup status: %d", err);
+
+       mutex_lock(&fs_info->qgroup_rescan_lock);
+       if (!btrfs_fs_closing(fs_info))
+               fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_RESCAN;
+       if (trans) {
+               ret = update_qgroup_status_item(trans);
+               if (ret < 0) {
+                       err = ret;
+                       btrfs_err(fs_info, "fail to update qgroup status: %d",
+                                 err);
+               }
        }
+       fs_info->qgroup_rescan_running = false;
+       complete_all(&fs_info->qgroup_rescan_completion);
+       mutex_unlock(&fs_info->qgroup_rescan_lock);
+
+       if (!trans)
+               return;
+
        btrfs_end_transaction(trans);
 
        if (btrfs_fs_closing(fs_info)) {
@@ -3192,12 +3215,6 @@ out:
        } else {
                btrfs_err(fs_info, "qgroup scan failed with %d", err);
        }
-
-done:
-       mutex_lock(&fs_info->qgroup_rescan_lock);
-       fs_info->qgroup_rescan_running = false;
-       mutex_unlock(&fs_info->qgroup_rescan_lock);
-       complete_all(&fs_info->qgroup_rescan_completion);
 }
 
 /*
@@ -3425,6 +3442,9 @@ cleanup:
        while ((unode = ulist_next(&reserved->range_changed, &uiter)))
                clear_extent_bit(&BTRFS_I(inode)->io_tree, unode->val,
                                 unode->aux, EXTENT_QGROUP_RESERVED, 0, 0, NULL);
+       /* Also free data bytes of already reserved one */
+       btrfs_qgroup_free_refroot(root->fs_info, root->root_key.objectid,
+                                 orig_reserved, BTRFS_QGROUP_RSV_DATA);
        extent_changeset_release(reserved);
        return ret;
 }
@@ -3469,7 +3489,7 @@ static int qgroup_free_reserved_data(struct inode *inode,
                 * EXTENT_QGROUP_RESERVED, we won't double free.
                 * So not need to rush.
                 */
-               ret = clear_record_extent_bits(&BTRFS_I(inode)->io_failure_tree,
+               ret = clear_record_extent_bits(&BTRFS_I(inode)->io_tree,
                                free_start, free_start + free_len - 1,
                                EXTENT_QGROUP_RESERVED, &changeset);
                if (ret < 0)