X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-check.c;h=c5faa2b3505cd1311a1046a3ed447353980ee11f;hb=d8814ff73e19fded3decde59b00c993116ffc120;hp=21b15c8d8a2693f3114b1b57a970eb5a55df8495;hpb=5cbedb02e1f50175e65610e39c6717fe2162a8ec;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-check.c b/cmds-check.c index 21b15c8..c5faa2b 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -40,7 +40,9 @@ #include "qgroup-verify.h" #include "rbtree-utils.h" #include "backref.h" -#include "ulist.h" +#include "kernel-shared/ulist.h" +#include "hash.h" +#include "help.h" enum task_position { TASK_EXTENTS, @@ -112,6 +114,24 @@ struct data_backref { u32 found_ref; }; +#define ROOT_DIR_ERROR (1<<1) /* bad ROOT_DIR */ +#define DIR_ITEM_MISSING (1<<2) /* DIR_ITEM not found */ +#define DIR_ITEM_MISMATCH (1<<3) /* DIR_ITEM found but not match */ +#define INODE_REF_MISSING (1<<4) /* INODE_REF/INODE_EXTREF not found */ +#define INODE_ITEM_MISSING (1<<5) /* INODE_ITEM not found */ +#define INODE_ITEM_MISMATCH (1<<6) /* INODE_ITEM found but not match */ +#define FILE_EXTENT_ERROR (1<<7) /* bad FILE_EXTENT */ +#define ODD_CSUM_ITEM (1<<8) /* CSUM_ITEM error */ +#define CSUM_ITEM_MISSING (1<<9) /* CSUM_ITEM not found */ +#define LINK_COUNT_ERROR (1<<10) /* INODE_ITEM nlink count error */ +#define NBYTES_ERROR (1<<11) /* INODE_ITEM nbytes count error */ +#define ISIZE_ERROR (1<<12) /* INODE_ITEM size count error */ +#define ORPHAN_ITEM (1<<13) /* INODE_ITEM no reference */ +#define NO_INODE_ITEM (1<<14) /* no inode_item */ +#define LAST_ITEM (1<<15) /* Complete this tree traversal */ +#define ROOT_REF_MISSING (1<<16) /* ROOT_REF not found */ +#define ROOT_REF_MISMATCH (1<<17) /* ROOT_REF found but not match */ + static inline struct data_backref* to_data_backref(struct extent_backref *back) { return container_of(back, struct data_backref, node); @@ -809,7 +829,8 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec) } if (!found) fprintf(stderr, "\tstart: 0, len: %llu\n", - round_up(rec->isize, root->sectorsize)); + round_up(rec->isize, + root->fs_info->sectorsize)); } } @@ -1458,8 +1479,7 @@ out: return has_parent ? 0 : 2; } -static int process_dir_item(struct btrfs_root *root, - struct extent_buffer *eb, +static int process_dir_item(struct extent_buffer *eb, int slot, struct btrfs_key *key, struct shared_node *active_node) { @@ -1493,13 +1513,19 @@ static int process_dir_item(struct btrfs_root *root, filetype = btrfs_dir_type(eb, di); rec->found_size += name_len; - if (name_len <= BTRFS_NAME_LEN) { + if (cur + sizeof(*di) + name_len > total || + name_len > BTRFS_NAME_LEN) { + error = REF_ERR_NAME_TOO_LONG; + + if (cur + sizeof(*di) > total) + break; + len = min_t(u32, total - cur - sizeof(*di), + BTRFS_NAME_LEN); + } else { len = name_len; error = 0; - } else { - len = BTRFS_NAME_LEN; - error = REF_ERR_NAME_TOO_LONG; } + read_extent_buffer(eb, namebuf, (unsigned long)(di + 1), len); if (location.type == BTRFS_INODE_ITEM_KEY) { @@ -1550,13 +1576,22 @@ static int process_inode_ref(struct extent_buffer *eb, while (cur < total) { name_len = btrfs_inode_ref_name_len(eb, ref); index = btrfs_inode_ref_index(eb, ref); - if (name_len <= BTRFS_NAME_LEN) { + + /* inode_ref + namelen should not cross item boundary */ + if (cur + sizeof(*ref) + name_len > total || + name_len > BTRFS_NAME_LEN) { + if (total < cur + sizeof(*ref)) + break; + + /* Still try to read out the remaining part */ + len = min_t(u32, total - cur - sizeof(*ref), + BTRFS_NAME_LEN); + error = REF_ERR_NAME_TOO_LONG; + } else { len = name_len; error = 0; - } else { - len = BTRFS_NAME_LEN; - error = REF_ERR_NAME_TOO_LONG; } + read_extent_buffer(eb, namebuf, (unsigned long)(ref + 1), len); add_inode_backref(inode_cache, key->objectid, key->offset, index, namebuf, len, 0, key->type, error); @@ -1665,7 +1700,8 @@ static int count_csum_range(struct btrfs_root *root, u64 start, start = key.offset; size = btrfs_item_size_nr(leaf, path.slots[0]); - csum_end = key.offset + (size / csum_size) * root->sectorsize; + csum_end = key.offset + (size / csum_size) * + root->fs_info->sectorsize; if (csum_end > start) { size = min(csum_end - start, len); len -= size; @@ -1692,7 +1728,7 @@ static int process_file_extent(struct btrfs_root *root, u64 num_bytes = 0; u64 disk_bytenr = 0; u64 extent_offset = 0; - u64 mask = root->sectorsize - 1; + u64 mask = root->fs_info->sectorsize - 1; int extent_type; int ret; @@ -1816,7 +1852,7 @@ static int process_one_leaf(struct btrfs_root *root, struct extent_buffer *eb, switch (key.type) { case BTRFS_DIR_ITEM_KEY: case BTRFS_DIR_INDEX_KEY: - ret = process_dir_item(root, eb, i, &key, active_node); + ret = process_dir_item(eb, i, &key, active_node); break; case BTRFS_INODE_REF_KEY: ret = process_inode_ref(eb, i, &key, active_node); @@ -1838,9 +1874,100 @@ static int process_one_leaf(struct btrfs_root *root, struct extent_buffer *eb, return ret; } +struct node_refs { + u64 bytenr[BTRFS_MAX_LEVEL]; + u64 refs[BTRFS_MAX_LEVEL]; + int need_check[BTRFS_MAX_LEVEL]; +}; + +static int update_nodes_refs(struct btrfs_root *root, u64 bytenr, + struct node_refs *nrefs, u64 level); +static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, + unsigned int ext_ref); + +/* + * Returns >0 Found error, not fatal, should continue + * Returns <0 Fatal error, must exit the whole check + * Returns 0 No errors found + */ +static int process_one_leaf_v2(struct btrfs_root *root, struct btrfs_path *path, + struct node_refs *nrefs, int *level, int ext_ref) +{ + struct extent_buffer *cur = path->nodes[0]; + struct btrfs_key key; + u64 cur_bytenr; + u32 nritems; + u64 first_ino = 0; + int root_level = btrfs_header_level(root->node); + int i; + int ret = 0; /* Final return value */ + int err = 0; /* Positive error bitmap */ + + cur_bytenr = cur->start; + + /* skip to first inode item or the first inode number change */ + nritems = btrfs_header_nritems(cur); + for (i = 0; i < nritems; i++) { + btrfs_item_key_to_cpu(cur, &key, i); + if (i == 0) + first_ino = key.objectid; + if (key.type == BTRFS_INODE_ITEM_KEY || + (first_ino && first_ino != key.objectid)) + break; + } + if (i == nritems) { + path->slots[0] = nritems; + return 0; + } + path->slots[0] = i; + +again: + err |= check_inode_item(root, path, ext_ref); + + if (err & LAST_ITEM) + goto out; + + /* still have inode items in thie leaf */ + if (cur->start == cur_bytenr) + goto again; + + /* + * we have switched to another leaf, above nodes may + * have changed, here walk down the path, if a node + * or leaf is shared, check whether we can skip this + * node or leaf. + */ + for (i = root_level; i >= 0; i--) { + if (path->nodes[i]->start == nrefs->bytenr[i]) + continue; + + ret = update_nodes_refs(root, + path->nodes[i]->start, + nrefs, i); + if (ret) + goto out; + + if (!nrefs->need_check[i]) { + *level += 1; + break; + } + } + + for (i = 0; i < *level; i++) { + free_extent_buffer(path->nodes[i]); + path->nodes[i] = NULL; + } +out: + err &= ~LAST_ITEM; + if (err && !ret) + ret = err; + return ret; +} + static void reada_walk_down(struct btrfs_root *root, struct extent_buffer *node, int slot) { + struct btrfs_fs_info *fs_info = root->fs_info; u64 bytenr; u64 ptr_gen; u32 nritems; @@ -1853,11 +1980,11 @@ static void reada_walk_down(struct btrfs_root *root, return; nritems = btrfs_header_nritems(node); - blocksize = root->nodesize; + blocksize = fs_info->nodesize; for (i = slot; i < nritems; i++) { bytenr = btrfs_node_blockptr(node, i); ptr_gen = btrfs_node_ptr_generation(node, i); - readahead_tree_block(root, bytenr, blocksize, ptr_gen); + readahead_tree_block(fs_info, bytenr, blocksize, ptr_gen); } } @@ -1874,8 +2001,7 @@ static void reada_walk_down(struct btrfs_root *root, * which makes leaf owner check not so strong, key check should be * sufficient enough for that case. */ -static int check_child_node(struct btrfs_root *root, - struct extent_buffer *parent, int slot, +static int check_child_node(struct extent_buffer *parent, int slot, struct extent_buffer *child) { struct btrfs_key parent_key; @@ -1911,10 +2037,66 @@ static int check_child_node(struct btrfs_root *root, return ret; } -struct node_refs { - u64 bytenr[BTRFS_MAX_LEVEL]; - u64 refs[BTRFS_MAX_LEVEL]; -}; +/* + * for a tree node or leaf, if it's shared, indeed we don't need to iterate it + * in every fs or file tree check. Here we find its all root ids, and only check + * it in the fs or file tree which has the smallest root id. + */ +static int need_check(struct btrfs_root *root, struct ulist *roots) +{ + struct rb_node *node; + struct ulist_node *u; + + if (roots->nnodes == 1) + return 1; + + node = rb_first(&roots->root); + u = rb_entry(node, struct ulist_node, rb_node); + /* + * current root id is not smallest, we skip it and let it be checked + * in the fs or file tree who hash the smallest root id. + */ + if (root->objectid != u->val) + return 0; + + return 1; +} + +/* + * for a tree node or leaf, we record its reference count, so later if we still + * process this node or leaf, don't need to compute its reference count again. + */ +static int update_nodes_refs(struct btrfs_root *root, u64 bytenr, + struct node_refs *nrefs, u64 level) +{ + int check, ret; + u64 refs; + struct ulist *roots; + + if (nrefs->bytenr[level] != bytenr) { + ret = btrfs_lookup_extent_info(NULL, root, bytenr, + level, 1, &refs, NULL); + if (ret < 0) + return ret; + + nrefs->bytenr[level] = bytenr; + nrefs->refs[level] = refs; + if (refs > 1) { + ret = btrfs_find_all_roots(NULL, root->fs_info, bytenr, + 0, &roots); + if (ret) + return -EIO; + + check = need_check(root, roots); + ulist_free(roots); + nrefs->need_check[level] = check; + } else { + nrefs->need_check[level] = 1; + } + } + + return 0; +} static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path, struct walk_control *wc, int *level, @@ -1923,6 +2105,7 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path, enum btrfs_tree_block_status status; u64 bytenr; u64 ptr_gen; + struct btrfs_fs_info *fs_info = root->fs_info; struct extent_buffer *next; struct extent_buffer *cur; u32 blocksize; @@ -1974,7 +2157,7 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path, } bytenr = btrfs_node_blockptr(cur, path->slots[*level]); ptr_gen = btrfs_node_ptr_generation(cur, path->slots[*level]); - blocksize = root->nodesize; + blocksize = fs_info->nodesize; if (bytenr == nrefs->bytenr[*level - 1]) { refs = nrefs->refs[*level - 1]; @@ -1998,11 +2181,11 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path, } } - next = btrfs_find_tree_block(root, bytenr, blocksize); + next = btrfs_find_tree_block(fs_info, bytenr, blocksize); if (!next || !btrfs_buffer_uptodate(next, ptr_gen)) { free_extent_buffer(next); reada_walk_down(root, cur, path->slots[*level]); - next = read_tree_block(root, bytenr, blocksize, + next = read_tree_block(root->fs_info, bytenr, blocksize, ptr_gen); if (!extent_buffer_uptodate(next)) { struct btrfs_key node_key; @@ -2013,14 +2196,16 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path, btrfs_add_corrupt_extent_record(root->fs_info, &node_key, path->nodes[*level]->start, - root->nodesize, *level); + root->fs_info->nodesize, + *level); err = -EIO; goto out; } } - ret = check_child_node(root, cur, path->slots[*level], next); + ret = check_child_node(cur, path->slots[*level], next); if (ret) { + free_extent_buffer(next); err = ret; goto out; } @@ -2045,6 +2230,117 @@ out: return err; } +static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, + unsigned int ext_ref); + +/* + * Returns >0 Found error, should continue + * Returns <0 Fatal error, must exit the whole check + * Returns 0 No errors found + */ +static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path, + int *level, struct node_refs *nrefs, int ext_ref) +{ + enum btrfs_tree_block_status status; + u64 bytenr; + u64 ptr_gen; + struct btrfs_fs_info *fs_info = root->fs_info; + struct extent_buffer *next; + struct extent_buffer *cur; + u32 blocksize; + int ret; + + WARN_ON(*level < 0); + WARN_ON(*level >= BTRFS_MAX_LEVEL); + + ret = update_nodes_refs(root, path->nodes[*level]->start, + nrefs, *level); + if (ret < 0) + return ret; + + while (*level >= 0) { + WARN_ON(*level < 0); + WARN_ON(*level >= BTRFS_MAX_LEVEL); + cur = path->nodes[*level]; + + if (btrfs_header_level(cur) != *level) + WARN_ON(1); + + if (path->slots[*level] >= btrfs_header_nritems(cur)) + break; + /* Don't forgot to check leaf/node validation */ + if (*level == 0) { + ret = btrfs_check_leaf(root, NULL, cur); + if (ret != BTRFS_TREE_BLOCK_CLEAN) { + ret = -EIO; + break; + } + ret = process_one_leaf_v2(root, path, nrefs, + level, ext_ref); + break; + } else { + ret = btrfs_check_node(root, NULL, cur); + if (ret != BTRFS_TREE_BLOCK_CLEAN) { + ret = -EIO; + break; + } + } + bytenr = btrfs_node_blockptr(cur, path->slots[*level]); + ptr_gen = btrfs_node_ptr_generation(cur, path->slots[*level]); + blocksize = fs_info->nodesize; + + ret = update_nodes_refs(root, bytenr, nrefs, *level - 1); + if (ret) + break; + if (!nrefs->need_check[*level - 1]) { + path->slots[*level]++; + continue; + } + + next = btrfs_find_tree_block(fs_info, bytenr, blocksize); + if (!next || !btrfs_buffer_uptodate(next, ptr_gen)) { + free_extent_buffer(next); + reada_walk_down(root, cur, path->slots[*level]); + next = read_tree_block(fs_info, bytenr, blocksize, + ptr_gen); + if (!extent_buffer_uptodate(next)) { + struct btrfs_key node_key; + + btrfs_node_key_to_cpu(path->nodes[*level], + &node_key, + path->slots[*level]); + btrfs_add_corrupt_extent_record(fs_info, + &node_key, + path->nodes[*level]->start, + fs_info->nodesize, + *level); + ret = -EIO; + break; + } + } + + ret = check_child_node(cur, path->slots[*level], next); + if (ret < 0) + break; + + if (btrfs_is_leaf(next)) + status = btrfs_check_leaf(root, NULL, next); + else + status = btrfs_check_node(root, NULL, next); + if (status != BTRFS_TREE_BLOCK_CLEAN) { + free_extent_buffer(next); + ret = -EIO; + break; + } + + *level = *level - 1; + free_extent_buffer(path->nodes[*level]); + path->nodes[*level] = next; + path->slots[*level] = 0; + } + return ret; +} + static int walk_up_tree(struct btrfs_root *root, struct btrfs_path *path, struct walk_control *wc, int *level) { @@ -2069,6 +2365,27 @@ static int walk_up_tree(struct btrfs_root *root, struct btrfs_path *path, return 1; } +static int walk_up_tree_v2(struct btrfs_root *root, struct btrfs_path *path, + int *level) +{ + int i; + struct extent_buffer *leaf; + + for (i = *level; i < BTRFS_MAX_LEVEL - 1 && path->nodes[i]; i++) { + leaf = path->nodes[i]; + if (path->slots[i] + 1 < btrfs_header_nritems(leaf)) { + path->slots[i]++; + *level = i; + return 0; + } else { + free_extent_buffer(path->nodes[*level]); + path->nodes[*level] = NULL; + *level = i + 1; + } + } + return 1; +} + static int check_root_dir(struct inode_record *rec) { struct inode_backref *backref; @@ -2244,8 +2561,6 @@ static int add_missing_dir_index(struct btrfs_root *root, } static int delete_dir_index(struct btrfs_root *root, - struct cache_tree *inode_cache, - struct inode_record *rec, struct inode_backref *backref) { struct btrfs_trans_handle *trans; @@ -2287,7 +2602,7 @@ static int delete_dir_index(struct btrfs_root *root, static int create_inode_item(struct btrfs_root *root, struct inode_record *rec, - struct inode_backref *backref, int root_dir) + int root_dir) { struct btrfs_trans_handle *trans; struct btrfs_inode_item inode_item; @@ -2353,7 +2668,7 @@ static int repair_inode_backrefs(struct btrfs_root *root, list_for_each_entry_safe(backref, tmp, &rec->backrefs, list) { if (!delete && rec->ino == root_dirid) { if (!rec->found_inode_item) { - ret = create_inode_item(root, rec, backref, 1); + ret = create_inode_item(root, rec, 1); if (ret) break; repaired++; @@ -2368,12 +2683,13 @@ static int repair_inode_backrefs(struct btrfs_root *root, ((backref->found_dir_index && !backref->found_inode_ref) || (backref->found_dir_index && backref->found_inode_ref && (backref->errors & REF_ERR_INDEX_UNMATCH)))) { - ret = delete_dir_index(root, inode_cache, rec, backref); + ret = delete_dir_index(root, backref); if (ret) break; repaired++; list_del(&backref->list); free(backref); + continue; } if (!delete && !backref->found_dir_index && @@ -2384,12 +2700,12 @@ static int repair_inode_backrefs(struct btrfs_root *root, break; repaired++; if (backref->found_dir_item && - backref->found_dir_index && backref->found_dir_index) { if (!backref->errors && backref->found_inode_ref) { list_del(&backref->list); free(backref); + continue; } } } @@ -2439,7 +2755,7 @@ static int repair_inode_backrefs(struct btrfs_root *root, backref->found_dir_item && !(backref->errors & REF_ERR_INDEX_UNMATCH) && !rec->found_inode_item)) { - ret = create_inode_item(root, rec, backref, 0); + ret = create_inode_item(root, rec, 0); if (ret) break; repaired++; @@ -2563,6 +2879,31 @@ out: return ret; } +static int get_highest_inode(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *path, + u64 *highest_ino) +{ + struct btrfs_key key, found_key; + int ret; + + btrfs_init_path(path); + key.objectid = BTRFS_LAST_FREE_OBJECTID; + key.offset = -1; + key.type = BTRFS_INODE_ITEM_KEY; + ret = btrfs_search_slot(trans, root, &key, path, -1, 1); + if (ret == 1) { + btrfs_item_key_to_cpu(path->nodes[0], &found_key, + path->slots[0] - 1); + *highest_ino = found_key.objectid; + ret = 0; + } + if (*highest_ino >= BTRFS_LAST_FREE_OBJECTID) + ret = -EOVERFLOW; + btrfs_release_path(path); + return ret; +} + static int repair_inode_nlinks(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, @@ -2608,11 +2949,9 @@ static int repair_inode_nlinks(struct btrfs_trans_handle *trans, } if (rec->found_link == 0) { - lost_found_ino = root->highest_inode; - if (lost_found_ino >= BTRFS_LAST_FREE_OBJECTID) { - ret = -EOVERFLOW; + ret = get_highest_inode(trans, root, path, &lost_found_ino); + if (ret < 0) goto out; - } lost_found_ino++; ret = btrfs_mkdir(trans, root, dir_name, strlen(dir_name), BTRFS_FIRST_FREE_OBJECTID, &lost_found_ino, @@ -2900,7 +3239,8 @@ static int repair_inode_discount_extent(struct btrfs_trans_handle *trans, /* special case for a file losing all its file extent */ if (!found) { ret = btrfs_punch_hole(trans, root, rec->ino, 0, - round_up(rec->isize, root->sectorsize)); + round_up(rec->isize, + root->fs_info->sectorsize)); if (ret < 0) goto out; } @@ -2976,21 +3316,6 @@ static int check_inode_recs(struct btrfs_root *root, } /* - * We need to record the highest inode number for later 'lost+found' - * dir creation. - * We must select an ino not used/referred by any existing inode, or - * 'lost+found' ino may be a missing ino in a corrupted leaf, - * this may cause 'lost+found' dir has wrong nlinks. - */ - cache = last_cache_extent(inode_cache); - if (cache) { - node = container_of(cache, struct ptr_node, cache); - rec = node->data; - if (rec->ino > root->highest_inode) - root->highest_inode = rec->ino; - } - - /* * We need to repair backrefs first because we could change some of the * errors in the inode recs. * @@ -3527,8 +3852,7 @@ static int repair_btree(struct btrfs_root *root, path.slots[level]); /* Remove the ptr */ - ret = btrfs_del_ptr(trans, root, &path, level, - path.slots[level]); + ret = btrfs_del_ptr(root, &path, level, path.slots[level]); if (ret < 0) goto out; /* @@ -3536,9 +3860,9 @@ static int repair_btree(struct btrfs_root *root, * return value is not concerned. */ btrfs_release_path(&path); - ret = btrfs_free_extent(trans, root, offset, root->nodesize, - 0, root->root_key.objectid, - level - 1, 0); + ret = btrfs_free_extent(trans, root, offset, + root->fs_info->nodesize, 0, + root->root_key.objectid, level - 1, 0); cache = next_cache_extent(cache); } @@ -3826,16 +4150,1173 @@ out: return err; } -static int all_backpointers_checked(struct extent_record *rec, int print_errs) +/* + * Find DIR_ITEM/DIR_INDEX for the given key and check it with the specified + * INODE_REF/INODE_EXTREF match. + * + * @root: the root of the fs/file tree + * @ref_key: the key of the INODE_REF/INODE_EXTREF + * @key: the key of the DIR_ITEM/DIR_INDEX + * @index: the index in the INODE_REF/INODE_EXTREF, be used to + * distinguish root_dir between normal dir/file + * @name: the name in the INODE_REF/INODE_EXTREF + * @namelen: the length of name in the INODE_REF/INODE_EXTREF + * @mode: the st_mode of INODE_ITEM + * + * Return 0 if no error occurred. + * Return ROOT_DIR_ERROR if found DIR_ITEM/DIR_INDEX for root_dir. + * Return DIR_ITEM_MISSING if couldn't find DIR_ITEM/DIR_INDEX for normal + * dir/file. + * Return DIR_ITEM_MISMATCH if INODE_REF/INODE_EXTREF and DIR_ITEM/DIR_INDEX + * not match for normal dir/file. + */ +static int find_dir_item(struct btrfs_root *root, struct btrfs_key *ref_key, + struct btrfs_key *key, u64 index, char *name, + u32 namelen, u32 mode) { - struct list_head *cur = rec->backrefs.next; - struct extent_backref *back; - struct tree_backref *tback; - struct data_backref *dback; - u64 found = 0; - int err = 0; + struct btrfs_path path; + struct extent_buffer *node; + struct btrfs_dir_item *di; + struct btrfs_key location; + char namebuf[BTRFS_NAME_LEN] = {0}; + u32 total; + u32 cur = 0; + u32 len; + u32 name_len; + u32 data_len; + u8 filetype; + int slot; + int ret; - while(cur != &rec->backrefs) { + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); + if (ret < 0) { + ret = DIR_ITEM_MISSING; + goto out; + } + + /* Process root dir and goto out*/ + if (index == 0) { + if (ret == 0) { + ret = ROOT_DIR_ERROR; + error( + "root %llu INODE %s[%llu %llu] ROOT_DIR shouldn't have %s", + root->objectid, + ref_key->type == BTRFS_INODE_REF_KEY ? + "REF" : "EXTREF", + ref_key->objectid, ref_key->offset, + key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX"); + } else { + ret = 0; + } + + goto out; + } + + /* Process normal file/dir */ + if (ret > 0) { + ret = DIR_ITEM_MISSING; + error( + "root %llu INODE %s[%llu %llu] doesn't have related %s[%llu %llu] namelen %u filename %s filetype %d", + root->objectid, + ref_key->type == BTRFS_INODE_REF_KEY ? "REF" : "EXTREF", + ref_key->objectid, ref_key->offset, + key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", + key->objectid, key->offset, namelen, name, + imode_to_type(mode)); + goto out; + } + + /* Check whether inode_id/filetype/name match */ + node = path.nodes[0]; + slot = path.slots[0]; + di = btrfs_item_ptr(node, slot, struct btrfs_dir_item); + total = btrfs_item_size_nr(node, slot); + while (cur < total) { + ret = DIR_ITEM_MISMATCH; + name_len = btrfs_dir_name_len(node, di); + data_len = btrfs_dir_data_len(node, di); + + btrfs_dir_item_key_to_cpu(node, di, &location); + if (location.objectid != ref_key->objectid || + location.type != BTRFS_INODE_ITEM_KEY || + location.offset != 0) + goto next; + + filetype = btrfs_dir_type(node, di); + if (imode_to_type(mode) != filetype) + goto next; + + if (cur + sizeof(*di) + name_len > total || + name_len > BTRFS_NAME_LEN) { + warning("root %llu %s[%llu %llu] name too long %u, trimmed", + root->objectid, + key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", + key->objectid, key->offset, name_len); + + if (cur + sizeof(*di) > total) + break; + len = min_t(u32, total - cur - sizeof(*di), + BTRFS_NAME_LEN); + } else { + len = name_len; + } + + read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len); + if (len != namelen || strncmp(namebuf, name, len)) + goto next; + + ret = 0; + goto out; +next: + len = sizeof(*di) + name_len + data_len; + di = (struct btrfs_dir_item *)((char *)di + len); + cur += len; + } + if (ret == DIR_ITEM_MISMATCH) + error( + "root %llu INODE %s[%llu %llu] and %s[%llu %llu] mismatch namelen %u filename %s filetype %d", + root->objectid, + ref_key->type == BTRFS_INODE_REF_KEY ? "REF" : "EXTREF", + ref_key->objectid, ref_key->offset, + key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", + key->objectid, key->offset, namelen, name, + imode_to_type(mode)); +out: + btrfs_release_path(&path); + return ret; +} + +/* + * Traverse the given INODE_REF and call find_dir_item() to find related + * DIR_ITEM/DIR_INDEX. + * + * @root: the root of the fs/file tree + * @ref_key: the key of the INODE_REF + * @refs: the count of INODE_REF + * @mode: the st_mode of INODE_ITEM + * + * Return 0 if no error occurred. + */ +static int check_inode_ref(struct btrfs_root *root, struct btrfs_key *ref_key, + struct extent_buffer *node, int slot, u64 *refs, + int mode) +{ + struct btrfs_key key; + struct btrfs_inode_ref *ref; + char namebuf[BTRFS_NAME_LEN] = {0}; + u32 total; + u32 cur = 0; + u32 len; + u32 name_len; + u64 index; + int ret, err = 0; + + ref = btrfs_item_ptr(node, slot, struct btrfs_inode_ref); + total = btrfs_item_size_nr(node, slot); + +next: + /* Update inode ref count */ + (*refs)++; + + index = btrfs_inode_ref_index(node, ref); + name_len = btrfs_inode_ref_name_len(node, ref); + if (cur + sizeof(*ref) + name_len > total || + name_len > BTRFS_NAME_LEN) { + warning("root %llu INODE_REF[%llu %llu] name too long", + root->objectid, ref_key->objectid, ref_key->offset); + + if (total < cur + sizeof(*ref)) + goto out; + len = min_t(u32, total - cur - sizeof(*ref), BTRFS_NAME_LEN); + } else { + len = name_len; + } + + read_extent_buffer(node, namebuf, (unsigned long)(ref + 1), len); + + /* Check root dir ref name */ + if (index == 0 && strncmp(namebuf, "..", name_len)) { + error("root %llu INODE_REF[%llu %llu] ROOT_DIR name shouldn't be %s", + root->objectid, ref_key->objectid, ref_key->offset, + namebuf); + err |= ROOT_DIR_ERROR; + } + + /* Find related DIR_INDEX */ + key.objectid = ref_key->offset; + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = index; + ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode); + err |= ret; + + /* Find related dir_item */ + key.objectid = ref_key->offset; + key.type = BTRFS_DIR_ITEM_KEY; + key.offset = btrfs_name_hash(namebuf, len); + ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode); + err |= ret; + + len = sizeof(*ref) + name_len; + ref = (struct btrfs_inode_ref *)((char *)ref + len); + cur += len; + if (cur < total) + goto next; + +out: + return err; +} + +/* + * Traverse the given INODE_EXTREF and call find_dir_item() to find related + * DIR_ITEM/DIR_INDEX. + * + * @root: the root of the fs/file tree + * @ref_key: the key of the INODE_EXTREF + * @refs: the count of INODE_EXTREF + * @mode: the st_mode of INODE_ITEM + * + * Return 0 if no error occurred. + */ +static int check_inode_extref(struct btrfs_root *root, + struct btrfs_key *ref_key, + struct extent_buffer *node, int slot, u64 *refs, + int mode) +{ + struct btrfs_key key; + struct btrfs_inode_extref *extref; + char namebuf[BTRFS_NAME_LEN] = {0}; + u32 total; + u32 cur = 0; + u32 len; + u32 name_len; + u64 index; + u64 parent; + int ret; + int err = 0; + + extref = btrfs_item_ptr(node, slot, struct btrfs_inode_extref); + total = btrfs_item_size_nr(node, slot); + +next: + /* update inode ref count */ + (*refs)++; + name_len = btrfs_inode_extref_name_len(node, extref); + index = btrfs_inode_extref_index(node, extref); + parent = btrfs_inode_extref_parent(node, extref); + if (name_len <= BTRFS_NAME_LEN) { + len = name_len; + } else { + len = BTRFS_NAME_LEN; + warning("root %llu INODE_EXTREF[%llu %llu] name too long", + root->objectid, ref_key->objectid, ref_key->offset); + } + read_extent_buffer(node, namebuf, (unsigned long)(extref + 1), len); + + /* Check root dir ref name */ + if (index == 0 && strncmp(namebuf, "..", name_len)) { + error("root %llu INODE_EXTREF[%llu %llu] ROOT_DIR name shouldn't be %s", + root->objectid, ref_key->objectid, ref_key->offset, + namebuf); + err |= ROOT_DIR_ERROR; + } + + /* find related dir_index */ + key.objectid = parent; + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = index; + ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode); + err |= ret; + + /* find related dir_item */ + key.objectid = parent; + key.type = BTRFS_DIR_ITEM_KEY; + key.offset = btrfs_name_hash(namebuf, len); + ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode); + err |= ret; + + len = sizeof(*extref) + name_len; + extref = (struct btrfs_inode_extref *)((char *)extref + len); + cur += len; + + if (cur < total) + goto next; + + return err; +} + +/* + * Find INODE_REF/INODE_EXTREF for the given key and check it with the specified + * DIR_ITEM/DIR_INDEX match. + * + * @root: the root of the fs/file tree + * @key: the key of the INODE_REF/INODE_EXTREF + * @name: the name in the INODE_REF/INODE_EXTREF + * @namelen: the length of name in the INODE_REF/INODE_EXTREF + * @index: the index in the INODE_REF/INODE_EXTREF, for DIR_ITEM set index + * to (u64)-1 + * @ext_ref: the EXTENDED_IREF feature + * + * Return 0 if no error occurred. + * Return >0 for error bitmap + */ +static int find_inode_ref(struct btrfs_root *root, struct btrfs_key *key, + char *name, int namelen, u64 index, + unsigned int ext_ref) +{ + struct btrfs_path path; + struct btrfs_inode_ref *ref; + struct btrfs_inode_extref *extref; + struct extent_buffer *node; + char ref_namebuf[BTRFS_NAME_LEN] = {0}; + u32 total; + u32 cur = 0; + u32 len; + u32 ref_namelen; + u64 ref_index; + u64 parent; + u64 dir_id; + int slot; + int ret; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); + if (ret) { + ret = INODE_REF_MISSING; + goto extref; + } + + node = path.nodes[0]; + slot = path.slots[0]; + + ref = btrfs_item_ptr(node, slot, struct btrfs_inode_ref); + total = btrfs_item_size_nr(node, slot); + + /* Iterate all entry of INODE_REF */ + while (cur < total) { + ret = INODE_REF_MISSING; + + ref_namelen = btrfs_inode_ref_name_len(node, ref); + ref_index = btrfs_inode_ref_index(node, ref); + if (index != (u64)-1 && index != ref_index) + goto next_ref; + + if (cur + sizeof(*ref) + ref_namelen > total || + ref_namelen > BTRFS_NAME_LEN) { + warning("root %llu INODE %s[%llu %llu] name too long", + root->objectid, + key->type == BTRFS_INODE_REF_KEY ? + "REF" : "EXTREF", + key->objectid, key->offset); + + if (cur + sizeof(*ref) > total) + break; + len = min_t(u32, total - cur - sizeof(*ref), + BTRFS_NAME_LEN); + } else { + len = ref_namelen; + } + + read_extent_buffer(node, ref_namebuf, (unsigned long)(ref + 1), + len); + + if (len != namelen || strncmp(ref_namebuf, name, len)) + goto next_ref; + + ret = 0; + goto out; +next_ref: + len = sizeof(*ref) + ref_namelen; + ref = (struct btrfs_inode_ref *)((char *)ref + len); + cur += len; + } + +extref: + /* Skip if not support EXTENDED_IREF feature */ + if (!ext_ref) + goto out; + + btrfs_release_path(&path); + btrfs_init_path(&path); + + dir_id = key->offset; + key->type = BTRFS_INODE_EXTREF_KEY; + key->offset = btrfs_extref_hash(dir_id, name, namelen); + + ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); + if (ret) { + ret = INODE_REF_MISSING; + goto out; + } + + node = path.nodes[0]; + slot = path.slots[0]; + + extref = btrfs_item_ptr(node, slot, struct btrfs_inode_extref); + cur = 0; + total = btrfs_item_size_nr(node, slot); + + /* Iterate all entry of INODE_EXTREF */ + while (cur < total) { + ret = INODE_REF_MISSING; + + ref_namelen = btrfs_inode_extref_name_len(node, extref); + ref_index = btrfs_inode_extref_index(node, extref); + parent = btrfs_inode_extref_parent(node, extref); + if (index != (u64)-1 && index != ref_index) + goto next_extref; + + if (parent != dir_id) + goto next_extref; + + if (ref_namelen <= BTRFS_NAME_LEN) { + len = ref_namelen; + } else { + len = BTRFS_NAME_LEN; + warning("root %llu INODE %s[%llu %llu] name too long", + root->objectid, + key->type == BTRFS_INODE_REF_KEY ? + "REF" : "EXTREF", + key->objectid, key->offset); + } + read_extent_buffer(node, ref_namebuf, + (unsigned long)(extref + 1), len); + + if (len != namelen || strncmp(ref_namebuf, name, len)) + goto next_extref; + + ret = 0; + goto out; + +next_extref: + len = sizeof(*extref) + ref_namelen; + extref = (struct btrfs_inode_extref *)((char *)extref + len); + cur += len; + + } +out: + btrfs_release_path(&path); + return ret; +} + +/* + * Traverse the given DIR_ITEM/DIR_INDEX and check related INODE_ITEM and + * call find_inode_ref() to check related INODE_REF/INODE_EXTREF. + * + * @root: the root of the fs/file tree + * @key: the key of the INODE_REF/INODE_EXTREF + * @size: the st_size of the INODE_ITEM + * @ext_ref: the EXTENDED_IREF feature + * + * Return 0 if no error occurred. + */ +static int check_dir_item(struct btrfs_root *root, struct btrfs_key *key, + struct extent_buffer *node, int slot, u64 *size, + unsigned int ext_ref) +{ + struct btrfs_dir_item *di; + struct btrfs_inode_item *ii; + struct btrfs_path path; + struct btrfs_key location; + char namebuf[BTRFS_NAME_LEN] = {0}; + u32 total; + u32 cur = 0; + u32 len; + u32 name_len; + u32 data_len; + u8 filetype; + u32 mode; + u64 index; + int ret; + int err = 0; + + /* + * For DIR_ITEM set index to (u64)-1, so that find_inode_ref + * ignore index check. + */ + index = (key->type == BTRFS_DIR_INDEX_KEY) ? key->offset : (u64)-1; + + di = btrfs_item_ptr(node, slot, struct btrfs_dir_item); + total = btrfs_item_size_nr(node, slot); + + while (cur < total) { + data_len = btrfs_dir_data_len(node, di); + if (data_len) + error("root %llu %s[%llu %llu] data_len shouldn't be %u", + root->objectid, key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", + key->objectid, key->offset, data_len); + + name_len = btrfs_dir_name_len(node, di); + if (cur + sizeof(*di) + name_len > total || + name_len > BTRFS_NAME_LEN) { + warning("root %llu %s[%llu %llu] name too long", + root->objectid, + key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", + key->objectid, key->offset); + + if (cur + sizeof(*di) > total) + break; + len = min_t(u32, total - cur - sizeof(*di), + BTRFS_NAME_LEN); + } else { + len = name_len; + } + (*size) += name_len; + + read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len); + filetype = btrfs_dir_type(node, di); + + btrfs_init_path(&path); + btrfs_dir_item_key_to_cpu(node, di, &location); + + /* Ignore related ROOT_ITEM check */ + if (location.type == BTRFS_ROOT_ITEM_KEY) + goto next; + + /* Check relative INODE_ITEM(existence/filetype) */ + ret = btrfs_search_slot(NULL, root, &location, &path, 0, 0); + if (ret) { + err |= INODE_ITEM_MISSING; + error("root %llu %s[%llu %llu] couldn't find relative INODE_ITEM[%llu] namelen %u filename %s filetype %x", + root->objectid, key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", key->objectid, + key->offset, location.objectid, name_len, + namebuf, filetype); + goto next; + } + + ii = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_inode_item); + mode = btrfs_inode_mode(path.nodes[0], ii); + + if (imode_to_type(mode) != filetype) { + err |= INODE_ITEM_MISMATCH; + error("root %llu %s[%llu %llu] relative INODE_ITEM filetype mismatch namelen %u filename %s filetype %d", + root->objectid, key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", key->objectid, + key->offset, name_len, namebuf, filetype); + } + + /* Check relative INODE_REF/INODE_EXTREF */ + location.type = BTRFS_INODE_REF_KEY; + location.offset = key->objectid; + ret = find_inode_ref(root, &location, namebuf, len, + index, ext_ref); + err |= ret; + if (ret & INODE_REF_MISSING) + error("root %llu %s[%llu %llu] relative INODE_REF missing namelen %u filename %s filetype %d", + root->objectid, key->type == BTRFS_DIR_ITEM_KEY ? + "DIR_ITEM" : "DIR_INDEX", key->objectid, + key->offset, name_len, namebuf, filetype); + +next: + btrfs_release_path(&path); + len = sizeof(*di) + name_len + data_len; + di = (struct btrfs_dir_item *)((char *)di + len); + cur += len; + + if (key->type == BTRFS_DIR_INDEX_KEY && cur < total) { + error("root %llu DIR_INDEX[%llu %llu] should contain only one entry", + root->objectid, key->objectid, key->offset); + break; + } + } + + return err; +} + +/* + * Check file extent datasum/hole, update the size of the file extents, + * check and update the last offset of the file extent. + * + * @root: the root of fs/file tree. + * @fkey: the key of the file extent. + * @nodatasum: INODE_NODATASUM feature. + * @size: the sum of all EXTENT_DATA items size for this inode. + * @end: the offset of the last extent. + * + * Return 0 if no error occurred. + */ +static int check_file_extent(struct btrfs_root *root, struct btrfs_key *fkey, + struct extent_buffer *node, int slot, + unsigned int nodatasum, u64 *size, u64 *end) +{ + struct btrfs_file_extent_item *fi; + u64 disk_bytenr; + u64 disk_num_bytes; + u64 extent_num_bytes; + u64 extent_offset; + u64 csum_found; /* In byte size, sectorsize aligned */ + u64 search_start; /* Logical range start we search for csum */ + u64 search_len; /* Logical range len we search for csum */ + unsigned int extent_type; + unsigned int is_hole; + int compressed = 0; + int ret; + int err = 0; + + fi = btrfs_item_ptr(node, slot, struct btrfs_file_extent_item); + + /* Check inline extent */ + extent_type = btrfs_file_extent_type(node, fi); + if (extent_type == BTRFS_FILE_EXTENT_INLINE) { + struct btrfs_item *e = btrfs_item_nr(slot); + u32 item_inline_len; + + item_inline_len = btrfs_file_extent_inline_item_len(node, e); + extent_num_bytes = btrfs_file_extent_inline_len(node, slot, fi); + compressed = btrfs_file_extent_compression(node, fi); + if (extent_num_bytes == 0) { + error( + "root %llu EXTENT_DATA[%llu %llu] has empty inline extent", + root->objectid, fkey->objectid, fkey->offset); + err |= FILE_EXTENT_ERROR; + } + if (!compressed && extent_num_bytes != item_inline_len) { + error( + "root %llu EXTENT_DATA[%llu %llu] wrong inline size, have: %llu, expected: %u", + root->objectid, fkey->objectid, fkey->offset, + extent_num_bytes, item_inline_len); + err |= FILE_EXTENT_ERROR; + } + *end += extent_num_bytes; + *size += extent_num_bytes; + return err; + } + + /* Check extent type */ + if (extent_type != BTRFS_FILE_EXTENT_REG && + extent_type != BTRFS_FILE_EXTENT_PREALLOC) { + err |= FILE_EXTENT_ERROR; + error("root %llu EXTENT_DATA[%llu %llu] type bad", + root->objectid, fkey->objectid, fkey->offset); + return err; + } + + /* Check REG_EXTENT/PREALLOC_EXTENT */ + disk_bytenr = btrfs_file_extent_disk_bytenr(node, fi); + disk_num_bytes = btrfs_file_extent_disk_num_bytes(node, fi); + extent_num_bytes = btrfs_file_extent_num_bytes(node, fi); + extent_offset = btrfs_file_extent_offset(node, fi); + compressed = btrfs_file_extent_compression(node, fi); + is_hole = (disk_bytenr == 0) && (disk_num_bytes == 0); + + /* + * Check EXTENT_DATA csum + * + * For plain (uncompressed) extent, we should only check the range + * we're referring to, as it's possible that part of prealloc extent + * has been written, and has csum: + * + * |<--- Original large preallocated extent A ---->| + * |<- Prealloc File Extent ->|<- Regular Extent ->| + * No csum Has csum + * + * For compressed extent, we should check the whole range. + */ + if (!compressed) { + search_start = disk_bytenr + extent_offset; + search_len = extent_num_bytes; + } else { + search_start = disk_bytenr; + search_len = disk_num_bytes; + } + ret = count_csum_range(root, search_start, search_len, &csum_found); + if (csum_found > 0 && nodatasum) { + err |= ODD_CSUM_ITEM; + error("root %llu EXTENT_DATA[%llu %llu] nodatasum shouldn't have datasum", + root->objectid, fkey->objectid, fkey->offset); + } else if (extent_type == BTRFS_FILE_EXTENT_REG && !nodatasum && + !is_hole && (ret < 0 || csum_found < search_len)) { + err |= CSUM_ITEM_MISSING; + error("root %llu EXTENT_DATA[%llu %llu] csum missing, have: %llu, expected: %llu", + root->objectid, fkey->objectid, fkey->offset, + csum_found, search_len); + } else if (extent_type == BTRFS_FILE_EXTENT_PREALLOC && csum_found > 0) { + err |= ODD_CSUM_ITEM; + error("root %llu EXTENT_DATA[%llu %llu] prealloc shouldn't have csum, but has: %llu", + root->objectid, fkey->objectid, fkey->offset, csum_found); + } + + /* Check EXTENT_DATA hole */ + if (!no_holes && *end != fkey->offset) { + err |= FILE_EXTENT_ERROR; + error("root %llu EXTENT_DATA[%llu %llu] interrupt", + root->objectid, fkey->objectid, fkey->offset); + } + + *end += extent_num_bytes; + if (!is_hole) + *size += extent_num_bytes; + + return err; +} + +/* + * Check INODE_ITEM and related ITEMs (the same inode number) + * 1. check link count + * 2. check inode ref/extref + * 3. check dir item/index + * + * @ext_ref: the EXTENDED_IREF feature + * + * Return 0 if no error occurred. + * Return >0 for error or hit the traversal is done(by error bitmap) + */ +static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, + unsigned int ext_ref) +{ + struct extent_buffer *node; + struct btrfs_inode_item *ii; + struct btrfs_key key; + u64 inode_id; + u32 mode; + u64 nlink; + u64 nbytes; + u64 isize; + u64 size = 0; + u64 refs = 0; + u64 extent_end = 0; + u64 extent_size = 0; + unsigned int dir; + unsigned int nodatasum; + int slot; + int ret; + int err = 0; + + node = path->nodes[0]; + slot = path->slots[0]; + + btrfs_item_key_to_cpu(node, &key, slot); + inode_id = key.objectid; + + if (inode_id == BTRFS_ORPHAN_OBJECTID) { + ret = btrfs_next_item(root, path); + if (ret > 0) + err |= LAST_ITEM; + return err; + } + + ii = btrfs_item_ptr(node, slot, struct btrfs_inode_item); + isize = btrfs_inode_size(node, ii); + nbytes = btrfs_inode_nbytes(node, ii); + mode = btrfs_inode_mode(node, ii); + dir = imode_to_type(mode) == BTRFS_FT_DIR; + nlink = btrfs_inode_nlink(node, ii); + nodatasum = btrfs_inode_flags(node, ii) & BTRFS_INODE_NODATASUM; + + while (1) { + ret = btrfs_next_item(root, path); + if (ret < 0) { + /* out will fill 'err' rusing current statistics */ + goto out; + } else if (ret > 0) { + err |= LAST_ITEM; + goto out; + } + + node = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(node, &key, slot); + if (key.objectid != inode_id) + goto out; + + switch (key.type) { + case BTRFS_INODE_REF_KEY: + ret = check_inode_ref(root, &key, node, slot, &refs, + mode); + err |= ret; + break; + case BTRFS_INODE_EXTREF_KEY: + if (key.type == BTRFS_INODE_EXTREF_KEY && !ext_ref) + warning("root %llu EXTREF[%llu %llu] isn't supported", + root->objectid, key.objectid, + key.offset); + ret = check_inode_extref(root, &key, node, slot, &refs, + mode); + err |= ret; + break; + case BTRFS_DIR_ITEM_KEY: + case BTRFS_DIR_INDEX_KEY: + if (!dir) { + warning("root %llu INODE[%llu] mode %u shouldn't have DIR_INDEX[%llu %llu]", + root->objectid, inode_id, + imode_to_type(mode), key.objectid, + key.offset); + } + ret = check_dir_item(root, &key, node, slot, &size, + ext_ref); + err |= ret; + break; + case BTRFS_EXTENT_DATA_KEY: + if (dir) { + warning("root %llu DIR INODE[%llu] shouldn't EXTENT_DATA[%llu %llu]", + root->objectid, inode_id, key.objectid, + key.offset); + } + ret = check_file_extent(root, &key, node, slot, + nodatasum, &extent_size, + &extent_end); + err |= ret; + break; + case BTRFS_XATTR_ITEM_KEY: + break; + default: + error("ITEM[%llu %u %llu] UNKNOWN TYPE", + key.objectid, key.type, key.offset); + } + } + +out: + /* verify INODE_ITEM nlink/isize/nbytes */ + if (dir) { + if (nlink != 1) { + err |= LINK_COUNT_ERROR; + error("root %llu DIR INODE[%llu] shouldn't have more than one link(%llu)", + root->objectid, inode_id, nlink); + } + + /* + * Just a warning, as dir inode nbytes is just an + * instructive value. + */ + if (!IS_ALIGNED(nbytes, root->fs_info->nodesize)) { + warning("root %llu DIR INODE[%llu] nbytes should be aligned to %u", + root->objectid, inode_id, + root->fs_info->nodesize); + } + + if (isize != size) { + err |= ISIZE_ERROR; + error("root %llu DIR INODE [%llu] size(%llu) not equal to %llu", + root->objectid, inode_id, isize, size); + } + } else { + if (nlink != refs) { + err |= LINK_COUNT_ERROR; + error("root %llu INODE[%llu] nlink(%llu) not equal to inode_refs(%llu)", + root->objectid, inode_id, nlink, refs); + } else if (!nlink) { + err |= ORPHAN_ITEM; + } + + if (!nbytes && !no_holes && extent_end < isize) { + err |= NBYTES_ERROR; + error("root %llu INODE[%llu] size (%llu) should have a file extent hole", + root->objectid, inode_id, isize); + } + + if (nbytes != extent_size) { + err |= NBYTES_ERROR; + error("root %llu INODE[%llu] nbytes(%llu) not equal to extent_size(%llu)", + root->objectid, inode_id, nbytes, extent_size); + } + } + + return err; +} + +static int check_fs_first_inode(struct btrfs_root *root, unsigned int ext_ref) +{ + struct btrfs_path path; + struct btrfs_key key; + int err = 0; + int ret; + + key.objectid = BTRFS_FIRST_FREE_OBJECTID; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; + + /* For root being dropped, we don't need to check first inode */ + if (btrfs_root_refs(&root->root_item) == 0 && + btrfs_disk_key_objectid(&root->root_item.drop_progress) >= + key.objectid) + return 0; + + btrfs_init_path(&path); + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + goto out; + if (ret > 0) { + ret = 0; + err |= INODE_ITEM_MISSING; + error("first inode item of root %llu is missing", + root->objectid); + } + + err |= check_inode_item(root, &path, ext_ref); + err &= ~LAST_ITEM; + if (err && !ret) + ret = -EIO; +out: + btrfs_release_path(&path); + return ret; +} + +/* + * Iterate all item on the tree and call check_inode_item() to check. + * + * @root: the root of the tree to be checked. + * @ext_ref: the EXTENDED_IREF feature + * + * Return 0 if no error found. + * Return <0 for error. + */ +static int check_fs_root_v2(struct btrfs_root *root, unsigned int ext_ref) +{ + struct btrfs_path path; + struct node_refs nrefs; + struct btrfs_root_item *root_item = &root->root_item; + int ret; + int level; + int err = 0; + + /* + * We need to manually check the first inode item(256) + * As the following traversal function will only start from + * the first inode item in the leaf, if inode item(256) is missing + * we will just skip it forever. + */ + ret = check_fs_first_inode(root, ext_ref); + if (ret < 0) + return ret; + + memset(&nrefs, 0, sizeof(nrefs)); + level = btrfs_header_level(root->node); + btrfs_init_path(&path); + + if (btrfs_root_refs(root_item) > 0 || + btrfs_disk_key_objectid(&root_item->drop_progress) == 0) { + path.nodes[level] = root->node; + path.slots[level] = 0; + extent_buffer_get(root->node); + } else { + struct btrfs_key key; + + btrfs_disk_key_to_cpu(&key, &root_item->drop_progress); + level = root_item->drop_level; + path.lowest_level = level; + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + goto out; + ret = 0; + } + + while (1) { + ret = walk_down_tree_v2(root, &path, &level, &nrefs, ext_ref); + err |= !!ret; + + /* if ret is negative, walk shall stop */ + if (ret < 0) { + ret = err; + break; + } + + ret = walk_up_tree_v2(root, &path, &level); + if (ret != 0) { + /* Normal exit, reset ret to err */ + ret = err; + break; + } + } + +out: + btrfs_release_path(&path); + return ret; +} + +/* + * Find the relative ref for root_ref and root_backref. + * + * @root: the root of the root tree. + * @ref_key: the key of the root ref. + * + * Return 0 if no error occurred. + */ +static int check_root_ref(struct btrfs_root *root, struct btrfs_key *ref_key, + struct extent_buffer *node, int slot) +{ + struct btrfs_path path; + struct btrfs_key key; + struct btrfs_root_ref *ref; + struct btrfs_root_ref *backref; + char ref_name[BTRFS_NAME_LEN] = {0}; + char backref_name[BTRFS_NAME_LEN] = {0}; + u64 ref_dirid; + u64 ref_seq; + u32 ref_namelen; + u64 backref_dirid; + u64 backref_seq; + u32 backref_namelen; + u32 len; + int ret; + int err = 0; + + ref = btrfs_item_ptr(node, slot, struct btrfs_root_ref); + ref_dirid = btrfs_root_ref_dirid(node, ref); + ref_seq = btrfs_root_ref_sequence(node, ref); + ref_namelen = btrfs_root_ref_name_len(node, ref); + + if (ref_namelen <= BTRFS_NAME_LEN) { + len = ref_namelen; + } else { + len = BTRFS_NAME_LEN; + warning("%s[%llu %llu] ref_name too long", + ref_key->type == BTRFS_ROOT_REF_KEY ? + "ROOT_REF" : "ROOT_BACKREF", ref_key->objectid, + ref_key->offset); + } + read_extent_buffer(node, ref_name, (unsigned long)(ref + 1), len); + + /* Find relative root_ref */ + key.objectid = ref_key->offset; + key.type = BTRFS_ROOT_BACKREF_KEY + BTRFS_ROOT_REF_KEY - ref_key->type; + key.offset = ref_key->objectid; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret) { + err |= ROOT_REF_MISSING; + error("%s[%llu %llu] couldn't find relative ref", + ref_key->type == BTRFS_ROOT_REF_KEY ? + "ROOT_REF" : "ROOT_BACKREF", + ref_key->objectid, ref_key->offset); + goto out; + } + + backref = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_root_ref); + backref_dirid = btrfs_root_ref_dirid(path.nodes[0], backref); + backref_seq = btrfs_root_ref_sequence(path.nodes[0], backref); + backref_namelen = btrfs_root_ref_name_len(path.nodes[0], backref); + + if (backref_namelen <= BTRFS_NAME_LEN) { + len = backref_namelen; + } else { + len = BTRFS_NAME_LEN; + warning("%s[%llu %llu] ref_name too long", + key.type == BTRFS_ROOT_REF_KEY ? + "ROOT_REF" : "ROOT_BACKREF", + key.objectid, key.offset); + } + read_extent_buffer(path.nodes[0], backref_name, + (unsigned long)(backref + 1), len); + + if (ref_dirid != backref_dirid || ref_seq != backref_seq || + ref_namelen != backref_namelen || + strncmp(ref_name, backref_name, len)) { + err |= ROOT_REF_MISMATCH; + error("%s[%llu %llu] mismatch relative ref", + ref_key->type == BTRFS_ROOT_REF_KEY ? + "ROOT_REF" : "ROOT_BACKREF", + ref_key->objectid, ref_key->offset); + } +out: + btrfs_release_path(&path); + return err; +} + +/* + * Check all fs/file tree in low_memory mode. + * + * 1. for fs tree root item, call check_fs_root_v2() + * 2. for fs tree root ref/backref, call check_root_ref() + * + * Return 0 if no error occurred. + */ +static int check_fs_roots_v2(struct btrfs_fs_info *fs_info) +{ + struct btrfs_root *tree_root = fs_info->tree_root; + struct btrfs_root *cur_root = NULL; + struct btrfs_path path; + struct btrfs_key key; + struct extent_buffer *node; + unsigned int ext_ref; + int slot; + int ret; + int err = 0; + + ext_ref = btrfs_fs_incompat(fs_info, EXTENDED_IREF); + + btrfs_init_path(&path); + key.objectid = BTRFS_FS_TREE_OBJECTID; + key.offset = 0; + key.type = BTRFS_ROOT_ITEM_KEY; + + ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0); + if (ret < 0) { + err = ret; + goto out; + } else if (ret > 0) { + err = -ENOENT; + goto out; + } + + while (1) { + node = path.nodes[0]; + slot = path.slots[0]; + btrfs_item_key_to_cpu(node, &key, slot); + if (key.objectid > BTRFS_LAST_FREE_OBJECTID) + goto out; + if (key.type == BTRFS_ROOT_ITEM_KEY && + fs_root_objectid(key.objectid)) { + if (key.objectid == BTRFS_TREE_RELOC_OBJECTID) { + cur_root = btrfs_read_fs_root_no_cache(fs_info, + &key); + } else { + key.offset = (u64)-1; + cur_root = btrfs_read_fs_root(fs_info, &key); + } + + if (IS_ERR(cur_root)) { + error("Fail to read fs/subvol tree: %lld", + key.objectid); + err = -EIO; + goto next; + } + + ret = check_fs_root_v2(cur_root, ext_ref); + err |= ret; + + if (key.objectid == BTRFS_TREE_RELOC_OBJECTID) + btrfs_free_fs_root(cur_root); + } else if (key.type == BTRFS_ROOT_REF_KEY || + key.type == BTRFS_ROOT_BACKREF_KEY) { + ret = check_root_ref(tree_root, &key, node, slot); + err |= ret; + } +next: + ret = btrfs_next_item(tree_root, &path); + if (ret > 0) + goto out; + if (ret < 0) { + err = ret; + goto out; + } + } + +out: + btrfs_release_path(&path); + return err; +} + +static int all_backpointers_checked(struct extent_record *rec, int print_errs) +{ + struct list_head *cur = rec->backrefs.next; + struct extent_backref *back; + struct tree_backref *tback; + struct data_backref *dback; + u64 found = 0; + int err = 0; + + while(cur != &rec->backrefs) { back = to_extent_backref(cur); cur = cur->next; if (!back->found_extent_tree) { @@ -3953,8 +5434,7 @@ static int free_all_extent_backrefs(struct extent_record *rec) return 0; } -static void free_extent_record_cache(struct btrfs_fs_info *fs_info, - struct cache_tree *extent_cache) +static void free_extent_record_cache(struct cache_tree *extent_cache) { struct cache_extent *cache; struct extent_record *rec; @@ -4153,9 +5633,7 @@ static int swap_values(struct btrfs_root *root, struct btrfs_path *path, return 0; } -static int fix_key_order(struct btrfs_trans_handle *trans, - struct btrfs_root *root, - struct btrfs_path *path) +static int fix_key_order(struct btrfs_root *root, struct btrfs_path *path) { struct extent_buffer *buf; struct btrfs_key k1, k2; @@ -4183,8 +5661,7 @@ static int fix_key_order(struct btrfs_trans_handle *trans, return ret; } -static int delete_bogus_item(struct btrfs_trans_handle *trans, - struct btrfs_root *root, +static int delete_bogus_item(struct btrfs_root *root, struct btrfs_path *path, struct extent_buffer *buf, int slot) { @@ -4219,9 +5696,7 @@ static int delete_bogus_item(struct btrfs_trans_handle *trans, return 0; } -static int fix_item_offset(struct btrfs_trans_handle *trans, - struct btrfs_root *root, - struct btrfs_path *path) +static int fix_item_offset(struct btrfs_root *root, struct btrfs_path *path) { struct extent_buffer *buf; int i; @@ -4238,8 +5713,7 @@ again: BTRFS_LEAF_DATA_SIZE(root)) { if (btrfs_item_end_nr(buf, i) > BTRFS_LEAF_DATA_SIZE(root)) { - ret = delete_bogus_item(trans, root, path, - buf, i); + ret = delete_bogus_item(root, path, buf, i); if (!ret) goto again; fprintf(stderr, "item is off the end of the " @@ -4253,8 +5727,7 @@ again: btrfs_item_offset_nr(buf, i - 1)) { if (btrfs_item_end_nr(buf, i) > btrfs_item_offset_nr(buf, i - 1)) { - ret = delete_bogus_item(trans, root, path, - buf, i); + ret = delete_bogus_item(root, path, buf, i); if (!ret) goto again; fprintf(stderr, "items overlap, can't fix\n"); @@ -4346,9 +5819,9 @@ static int try_to_fix_bad_block(struct btrfs_root *root, break; } if (status == BTRFS_TREE_BLOCK_BAD_KEY_ORDER) - ret = fix_key_order(trans, search_root, &path); + ret = fix_key_order(search_root, &path); else if (status == BTRFS_TREE_BLOCK_INVALID_OFFSETS) - ret = fix_item_offset(trans, search_root, &path); + ret = fix_item_offset(search_root, &path); if (ret) { btrfs_commit_transaction(trans, search_root); break; @@ -4601,6 +6074,7 @@ static int add_extent_rec_nolookup(struct cache_tree *extent_cache, struct extent_record *rec; int ret = 0; + BUG_ON(tmpl->max_size == 0); rec = malloc(sizeof(*rec)); if (!rec) return -ENOMEM; @@ -4635,7 +6109,7 @@ static int add_extent_rec_nolookup(struct cache_tree *extent_cache, if (tmpl->metadata) rec->crossing_stripes = check_crossing_stripes(global_info, - rec->start, global_info->tree_root->nodesize); + rec->start, global_info->nodesize); check_extent_type(rec); return ret; } @@ -4737,7 +6211,7 @@ static int add_extent_rec(struct cache_tree *extent_cache, if (tmpl->metadata) rec->crossing_stripes = check_crossing_stripes( global_info, rec->start, - global_info->tree_root->nodesize); + global_info->nodesize); check_extent_type(rec); maybe_free_extent_rec(extent_cache, rec); return ret; @@ -4764,6 +6238,7 @@ static int add_tree_backref(struct cache_tree *extent_cache, u64 bytenr, tmpl.start = bytenr; tmpl.nr = 1; tmpl.metadata = 1; + tmpl.max_size = 1; ret = add_extent_rec_nolookup(extent_cache, &tmpl); if (ret) @@ -5151,7 +6626,7 @@ static int process_chunk_item(struct cache_tree *chunk_cache, * wrong onwer(3) out of chunk tree, to pass both chunk tree check * and owner<->key_type check. */ - ret = btrfs_check_chunk_valid(global_info->tree_root, eb, chunk, slot, + ret = btrfs_check_chunk_valid(global_info, eb, chunk, slot, key->offset); if (ret < 0) { error("chunk(%llu, %llu) is not valid, ignore it", @@ -5333,14 +6808,14 @@ static int process_extent_item(struct btrfs_root *root, if (key.type == BTRFS_METADATA_ITEM_KEY) { metadata = 1; - num_bytes = root->nodesize; + num_bytes = root->fs_info->nodesize; } else { num_bytes = key.offset; } - if (!IS_ALIGNED(key.objectid, root->sectorsize)) { + if (!IS_ALIGNED(key.objectid, root->fs_info->sectorsize)) { error("ignoring invalid extent, bytenr %llu is not aligned to %u", - key.objectid, root->sectorsize); + key.objectid, root->fs_info->sectorsize); return -EIO; } if (item_size < sizeof(*ei)) { @@ -5369,14 +6844,14 @@ static int process_extent_item(struct btrfs_root *root, metadata = 1; else metadata = 0; - if (metadata && num_bytes != root->nodesize) { + if (metadata && num_bytes != root->fs_info->nodesize) { error("ignore invalid metadata extent, length %llu does not equal to %u", - num_bytes, root->nodesize); + num_bytes, root->fs_info->nodesize); return -EIO; } - if (!metadata && !IS_ALIGNED(num_bytes, root->sectorsize)) { + if (!metadata && !IS_ALIGNED(num_bytes, root->fs_info->sectorsize)) { error("ignore invalid data extent, length %llu is not aligned to %u", - num_bytes, root->sectorsize); + num_bytes, root->fs_info->sectorsize); return -EIO; } @@ -5404,14 +6879,16 @@ static int process_extent_item(struct btrfs_root *root, ret = add_tree_backref(extent_cache, key.objectid, 0, offset, 0); if (ret < 0) - error("add_tree_backref failed: %s", + error( + "add_tree_backref failed (extent items tree block): %s", strerror(-ret)); break; case BTRFS_SHARED_BLOCK_REF_KEY: ret = add_tree_backref(extent_cache, key.objectid, offset, 0, 0); if (ret < 0) - error("add_tree_backref failed: %s", + error( + "add_tree_backref failed (extent items shared block): %s", strerror(-ret)); break; case BTRFS_EXTENT_DATA_REF_KEY: @@ -5455,7 +6932,7 @@ static int check_cache_range(struct btrfs_root *root, for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) { bytenr = btrfs_sb_offset(i); - ret = btrfs_rmap_block(&root->fs_info->mapping_tree, + ret = btrfs_rmap_block(root->fs_info, cache->key.objectid, bytenr, 0, &logical, &nr, &stripe_len); if (ret) @@ -5582,7 +7059,7 @@ static int verify_space_cache(struct btrfs_root *root, if (key.type == BTRFS_EXTENT_ITEM_KEY) last = key.objectid + key.offset; else - last = key.objectid + root->nodesize; + last = key.objectid + root->fs_info->nodesize; path.slots[0]++; continue; } @@ -5594,7 +7071,7 @@ static int verify_space_cache(struct btrfs_root *root, if (key.type == BTRFS_EXTENT_ITEM_KEY) last = key.objectid + key.offset; else - last = key.objectid + root->nodesize; + last = key.objectid + root->fs_info->nodesize; path.slots[0]++; } @@ -5644,7 +7121,7 @@ static int check_space_cache(struct btrfs_root *root) start = cache->key.objectid + cache->key.offset; if (!cache->free_space_ctl) { if (btrfs_init_free_space_ctl(cache, - root->sectorsize)) { + root->fs_info->sectorsize)) { ret = -ENOMEM; break; } @@ -5652,8 +7129,7 @@ static int check_space_cache(struct btrfs_root *root) btrfs_remove_free_space_cache(cache); } - if (btrfs_fs_compat_ro(root->fs_info, - BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE)) { + if (btrfs_fs_compat_ro(root->fs_info, FREE_SPACE_TREE)) { ret = exclude_super_stripes(root, cache); if (ret) { fprintf(stderr, "could not exclude super stripes: %s\n", @@ -5693,8 +7169,9 @@ static int check_extent_csums(struct btrfs_root *root, u64 bytenr, u64 num_bytes, unsigned long leaf_offset, struct extent_buffer *eb) { + struct btrfs_fs_info *fs_info = root->fs_info; u64 offset = 0; - u16 csum_size = btrfs_super_csum_size(root->fs_info->super_copy); + u16 csum_size = btrfs_super_csum_size(fs_info->super_copy); char *data; unsigned long csum_offset; u32 csum; @@ -5706,7 +7183,7 @@ static int check_extent_csums(struct btrfs_root *root, u64 bytenr, int mirror; int num_copies; - if (num_bytes % root->sectorsize) + if (num_bytes % fs_info->sectorsize) return -EINVAL; data = malloc(num_bytes); @@ -5718,7 +7195,7 @@ static int check_extent_csums(struct btrfs_root *root, u64 bytenr, again: read_len = num_bytes - offset; /* read as much space once a time */ - ret = read_extent_data(root, data + offset, + ret = read_extent_data(fs_info, data + offset, bytenr + offset, &read_len, mirror); if (ret) goto out; @@ -5728,12 +7205,12 @@ again: csum = ~(u32)0; tmp = offset + data_checked; - csum = btrfs_csum_data(NULL, (char *)data + tmp, - csum, root->sectorsize); + csum = btrfs_csum_data((char *)data + tmp, + csum, fs_info->sectorsize); btrfs_csum_final(csum, (u8 *)&csum); csum_offset = leaf_offset + - tmp / root->sectorsize * csum_size; + tmp / fs_info->sectorsize * csum_size; read_extent_buffer(eb, (char *)&csum_expected, csum_offset, csum_size); /* try another mirror */ @@ -5741,15 +7218,14 @@ again: fprintf(stderr, "mirror %d bytenr %llu csum %u expected csum %u\n", mirror, bytenr + tmp, csum, csum_expected); - num_copies = btrfs_num_copies( - &root->fs_info->mapping_tree, + num_copies = btrfs_num_copies(root->fs_info, bytenr, num_bytes); if (mirror < num_copies - 1) { mirror += 1; goto again; } } - data_checked += root->sectorsize; + data_checked += fs_info->sectorsize; } offset += read_len; } @@ -5950,7 +7426,7 @@ static int check_csums(struct btrfs_root *root) } data_len = (btrfs_item_size_nr(leaf, path.slots[0]) / - csum_size) * root->sectorsize; + csum_size) * root->fs_info->sectorsize; if (!check_data_csum) goto skip_csum_check; leaf_offset = btrfs_item_ptr_offset(leaf, path.slots[0]); @@ -6015,8 +7491,7 @@ static int is_dropped_key(struct btrfs_key *key, * assumption and simply indicate that we _think_ that the FULL BACKREF needs to * be set or not and then we can check later once we've gathered all the refs. */ -static int calc_extent_flag(struct btrfs_root *root, - struct cache_tree *extent_cache, +static int calc_extent_flag(struct cache_tree *extent_cache, struct extent_buffer *buf, struct root_item_record *ri, u64 *flags) @@ -6136,6 +7611,7 @@ static int run_next_block(struct btrfs_root *root, struct device_extent_tree *dev_extent_cache, struct root_item_record *ri) { + struct btrfs_fs_info *fs_info = root->fs_info; struct extent_buffer *buf; struct extent_record *rec = NULL; u64 bytenr; @@ -6165,7 +7641,7 @@ static int run_next_block(struct btrfs_root *root, continue; /* fixme, get the parent transid */ - readahead_tree_block(root, bits[i].start, + readahead_tree_block(fs_info, bits[i].start, bits[i].size, 0); } } @@ -6195,7 +7671,7 @@ static int run_next_block(struct btrfs_root *root, } /* fixme, get the real parent transid */ - buf = read_tree_block(root, bytenr, size, gen); + buf = read_tree_block(root->fs_info, bytenr, size, gen); if (!extent_buffer_uptodate(buf)) { record_bad_block_io(root->fs_info, extent_cache, bytenr, size); @@ -6210,7 +7686,7 @@ static int run_next_block(struct btrfs_root *root, btrfs_header_level(buf), 1, NULL, &flags); if (ret < 0) { - ret = calc_extent_flag(root, extent_cache, buf, ri, &flags); + ret = calc_extent_flag(extent_cache, buf, ri, &flags); if (ret < 0) { fprintf(stderr, "Couldn't calc extent flags\n"); flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF; @@ -6218,7 +7694,7 @@ static int run_next_block(struct btrfs_root *root, } } else { flags = 0; - ret = calc_extent_flag(root, extent_cache, buf, ri, &flags); + ret = calc_extent_flag(extent_cache, buf, ri, &flags); if (ret < 0) { fprintf(stderr, "Couldn't calc extent flags\n"); flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF; @@ -6327,7 +7803,8 @@ static int run_next_block(struct btrfs_root *root, ret = add_tree_backref(extent_cache, key.objectid, 0, key.offset, 0); if (ret < 0) - error("add_tree_backref failed: %s", + error( + "add_tree_backref failed (leaf tree block): %s", strerror(-ret)); continue; } @@ -6335,7 +7812,8 @@ static int run_next_block(struct btrfs_root *root, ret = add_tree_backref(extent_cache, key.objectid, key.offset, 0, 0); if (ret < 0) - error("add_tree_backref failed: %s", + error( + "add_tree_backref failed (leaf shared block): %s", strerror(-ret)); continue; } @@ -6350,7 +7828,7 @@ static int run_next_block(struct btrfs_root *root, ref), btrfs_extent_data_ref_offset(buf, ref), btrfs_extent_data_ref_count(buf, ref), - 0, root->sectorsize); + 0, root->fs_info->sectorsize); continue; } if (key.type == BTRFS_SHARED_DATA_REF_KEY) { @@ -6360,7 +7838,7 @@ static int run_next_block(struct btrfs_root *root, add_data_backref(extent_cache, key.objectid, key.offset, 0, 0, 0, btrfs_shared_data_ref_count(buf, ref), - 0, root->sectorsize); + 0, root->fs_info->sectorsize); continue; } if (key.type == BTRFS_ORPHAN_ITEM_KEY) { @@ -6392,7 +7870,7 @@ static int run_next_block(struct btrfs_root *root, data_bytes_allocated += btrfs_file_extent_disk_num_bytes(buf, fi); - if (data_bytes_allocated < root->sectorsize) { + if (data_bytes_allocated < root->fs_info->sectorsize) { abort(); } data_bytes_referenced += @@ -6416,7 +7894,7 @@ static int run_next_block(struct btrfs_root *root, struct extent_record tmpl; ptr = btrfs_node_blockptr(buf, i); - size = root->nodesize; + size = root->fs_info->nodesize; btrfs_node_key_to_cpu(buf, &key, i); if (ri != NULL) { if ((level == ri->drop_level) @@ -6440,7 +7918,8 @@ static int run_next_block(struct btrfs_root *root, ret = add_tree_backref(extent_cache, ptr, parent, owner, 1); if (ret < 0) { - error("add_tree_backref failed: %s", + error( + "add_tree_backref failed (non-leaf block): %s", strerror(-ret)); continue; } @@ -6577,7 +8056,7 @@ out: static int delete_extent_records(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, - u64 bytenr, u64 new_len) + u64 bytenr) { struct btrfs_key key; struct btrfs_key found_key; @@ -6641,7 +8120,7 @@ static int delete_extent_records(struct btrfs_trans_handle *trans, if (found_key.type == BTRFS_EXTENT_ITEM_KEY || found_key.type == BTRFS_METADATA_ITEM_KEY) { u64 bytes = (found_key.type == BTRFS_EXTENT_ITEM_KEY) ? - found_key.offset : root->nodesize; + found_key.offset : root->fs_info->nodesize; ret = btrfs_update_block_group(trans, root, bytenr, bytes, 0, 0); @@ -6665,18 +8144,17 @@ static int record_extent(struct btrfs_trans_handle *trans, struct extent_backref *back, int allocated, u64 flags) { - int ret; + int ret = 0; struct btrfs_root *extent_root = info->extent_root; struct extent_buffer *leaf; struct btrfs_key ins_key; struct btrfs_extent_item *ei; - struct tree_backref *tback; struct data_backref *dback; struct btrfs_tree_block_info *bi; if (!back->is_data) rec->max_size = max_t(u64, rec->max_size, - info->extent_root->nodesize); + info->nodesize); if (!allocated) { u32 item_size = sizeof(*ei); @@ -6706,7 +8184,6 @@ static int record_extent(struct btrfs_trans_handle *trans, } else { struct btrfs_disk_key copy_key;; - tback = to_tree_backref(back); bi = (struct btrfs_tree_block_info *)(ei + 1); memset_extent_buffer(leaf, 0, (unsigned long)bi, sizeof(*bi)); @@ -6772,6 +8249,7 @@ static int record_extent(struct btrfs_trans_handle *trans, dback->found_ref); } else { u64 parent; + struct tree_backref *tback; tback = to_tree_backref(back); if (back->full_backref) @@ -6809,11 +8287,6 @@ static struct extent_entry *find_most_right_entry(struct list_head *entries) struct extent_entry *entry, *best = NULL, *prev = NULL; list_for_each_entry(entry, entries, list) { - if (!prev) { - prev = entry; - continue; - } - /* * If there are as many broken entries as entries then we know * not to trust this particular entry. @@ -6822,6 +8295,16 @@ static struct extent_entry *find_most_right_entry(struct list_head *entries) continue; /* + * Special case, when there are only two entries and 'best' is + * the first one + */ + if (!prev) { + best = entry; + prev = entry; + continue; + } + + /* * If our current entry == best then we can't be sure our best * is really the best, so we need to keep searching. */ @@ -7183,8 +8666,7 @@ out: return ret; } -static int process_duplicates(struct btrfs_root *root, - struct cache_tree *extent_cache, +static int process_duplicates(struct cache_tree *extent_cache, struct extent_record *rec) { struct extent_record *good, *tmp; @@ -7579,7 +9061,7 @@ static int fixup_extent_refs(struct btrfs_fs_info *info, /* step two, delete all the existing records */ ret = delete_extent_records(trans, info->extent_root, &path, - rec->start, rec->max_size); + rec->start); if (ret < 0) goto out; @@ -7618,6 +9100,10 @@ out: ret = err; } + if (!ret) + fprintf(stderr, "Repaired extent references for %llu\n", + (unsigned long long)rec->start); + btrfs_release_path(&path); return ret; } @@ -7675,7 +9161,12 @@ static int fixup_extent_flags(struct btrfs_fs_info *fs_info, btrfs_set_extent_flags(path.nodes[0], ei, flags); btrfs_mark_buffer_dirty(path.nodes[0]); btrfs_release_path(&path); - return btrfs_commit_transaction(trans, root); + ret = btrfs_commit_transaction(trans, root); + if (!ret) + fprintf(stderr, "Repaired extent flags for %llu\n", + (unsigned long long)rec->start); + + return ret; } /* right now we only prune from the extent allocation tree */ @@ -7740,7 +9231,7 @@ again: del_ptr: printk("deleting pointer to block %Lu\n", corrupt->cache.start); - ret = btrfs_del_ptr(trans, info->extent_root, &path, level, slot); + ret = btrfs_del_ptr(info->extent_root, &path, level, slot); out: btrfs_release_path(&path); @@ -7782,8 +9273,7 @@ static void reset_cached_block_groups(struct btrfs_fs_info *fs_info) &start, &end, EXTENT_DIRTY); if (ret) break; - clear_extent_dirty(&fs_info->free_space_cache, start, end, - GFP_NOFS); + clear_extent_dirty(&fs_info->free_space_cache, start, end); } start = 0; @@ -7802,11 +9292,8 @@ static int check_extent_refs(struct btrfs_root *root, { struct extent_record *rec; struct cache_extent *cache; - int err = 0; int ret = 0; - int fixed = 0; int had_dups = 0; - int recorded = 0; if (repair) { /* @@ -7820,8 +9307,7 @@ static int check_extent_refs(struct btrfs_root *root, rec = container_of(cache, struct extent_record, cache); set_extent_dirty(root->fs_info->excluded_extents, rec->start, - rec->start + rec->max_size - 1, - GFP_NOFS); + rec->start + rec->max_size - 1); cache = next_cache_extent(cache); } @@ -7830,8 +9316,7 @@ static int check_extent_refs(struct btrfs_root *root, while(cache) { set_extent_dirty(root->fs_info->excluded_extents, cache->start, - cache->start + cache->size - 1, - GFP_NOFS); + cache->start + cache->size - 1); cache = next_cache_extent(cache); } prune_corrupt_blocks(root->fs_info); @@ -7856,7 +9341,7 @@ static int check_extent_refs(struct btrfs_root *root, * process_duplicates() will return 0, otherwise it will return * 1 and we */ - if (process_duplicates(root, extent_cache, rec)) + if (process_duplicates(extent_cache, rec)) continue; ret = delete_duplicate_records(root, rec); if (ret < 0) @@ -7875,9 +9360,8 @@ static int check_extent_refs(struct btrfs_root *root, while(1) { int cur_err = 0; + int fix = 0; - fixed = 0; - recorded = 0; cache = search_cache_extent(extent_cache, 0); if (!cache) break; @@ -7885,7 +9369,6 @@ static int check_extent_refs(struct btrfs_root *root, if (rec->num_duplicates) { fprintf(stderr, "extent item %llu has multiple extent " "items\n", (unsigned long long)rec->start); - err = 1; cur_err = 1; } @@ -7899,54 +9382,31 @@ static int check_extent_refs(struct btrfs_root *root, ret = record_orphan_data_extents(root->fs_info, rec); if (ret < 0) goto repair_abort; - if (ret == 0) { - recorded = 1; - } else { - /* - * we can't use the extent to repair file - * extent, let the fallback method handle it. - */ - if (!fixed && repair) { - ret = fixup_extent_refs( - root->fs_info, - extent_cache, rec); - if (ret) - goto repair_abort; - fixed = 1; - } - } - err = 1; + fix = ret; cur_err = 1; } if (all_backpointers_checked(rec, 1)) { fprintf(stderr, "backpointer mismatch on [%llu %llu]\n", (unsigned long long)rec->start, (unsigned long long)rec->nr); - - if (!fixed && !recorded && repair) { - ret = fixup_extent_refs(root->fs_info, - extent_cache, rec); - if (ret) - goto repair_abort; - fixed = 1; - } + fix = 1; cur_err = 1; - err = 1; } if (!rec->owner_ref_checked) { fprintf(stderr, "owner ref check failed [%llu %llu]\n", (unsigned long long)rec->start, (unsigned long long)rec->nr); - if (!fixed && !recorded && repair) { - ret = fixup_extent_refs(root->fs_info, - extent_cache, rec); - if (ret) - goto repair_abort; - fixed = 1; - } - err = 1; + fix = 1; cur_err = 1; } + + if (repair && fix) { + ret = fixup_extent_refs(root->fs_info, extent_cache, rec); + if (ret) + goto repair_abort; + } + + if (rec->bad_full_backref) { fprintf(stderr, "bad full backref, on [%llu]\n", (unsigned long long)rec->start); @@ -7954,9 +9414,8 @@ static int check_extent_refs(struct btrfs_root *root, ret = fixup_extent_flags(root->fs_info, rec); if (ret) goto repair_abort; - fixed = 1; + fix = 1; } - err = 1; cur_err = 1; } /* @@ -7968,7 +9427,6 @@ static int check_extent_refs(struct btrfs_root *root, fprintf(stderr, "bad metadata [%llu, %llu) crossing stripe boundary\n", rec->start, rec->start + rec->max_size); - err = 1; cur_err = 1; } @@ -7976,17 +9434,15 @@ static int check_extent_refs(struct btrfs_root *root, fprintf(stderr, "bad extent [%llu, %llu), type mismatch with chunk\n", rec->start, rec->start + rec->max_size); - err = 1; cur_err = 1; } remove_cache_extent(extent_cache, cache); free_all_extent_backrefs(rec); - if (!init_extent_tree && repair && (!cur_err || fixed)) + if (!init_extent_tree && repair && (!cur_err || fix)) clear_extent_dirty(root->fs_info->excluded_extents, rec->start, - rec->start + rec->max_size - 1, - GFP_NOFS); + rec->start + rec->max_size - 1); free(rec); } repair_abort: @@ -8009,11 +9465,9 @@ repair_abort: if (ret) goto repair_abort; } - if (err) - fprintf(stderr, "repaired damaged extent references\n"); return ret; } - return err; + return 0; } u64 calc_stripe_length(u64 type, u64 length, int num_stripes) @@ -8330,7 +9784,7 @@ static int deal_root_from_list(struct list_head *list, rec = list_entry(list->next, struct root_item_record, list); last = 0; - buf = read_tree_block(root->fs_info->tree_root, + buf = read_tree_block(root->fs_info, rec->bytenr, rec->level_size, 0); if (!extent_buffer_uptodate(buf)) { free_extent_buffer(buf); @@ -8443,14 +9897,14 @@ again: level = btrfs_header_level(root1->node); ret = add_root_item_to_list(&normal_trees, root1->root_key.objectid, root1->node->start, 0, level, 0, - root1->nodesize, NULL); + root1->fs_info->nodesize, NULL); if (ret < 0) goto out; root1 = root->fs_info->chunk_root; level = btrfs_header_level(root1->node); ret = add_root_item_to_list(&normal_trees, root1->root_key.objectid, root1->node->start, 0, level, 0, - root1->nodesize, NULL); + root1->fs_info->nodesize, NULL); if (ret < 0) goto out; btrfs_init_path(&path); @@ -8481,7 +9935,7 @@ again: last_snapshot = btrfs_root_last_snapshot(&ri); if (btrfs_disk_key_objectid(&ri.drop_progress) == 0) { level = btrfs_root_level(&ri); - level_size = root->nodesize; + level_size = root->fs_info->nodesize; ret = add_root_item_to_list(&normal_trees, found_key.objectid, btrfs_root_bytenr(&ri), @@ -8491,7 +9945,7 @@ again: goto out; } else { level = btrfs_root_level(&ri); - level_size = root->nodesize; + level_size = root->fs_info->nodesize; objectid = found_key.objectid; btrfs_disk_key_to_cpu(&found_key, &ri.drop_progress); @@ -8571,6 +10025,8 @@ out: free_extent_cache_tree(&pending); free_extent_cache_tree(&reada); free_extent_cache_tree(&nodes); + free_root_item_list(&normal_trees); + free_root_item_list(&dropping_trees); return ret; loop: free_corrupt_blocks_tree(root->fs_info->corrupt_blocks); @@ -8582,7 +10038,7 @@ loop: free_block_group_tree(&block_group_cache); free_device_cache_tree(&dev_cache); free_device_extent_tree(&dev_extent_cache); - free_extent_record_cache(root->fs_info, &extent_cache); + free_extent_record_cache(&extent_cache); free_root_item_list(&normal_trees); free_root_item_list(&dropping_trees); extent_io_tree_cleanup(&excluded_extents); @@ -8616,17 +10072,21 @@ static int check_tree_block_ref(struct btrfs_root *root, int slot; int skinny_level; int type; - u32 nodesize = root->nodesize; + u32 nodesize = root->fs_info->nodesize; u32 item_size; u64 offset; + int tree_reloc_root = 0; int found_ref = 0; int err = 0; int ret; + if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID && + btrfs_header_bytenr(root->node) == bytenr) + tree_reloc_root = 1; + btrfs_init_path(&path); key.objectid = bytenr; - if (btrfs_fs_incompat(root->fs_info, - BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA)) + if (btrfs_fs_incompat(root->fs_info, SKINNY_METADATA)) key.type = BTRFS_METADATA_ITEM_KEY; else key.type = BTRFS_EXTENT_ITEM_KEY; @@ -8711,9 +10171,16 @@ static int check_tree_block_ref(struct btrfs_root *root, (offset == root->objectid || offset == owner)) { found_ref = 1; } else if (type == BTRFS_SHARED_BLOCK_REF_KEY) { + /* + * Backref of tree reloc root points to itself, no need + * to check backref any more. + */ + if (tree_reloc_root) + found_ref = 1; + else /* Check if the backref points to valid referencer */ - found_ref = !check_tree_block_ref(root, NULL, offset, - level + 1, owner); + found_ref = !check_tree_block_ref(root, NULL, + offset, level + 1, owner); } if (found_ref) @@ -8764,12 +10231,10 @@ static int check_extent_data_item(struct btrfs_root *root, struct btrfs_extent_inline_ref *iref; struct btrfs_extent_data_ref *dref; u64 owner; - u64 file_extent_gen; u64 disk_bytenr; u64 disk_num_bytes; u64 extent_num_bytes; u64 extent_flags; - u64 extent_gen; u32 item_size; unsigned long end; unsigned long ptr; @@ -8781,7 +10246,6 @@ static int check_extent_data_item(struct btrfs_root *root, btrfs_item_key_to_cpu(eb, &fi_key, slot); fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item); - file_extent_gen = btrfs_file_extent_generation(eb, fi); /* Nothing to check for hole and inline data extents */ if (btrfs_file_extent_type(eb, fi) == BTRFS_FILE_EXTENT_INLINE || @@ -8793,20 +10257,20 @@ static int check_extent_data_item(struct btrfs_root *root, extent_num_bytes = btrfs_file_extent_num_bytes(eb, fi); /* Check unaligned disk_num_bytes and num_bytes */ - if (!IS_ALIGNED(disk_num_bytes, root->sectorsize)) { + if (!IS_ALIGNED(disk_num_bytes, root->fs_info->sectorsize)) { error( "file extent [%llu, %llu] has unaligned disk num bytes: %llu, should be aligned to %u", fi_key.objectid, fi_key.offset, disk_num_bytes, - root->sectorsize); + root->fs_info->sectorsize); err |= BYTES_UNALIGNED; } else { data_bytes_allocated += disk_num_bytes; } - if (!IS_ALIGNED(extent_num_bytes, root->sectorsize)) { + if (!IS_ALIGNED(extent_num_bytes, root->fs_info->sectorsize)) { error( "file extent [%llu, %llu] has unaligned num bytes: %llu, should be aligned to %u", fi_key.objectid, fi_key.offset, extent_num_bytes, - root->sectorsize); + root->fs_info->sectorsize); err |= BYTES_UNALIGNED; } else { data_bytes_referenced += extent_num_bytes; @@ -8820,17 +10284,14 @@ static int check_extent_data_item(struct btrfs_root *root, dbref_key.offset = btrfs_file_extent_disk_num_bytes(eb, fi); ret = btrfs_search_slot(NULL, extent_root, &dbref_key, &path, 0, 0); - if (ret) { - err |= BACKREF_MISSING; - goto error; - } + if (ret) + goto out; leaf = path.nodes[0]; slot = path.slots[0]; ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item); extent_flags = btrfs_extent_flags(leaf, ei); - extent_gen = btrfs_extent_generation(leaf, ei); if (!(extent_flags & BTRFS_EXTENT_FLAG_DATA)) { error( @@ -8840,14 +10301,6 @@ static int check_extent_data_item(struct btrfs_root *root, err |= BACKREF_MISMATCH; } - if (file_extent_gen < extent_gen) { - error( -"extent[%llu %llu] backref generation mismatch, wanted: <=%llu, have: %llu", - disk_bytenr, disk_num_bytes, file_extent_gen, - extent_gen); - err |= BACKREF_MISMATCH; - } - /* Check data backref inside that extent item */ item_size = btrfs_item_size_nr(leaf, path.slots[0]); iref = (struct btrfs_extent_inline_ref *)(ei + 1); @@ -8873,11 +10326,10 @@ static int check_extent_data_item(struct btrfs_root *root, ptr += btrfs_extent_inline_ref_size(type); } - /* Didn't found inlined data backref, try EXTENT_DATA_REF_KEY */ if (!found_dbackref) { btrfs_release_path(&path); - btrfs_init_path(&path); + /* Didn't find inlined data backref, try EXTENT_DATA_REF_KEY */ dbref_key.objectid = btrfs_file_extent_disk_bytenr(eb, fi); dbref_key.type = BTRFS_EXTENT_DATA_REF_KEY; dbref_key.offset = hash_extent_data_ref(root->objectid, @@ -8885,13 +10337,32 @@ static int check_extent_data_item(struct btrfs_root *root, ret = btrfs_search_slot(NULL, root->fs_info->extent_root, &dbref_key, &path, 0, 0); - if (!ret) + if (!ret) { + found_dbackref = 1; + goto out; + } + + btrfs_release_path(&path); + + /* + * Neither inlined nor EXTENT_DATA_REF found, try + * SHARED_DATA_REF as last chance. + */ + dbref_key.objectid = disk_bytenr; + dbref_key.type = BTRFS_SHARED_DATA_REF_KEY; + dbref_key.offset = eb->start; + + ret = btrfs_search_slot(NULL, root->fs_info->extent_root, + &dbref_key, &path, 0, 0); + if (!ret) { found_dbackref = 1; + goto out; + } } +out: if (!found_dbackref) err |= BACKREF_MISSING; -error: btrfs_release_path(&path); if (err & BACKREF_MISSING) { error("data extent[%llu %llu] backref lost", @@ -8959,7 +10430,7 @@ static int query_tree_block_level(struct btrfs_fs_info *fs_info, u64 bytenr) btrfs_release_path(&path); /* Get level from tree block as an alternative source */ - eb = read_tree_block_fs_info(fs_info, bytenr, nodesize, transid); + eb = read_tree_block(fs_info, bytenr, nodesize, transid); if (!extent_buffer_uptodate(eb)) { free_extent_buffer(eb); return -EIO; @@ -9012,7 +10483,7 @@ static int check_tree_block_backref(struct btrfs_fs_info *fs_info, u64 root_id, } /* Read out the tree block to get item/node key */ - eb = read_tree_block(root, bytenr, root->nodesize, 0); + eb = read_tree_block(fs_info, bytenr, root->fs_info->nodesize, 0); if (!extent_buffer_uptodate(eb)) { err |= REFERENCER_MISSING; free_extent_buffer(eb); @@ -9074,6 +10545,34 @@ out: } /* + * Check if tree block @eb is tree reloc root. + * Return 0 if it's not or any problem happens + * Return 1 if it's a tree reloc root + */ +static int is_tree_reloc_root(struct btrfs_fs_info *fs_info, + struct extent_buffer *eb) +{ + struct btrfs_root *tree_reloc_root; + struct btrfs_key key; + u64 bytenr = btrfs_header_bytenr(eb); + u64 owner = btrfs_header_owner(eb); + int ret = 0; + + key.objectid = BTRFS_TREE_RELOC_OBJECTID; + key.offset = owner; + key.type = BTRFS_ROOT_ITEM_KEY; + + tree_reloc_root = btrfs_read_fs_root_no_cache(fs_info, &key); + if (IS_ERR(tree_reloc_root)) + return 0; + + if (bytenr == btrfs_header_bytenr(tree_reloc_root->node)) + ret = 1; + btrfs_free_fs_root(tree_reloc_root); + return ret; +} + +/* * Check referencer for shared block backref * If level == -1, this function will resolve the level. */ @@ -9086,7 +10585,7 @@ static int check_shared_block_backref(struct btrfs_fs_info *fs_info, int found_parent = 0; int i; - eb = read_tree_block_fs_info(fs_info, parent, nodesize, 0); + eb = read_tree_block(fs_info, parent, nodesize, 0); if (!extent_buffer_uptodate(eb)) goto out; @@ -9095,6 +10594,13 @@ static int check_shared_block_backref(struct btrfs_fs_info *fs_info, if (level < 0) goto out; + /* It's possible it's a tree reloc root */ + if (parent == bytenr) { + if (is_tree_reloc_root(fs_info, eb)) + found_parent = 1; + goto out; + } + if (level + 1 != btrfs_header_level(eb)) goto out; @@ -9186,6 +10692,8 @@ static int check_extent_data_backref(struct btrfs_fs_info *fs_info, leaf = path.nodes[0]; slot = path.slots[0]; + if (slot >= btrfs_header_nritems(leaf)) + goto next; btrfs_item_key_to_cpu(leaf, &key, slot); if (key.objectid != objectid || key.type != BTRFS_EXTENT_DATA_KEY) break; @@ -9201,6 +10709,7 @@ static int check_extent_data_backref(struct btrfs_fs_info *fs_info, offset) found_count++; +next: ret = btrfs_next_item(root, &path); if (ret) break; @@ -9230,7 +10739,7 @@ static int check_shared_data_backref(struct btrfs_fs_info *fs_info, int found_parent = 0; int i; - eb = read_tree_block_fs_info(fs_info, parent, nodesize, 0); + eb = read_tree_block(fs_info, parent, nodesize, 0); if (!extent_buffer_uptodate(eb)) goto out; @@ -9328,13 +10837,20 @@ static int check_extent_item(struct btrfs_fs_info *fs_info, } end = (unsigned long)ei + item_size; - if (ptr >= end) { +next: + /* Reached extent item end normally */ + if (ptr == end) + goto out; + + /* Beyond extent item end, wrong item size */ + if (ptr > end) { err |= ITEM_SIZE_MISMATCH; + error("extent item at bytenr %llu slot %d has wrong size", + eb->start, slot); goto out; } /* Now check every backref in this extent item */ -next: iref = (struct btrfs_extent_inline_ref *)ptr; type = btrfs_extent_inline_ref_type(eb, iref); offset = btrfs_extent_inline_ref_offset(eb, iref); @@ -9371,8 +10887,7 @@ next: } ptr += btrfs_extent_inline_ref_size(type); - if (ptr < end) - goto next; + goto next; out: return err; @@ -9412,7 +10927,12 @@ static int check_dev_extent_item(struct btrfs_fs_info *fs_info, l = path.nodes[0]; chunk = btrfs_item_ptr(l, path.slots[0], struct btrfs_chunk); - if (btrfs_chunk_length(l, chunk) != length) + ret = btrfs_check_chunk_valid(fs_info, l, chunk, path.slots[0], + chunk_key.offset); + if (ret < 0) + goto out; + + if (btrfs_stripe_length(fs_info, l, chunk) != length) goto out; num_stripes = btrfs_chunk_num_stripes(l, chunk); @@ -9473,8 +10993,10 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, /* Iterate dev_extents to calculate the used space of a device */ while (1) { - btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) + goto next; + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); if (key.objectid > dev_id) break; if (key.type != BTRFS_DEV_EXTENT_KEY || key.objectid != dev_id) @@ -9571,6 +11093,11 @@ static int check_block_group_item(struct btrfs_fs_info *fs_info, /* Iterate extent tree to account used space */ while (1) { leaf = path.nodes[0]; + + /* Search slot can point to the last item beyond leaf nritems */ + if (path.slots[0] >= btrfs_header_nritems(leaf)) + goto next; + btrfs_item_key_to_cpu(leaf, &extent_key, path.slots[0]); if (extent_key.objectid >= bg_key.objectid + bg_key.offset) break; @@ -9643,11 +11170,10 @@ static int check_chunk_item(struct btrfs_fs_info *fs_info, struct btrfs_block_group_item *bi; struct btrfs_block_group_item bg_item; struct btrfs_dev_extent *ptr; - u32 sectorsize = btrfs_super_sectorsize(fs_info->super_copy); u64 length; u64 chunk_end; + u64 stripe_len; u64 type; - u64 profile; int num_stripes; u64 offset; u64 objectid; @@ -9659,25 +11185,15 @@ static int check_chunk_item(struct btrfs_fs_info *fs_info, chunk = btrfs_item_ptr(eb, slot, struct btrfs_chunk); length = btrfs_chunk_length(eb, chunk); chunk_end = chunk_key.offset + length; - if (!IS_ALIGNED(length, sectorsize)) { - error("chunk[%llu %llu) not aligned to %u", - chunk_key.offset, chunk_end, sectorsize); - err |= BYTES_UNALIGNED; + ret = btrfs_check_chunk_valid(fs_info, eb, chunk, slot, + chunk_key.offset); + if (ret < 0) { + error("chunk[%llu %llu) is invalid", chunk_key.offset, + chunk_end); + err |= BYTES_UNALIGNED | UNKNOWN_TYPE; goto out; } - type = btrfs_chunk_type(eb, chunk); - profile = type & BTRFS_BLOCK_GROUP_PROFILE_MASK; - if (!(type & BTRFS_BLOCK_GROUP_TYPE_MASK)) { - error("chunk[%llu %llu) has no chunk type", - chunk_key.offset, chunk_end); - err |= UNKNOWN_TYPE; - } - if (profile && (profile & (profile - 1))) { - error("chunk[%llu %llu) multiple profiles detected: %llx", - chunk_key.offset, chunk_end, profile); - err |= UNKNOWN_TYPE; - } bg_key.objectid = chunk_key.offset; bg_key.type = BTRFS_BLOCK_GROUP_ITEM_KEY; @@ -9706,6 +11222,7 @@ static int check_chunk_item(struct btrfs_fs_info *fs_info, } num_stripes = btrfs_chunk_num_stripes(eb, chunk); + stripe_len = btrfs_stripe_length(fs_info, eb, chunk); for (i = 0; i < num_stripes; i++) { btrfs_release_path(&path); btrfs_init_path(&path); @@ -9725,7 +11242,7 @@ static int check_chunk_item(struct btrfs_fs_info *fs_info, offset = btrfs_dev_extent_chunk_offset(leaf, ptr); if (objectid != chunk_key.objectid || offset != chunk_key.offset || - btrfs_dev_extent_length(leaf, ptr) != length) + btrfs_dev_extent_length(leaf, ptr) != stripe_len) goto not_match_dev; continue; not_match_dev: @@ -9984,7 +11501,8 @@ static int traverse_tree_block(struct btrfs_root *root, * As a btrfs tree has most 8 levels (0..7), so it's quite safe * to call the function itself. */ - eb = read_tree_block(root, blocknr, root->nodesize, 0); + eb = read_tree_block(root->fs_info, blocknr, + root->fs_info->nodesize, 0); if (extent_buffer_uptodate(eb)) { ret = traverse_tree_block(root, eb); err |= ret; @@ -10032,7 +11550,11 @@ static int check_chunks_and_extents_v2(struct btrfs_root *root) goto next; key.offset = (u64)-1; - cur_root = btrfs_read_fs_root(root->fs_info, &key); + if (key.objectid == BTRFS_TREE_RELOC_OBJECTID) + cur_root = btrfs_read_fs_root_no_cache(root->fs_info, + &key); + else + cur_root = btrfs_read_fs_root(root->fs_info, &key); if (IS_ERR(cur_root) || !cur_root) { error("failed to read tree: %lld", key.objectid); goto next; @@ -10041,6 +11563,8 @@ static int check_chunks_and_extents_v2(struct btrfs_root *root) ret = traverse_tree_block(cur_root, cur_root->node); err |= ret; + if (key.objectid == BTRFS_TREE_RELOC_OBJECTID) + btrfs_free_fs_root(cur_root); next: ret = btrfs_next_item(root1, &path); if (ret) @@ -10069,7 +11593,7 @@ static int btrfs_fsck_reinit_root(struct btrfs_trans_handle *trans, goto init; } c = btrfs_alloc_free_block(trans, root, - root->nodesize, + root->fs_info->nodesize, root->root_key.objectid, &disk_key, level, 0, 0); if (IS_ERR(c)) { @@ -10164,8 +11688,7 @@ static int pin_down_tree_blocks(struct btrfs_fs_info *fs_info, * in, but for now this doesn't actually use the root so * just pass in extent_root. */ - tmp = read_tree_block(fs_info->extent_root, bytenr, - nodesize, 0); + tmp = read_tree_block(fs_info, bytenr, nodesize, 0); if (!extent_buffer_uptodate(tmp)) { fprintf(stderr, "Error reading root block\n"); return -EIO; @@ -10183,7 +11706,7 @@ static int pin_down_tree_blocks(struct btrfs_fs_info *fs_info, continue; } - tmp = read_tree_block(fs_info->extent_root, bytenr, + tmp = read_tree_block(fs_info, bytenr, nodesize, 0); if (!extent_buffer_uptodate(tmp)) { fprintf(stderr, "Error reading tree block\n"); @@ -10265,8 +11788,7 @@ static int reset_block_groups(struct btrfs_fs_info *fs_info) key.objectid, key.offset, btrfs_chunk_length(leaf, chunk)); set_extent_dirty(&fs_info->free_space_cache, key.offset, - key.offset + btrfs_chunk_length(leaf, chunk), - GFP_NOFS); + key.offset + btrfs_chunk_length(leaf, chunk)); path.slots[0]++; } start = 0; @@ -10398,7 +11920,7 @@ static int reinit_extent_tree(struct btrfs_trans_handle *trans, * the leaves of any fs roots and pin down the bytes for any file * extents we find. Not hard but why do it if we don't have to? */ - if (btrfs_fs_incompat(fs_info, BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS)) { + if (btrfs_fs_incompat(fs_info, MIXED_GROUPS)) { fprintf(stderr, "We don't support re-initing the extent tree " "for mixed block groups yet, please notify a btrfs " "developer you want to do this so they can add this " @@ -10563,13 +12085,14 @@ static int populate_csum(struct btrfs_trans_handle *trans, struct btrfs_root *csum_root, char *buf, u64 start, u64 len) { + struct btrfs_fs_info *fs_info = csum_root->fs_info; u64 offset = 0; u64 sectorsize; int ret = 0; while (offset < len) { - sectorsize = csum_root->sectorsize; - ret = read_extent_data(csum_root, buf, start + offset, + sectorsize = fs_info->sectorsize; + ret = read_extent_data(fs_info, buf, start + offset, §orsize, 0); if (ret) break; @@ -10586,7 +12109,7 @@ static int fill_csum_tree_from_one_fs_root(struct btrfs_trans_handle *trans, struct btrfs_root *csum_root, struct btrfs_root *cur_root) { - struct btrfs_path *path; + struct btrfs_path path; struct btrfs_key key; struct extent_buffer *node; struct btrfs_file_extent_item *fi; @@ -10596,30 +12119,25 @@ static int fill_csum_tree_from_one_fs_root(struct btrfs_trans_handle *trans, int slot = 0; int ret = 0; - path = btrfs_alloc_path(); - if (!path) + buf = malloc(cur_root->fs_info->sectorsize); + if (!buf) return -ENOMEM; - buf = malloc(cur_root->fs_info->csum_root->sectorsize); - if (!buf) { - ret = -ENOMEM; - goto out; - } + btrfs_init_path(&path); key.objectid = 0; key.offset = 0; key.type = 0; - - ret = btrfs_search_slot(NULL, cur_root, &key, path, 0, 0); + ret = btrfs_search_slot(NULL, cur_root, &key, &path, 0, 0); if (ret < 0) goto out; /* Iterate all regular file extents and fill its csum */ while (1) { - btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); if (key.type != BTRFS_EXTENT_DATA_KEY) goto next; - node = path->nodes[0]; - slot = path->slots[0]; + node = path.nodes[0]; + slot = path.slots[0]; fi = btrfs_item_ptr(node, slot, struct btrfs_file_extent_item); if (btrfs_file_extent_type(node, fi) != BTRFS_FILE_EXTENT_REG) goto next; @@ -10636,7 +12154,7 @@ next: * TODO: if next leaf is corrupted, jump to nearest next valid * leaf. */ - ret = btrfs_next_item(cur_root, path); + ret = btrfs_next_item(cur_root, &path); if (ret < 0) goto out; if (ret > 0) { @@ -10646,7 +12164,7 @@ next: } out: - btrfs_free_path(path); + btrfs_release_path(&path); free(buf); return ret; } @@ -10655,7 +12173,7 @@ static int fill_csum_tree_from_fs(struct btrfs_trans_handle *trans, struct btrfs_root *csum_root) { struct btrfs_fs_info *fs_info = csum_root->fs_info; - struct btrfs_path *path; + struct btrfs_path path; struct btrfs_root *tree_root = fs_info->tree_root; struct btrfs_root *cur_root; struct extent_buffer *node; @@ -10663,15 +12181,11 @@ static int fill_csum_tree_from_fs(struct btrfs_trans_handle *trans, int slot = 0; int ret = 0; - path = btrfs_alloc_path(); - if (!path) - return -ENOMEM; - + btrfs_init_path(&path); key.objectid = BTRFS_FS_TREE_OBJECTID; key.offset = 0; key.type = BTRFS_ROOT_ITEM_KEY; - - ret = btrfs_search_slot(NULL, tree_root, &key, path, 0, 0); + ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0); if (ret < 0) goto out; if (ret > 0) { @@ -10680,8 +12194,8 @@ static int fill_csum_tree_from_fs(struct btrfs_trans_handle *trans, } while (1) { - node = path->nodes[0]; - slot = path->slots[0]; + node = path.nodes[0]; + slot = path.slots[0]; btrfs_item_key_to_cpu(node, &key, slot); if (key.objectid > BTRFS_LAST_FREE_OBJECTID) goto out; @@ -10702,7 +12216,7 @@ static int fill_csum_tree_from_fs(struct btrfs_trans_handle *trans, if (ret < 0) goto out; next: - ret = btrfs_next_item(tree_root, path); + ret = btrfs_next_item(tree_root, &path); if (ret > 0) { ret = 0; goto out; @@ -10712,7 +12226,7 @@ next: } out: - btrfs_free_path(path); + btrfs_release_path(&path); return ret; } @@ -10720,36 +12234,32 @@ static int fill_csum_tree_from_extent(struct btrfs_trans_handle *trans, struct btrfs_root *csum_root) { struct btrfs_root *extent_root = csum_root->fs_info->extent_root; - struct btrfs_path *path; + struct btrfs_path path; struct btrfs_extent_item *ei; struct extent_buffer *leaf; char *buf; struct btrfs_key key; int ret; - path = btrfs_alloc_path(); - if (!path) - return -ENOMEM; - + btrfs_init_path(&path); key.objectid = 0; key.type = BTRFS_EXTENT_ITEM_KEY; key.offset = 0; - - ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0); + ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0); if (ret < 0) { - btrfs_free_path(path); + btrfs_release_path(&path); return ret; } - buf = malloc(csum_root->sectorsize); + buf = malloc(csum_root->fs_info->sectorsize); if (!buf) { - btrfs_free_path(path); + btrfs_release_path(&path); return -ENOMEM; } while (1) { - if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { - ret = btrfs_next_leaf(extent_root, path); + if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) { + ret = btrfs_next_leaf(extent_root, &path); if (ret < 0) break; if (ret) { @@ -10757,19 +12267,19 @@ static int fill_csum_tree_from_extent(struct btrfs_trans_handle *trans, break; } } - leaf = path->nodes[0]; + leaf = path.nodes[0]; - btrfs_item_key_to_cpu(leaf, &key, path->slots[0]); + btrfs_item_key_to_cpu(leaf, &key, path.slots[0]); if (key.type != BTRFS_EXTENT_ITEM_KEY) { - path->slots[0]++; + path.slots[0]++; continue; } - ei = btrfs_item_ptr(leaf, path->slots[0], + ei = btrfs_item_ptr(leaf, path.slots[0], struct btrfs_extent_item); if (!(btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_DATA)) { - path->slots[0]++; + path.slots[0]++; continue; } @@ -10777,10 +12287,10 @@ static int fill_csum_tree_from_extent(struct btrfs_trans_handle *trans, key.offset); if (ret) break; - path->slots[0]++; + path.slots[0]++; } - btrfs_free_path(path); + btrfs_release_path(&path); free(buf); return ret; } @@ -10828,7 +12338,7 @@ static int build_roots_info_cache(struct btrfs_fs_info *info) int ret = 0; struct btrfs_key key; struct extent_buffer *leaf; - struct btrfs_path *path; + struct btrfs_path path; if (!roots_info_cache) { roots_info_cache = malloc(sizeof(*roots_info_cache)); @@ -10837,24 +12347,20 @@ static int build_roots_info_cache(struct btrfs_fs_info *info) cache_tree_init(roots_info_cache); } - path = btrfs_alloc_path(); - if (!path) - return -ENOMEM; - + btrfs_init_path(&path); key.objectid = 0; key.type = BTRFS_EXTENT_ITEM_KEY; key.offset = 0; - - ret = btrfs_search_slot(NULL, info->extent_root, &key, path, 0, 0); + ret = btrfs_search_slot(NULL, info->extent_root, &key, &path, 0, 0); if (ret < 0) goto out; - leaf = path->nodes[0]; + leaf = path.nodes[0]; while (1) { struct btrfs_key found_key; struct btrfs_extent_item *ei; struct btrfs_extent_inline_ref *iref; - int slot = path->slots[0]; + int slot = path.slots[0]; int type; u64 flags; u64 root_id; @@ -10863,18 +12369,18 @@ static int build_roots_info_cache(struct btrfs_fs_info *info) struct root_item_info *rii; if (slot >= btrfs_header_nritems(leaf)) { - ret = btrfs_next_leaf(info->extent_root, path); + ret = btrfs_next_leaf(info->extent_root, &path); if (ret < 0) { break; } else if (ret) { ret = 0; break; } - leaf = path->nodes[0]; - slot = path->slots[0]; + leaf = path.nodes[0]; + slot = path.slots[0]; } - btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + btrfs_item_key_to_cpu(leaf, &found_key, path.slots[0]); if (found_key.type != BTRFS_EXTENT_ITEM_KEY && found_key.type != BTRFS_METADATA_ITEM_KEY) @@ -10937,17 +12443,16 @@ static int build_roots_info_cache(struct btrfs_fs_info *info) rii->node_count++; } next: - path->slots[0]++; + path.slots[0]++; } out: - btrfs_free_path(path); + btrfs_release_path(&path); return ret; } -static int maybe_repair_root_item(struct btrfs_fs_info *info, - struct btrfs_path *path, +static int maybe_repair_root_item(struct btrfs_path *path, const struct btrfs_key *root_key, const int read_only_mode) { @@ -11037,7 +12542,7 @@ static int maybe_repair_root_item(struct btrfs_fs_info *info, */ static int repair_root_items(struct btrfs_fs_info *info) { - struct btrfs_path *path = NULL; + struct btrfs_path path; struct btrfs_key key; struct extent_buffer *leaf; struct btrfs_trans_handle *trans = NULL; @@ -11045,16 +12550,12 @@ static int repair_root_items(struct btrfs_fs_info *info) int bad_roots = 0; int need_trans = 0; + btrfs_init_path(&path); + ret = build_roots_info_cache(info); if (ret) goto out; - path = btrfs_alloc_path(); - if (!path) { - ret = -ENOMEM; - goto out; - } - key.objectid = BTRFS_FIRST_FREE_OBJECTID; key.type = BTRFS_ROOT_ITEM_KEY; key.offset = 0; @@ -11073,19 +12574,19 @@ again: } } - ret = btrfs_search_slot(trans, info->tree_root, &key, path, + ret = btrfs_search_slot(trans, info->tree_root, &key, &path, 0, trans ? 1 : 0); if (ret < 0) goto out; - leaf = path->nodes[0]; + leaf = path.nodes[0]; while (1) { struct btrfs_key found_key; - if (path->slots[0] >= btrfs_header_nritems(leaf)) { - int no_more_keys = find_next_key(path, &key); + if (path.slots[0] >= btrfs_header_nritems(leaf)) { + int no_more_keys = find_next_key(&path, &key); - btrfs_release_path(path); + btrfs_release_path(&path); if (trans) { ret = btrfs_commit_transaction(trans, info->tree_root); @@ -11099,33 +12600,32 @@ again: goto again; } - btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + btrfs_item_key_to_cpu(leaf, &found_key, path.slots[0]); if (found_key.type != BTRFS_ROOT_ITEM_KEY) goto next; if (found_key.objectid == BTRFS_TREE_RELOC_OBJECTID) goto next; - ret = maybe_repair_root_item(info, path, &found_key, - trans ? 0 : 1); + ret = maybe_repair_root_item(&path, &found_key, trans ? 0 : 1); if (ret < 0) goto out; if (ret) { if (!trans && repair) { need_trans = 1; key = found_key; - btrfs_release_path(path); + btrfs_release_path(&path); goto again; } bad_roots++; } next: - path->slots[0]++; + path.slots[0]++; } ret = 0; out: free_roots_info_cache(); - btrfs_free_path(path); + btrfs_release_path(&path); if (trans) btrfs_commit_transaction(trans, info->tree_root); if (ret < 0) @@ -11192,7 +12692,6 @@ const char * const cmd_check_usage[] = { "--chunk-root use the given bytenr for the chunk tree root", "-p|--progress indicate progress", "--clear-space-cache v1|v2 clear space cache for v1 or v2", - " NOTE: v1 support implemented", NULL }; @@ -11207,6 +12706,7 @@ int cmd_check(int argc, char **argv) u64 chunk_root_bytenr = 0; char uuidbuf[BTRFS_UUID_UNPARSED_SIZE]; int ret; + int err = 0; u64 num; int init_csum_tree = 0; int readonly = 0; @@ -11314,13 +12814,16 @@ int cmd_check(int argc, char **argv) } break; case GETOPT_VAL_CLEAR_SPACE_CACHE: - if (strcmp(optarg, "v1") != 0) { + if (strcmp(optarg, "v1") == 0) { + clear_space_cache = 1; + } else if (strcmp(optarg, "v2") == 0) { + clear_space_cache = 2; + ctree_flags |= OPEN_CTREE_INVALIDATE_FST; + } else { error( - "only v1 support implmented, unrecognized value %s", - optarg); + "invalid argument to --clear-space-cache, must be v1 or v2"); exit(1); } - clear_space_cache = 1; ctree_flags |= OPEN_CTREE_WRITES; break; } @@ -11353,10 +12856,12 @@ int cmd_check(int argc, char **argv) if((ret = check_mounted(argv[optind])) < 0) { error("could not check mount status: %s", strerror(-ret)); + err |= !!ret; goto err_out; } else if(ret) { error("%s is currently mounted, aborting", argv[optind]); ret = -EBUSY; + err |= !!ret; goto err_out; } @@ -11369,16 +12874,16 @@ int cmd_check(int argc, char **argv) if (!info) { error("cannot open file system"); ret = -EIO; + err |= !!ret; goto err_out; } global_info = info; root = info->fs_root; - if (clear_space_cache) { - if (btrfs_fs_compat_ro(info, - BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE)) { + if (clear_space_cache == 1) { + if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE)) { error( - "free space cache v2 detected, clearing not implemented"); + "free space cache v2 detected, use --clear-space-cache v2"); ret = 1; goto close_out; } @@ -11391,6 +12896,21 @@ int cmd_check(int argc, char **argv) printf("Free space cache cleared\n"); } goto close_out; + } else if (clear_space_cache == 2) { + if (!btrfs_fs_compat_ro(info, FREE_SPACE_TREE)) { + printf("no free space cache v2 to clear\n"); + ret = 0; + goto close_out; + } + printf("Clear free space cache v2\n"); + ret = btrfs_clear_free_space_tree(info); + if (ret) { + error("failed to clear free space cache v2: %d", ret); + ret = 1; + } else { + printf("free space cache v2 cleared\n"); + } + goto close_out; } /* @@ -11401,9 +12921,11 @@ int cmd_check(int argc, char **argv) ret = ask_user("repair mode will force to clear out log tree, are you sure?"); if (!ret) { ret = 1; + err |= !!ret; goto close_out; } ret = zero_log_tree(root); + err |= !!ret; if (ret) { error("failed to zero log tree: %d", ret); goto close_out; @@ -11415,6 +12937,7 @@ int cmd_check(int argc, char **argv) printf("Print quota groups for %s\nUUID: %s\n", argv[optind], uuidbuf); ret = qgroup_verify_all(info); + err |= !!ret; if (ret == 0) report_qgroups(1); goto close_out; @@ -11423,6 +12946,7 @@ int cmd_check(int argc, char **argv) printf("Print extent state for subvolume %llu on %s\nUUID: %s\n", subvolid, argv[optind], uuidbuf); ret = print_extent_state(info, subvolid); + err |= !!ret; goto close_out; } printf("Checking filesystem on %s\nUUID: %s\n", argv[optind], uuidbuf); @@ -11431,6 +12955,7 @@ int cmd_check(int argc, char **argv) !extent_buffer_uptodate(info->dev_root->node) || !extent_buffer_uptodate(info->chunk_root->node)) { error("critical roots corrupted, unable to check the filesystem"); + err |= !!ret; ret = -EIO; goto close_out; } @@ -11442,12 +12967,14 @@ int cmd_check(int argc, char **argv) if (IS_ERR(trans)) { error("error starting transaction"); ret = PTR_ERR(trans); + err |= !!ret; goto close_out; } if (init_extent_tree) { printf("Creating a new extent tree\n"); ret = reinit_extent_tree(trans, info); + err |= !!ret; if (ret) goto close_out; } @@ -11459,11 +12986,13 @@ int cmd_check(int argc, char **argv) error("checksum tree initialization failed: %d", ret); ret = -EIO; + err |= !!ret; goto close_out; } ret = fill_csum_tree(trans, info->csum_root, init_extent_tree); + err |= !!ret; if (ret) { error("checksum tree refilling failed: %d", ret); return -EIO; @@ -11474,32 +13003,40 @@ int cmd_check(int argc, char **argv) * extent entries for all of the items it finds. */ ret = btrfs_commit_transaction(trans, info->extent_root); + err |= !!ret; if (ret) goto close_out; } if (!extent_buffer_uptodate(info->extent_root->node)) { error("critical: extent_root, unable to check the filesystem"); ret = -EIO; + err |= !!ret; goto close_out; } if (!extent_buffer_uptodate(info->csum_root->node)) { error("critical: csum_root, unable to check the filesystem"); ret = -EIO; + err |= !!ret; goto close_out; } if (!ctx.progress_enabled) - printf("checking extents"); + fprintf(stderr, "checking extents\n"); if (check_mode == CHECK_MODE_LOWMEM) ret = check_chunks_and_extents_v2(root); else ret = check_chunks_and_extents(root); + err |= !!ret; if (ret) - printf("Errors found in extent allocation tree or chunk allocation"); + error( + "errors found in extent allocation tree or chunk allocation"); ret = repair_root_items(info); - if (ret < 0) + err |= !!ret; + if (ret < 0) { + error("failed to repair root items: %s", strerror(-ret)); goto close_out; + } if (repair) { fprintf(stderr, "Fixed %d roots.\n", ret); ret = 0; @@ -11510,18 +13047,25 @@ int cmd_check(int argc, char **argv) fprintf(stderr, "Please run a filesystem check with the option --repair to fix them.\n"); ret = 1; + err |= !!ret; goto close_out; } if (!ctx.progress_enabled) { - if (btrfs_fs_compat_ro(info, BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE)) + if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE)) fprintf(stderr, "checking free space tree\n"); else fprintf(stderr, "checking free space cache\n"); } ret = check_space_cache(root); - if (ret) + err |= !!ret; + if (ret) { + if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE)) + error("errors found in free space tree"); + else + error("errors found in free space cache"); goto out; + } /* * We used to have to have these hole extents in between our real @@ -11529,23 +13073,37 @@ int cmd_check(int argc, char **argv) * are no gaps in the file extents for inodes, otherwise we can just * ignore it when this happens. */ - no_holes = btrfs_fs_incompat(root->fs_info, - BTRFS_FEATURE_INCOMPAT_NO_HOLES); + no_holes = btrfs_fs_incompat(root->fs_info, NO_HOLES); if (!ctx.progress_enabled) fprintf(stderr, "checking fs roots\n"); - ret = check_fs_roots(root, &root_cache); - if (ret) + if (check_mode == CHECK_MODE_LOWMEM) + ret = check_fs_roots_v2(root->fs_info); + else + ret = check_fs_roots(root, &root_cache); + err |= !!ret; + if (ret) { + error("errors found in fs roots"); goto out; + } fprintf(stderr, "checking csums\n"); ret = check_csums(root); - if (ret) + err |= !!ret; + if (ret) { + error("errors found in csum tree"); goto out; + } fprintf(stderr, "checking root refs\n"); - ret = check_root_refs(root, &root_cache); - if (ret) - goto out; + /* For low memory mode, check_fs_roots_v2 handles root refs */ + if (check_mode != CHECK_MODE_LOWMEM) { + ret = check_root_refs(root, &root_cache); + err |= !!ret; + if (ret) { + error("errors found in root refs"); + goto out; + } + } while (repair && !list_empty(&root->fs_info->recow_ebs)) { struct extent_buffer *eb; @@ -11554,8 +13112,11 @@ int cmd_check(int argc, char **argv) struct extent_buffer, recow); list_del_init(&eb->recow); ret = recow_extent_buffer(root, eb); - if (ret) + err |= !!ret; + if (ret) { + error("fails to fix transid errors"); break; + } } while (!list_empty(&delete_items)) { @@ -11563,32 +13124,37 @@ int cmd_check(int argc, char **argv) bad = list_first_entry(&delete_items, struct bad_item, list); list_del_init(&bad->list); - if (repair) + if (repair) { ret = delete_bad_item(root, bad); + err |= !!ret; + } free(bad); } if (info->quota_enabled) { - int err; fprintf(stderr, "checking quota groups\n"); - err = qgroup_verify_all(info); - if (err) + ret = qgroup_verify_all(info); + err |= !!ret; + if (ret) { + error("failed to check quota groups"); goto out; + } report_qgroups(0); - err = repair_qgroups(info, &qgroups_repaired); - if (err) + ret = repair_qgroups(info, &qgroups_repaired); + err |= !!ret; + if (err) { + error("failed to repair quota groups"); goto out; + } + ret = 0; } if (!list_empty(&root->fs_info->recow_ebs)) { error("transid errors in file system"); ret = 1; + err |= !!ret; } out: - /* Don't override original ret */ - if (!ret && qgroups_repaired) - ret = qgroups_repaired; - if (found_old_backref) { /* * there was a disk format change when mixed * backref was in testing tree. The old format @@ -11598,10 +13164,14 @@ out: "The old format is not supported! *" "\n * Please mount the FS in readonly mode, " "backup data and re-format the FS. *\n\n"); - ret = 1; + err |= 1; } - printf("found %llu bytes used err is %d\n", - (unsigned long long)bytes_used, ret); + printf("found %llu bytes used, ", + (unsigned long long)bytes_used); + if (err) + printf("error(s) found\n"); + else + printf("no error found\n"); printf("total csum bytes: %llu\n",(unsigned long long)total_csum_bytes); printf("total tree bytes: %llu\n", (unsigned long long)total_btree_bytes); @@ -11623,5 +13193,5 @@ err_out: if (ctx.progress_enabled) task_deinit(ctx.info); - return ret; + return err; }