btrfs: remove free space items when disabling space cache v1
[sfrench/cifs-2.6.git] / fs / btrfs / free-space-cache.c
index e71def319655775c0fa2e3a8b3f7f7b06e63ded6..4d8897879c9cbd98de1032e4e1b6ea0857c3f64a 100644 (file)
@@ -206,6 +206,65 @@ int create_free_space_inode(struct btrfs_trans_handle *trans,
                                         ino, block_group->start);
 }
 
+/*
+ * inode is an optional sink: if it is NULL, btrfs_remove_free_space_inode
+ * handles lookup, otherwise it takes ownership and iputs the inode.
+ * Don't reuse an inode pointer after passing it into this function.
+ */
+int btrfs_remove_free_space_inode(struct btrfs_trans_handle *trans,
+                                 struct inode *inode,
+                                 struct btrfs_block_group *block_group)
+{
+       struct btrfs_path *path;
+       struct btrfs_key key;
+       int ret = 0;
+
+       path = btrfs_alloc_path();
+       if (!path)
+               return -ENOMEM;
+
+       if (!inode)
+               inode = lookup_free_space_inode(block_group, path);
+       if (IS_ERR(inode)) {
+               if (PTR_ERR(inode) != -ENOENT)
+                       ret = PTR_ERR(inode);
+               goto out;
+       }
+       ret = btrfs_orphan_add(trans, BTRFS_I(inode));
+       if (ret) {
+               btrfs_add_delayed_iput(inode);
+               goto out;
+       }
+       clear_nlink(inode);
+       /* One for the block groups ref */
+       spin_lock(&block_group->lock);
+       if (block_group->iref) {
+               block_group->iref = 0;
+               block_group->inode = NULL;
+               spin_unlock(&block_group->lock);
+               iput(inode);
+       } else {
+               spin_unlock(&block_group->lock);
+       }
+       /* One for the lookup ref */
+       btrfs_add_delayed_iput(inode);
+
+       key.objectid = BTRFS_FREE_SPACE_OBJECTID;
+       key.type = 0;
+       key.offset = block_group->start;
+       ret = btrfs_search_slot(trans, trans->fs_info->tree_root, &key, path,
+                               -1, 1);
+       if (ret) {
+               if (ret > 0)
+                       ret = 0;
+               goto out;
+       }
+       ret = btrfs_del_item(trans, trans->fs_info->tree_root, path);
+out:
+       btrfs_free_path(path);
+       return ret;
+}
+
 int btrfs_check_trunc_cache_free_space(struct btrfs_fs_info *fs_info,
                                       struct btrfs_block_rsv *rsv)
 {
@@ -3768,24 +3827,56 @@ bool btrfs_free_space_cache_v1_active(struct btrfs_fs_info *fs_info)
        return btrfs_super_cache_generation(fs_info->super_copy);
 }
 
+static int cleanup_free_space_cache_v1(struct btrfs_fs_info *fs_info,
+                                      struct btrfs_trans_handle *trans)
+{
+       struct btrfs_block_group *block_group;
+       struct rb_node *node;
+       int ret;
+
+       btrfs_info(fs_info, "cleaning free space cache v1");
+
+       node = rb_first(&fs_info->block_group_cache_tree);
+       while (node) {
+               block_group = rb_entry(node, struct btrfs_block_group, cache_node);
+               ret = btrfs_remove_free_space_inode(trans, NULL, block_group);
+               if (ret)
+                       goto out;
+               node = rb_next(node);
+       }
+out:
+       return ret;
+}
+
 int btrfs_set_free_space_cache_v1_active(struct btrfs_fs_info *fs_info, bool active)
 {
        struct btrfs_trans_handle *trans;
        int ret;
 
        /*
-        * update_super_roots will appropriately set
-        * super_copy->cache_generation based on the SPACE_CACHE option, so all
-        * we have to do is trigger a transaction commit.
+        * update_super_roots will appropriately set or unset
+        * super_copy->cache_generation based on SPACE_CACHE and
+        * BTRFS_FS_CLEANUP_SPACE_CACHE_V1. For this reason, we need a
+        * transaction commit whether we are enabling space cache v1 and don't
+        * have any other work to do, or are disabling it and removing free
+        * space inodes.
         */
        trans = btrfs_start_transaction(fs_info->tree_root, 0);
        if (IS_ERR(trans))
                return PTR_ERR(trans);
 
-       if (!active)
+       if (!active) {
                set_bit(BTRFS_FS_CLEANUP_SPACE_CACHE_V1, &fs_info->flags);
+               ret = cleanup_free_space_cache_v1(fs_info, trans);
+               if (ret) {
+                       btrfs_abort_transaction(trans, ret);
+                       btrfs_end_transaction(trans);
+                       goto out;
+               }
+       }
 
        ret = btrfs_commit_transaction(trans);
+out:
        clear_bit(BTRFS_FS_CLEANUP_SPACE_CACHE_V1, &fs_info->flags);
 
        return ret;