summaryrefslogtreecommitdiff
path: root/check/mode-lowmem.c
diff options
context:
space:
mode:
Diffstat (limited to 'check/mode-lowmem.c')
-rw-r--r--check/mode-lowmem.c165
1 files changed, 165 insertions, 0 deletions
diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
index de96c12b..eb50c7e6 100644
--- a/check/mode-lowmem.c
+++ b/check/mode-lowmem.c
@@ -326,6 +326,171 @@ static int clear_block_groups_full(struct btrfs_fs_info *fs_info, u64 flags)
return modify_block_groups_cache(fs_info, flags, 0);
}
+static int create_chunk_and_block_group(struct btrfs_fs_info *fs_info,
+ u64 flags, u64 *start, u64 *nbytes)
+{
+ struct btrfs_trans_handle *trans;
+ struct btrfs_root *root = fs_info->extent_root;
+ int ret;
+
+ if ((flags & BTRFS_BLOCK_GROUP_TYPE_MASK) == 0)
+ return -EINVAL;
+
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ error("error starting transaction %s", strerror(-ret));
+ return ret;
+ }
+ ret = btrfs_alloc_chunk(trans, fs_info, start, nbytes, flags);
+ if (ret) {
+ error("fail to allocate new chunk %s", strerror(-ret));
+ goto out;
+ }
+ ret = btrfs_make_block_group(trans, fs_info, 0, flags, *start,
+ *nbytes);
+ if (ret) {
+ error("fail to make block group for chunk %llu %llu %s",
+ *start, *nbytes, strerror(-ret));
+ goto out;
+ }
+out:
+ btrfs_commit_transaction(trans, root);
+ return ret;
+}
+
+static int force_cow_in_new_chunk(struct btrfs_fs_info *fs_info,
+ u64 *start_ret)
+{
+ struct btrfs_block_group_cache *bg;
+ u64 start;
+ u64 nbytes;
+ u64 alloc_profile;
+ u64 flags;
+ int ret;
+
+ alloc_profile = (fs_info->avail_metadata_alloc_bits &
+ fs_info->metadata_alloc_profile);
+ flags = BTRFS_BLOCK_GROUP_METADATA | alloc_profile;
+ if (btrfs_fs_incompat(fs_info, MIXED_GROUPS))
+ flags |= BTRFS_BLOCK_GROUP_DATA;
+
+ ret = create_chunk_and_block_group(fs_info, flags, &start, &nbytes);
+ if (ret)
+ goto err;
+ printf("Created new chunk [%llu %llu]\n", start, nbytes);
+
+ flags = BTRFS_BLOCK_GROUP_METADATA;
+ /* Mark all metadata block groups cached and full in free space*/
+ ret = mark_block_groups_full(fs_info, flags);
+ if (ret)
+ goto clear_bgs_full;
+
+ bg = btrfs_lookup_block_group(fs_info, start);
+ if (!bg) {
+ ret = -ENOENT;
+ error("fail to look up block group %llu %llu", start, nbytes);
+ goto clear_bgs_full;
+ }
+
+ /* Clear block group cache just allocated */
+ ret = modify_block_group_cache(fs_info, bg, 0);
+ if (ret)
+ goto clear_bgs_full;
+ if (start_ret)
+ *start_ret = start;
+ return 0;
+
+clear_bgs_full:
+ clear_block_groups_full(fs_info, flags);
+err:
+ return ret;
+}
+
+/*
+ * Returns 0 means not almost full.
+ * Returns >0 means almost full.
+ * Returns <0 means fatal error.
+ */
+static int is_chunk_almost_full(struct btrfs_fs_info *fs_info, u64 start)
+{
+ struct btrfs_path path;
+ struct btrfs_key key;
+ struct btrfs_root *root = fs_info->extent_root;
+ struct btrfs_block_group_item *bi;
+ struct btrfs_block_group_item bg_item;
+ struct extent_buffer *eb;
+ u64 used;
+ u64 total;
+ u64 min_free;
+ int ret;
+ int slot;
+
+ key.objectid = start;
+ key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
+ key.offset = (u64)-1;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (!ret)
+ ret = -EIO;
+ if (ret < 0)
+ goto out;
+ ret = btrfs_previous_item(root, &path, start,
+ BTRFS_BLOCK_GROUP_ITEM_KEY);
+ if (ret) {
+ error("failed to find block group %llu", start);
+ ret = -ENOENT;
+ goto out;
+ }
+
+ eb = path.nodes[0];
+ slot = path.slots[0];
+ btrfs_item_key_to_cpu(eb, &key, slot);
+ if (key.objectid != start) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ total = key.offset;
+ bi = btrfs_item_ptr(eb, slot, struct btrfs_block_group_item);
+ read_extent_buffer(eb, &bg_item, (unsigned long)bi, sizeof(bg_item));
+ used = btrfs_block_group_used(&bg_item);
+
+ /*
+ * if the free space in the chunk is less than %10 of total,
+ * or not not enough for CoW once, we think the chunk is almost full.
+ */
+ min_free = max_t(u64, (BTRFS_MAX_LEVEL + 1) * fs_info->nodesize,
+ div_factor(total, 1));
+
+ if ((total - used) > min_free)
+ ret = 0;
+ else
+ ret = 1;
+out:
+ btrfs_release_path(&path);
+ return ret;
+}
+
+/*
+ * Returns <0 for error.
+ * Returns 0 for success.
+ */
+static int try_to_force_cow_in_new_chunk(struct btrfs_fs_info *fs_info,
+ u64 old_start, u64 *new_start)
+{
+ int ret;
+
+ if (old_start) {
+ ret = is_chunk_almost_full(fs_info, old_start);
+ if (ret <= 0)
+ return ret;
+ }
+ ret = force_cow_in_new_chunk(fs_info, new_start);
+ return ret;
+}
+
/*
* This function only handles BACKREF_MISSING,
* If corresponding extent item exists, increase the ref, else insert an extent