mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/kdave/btrfs-progs.git
synced 2024-12-12 21:53:43 +08:00
004eabb1ad
[BUG] btrfs-find-root may not output desire result, as due to search_extent_cache() may return a result that doesn't cover the desired range, generation cache can be screwed up if higher generation tree root is found before lower generation tree root. For example: ======= ./btrfs-find-root /dev/sda6 -a Superblock thinks the generation is 8 Superblock thinks the level is 0 adding bytenr: 4194304, gen: 8 <<< Debug output adding bytenr: 24715264, gen: 7 <<< gen is 7 at read_tree_block time Well block 4194304(gen: 8 level: 0) seems good, and it matches superblock Well block 24715264(gen: 8 level: 0) seems good, and it matches superblock <<< But its gen is wrong at result output time ======= [Fix] Add a new check to make sure the search_extent_cache() is returning the desired result. Reported-by: Marc Merlin <marc@merlins.org> Signed-off-by: Qu Wenruo <quwenruo@cn.fujitsu.com> Signed-off-by: David Sterba <dsterba@suse.com>
145 lines
4.0 KiB
C
145 lines
4.0 KiB
C
/*
|
|
* Copyright (C) 2015 Fujitsu. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 021110-1307, USA.
|
|
*/
|
|
|
|
#include "kerncompat.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "ctree.h"
|
|
#include "utils.h"
|
|
#include "find-root.h"
|
|
#include "volumes.h"
|
|
#include "disk-io.h"
|
|
#include "extent-cache.h"
|
|
|
|
/* Return value is the same as btrfs_find_root_search(). */
|
|
static int add_eb_to_result(struct extent_buffer *eb,
|
|
struct cache_tree *result,
|
|
u32 leafsize,
|
|
struct btrfs_find_root_filter *filter,
|
|
struct cache_extent **match)
|
|
{
|
|
u64 generation = btrfs_header_generation(eb);
|
|
u64 level = btrfs_header_level(eb);
|
|
u64 owner = btrfs_header_owner(eb);
|
|
u64 start = eb->start;
|
|
struct cache_extent *cache;
|
|
struct btrfs_find_root_gen_cache *gen_cache = NULL;
|
|
int ret = 0;
|
|
|
|
if (owner != filter->objectid || level < filter->level ||
|
|
generation < filter->generation)
|
|
return ret;
|
|
|
|
/*
|
|
* Get the generation cache or create one
|
|
*
|
|
* NOTE: search_cache_extent() may return cache that doesn't cover
|
|
* the range. So we need an extra check to make sure it's the right one.
|
|
*/
|
|
cache = search_cache_extent(result, generation);
|
|
if (!cache || cache->start != generation) {
|
|
gen_cache = malloc(sizeof(*gen_cache));
|
|
BUG_ON(!gen_cache);
|
|
cache = &gen_cache->cache;
|
|
cache->start = generation;
|
|
cache->size = 1;
|
|
cache->objectid = 0;
|
|
gen_cache->highest_level = 0;
|
|
cache_tree_init(&gen_cache->eb_tree);
|
|
|
|
ret = insert_cache_extent(result, cache);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
gen_cache = container_of(cache, struct btrfs_find_root_gen_cache,
|
|
cache);
|
|
|
|
/* Higher level, clean tree and insert the new one */
|
|
if (level > gen_cache->highest_level) {
|
|
free_extent_cache_tree(&gen_cache->eb_tree);
|
|
gen_cache->highest_level = level;
|
|
/* Fall into the insert routine */
|
|
}
|
|
|
|
/* Same level, insert it into the eb_tree */
|
|
if (level == gen_cache->highest_level) {
|
|
ret = add_cache_extent(&gen_cache->eb_tree,
|
|
start, leafsize);
|
|
if (ret < 0 && ret != -EEXIST)
|
|
return ret;
|
|
ret = 0;
|
|
}
|
|
if (generation == filter->match_gen &&
|
|
level == filter->match_level &&
|
|
!filter->search_all) {
|
|
ret = 1;
|
|
if (match)
|
|
*match = search_cache_extent(&gen_cache->eb_tree,
|
|
start);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Return 0 if iterating all the metadata extents.
|
|
* Return 1 if found root with given gen/level and set *match to it.
|
|
* Return <0 if error happens
|
|
*/
|
|
int btrfs_find_root_search(struct btrfs_root *chunk_root,
|
|
struct btrfs_find_root_filter *filter,
|
|
struct cache_tree *result,
|
|
struct cache_extent **match)
|
|
{
|
|
struct btrfs_fs_info *fs_info = chunk_root->fs_info;
|
|
struct extent_buffer *eb;
|
|
u64 metadata_offset = 0;
|
|
u64 metadata_size = 0;
|
|
u64 offset = 0;
|
|
u32 leafsize = chunk_root->leafsize;
|
|
int suppress_errors = 0;
|
|
int ret = 0;
|
|
|
|
suppress_errors = fs_info->suppress_check_block_errors;
|
|
fs_info->suppress_check_block_errors = 1;
|
|
while (1) {
|
|
ret = btrfs_next_metadata(&fs_info->mapping_tree,
|
|
&metadata_offset, &metadata_size);
|
|
if (ret) {
|
|
if (ret == -ENOENT)
|
|
ret = 0;
|
|
break;
|
|
}
|
|
for (offset = metadata_offset;
|
|
offset < metadata_offset + metadata_size;
|
|
offset += chunk_root->leafsize) {
|
|
eb = read_tree_block(chunk_root, offset, leafsize, 0);
|
|
if (!eb || IS_ERR(eb))
|
|
continue;
|
|
ret = add_eb_to_result(eb, result, leafsize, filter,
|
|
match);
|
|
free_extent_buffer(eb);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
fs_info->suppress_check_block_errors = suppress_errors;
|
|
return ret;
|
|
}
|