btrfs: qgroup: Fix qgroup reserved space underflow by only freeing reserved ranges
[linux-2.6-block.git] / fs / btrfs / qgroup.c
index 0020889370216f5cfa1e187ef99c17c7ff3162ed..fc9dffaa9524b31c96ee3e74deb42da6262372c2 100644 (file)
@@ -2892,13 +2892,72 @@ cleanup:
        return ret;
 }
 
-static int __btrfs_qgroup_release_data(struct inode *inode, u64 start, u64 len,
-                                      int free)
+/* Free ranges specified by @reserved, normally in error path */
+static int qgroup_free_reserved_data(struct inode *inode,
+                       struct extent_changeset *reserved, u64 start, u64 len)
+{
+       struct btrfs_root *root = BTRFS_I(inode)->root;
+       struct ulist_node *unode;
+       struct ulist_iterator uiter;
+       struct extent_changeset changeset;
+       int freed = 0;
+       int ret;
+
+       extent_changeset_init(&changeset);
+       len = round_up(start + len, root->fs_info->sectorsize);
+       start = round_down(start, root->fs_info->sectorsize);
+
+       ULIST_ITER_INIT(&uiter);
+       while ((unode = ulist_next(&reserved->range_changed, &uiter))) {
+               u64 range_start = unode->val;
+               /* unode->aux is the inclusive end */
+               u64 range_len = unode->aux - range_start + 1;
+               u64 free_start;
+               u64 free_len;
+
+               extent_changeset_release(&changeset);
+
+               /* Only free range in range [start, start + len) */
+               if (range_start >= start + len ||
+                   range_start + range_len <= start)
+                       continue;
+               free_start = max(range_start, start);
+               free_len = min(start + len, range_start + range_len) -
+                          free_start;
+               /*
+                * TODO: To also modify reserved->ranges_reserved to reflect
+                * the modification.
+                *
+                * However as long as we free qgroup reserved according to
+                * EXTENT_QGROUP_RESERVED, we won't double free.
+                * So not need to rush.
+                */
+               ret = clear_record_extent_bits(&BTRFS_I(inode)->io_failure_tree,
+                               free_start, free_start + free_len - 1,
+                               EXTENT_QGROUP_RESERVED, &changeset);
+               if (ret < 0)
+                       goto out;
+               freed += changeset.bytes_changed;
+       }
+       btrfs_qgroup_free_refroot(root->fs_info, root->objectid, freed);
+       ret = freed;
+out:
+       extent_changeset_release(&changeset);
+       return ret;
+}
+
+static int __btrfs_qgroup_release_data(struct inode *inode,
+                       struct extent_changeset *reserved, u64 start, u64 len,
+                       int free)
 {
        struct extent_changeset changeset;
        int trace_op = QGROUP_RELEASE;
        int ret;
 
+       /* In release case, we shouldn't have @reserved */
+       WARN_ON(!free && reserved);
+       if (free && reserved)
+               return qgroup_free_reserved_data(inode, reserved, start, len);
        extent_changeset_init(&changeset);
        ret = clear_record_extent_bits(&BTRFS_I(inode)->io_tree, start, 
                        start + len -1, EXTENT_QGROUP_RESERVED, &changeset);
@@ -2924,14 +2983,17 @@ out:
  *
  * Should be called when a range of pages get invalidated before reaching disk.
  * Or for error cleanup case.
+ * if @reserved is given, only reserved range in [@start, @start + @len) will
+ * be freed.
  *
  * For data written to disk, use btrfs_qgroup_release_data().
  *
  * NOTE: This function may sleep for memory allocation.
  */
-int btrfs_qgroup_free_data(struct inode *inode, u64 start, u64 len)
+int btrfs_qgroup_free_data(struct inode *inode,
+                       struct extent_changeset *reserved, u64 start, u64 len)
 {
-       return __btrfs_qgroup_release_data(inode, start, len, 1);
+       return __btrfs_qgroup_release_data(inode, reserved, start, len, 1);
 }
 
 /*
@@ -2951,7 +3013,7 @@ int btrfs_qgroup_free_data(struct inode *inode, u64 start, u64 len)
  */
 int btrfs_qgroup_release_data(struct inode *inode, u64 start, u64 len)
 {
-       return __btrfs_qgroup_release_data(inode, start, len, 0);
+       return __btrfs_qgroup_release_data(inode, NULL, start, len, 0);
 }
 
 int btrfs_qgroup_reserve_meta(struct btrfs_root *root, int num_bytes,