diff --git a/btrfs-convert.c b/btrfs-convert.c index 81871404..856fac60 100644 --- a/btrfs-convert.c +++ b/btrfs-convert.c @@ -2315,6 +2315,66 @@ fail: return -1; } +/* + * Check if a non 1:1 mapped chunk can be rolled back. + * For new convert, it's OK while for old convert it's not. + */ +static int may_rollback_chunk(struct btrfs_fs_info *fs_info, u64 bytenr) +{ + struct btrfs_block_group_cache *bg; + struct btrfs_key key; + struct btrfs_path path; + struct btrfs_root *extent_root = fs_info->extent_root; + u64 bg_start; + u64 bg_end; + int ret; + + bg = btrfs_lookup_first_block_group(fs_info, bytenr); + if (!bg) + return -ENOENT; + bg_start = bg->key.objectid; + bg_end = bg->key.objectid + bg->key.offset; + + key.objectid = bg_end; + key.type = BTRFS_METADATA_ITEM_KEY; + key.offset = 0; + btrfs_init_path(&path); + + ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0); + if (ret < 0) + return ret; + + while (1) { + struct btrfs_extent_item *ei; + + ret = btrfs_previous_extent_item(extent_root, &path, bg_start); + if (ret > 0) { + ret = 0; + break; + } + if (ret < 0) + break; + + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + if (key.type == BTRFS_METADATA_ITEM_KEY) + continue; + /* Now it's EXTENT_ITEM_KEY only */ + ei = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_extent_item); + /* + * Found data extent, means this is old convert must follow 1:1 + * mapping. + */ + if (btrfs_extent_flags(path.nodes[0], ei) + & BTRFS_EXTENT_FLAG_DATA) { + ret = -EINVAL; + break; + } + } + btrfs_release_path(&path); + return ret; +} + static int may_rollback(struct btrfs_root *root) { struct btrfs_fs_info *info = root->fs_info; @@ -2351,8 +2411,25 @@ static int may_rollback(struct btrfs_root *root) physical = multi->stripes[0].physical; kfree(multi); - if (num_stripes != 1 || physical != bytenr) + if (num_stripes != 1) { + error("num stripes for bytenr %llu is not 1", bytenr); goto fail; + } + + /* + * Extra check for new convert, as metadata chunk from new + * convert is much more free than old convert, it doesn't need + * to do 1:1 mapping. + */ + if (physical != bytenr) { + /* + * Check if it's a metadata chunk and has only metadata + * extent. + */ + ret = may_rollback_chunk(info, bytenr); + if (ret < 0) + goto fail; + } next: bytenr += length; if (bytenr >= total_bytes)