diff --git a/kernel-shared/zoned.c b/kernel-shared/zoned.c index 6894d421..28423118 100644 --- a/kernel-shared/zoned.c +++ b/kernel-shared/zoned.c @@ -648,6 +648,67 @@ u64 btrfs_find_allocatable_zones(struct btrfs_device *device, u64 hole_start, return pos; } +/* + * Calculate an allocation pointer from the extent allocation information + * for a block group consisting of conventional zones. It is pointed to the + * end of the highest addressed extent in the block group as an allocation + * offset. + */ +static int calculate_alloc_pointer(struct btrfs_fs_info *fs_info, + struct btrfs_block_group *cache, + u64 *offset_ret) +{ + struct btrfs_root *root = fs_info->extent_root; + struct btrfs_path *path; + struct btrfs_key key; + struct btrfs_key found_key; + int ret; + u64 length; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + key.objectid = cache->start + cache->length; + key.type = 0; + key.offset = 0; + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + /* There should be no exact match (ie. an extent) at this address */ + if (!ret) + ret = -EUCLEAN; + if (ret < 0) + goto out; + + ret = btrfs_previous_extent_item(root, path, cache->start); + if (ret) { + if (ret == 1) { + ret = 0; + *offset_ret = 0; + } + goto out; + } + + btrfs_item_key_to_cpu(path->nodes[0], &found_key, path->slots[0]); + + if (found_key.type == BTRFS_EXTENT_ITEM_KEY) + length = found_key.offset; + else + length = fs_info->nodesize; + + if (!(found_key.objectid >= cache->start && + found_key.objectid + length <= cache->start + cache->length)) { + ret = -EUCLEAN; + goto out; + } + *offset_ret = found_key.objectid + length - cache->start; + ret = 0; + +out: + btrfs_free_path(path); + return ret; +} + int btrfs_load_block_group_zone_info(struct btrfs_fs_info *fs_info, struct btrfs_block_group *cache) { @@ -661,6 +722,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_fs_info *fs_info, int ret = 0; int i; u64 *alloc_offsets = NULL; + u64 last_alloc = 0; u32 num_sequential = 0, num_conventional = 0; if (!btrfs_is_zoned(fs_info)) @@ -746,12 +808,16 @@ int btrfs_load_block_group_zone_info(struct btrfs_fs_info *fs_info, } if (num_conventional > 0) { - /* - * Since conventional zones do not have a write pointer, we - * cannot determine alloc_offset from the pointer - */ - ret = -EINVAL; - goto out; + ret = calculate_alloc_pointer(fs_info, cache, &last_alloc); + if (ret || map->num_stripes == num_conventional) { + if (!ret) + cache->alloc_offset = last_alloc; + else + error( + "zoned: failed to determine allocation offset of block group %llu", + cache->start); + goto out; + } } switch (map->type & BTRFS_BLOCK_GROUP_PROFILE_MASK) { @@ -773,6 +839,14 @@ int btrfs_load_block_group_zone_info(struct btrfs_fs_info *fs_info, } out: + /* An extent is allocated after the write pointer */ + if (!ret && num_conventional && last_alloc > cache->alloc_offset) { + error( + "zoned: got wrong write pointer in block group %llu: %llu > %llu", + logical, last_alloc, cache->alloc_offset); + ret = -EIO; + } + free(alloc_offsets); return ret; }