Btrfs: fix free space cache when there are pinned extents and clusters V2
Josef Bacik [Fri, 1 Apr 2011 14:55:00 +0000 (14:55 +0000)]
I noticed a huge problem with the free space cache that was presenting
as an early ENOSPC.  Turns out when writing the free space cache out I
forgot to take into account pinned extents and more importantly
clusters.  This would result in us leaking free space everytime we
unmounted the filesystem and remounted it.

I fix this by making sure to check and see if the current block group
has a cluster and writing out any entries that are in the cluster to the
cache, as well as writing any pinned extents we currently have to the
cache since those will be available for us to use the next time the fs
mounts.

This patch also adds a check to the end of load_free_space_cache to make
sure we got the right amount of free space cache, and if not make sure
to clear the cache and re-cache the old fashioned way.

Signed-off-by: Josef Bacik <josef@redhat.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>

fs/btrfs/free-space-cache.c

index 13575de..f561c95 100644 (file)
@@ -24,6 +24,7 @@
 #include "free-space-cache.h"
 #include "transaction.h"
 #include "disk-io.h"
+#include "extent_io.h"
 
 #define BITS_PER_BITMAP                (PAGE_CACHE_SIZE * 8)
 #define MAX_CACHE_BYTES_PER_GIG        (32 * 1024)
@@ -224,6 +225,7 @@ int load_free_space_cache(struct btrfs_fs_info *fs_info,
        u64 num_entries;
        u64 num_bitmaps;
        u64 generation;
+       u64 used = btrfs_block_group_used(&block_group->item);
        u32 cur_crc = ~(u32)0;
        pgoff_t index = 0;
        unsigned long first_page_offset;
@@ -469,6 +471,17 @@ next:
                index++;
        }
 
+       spin_lock(&block_group->tree_lock);
+       if (block_group->free_space != (block_group->key.offset - used -
+                                       block_group->bytes_super)) {
+               spin_unlock(&block_group->tree_lock);
+               printk(KERN_ERR "block group %llu has an wrong amount of free "
+                      "space\n", block_group->key.objectid);
+               ret = 0;
+               goto free_cache;
+       }
+       spin_unlock(&block_group->tree_lock);
+
        ret = 1;
 out:
        kfree(checksums);
@@ -497,8 +510,11 @@ int btrfs_write_out_cache(struct btrfs_root *root,
        struct list_head *pos, *n;
        struct page *page;
        struct extent_state *cached_state = NULL;
+       struct btrfs_free_cluster *cluster = NULL;
+       struct extent_io_tree *unpin = NULL;
        struct list_head bitmap_list;
        struct btrfs_key key;
+       u64 start, end, len;
        u64 bytes = 0;
        u32 *crc, *checksums;
        pgoff_t index = 0, last_index = 0;
@@ -507,6 +523,7 @@ int btrfs_write_out_cache(struct btrfs_root *root,
        int entries = 0;
        int bitmaps = 0;
        int ret = 0;
+       bool next_page = false;
 
        root = root->fs_info->tree_root;
 
@@ -553,6 +570,18 @@ int btrfs_write_out_cache(struct btrfs_root *root,
         */
        first_page_offset = (sizeof(u32) * num_checksums) + sizeof(u64);
 
+       /* Get the cluster for this block_group if it exists */
+       if (!list_empty(&block_group->cluster_list))
+               cluster = list_entry(block_group->cluster_list.next,
+                                    struct btrfs_free_cluster,
+                                    block_group_list);
+
+       /*
+        * We shouldn't have switched the pinned extents yet so this is the
+        * right one
+        */
+       unpin = root->fs_info->pinned_extents;
+
        /*
         * Lock all pages first so we can lock the extent safely.
         *
@@ -582,6 +611,12 @@ int btrfs_write_out_cache(struct btrfs_root *root,
        lock_extent_bits(&BTRFS_I(inode)->io_tree, 0, i_size_read(inode) - 1,
                         0, &cached_state, GFP_NOFS);
 
+       /*
+        * When searching for pinned extents, we need to start at our start
+        * offset.
+        */
+       start = block_group->key.objectid;
+
        /* Write out the extent entries */
        do {
                struct btrfs_free_space_entry *entry;
@@ -589,6 +624,8 @@ int btrfs_write_out_cache(struct btrfs_root *root,
                unsigned long offset = 0;
                unsigned long start_offset = 0;
 
+               next_page = false;
+
                if (index == 0) {
                        start_offset = first_page_offset;
                        offset = start_offset;
@@ -600,7 +637,7 @@ int btrfs_write_out_cache(struct btrfs_root *root,
                entry = addr + start_offset;
 
                memset(addr, 0, PAGE_CACHE_SIZE);
-               while (1) {
+               while (node && !next_page) {
                        struct btrfs_free_space *e;
 
                        e = rb_entry(node, struct btrfs_free_space, offset_index);
@@ -616,12 +653,49 @@ int btrfs_write_out_cache(struct btrfs_root *root,
                                entry->type = BTRFS_FREE_SPACE_EXTENT;
                        }
                        node = rb_next(node);
-                       if (!node)
-                               break;
+                       if (!node && cluster) {
+                               node = rb_first(&cluster->root);
+                               cluster = NULL;
+                       }
                        offset += sizeof(struct btrfs_free_space_entry);
                        if (offset + sizeof(struct btrfs_free_space_entry) >=
                            PAGE_CACHE_SIZE)
+                               next_page = true;
+                       entry++;
+               }
+
+               /*
+                * We want to add any pinned extents to our free space cache
+                * so we don't leak the space
+                */
+               while (!next_page && (start < block_group->key.objectid +
+                                     block_group->key.offset)) {
+                       ret = find_first_extent_bit(unpin, start, &start, &end,
+                                                   EXTENT_DIRTY);
+                       if (ret) {
+                               ret = 0;
+                               break;
+                       }
+
+                       /* This pinned extent is out of our range */
+                       if (start >= block_group->key.objectid +
+                           block_group->key.offset)
                                break;
+
+                       len = block_group->key.objectid +
+                               block_group->key.offset - start;
+                       len = min(len, end + 1 - start);
+
+                       entries++;
+                       entry->offset = cpu_to_le64(start);
+                       entry->bytes = cpu_to_le64(len);
+                       entry->type = BTRFS_FREE_SPACE_EXTENT;
+
+                       start = end + 1;
+                       offset += sizeof(struct btrfs_free_space_entry);
+                       if (offset + sizeof(struct btrfs_free_space_entry) >=
+                           PAGE_CACHE_SIZE)
+                               next_page = true;
                        entry++;
                }
                *crc = ~(u32)0;
@@ -652,7 +726,7 @@ int btrfs_write_out_cache(struct btrfs_root *root,
                page_cache_release(page);
 
                index++;
-       } while (node);
+       } while (node || next_page);
 
        /* Write out the bitmaps */
        list_for_each_safe(pos, n, &bitmap_list) {