| From 3061801420469610c8fa6080a950e56770773ef1 Mon Sep 17 00:00:00 2001 |
| From: Naohiro Aota <naohiro.aota@wdc.com> |
| Date: Sun, 29 Jun 2025 23:07:42 +0900 |
| Subject: btrfs: zoned: do not remove unwritten non-data block group |
| |
| From: Naohiro Aota <naohiro.aota@wdc.com> |
| |
| commit 3061801420469610c8fa6080a950e56770773ef1 upstream. |
| |
| There are some reports of "unable to find chunk map for logical 2147483648 |
| length 16384" error message appears in dmesg. This means some IOs are |
| occurring after a block group is removed. |
| |
| When a metadata tree node is cleaned on a zoned setup, we keep that node |
| still dirty and write it out not to create a write hole. However, this can |
| make a block group's used bytes == 0 while there is a dirty region left. |
| |
| Such an unused block group is moved into the unused_bg list and processed |
| for removal. When the removal succeeds, the block group is removed from the |
| transaction->dirty_bgs list, so the unused dirty nodes in the block group |
| are not sent at the transaction commit time. It will be written at some |
| later time e.g, sync or umount, and causes "unable to find chunk map" |
| errors. |
| |
| This can happen relatively easy on SMR whose zone size is 256MB. However, |
| calling do_zone_finish() on such block group returns -EAGAIN and keep that |
| block group intact, which is why the issue is hidden until now. |
| |
| Fixes: afba2bc036b0 ("btrfs: zoned: implement active zone tracking") |
| CC: stable@vger.kernel.org # 6.1+ |
| Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com> |
| Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com> |
| Signed-off-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/btrfs/block-group.c | 27 +++++++++++++++++++++++++-- |
| 1 file changed, 25 insertions(+), 2 deletions(-) |
| |
| --- a/fs/btrfs/block-group.c |
| +++ b/fs/btrfs/block-group.c |
| @@ -34,6 +34,19 @@ int btrfs_should_fragment_free_space(str |
| } |
| #endif |
| |
| +static inline bool has_unwritten_metadata(struct btrfs_block_group *block_group) |
| +{ |
| + /* The meta_write_pointer is available only on the zoned setup. */ |
| + if (!btrfs_is_zoned(block_group->fs_info)) |
| + return false; |
| + |
| + if (block_group->flags & BTRFS_BLOCK_GROUP_DATA) |
| + return false; |
| + |
| + return block_group->start + block_group->alloc_offset > |
| + block_group->meta_write_pointer; |
| +} |
| + |
| /* |
| * Return target flags in extended format or 0 if restripe for this chunk_type |
| * is not in progress |
| @@ -1240,6 +1253,15 @@ int btrfs_remove_block_group(struct btrf |
| goto out; |
| |
| spin_lock(&block_group->lock); |
| + /* |
| + * Hitting this WARN means we removed a block group with an unwritten |
| + * region. It will cause "unable to find chunk map for logical" errors. |
| + */ |
| + if (WARN_ON(has_unwritten_metadata(block_group))) |
| + btrfs_warn(fs_info, |
| + "block group %llu is removed before metadata write out", |
| + block_group->start); |
| + |
| set_bit(BLOCK_GROUP_FLAG_REMOVED, &block_group->runtime_flags); |
| |
| /* |
| @@ -1563,8 +1585,9 @@ void btrfs_delete_unused_bgs(struct btrf |
| * needing to allocate extents from the block group. |
| */ |
| used = btrfs_space_info_used(space_info, true); |
| - if (space_info->total_bytes - block_group->length < used && |
| - block_group->zone_unusable < block_group->length) { |
| + if ((space_info->total_bytes - block_group->length < used && |
| + block_group->zone_unusable < block_group->length) || |
| + has_unwritten_metadata(block_group)) { |
| /* |
| * Add a reference for the list, compensate for the ref |
| * drop under the "next" label for the |