X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-check.c;h=a93ac2c88a381cb20fae02ca5e7eb6c2a8fd3168;hb=658cc4daa6ed6e6463cba2f30731abd9a91a7b2d;hp=3c2f1439f817220f0f575ceed0463cbf14996764;hpb=281eec7a9ddf30db3232616a91e40aeb5c7022cd;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-check.c b/cmds-check.c index 3c2f143..a93ac2c 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -130,6 +130,10 @@ struct data_backref { #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 */ +#define DIR_INDEX_MISSING (1<<18) /* INODE_INDEX not found */ +#define DIR_INDEX_MISMATCH (1<<19) /* INODE_INDEX found but not match */ +#define DIR_COUNT_AGAIN (1<<20) /* DIR isize should be recalculated */ +#define BG_ACCOUNTING_ERROR (1<<21) /* Block group accounting error */ static inline struct data_backref* to_data_backref(struct extent_backref *back) { @@ -1971,10 +1975,15 @@ struct node_refs { u64 bytenr[BTRFS_MAX_LEVEL]; u64 refs[BTRFS_MAX_LEVEL]; int need_check[BTRFS_MAX_LEVEL]; + /* field for checking all trees */ + int checked[BTRFS_MAX_LEVEL]; + /* the corresponding extent should be marked as full backref or not */ + int full_backref[BTRFS_MAX_LEVEL]; }; static int update_nodes_refs(struct btrfs_root *root, u64 bytenr, - struct node_refs *nrefs, u64 level); + struct extent_buffer *eb, struct node_refs *nrefs, + u64 level, int check_all); static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, unsigned int ext_ref); @@ -2037,9 +2046,8 @@ again: if (path->nodes[i]->start == nrefs->bytenr[i]) continue; - ret = update_nodes_refs(root, - path->nodes[i]->start, - nrefs, i); + ret = update_nodes_refs(root, path->nodes[i]->start, + path->nodes[i], nrefs, i, 0); if (ret) goto out; @@ -2156,25 +2164,143 @@ static int need_check(struct btrfs_root *root, struct ulist *roots) return 1; } +static int calc_extent_flag_v2(struct btrfs_root *root, struct extent_buffer *eb, + u64 *flags_ret) +{ + struct btrfs_root *extent_root = root->fs_info->extent_root; + struct btrfs_root_item *ri = &root->root_item; + struct btrfs_extent_inline_ref *iref; + struct btrfs_extent_item *ei; + struct btrfs_key key; + struct btrfs_path *path = NULL; + unsigned long ptr; + unsigned long end; + u64 flags; + u64 owner = 0; + u64 offset; + int slot; + int type; + int ret = 0; + + /* + * Except file/reloc tree, we can not have FULL BACKREF MODE + */ + if (root->objectid < BTRFS_FIRST_FREE_OBJECTID) + goto normal; + + /* root node */ + if (eb->start == btrfs_root_bytenr(ri)) + goto normal; + + if (btrfs_header_flag(eb, BTRFS_HEADER_FLAG_RELOC)) + goto full_backref; + + owner = btrfs_header_owner(eb); + if (owner == root->objectid) + goto normal; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + key.objectid = btrfs_header_bytenr(eb); + key.type = (u8)-1; + key.offset = (u64)-1; + + ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0); + if (ret <= 0) { + ret = -EIO; + goto out; + } + + if (ret > 0) { + ret = btrfs_previous_extent_item(extent_root, path, + key.objectid); + if (ret) + goto full_backref; + + } + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + + eb = path->nodes[0]; + slot = path->slots[0]; + ei = btrfs_item_ptr(eb, slot, struct btrfs_extent_item); + + flags = btrfs_extent_flags(eb, ei); + if (flags & BTRFS_BLOCK_FLAG_FULL_BACKREF) + goto full_backref; + + ptr = (unsigned long)(ei + 1); + end = (unsigned long)ei + btrfs_item_size_nr(eb, slot); + + if (key.type == BTRFS_EXTENT_ITEM_KEY) + ptr += sizeof(struct btrfs_tree_block_info); + +next: + /* Reached extent item ends normally */ + if (ptr == end) + goto full_backref; + + /* Beyond extent item end, wrong item size */ + if (ptr > end) { + error("extent item at bytenr %llu slot %d has wrong size", + eb->start, slot); + goto full_backref; + } + + iref = (struct btrfs_extent_inline_ref *)ptr; + offset = btrfs_extent_inline_ref_offset(eb, iref); + type = btrfs_extent_inline_ref_type(eb, iref); + + if (type == BTRFS_TREE_BLOCK_REF_KEY && offset == owner) + goto normal; + ptr += btrfs_extent_inline_ref_size(type); + goto next; + +normal: + *flags_ret &= ~BTRFS_BLOCK_FLAG_FULL_BACKREF; + goto out; + +full_backref: + *flags_ret |= BTRFS_BLOCK_FLAG_FULL_BACKREF; +out: + btrfs_free_path(path); + return ret; +} + /* * 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. + * + * @bytenr if @bytenr == (u64)-1, only update nrefs->full_backref[level] */ static int update_nodes_refs(struct btrfs_root *root, u64 bytenr, - struct node_refs *nrefs, u64 level) + struct extent_buffer *eb, struct node_refs *nrefs, + u64 level, int check_all) { - int check, ret; - u64 refs; struct ulist *roots; + u64 refs = 0; + u64 flags = 0; + int root_level = btrfs_header_level(root->node); + int check; + int ret; - if (nrefs->bytenr[level] != bytenr) { + if (nrefs->bytenr[level] == bytenr) + return 0; + + if (bytenr != (u64)-1) { + /* the return value of this function seems a mistake */ ret = btrfs_lookup_extent_info(NULL, root, bytenr, - level, 1, &refs, NULL); - if (ret < 0) + level, 1, &refs, &flags); + /* temporary fix */ + if (ret < 0 && !check_all) return ret; nrefs->bytenr[level] = bytenr; nrefs->refs[level] = refs; + nrefs->full_backref[level] = 0; + nrefs->checked[level] = 0; + if (refs > 1) { ret = btrfs_find_all_roots(NULL, root->fs_info, bytenr, 0, &roots); @@ -2185,13 +2311,58 @@ static int update_nodes_refs(struct btrfs_root *root, u64 bytenr, ulist_free(roots); nrefs->need_check[level] = check; } else { - nrefs->need_check[level] = 1; + if (!check_all) { + nrefs->need_check[level] = 1; + } else { + if (level == root_level) { + nrefs->need_check[level] = 1; + } else { + /* + * The node refs may have not been + * updated if upper needs checking (the + * lowest root_objectid) the node can + * be checked. + */ + nrefs->need_check[level] = + nrefs->need_check[level + 1]; + } + } } } + if (check_all && eb) { + calc_extent_flag_v2(root, eb, &flags); + if (flags & BTRFS_BLOCK_FLAG_FULL_BACKREF) + nrefs->full_backref[level] = 1; + } + return 0; } +/* + * @level if @level == -1 means extent data item + * else normal treeblocl. + */ +static int should_check_extent_strictly(struct btrfs_root *root, + struct node_refs *nrefs, int level) +{ + int root_level = btrfs_header_level(root->node); + + if (level > root_level || level < -1) + return 1; + if (level == root_level) + return 1; + /* + * if the upper node is marked full backref, it should contain shared + * backref of the parent (except owner == root->objectid). + */ + while (++level <= root_level) + if (nrefs->refs[level] > 1) + return 0; + + return 1; +} + static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path, struct walk_control *wc, int *level, struct node_refs *nrefs) @@ -2321,16 +2492,197 @@ out: return err; } +static int fs_root_objectid(u64 objectid); + +/* + * Update global fs information. + */ +static void account_bytes(struct btrfs_root *root, struct btrfs_path *path, + int level) +{ + u32 free_nrs; + struct extent_buffer *eb = path->nodes[level]; + + total_btree_bytes += eb->len; + if (fs_root_objectid(root->objectid)) + total_fs_tree_bytes += eb->len; + if (btrfs_header_owner(eb) == BTRFS_EXTENT_TREE_OBJECTID) + total_extent_tree_bytes += eb->len; + + if (level == 0) { + btree_space_waste += btrfs_leaf_free_space(root, eb); + } else { + free_nrs = (BTRFS_NODEPTRS_PER_BLOCK(root) - + btrfs_header_nritems(eb)); + btree_space_waste += free_nrs * sizeof(struct btrfs_key_ptr); + } +} + +/* + * This function only handles BACKREF_MISSING, + * If corresponding extent item exists, increase the ref, else insert an extent + * item and backref. + * + * Returns error bits after repair. + */ +static int repair_tree_block_ref(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct extent_buffer *node, + struct node_refs *nrefs, int level, int err) +{ + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_root *extent_root = fs_info->extent_root; + struct btrfs_path path; + struct btrfs_extent_item *ei; + struct btrfs_tree_block_info *bi; + struct btrfs_key key; + struct extent_buffer *eb; + u32 size = sizeof(*ei); + u32 node_size = root->fs_info->nodesize; + int insert_extent = 0; + int skinny_metadata = btrfs_fs_incompat(fs_info, SKINNY_METADATA); + int root_level = btrfs_header_level(root->node); + int generation; + int ret; + u64 owner; + u64 bytenr; + u64 flags = BTRFS_EXTENT_FLAG_TREE_BLOCK; + u64 parent = 0; + + if ((err & BACKREF_MISSING) == 0) + return err; + + WARN_ON(level > BTRFS_MAX_LEVEL); + WARN_ON(level < 0); + + btrfs_init_path(&path); + bytenr = btrfs_header_bytenr(node); + owner = btrfs_header_owner(node); + generation = btrfs_header_generation(node); + + key.objectid = bytenr; + key.type = (u8)-1; + key.offset = (u64)-1; + + /* Search for the extent item */ + ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0); + if (ret <= 0) { + ret = -EIO; + goto out; + } + + ret = btrfs_previous_extent_item(extent_root, &path, bytenr); + if (ret) + insert_extent = 1; + + /* calculate if the extent item flag is full backref or not */ + if (nrefs->full_backref[level] != 0) + flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF; + + /* insert an extent item */ + if (insert_extent) { + struct btrfs_disk_key copy_key; + + generation = btrfs_header_generation(node); + + if (level < root_level && nrefs->full_backref[level + 1] && + owner != root->objectid) { + flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF; + } + + key.objectid = bytenr; + if (!skinny_metadata) { + key.type = BTRFS_EXTENT_ITEM_KEY; + key.offset = node_size; + size += sizeof(*bi); + } else { + key.type = BTRFS_METADATA_ITEM_KEY; + key.offset = level; + } + + btrfs_release_path(&path); + ret = btrfs_insert_empty_item(trans, extent_root, &path, &key, + size); + if (ret) + goto out; + + eb = path.nodes[0]; + ei = btrfs_item_ptr(eb, path.slots[0], struct btrfs_extent_item); + + btrfs_set_extent_refs(eb, ei, 0); + btrfs_set_extent_generation(eb, ei, generation); + btrfs_set_extent_flags(eb, ei, flags); + + if (!skinny_metadata) { + bi = (struct btrfs_tree_block_info *)(ei + 1); + memset_extent_buffer(eb, 0, (unsigned long)bi, + sizeof(*bi)); + btrfs_set_disk_key_objectid(©_key, root->objectid); + btrfs_set_disk_key_type(©_key, 0); + btrfs_set_disk_key_offset(©_key, 0); + + btrfs_set_tree_block_level(eb, bi, level); + btrfs_set_tree_block_key(eb, bi, ©_key); + } + btrfs_mark_buffer_dirty(eb); + printf("Added an extent item [%llu %u]\n", bytenr, node_size); + btrfs_update_block_group(trans, extent_root, bytenr, node_size, + 1, 0); + + nrefs->refs[level] = 0; + nrefs->full_backref[level] = + flags & BTRFS_BLOCK_FLAG_FULL_BACKREF; + btrfs_release_path(&path); + } + + if (level < root_level && nrefs->full_backref[level + 1] && + owner != root->objectid) + parent = nrefs->bytenr[level + 1]; + + /* increase the ref */ + ret = btrfs_inc_extent_ref(trans, extent_root, bytenr, node_size, + parent, root->objectid, level, 0); + + nrefs->refs[level]++; +out: + btrfs_release_path(&path); + if (ret) { + error( + "failed to repair tree block ref start %llu root %llu due to %s", + bytenr, root->objectid, strerror(-ret)); + } else { + printf("Added one tree block ref start %llu %s %llu\n", + bytenr, parent ? "parent" : "root", + parent ? parent : root->objectid); + err &= ~BACKREF_MISSING; + } + + return err; +} + static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, unsigned int ext_ref); +static int check_tree_block_ref(struct btrfs_root *root, + struct extent_buffer *eb, u64 bytenr, + int level, u64 owner, struct node_refs *nrefs); +static int check_leaf_items(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_path *path, + struct node_refs *nrefs, int account_bytes); /* + * @trans just for lowmem repair mode + * @check all if not 0 then check all tree block backrefs and items + * 0 then just check relationship of items in fs tree(s) + * * 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) +static int walk_down_tree_v2(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_path *path, + int *level, struct node_refs *nrefs, int ext_ref, + int check_all) + { enum btrfs_tree_block_status status; u64 bytenr; @@ -2339,12 +2691,15 @@ static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path, struct extent_buffer *next; struct extent_buffer *cur; int ret; + int err = 0; + int check; + int account_file_data = 0; WARN_ON(*level < 0); WARN_ON(*level >= BTRFS_MAX_LEVEL); - ret = update_nodes_refs(root, path->nodes[*level]->start, - nrefs, *level); + ret = update_nodes_refs(root, btrfs_header_bytenr(path->nodes[*level]), + path->nodes[*level], nrefs, *level, check_all); if (ret < 0) return ret; @@ -2352,37 +2707,80 @@ static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path, WARN_ON(*level < 0); WARN_ON(*level >= BTRFS_MAX_LEVEL); cur = path->nodes[*level]; + bytenr = btrfs_header_bytenr(cur); + check = nrefs->need_check[*level]; if (btrfs_header_level(cur) != *level) WARN_ON(1); + /* + * Update bytes accounting and check tree block ref + * NOTE: Doing accounting and check before checking nritems + * is necessary because of empty node/leaf. + */ + if ((check_all && !nrefs->checked[*level]) || + (!check_all && nrefs->need_check[*level])) { + ret = check_tree_block_ref(root, cur, + btrfs_header_bytenr(cur), btrfs_header_level(cur), + btrfs_header_owner(cur), nrefs); + + if (repair && ret) + ret = repair_tree_block_ref(trans, root, + path->nodes[*level], nrefs, *level, ret); + err |= ret; + + if (check_all && nrefs->need_check[*level] && + nrefs->refs[*level]) { + account_bytes(root, path, *level); + account_file_data = 1; + } + nrefs->checked[*level] = 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; + /* skip duplicate check */ + if (check || !check_all) { + ret = btrfs_check_leaf(root, NULL, cur); + if (ret != BTRFS_TREE_BLOCK_CLEAN) { + err |= -EIO; + break; + } } - ret = process_one_leaf_v2(root, path, nrefs, - level, ext_ref); - cur = path->nodes[*level]; + + ret = 0; + if (!check_all) + ret = process_one_leaf_v2(root, path, nrefs, + level, ext_ref); + else + ret = check_leaf_items(trans, root, path, + nrefs, account_file_data); + err |= ret; break; } else { - ret = btrfs_check_node(root, NULL, cur); - if (ret != BTRFS_TREE_BLOCK_CLEAN) { - ret = -EIO; - break; + if (check || !check_all) { + ret = btrfs_check_node(root, NULL, cur); + if (ret != BTRFS_TREE_BLOCK_CLEAN) { + err |= -EIO; + break; + } } } + bytenr = btrfs_node_blockptr(cur, path->slots[*level]); ptr_gen = btrfs_node_ptr_generation(cur, path->slots[*level]); - ret = update_nodes_refs(root, bytenr, nrefs, *level - 1); - if (ret) + ret = update_nodes_refs(root, bytenr, NULL, nrefs, *level - 1, + check_all); + if (ret < 0) break; - if (!nrefs->need_check[*level - 1]) { + /* + * check all trees in check_chunks_and_extent_v2 + * check shared node once in check_fs_roots + */ + if (!check_all && !nrefs->need_check[*level - 1]) { path->slots[*level]++; continue; } @@ -2399,16 +2797,15 @@ static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path, &node_key, path->slots[*level]); btrfs_add_corrupt_extent_record(fs_info, - &node_key, - path->nodes[*level]->start, - fs_info->nodesize, - *level); - ret = -EIO; + &node_key, path->nodes[*level]->start, + fs_info->nodesize, *level); + err |= -EIO; break; } } ret = check_child_node(cur, path->slots[*level], next); + err |= ret; if (ret < 0) break; @@ -2418,7 +2815,7 @@ static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path, status = btrfs_check_node(root, NULL, next); if (status != BTRFS_TREE_BLOCK_CLEAN) { free_extent_buffer(next); - ret = -EIO; + err |= -EIO; break; } @@ -2426,8 +2823,11 @@ static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path, free_extent_buffer(path->nodes[*level]); path->nodes[*level] = next; path->slots[*level] = 0; + account_file_data = 0; + + update_nodes_refs(root, (u64)-1, next, nrefs, *level, check_all); } - return ret; + return err; } static int walk_up_tree(struct btrfs_root *root, struct btrfs_path *path, @@ -2689,13 +3089,54 @@ static int delete_dir_index(struct btrfs_root *root, return ret; } +static int __create_inode_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, u64 ino, u64 size, + u64 nbytes, u64 nlink, u32 mode) +{ + struct btrfs_inode_item ii; + time_t now = time(NULL); + int ret; + + btrfs_set_stack_inode_size(&ii, size); + btrfs_set_stack_inode_nbytes(&ii, nbytes); + btrfs_set_stack_inode_nlink(&ii, nlink); + btrfs_set_stack_inode_mode(&ii, mode); + btrfs_set_stack_inode_generation(&ii, trans->transid); + btrfs_set_stack_timespec_nsec(&ii.atime, 0); + btrfs_set_stack_timespec_sec(&ii.ctime, now); + btrfs_set_stack_timespec_nsec(&ii.ctime, 0); + btrfs_set_stack_timespec_sec(&ii.mtime, now); + btrfs_set_stack_timespec_nsec(&ii.mtime, 0); + btrfs_set_stack_timespec_sec(&ii.otime, 0); + btrfs_set_stack_timespec_nsec(&ii.otime, 0); + + ret = btrfs_insert_inode(trans, root, ino, &ii); + ASSERT(!ret); + + warning("root %llu inode %llu recreating inode item, this may " + "be incomplete, please check permissions and content after " + "the fsck completes.\n", (unsigned long long)root->objectid, + (unsigned long long)ino); + + return 0; +} + +static int create_inode_item_lowmem(struct btrfs_trans_handle *trans, + struct btrfs_root *root, u64 ino, + u8 filetype) +{ + u32 mode = (filetype == BTRFS_FT_DIR ? S_IFDIR : S_IFREG) | 0755; + + return __create_inode_item(trans, root, ino, 0, 0, 0, mode); +} + static int create_inode_item(struct btrfs_root *root, - struct inode_record *rec, - int root_dir) + struct inode_record *rec, int root_dir) { struct btrfs_trans_handle *trans; - struct btrfs_inode_item inode_item; - time_t now = time(NULL); + u64 nlink = 0; + u32 mode = 0; + u64 size = 0; int ret; trans = btrfs_start_transaction(root, 1); @@ -2704,18 +3145,7 @@ static int create_inode_item(struct btrfs_root *root, return ret; } - fprintf(stderr, "root %llu inode %llu recreating inode item, this may " - "be incomplete, please check permissions and content after " - "the fsck completes.\n", (unsigned long long)root->objectid, - (unsigned long long)rec->ino); - - memset(&inode_item, 0, sizeof(inode_item)); - btrfs_set_stack_inode_generation(&inode_item, trans->transid); - if (root_dir) - btrfs_set_stack_inode_nlink(&inode_item, 1); - else - btrfs_set_stack_inode_nlink(&inode_item, rec->found_link); - btrfs_set_stack_inode_nbytes(&inode_item, rec->found_size); + nlink = root_dir ? 1 : rec->found_link; if (rec->found_dir_item) { if (rec->found_file_extent) fprintf(stderr, "root %llu inode %llu has both a dir " @@ -2723,23 +3153,15 @@ static int create_inode_item(struct btrfs_root *root, "regular file so setting it as a directory\n", (unsigned long long)root->objectid, (unsigned long long)rec->ino); - btrfs_set_stack_inode_mode(&inode_item, S_IFDIR | 0755); - btrfs_set_stack_inode_size(&inode_item, rec->found_size); + mode = S_IFDIR | 0755; + size = rec->found_size; } else if (!rec->found_dir_item) { - btrfs_set_stack_inode_size(&inode_item, rec->extent_end); - btrfs_set_stack_inode_mode(&inode_item, S_IFREG | 0755); - } - btrfs_set_stack_timespec_sec(&inode_item.atime, now); - btrfs_set_stack_timespec_nsec(&inode_item.atime, 0); - btrfs_set_stack_timespec_sec(&inode_item.ctime, now); - btrfs_set_stack_timespec_nsec(&inode_item.ctime, 0); - btrfs_set_stack_timespec_sec(&inode_item.mtime, now); - btrfs_set_stack_timespec_nsec(&inode_item.mtime, 0); - btrfs_set_stack_timespec_sec(&inode_item.otime, 0); - btrfs_set_stack_timespec_nsec(&inode_item.otime, 0); - - ret = btrfs_insert_inode(trans, root, rec->ino, &inode_item); - BUG_ON(ret); + size = rec->extent_end; + mode = S_IFREG | 0755; + } + + ret = __create_inode_item(trans, root, rec->ino, size, rec->nbytes, + nlink, mode); btrfs_commit_transaction(trans, root); return 0; } @@ -2959,7 +3381,7 @@ static int reset_nlink(struct btrfs_trans_handle *trans, list_for_each_entry(backref, &rec->backrefs, list) { ret = btrfs_add_link(trans, root, rec->ino, backref->dir, backref->name, backref->namelen, - backref->filetype, &backref->index, 1); + backref->filetype, &backref->index, 1, 0); if (ret < 0) goto out; } @@ -2993,15 +3415,79 @@ static int get_highest_inode(struct btrfs_trans_handle *trans, return ret; } -static int repair_inode_nlinks(struct btrfs_trans_handle *trans, - struct btrfs_root *root, - struct btrfs_path *path, - struct inode_record *rec) +/* + * Link inode to dir 'lost+found'. Increase @ref_count. + * + * Returns 0 means success. + * Returns <0 means failure. + */ +static int link_inode_to_lostfound(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *path, + u64 ino, char *namebuf, u32 name_len, + u8 filetype, u64 *ref_count) { char *dir_name = "lost+found"; - char namebuf[BTRFS_NAME_LEN] = {0}; u64 lost_found_ino; + int ret; u32 mode = 0700; + + btrfs_release_path(path); + 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, + mode); + if (ret < 0) { + error("failed to create '%s' dir: %s", dir_name, strerror(-ret)); + goto out; + } + ret = btrfs_add_link(trans, root, ino, lost_found_ino, + namebuf, name_len, filetype, NULL, 1, 0); + /* + * Add ".INO" suffix several times to handle case where + * "FILENAME.INO" is already taken by another file. + */ + while (ret == -EEXIST) { + /* + * Conflicting file name, add ".INO" as suffix * +1 for '.' + */ + if (name_len + count_digits(ino) + 1 > BTRFS_NAME_LEN) { + ret = -EFBIG; + goto out; + } + snprintf(namebuf + name_len, BTRFS_NAME_LEN - name_len, + ".%llu", ino); + name_len += count_digits(ino) + 1; + ret = btrfs_add_link(trans, root, ino, lost_found_ino, namebuf, + name_len, filetype, NULL, 1, 0); + } + if (ret < 0) { + error("failed to link the inode %llu to %s dir: %s", + ino, dir_name, strerror(-ret)); + goto out; + } + + ++*ref_count; + printf("Moving file '%.*s' to '%s' dir since it has no valid backref\n", + name_len, namebuf, dir_name); +out: + btrfs_release_path(path); + if (ret) + error("failed to move file '%.*s' to '%s' dir", name_len, + namebuf, dir_name); + return ret; +} + +static int repair_inode_nlinks(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *path, + struct inode_record *rec) +{ + char namebuf[BTRFS_NAME_LEN] = {0}; u8 type = 0; int namelen = 0; int name_recovered = 0; @@ -3038,55 +3524,11 @@ static int repair_inode_nlinks(struct btrfs_trans_handle *trans, } if (rec->found_link == 0) { - 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, - mode); - if (ret < 0) { - fprintf(stderr, "Failed to create '%s' dir: %s\n", - dir_name, strerror(-ret)); - goto out; - } - ret = btrfs_add_link(trans, root, rec->ino, lost_found_ino, - namebuf, namelen, type, NULL, 1); - /* - * Add ".INO" suffix several times to handle case where - * "FILENAME.INO" is already taken by another file. - */ - while (ret == -EEXIST) { - /* - * Conflicting file name, add ".INO" as suffix * +1 for '.' - */ - if (namelen + count_digits(rec->ino) + 1 > - BTRFS_NAME_LEN) { - ret = -EFBIG; - goto out; - } - snprintf(namebuf + namelen, BTRFS_NAME_LEN - namelen, - ".%llu", rec->ino); - namelen += count_digits(rec->ino) + 1; - ret = btrfs_add_link(trans, root, rec->ino, - lost_found_ino, namebuf, - namelen, type, NULL, 1); - } - if (ret < 0) { - fprintf(stderr, - "Failed to link the inode %llu to %s dir: %s\n", - rec->ino, dir_name, strerror(-ret)); + ret = link_inode_to_lostfound(trans, root, path, rec->ino, + namebuf, namelen, type, + (u64 *)&rec->found_link); + if (ret) goto out; - } - /* - * Just increase the found_link, don't actually add the - * backref. This will make things easier and this inode - * record will be freed after the repair is done. - * So fsck will not report problem about this inode. - */ - rec->found_link++; - printf("Moving file '%.*s' to '%s' dir since it has no valid backref\n", - namelen, namebuf, dir_name); } printf("Fixed the nlink of inode %llu\n", rec->ino); out: @@ -4240,270 +4682,522 @@ out: } /* - * Find DIR_ITEM/DIR_INDEX for the given key and check it with the specified - * INODE_REF/INODE_EXTREF match. + * Find the @index according by @ino and name. + * Notice:time efficiency is O(N) * * @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 + * @index_ret: the index as return value + * @namebuf: the name to match + * @name_len: the length of name to match + * @file_type: the file_type of INODE_ITEM to match * - * 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. + * Returns 0 if found and *@index_ret will be modified with right value + * Returns< 0 not found and *@index_ret will be (u64)-1 */ -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) +static int find_dir_index(struct btrfs_root *root, u64 dirid, u64 location_id, + u64 *index_ret, char *namebuf, u32 name_len, + u8 file_type) { struct btrfs_path path; struct extent_buffer *node; struct btrfs_dir_item *di; + struct btrfs_key key; struct btrfs_key location; - char namebuf[BTRFS_NAME_LEN] = {0}; + char name[BTRFS_NAME_LEN] = {0}; + u32 total; u32 cur = 0; u32 len; - u32 name_len; u32 data_len; u8 filetype; int slot; int ret; - btrfs_init_path(&path); - ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); - if (ret < 0) { - ret = DIR_ITEM_MISSING; - goto out; - } + ASSERT(index_ret); - /* 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; - } + /* search from the last index */ + key.objectid = dirid; + key.offset = (u64)-1; + key.type = BTRFS_DIR_INDEX_KEY; - goto out; - } + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; - /* 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)); +loop: + ret = btrfs_previous_item(root, &path, dirid, BTRFS_DIR_INDEX_KEY); + if (ret) { + ret = -ENOENT; + *index_ret = (64)-1; 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); + ret = -ENOENT; + 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 || + if (location.objectid != location_id || + location.type != BTRFS_INODE_ITEM_KEY || location.offset != 0) goto next; filetype = btrfs_dir_type(node, di); - if (imode_to_type(mode) != filetype) + if (file_type != 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; - } + if (len > BTRFS_NAME_LEN) + len = BTRFS_NAME_LEN; - read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len); - if (len != namelen || strncmp(namebuf, name, len)) + read_extent_buffer(node, name, (unsigned long)(di + 1), len); + if (len != name_len || strncmp(namebuf, name, len)) goto next; + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + *index_ret = key.offset; ret = 0; goto out; next: - len = sizeof(*di) + name_len + data_len; + len += sizeof(*di) + 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)); + goto loop; + out: btrfs_release_path(&path); return ret; } /* - * Traverse the given INODE_REF and call find_dir_item() to find related - * DIR_ITEM/DIR_INDEX. + * 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 - * @refs: the count of INODE_REF - * @mode: the st_mode of INODE_ITEM + * @key: the key of the DIR_ITEM/DIR_INDEX, key->offset will be right + * value while find index + * @location_key: location key of the struct btrfs_dir_item to match + * @name: the name to match + * @namelen: the length of name + * @file_type: the type of file to math * * Return 0 if no error occurred. + * Return DIR_ITEM_MISSING/DIR_INDEX_MISSING if couldn't find + * DIR_ITEM/DIR_INDEX + * Return DIR_ITEM_MISMATCH/DIR_INDEX_MISMATCH if INODE_REF/INODE_EXTREF + * and DIR_ITEM/DIR_INDEX mismatch */ -static int check_inode_ref(struct btrfs_root *root, struct btrfs_key *ref_key, - struct extent_buffer *node, int slot, u64 *refs, - int mode) +static int find_dir_item(struct btrfs_root *root, struct btrfs_key *key, + struct btrfs_key *location_key, char *name, + u32 namelen, u8 file_type) { - struct btrfs_key key; - struct btrfs_inode_ref *ref; + 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; - u64 index; - int ret, err = 0; + u32 data_len; + u8 filetype; + int slot; + int ret; - ref = btrfs_item_ptr(node, slot, struct btrfs_inode_ref); + /* get the index by traversing all index */ + if (key->type == BTRFS_DIR_INDEX_KEY && key->offset == (u64)-1) { + ret = find_dir_index(root, key->objectid, + location_key->objectid, &key->offset, + name, namelen, file_type); + if (ret) + ret = DIR_INDEX_MISSING; + return ret; + } + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); + if (ret) { + ret = key->type == BTRFS_DIR_ITEM_KEY ? DIR_ITEM_MISSING : + DIR_INDEX_MISSING; + 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 = key->type == BTRFS_DIR_ITEM_KEY ? + DIR_ITEM_MISMATCH : DIR_INDEX_MISMATCH; -next: - /* Update inode ref count */ - (*refs)++; + len = btrfs_dir_name_len(node, di); + data_len = btrfs_dir_data_len(node, di); - 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); + btrfs_dir_item_key_to_cpu(node, di, &location); + if (location.objectid != location_key->objectid || + location.type != location_key->type || + location.offset != location_key->offset) + goto next; - if (total < cur + sizeof(*ref)) - goto out; - len = min_t(u32, total - cur - sizeof(*ref), BTRFS_NAME_LEN); - } else { - len = name_len; - } + filetype = btrfs_dir_type(node, di); + if (file_type != filetype) + goto next; - read_extent_buffer(node, namebuf, (unsigned long)(ref + 1), len); + if (len > BTRFS_NAME_LEN) { + 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, len); + } + read_extent_buffer(node, namebuf, (unsigned long)(di + 1), + len); + if (len != namelen || strncmp(namebuf, name, len)) + goto next; - /* 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; + ret = 0; + goto out; +next: + len += sizeof(*di) + data_len; + di = (struct btrfs_dir_item *)((char *)di + len); + cur += len; } - /* 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; +out: + btrfs_release_path(&path); + return 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; +/* + * Prints inode ref error message + */ +static void print_inode_ref_err(struct btrfs_root *root, struct btrfs_key *key, + u64 index, const char *namebuf, int name_len, + u8 filetype, int err) +{ + if (!err) + return; - len = sizeof(*ref) + name_len; - ref = (struct btrfs_inode_ref *)((char *)ref + len); - cur += len; - if (cur < total) - goto next; + /* root dir error */ + if (key->objectid == BTRFS_FIRST_FREE_OBJECTID) { + error( + "root %llu root dir shouldn't have INODE REF[%llu %llu] name %s", + root->objectid, key->objectid, key->offset, namebuf); + return; + } -out: - return err; + /* normal error */ + if (err & (DIR_ITEM_MISMATCH | DIR_ITEM_MISSING)) + error("root %llu DIR ITEM[%llu %llu] %s name %s filetype %u", + root->objectid, key->offset, + btrfs_name_hash(namebuf, name_len), + err & DIR_ITEM_MISMATCH ? "mismatch" : "missing", + namebuf, filetype); + if (err & (DIR_INDEX_MISMATCH | DIR_INDEX_MISSING)) + error("root %llu DIR INDEX[%llu %llu] %s name %s filetype %u", + root->objectid, key->offset, index, + err & DIR_ITEM_MISMATCH ? "mismatch" : "missing", + namebuf, filetype); } /* - * 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 + * Insert the missing inode item. * - * Return 0 if no error occurred. + * Returns 0 means success. + * Returns <0 means error. */ -static int check_inode_extref(struct btrfs_root *root, - struct btrfs_key *ref_key, - struct extent_buffer *node, int slot, u64 *refs, - int mode) +static int repair_inode_item_missing(struct btrfs_root *root, u64 ino, + u8 filetype) { 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; + struct btrfs_trans_handle *trans; + struct btrfs_path path; int ret; - int err = 0; - extref = btrfs_item_ptr(node, slot, struct btrfs_inode_extref); - total = btrfs_item_size_nr(node, slot); + key.objectid = ino; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; -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); - } + btrfs_init_path(&path); + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = -EIO; + goto out; + } + + ret = btrfs_search_slot(trans, root, &key, &path, 1, 1); + if (ret < 0 || !ret) + goto fail; + + /* insert inode item */ + create_inode_item_lowmem(trans, root, ino, filetype); + ret = 0; +fail: + btrfs_commit_transaction(trans, root); +out: + if (ret) + error("failed to repair root %llu INODE ITEM[%llu] missing", + root->objectid, ino); + btrfs_release_path(&path); + return ret; +} + +/* + * The ternary means dir item, dir index and relative inode ref. + * The function handles errs: INODE_MISSING, DIR_INDEX_MISSING + * DIR_INDEX_MISMATCH, DIR_ITEM_MISSING, DIR_ITEM_MISMATCH by the follow + * strategy: + * If two of three is missing or mismatched, delete the existing one. + * If one of three is missing or mismatched, add the missing one. + * + * returns 0 means success. + * returns not 0 means on error; + */ +int repair_ternary_lowmem(struct btrfs_root *root, u64 dir_ino, u64 ino, + u64 index, char *name, int name_len, u8 filetype, + int err) +{ + struct btrfs_trans_handle *trans; + int stage = 0; + int ret = 0; + + /* + * stage shall be one of following valild values: + * 0: Fine, nothing to do. + * 1: One of three is wrong, so add missing one. + * 2: Two of three is wrong, so delete existed one. + */ + if (err & (DIR_INDEX_MISMATCH | DIR_INDEX_MISSING)) + stage++; + if (err & (DIR_ITEM_MISMATCH | DIR_ITEM_MISSING)) + stage++; + if (err & (INODE_REF_MISSING)) + stage++; + + /* stage must be smllarer than 3 */ + ASSERT(stage < 3); + + trans = btrfs_start_transaction(root, 1); + if (stage == 2) { + ret = btrfs_unlink(trans, root, ino, dir_ino, index, name, + name_len, 0); + goto out; + } + if (stage == 1) { + ret = btrfs_add_link(trans, root, ino, dir_ino, name, name_len, + filetype, &index, 1, 1); + goto out; + } +out: + btrfs_commit_transaction(trans, root); + + if (ret) + error("fail to repair inode %llu name %s filetype %u", + ino, name, filetype); + else + printf("%s ref/dir_item of inode %llu name %s filetype %u\n", + stage == 2 ? "Delete" : "Add", + ino, name, filetype); + + 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 + * @path the path provides node and slot + * @refs: the count of INODE_REF + * @mode: the st_mode of INODE_ITEM + * @name_ret: returns with the first ref's name + * @name_len_ret: len of the name_ret + * + * Return 0 if no error occurred. + */ +static int check_inode_ref(struct btrfs_root *root, struct btrfs_key *ref_key, + struct btrfs_path *path, char *name_ret, + u32 *namelen_ret, u64 *refs_ret, int mode) +{ + struct btrfs_key key; + struct btrfs_key location; + struct btrfs_inode_ref *ref; + struct extent_buffer *node; + char namebuf[BTRFS_NAME_LEN] = {0}; + u32 total; + u32 cur = 0; + u32 len; + u32 name_len; + u64 index; + int ret; + int err = 0; + int tmp_err; + int slot; + int need_research = 0; + u64 refs; + +begin: + err = 0; + cur = 0; + refs = *refs_ret; + + /* since after repair, path and the dir item may be changed */ + if (need_research) { + need_research = 0; + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, ref_key, path, 0, 0); + /* the item was deleted, let path point to the last checked item */ + if (ret > 0) { + if (path->slots[0] == 0) + btrfs_prev_leaf(root, path); + else + path->slots[0]--; + } + if (ret) + goto out; + } + + location.objectid = ref_key->objectid; + location.type = BTRFS_INODE_ITEM_KEY; + location.offset = 0; + node = path->nodes[0]; + slot = path->slots[0]; + + memset(namebuf, 0, sizeof(namebuf) / sizeof(*namebuf)); + ref = btrfs_item_ptr(node, slot, struct btrfs_inode_ref); + total = btrfs_item_size_nr(node, slot); + +next: + /* Update inode ref count */ + refs++; + tmp_err = 0; + index = btrfs_inode_ref_index(node, ref); + name_len = btrfs_inode_ref_name_len(node, ref); + + if (name_len <= BTRFS_NAME_LEN) { + len = name_len; + } else { + len = BTRFS_NAME_LEN; + warning("root %llu INODE_REF[%llu %llu] name too long", + root->objectid, ref_key->objectid, ref_key->offset); + } + + read_extent_buffer(node, namebuf, (unsigned long)(ref + 1), len); + + /* copy the first name found to name_ret */ + if (refs == 1 && name_ret) { + memcpy(name_ret, namebuf, len); + *namelen_ret = len; + } + + /* Check root dir ref */ + if (ref_key->objectid == BTRFS_FIRST_FREE_OBJECTID) { + if (index != 0 || len != strlen("..") || + strncmp("..", namebuf, len) || + ref_key->offset != BTRFS_FIRST_FREE_OBJECTID) { + /* set err bits then repair will delete the ref */ + err |= DIR_INDEX_MISSING; + err |= DIR_ITEM_MISSING; + } + goto end; + } + + /* Find related DIR_INDEX */ + key.objectid = ref_key->offset; + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = index; + tmp_err |= find_dir_item(root, &key, &location, namebuf, len, + imode_to_type(mode)); + + /* Find related dir_item */ + key.objectid = ref_key->offset; + key.type = BTRFS_DIR_ITEM_KEY; + key.offset = btrfs_name_hash(namebuf, len); + tmp_err |= find_dir_item(root, &key, &location, namebuf, len, + imode_to_type(mode)); +end: + if (tmp_err && repair) { + ret = repair_ternary_lowmem(root, ref_key->offset, + ref_key->objectid, index, namebuf, + name_len, imode_to_type(mode), + tmp_err); + if (!ret) { + need_research = 1; + goto begin; + } + } + print_inode_ref_err(root, ref_key, index, namebuf, name_len, + imode_to_type(mode), tmp_err); + err |= tmp_err; + len = sizeof(*ref) + name_len; + ref = (struct btrfs_inode_ref *)((char *)ref + len); + cur += len; + if (cur < total) + goto next; + +out: + *refs_ret = refs; + 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_key location; + 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; + + location.objectid = ref_key->objectid; + location.type = BTRFS_INODE_ITEM_KEY; + location.offset = 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 */ @@ -4518,14 +5212,14 @@ next: key.objectid = parent; key.type = BTRFS_DIR_INDEX_KEY; key.offset = index; - ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode); + ret = find_dir_item(root, &key, &location, 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); + ret = find_dir_item(root, &key, &location, namebuf, len, mode); err |= ret; len = sizeof(*extref) + name_len; @@ -4541,20 +5235,21 @@ next: /* * Find INODE_REF/INODE_EXTREF for the given key and check it with the specified * DIR_ITEM/DIR_INDEX match. + * Return with @index_ret. * * @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 + * @index_ret: the index in the INODE_REF/INODE_EXTREF, + * value (64)-1 means do not check index * @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, + char *name, int namelen, u64 *index_ret, unsigned int ext_ref) { struct btrfs_path path; @@ -4572,6 +5267,8 @@ static int find_inode_ref(struct btrfs_root *root, struct btrfs_key *key, int slot; int ret; + ASSERT(index_ret); + btrfs_init_path(&path); ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); if (ret) { @@ -4591,7 +5288,7 @@ static int find_inode_ref(struct btrfs_root *root, struct btrfs_key *key, ref_namelen = btrfs_inode_ref_name_len(node, ref); ref_index = btrfs_inode_ref_index(node, ref); - if (index != (u64)-1 && index != ref_index) + if (*index_ret != (u64)-1 && *index_ret != ref_index) goto next_ref; if (cur + sizeof(*ref) + ref_namelen > total || @@ -4616,6 +5313,7 @@ static int find_inode_ref(struct btrfs_root *root, struct btrfs_key *key, if (len != namelen || strncmp(ref_namebuf, name, len)) goto next_ref; + *index_ret = ref_index; ret = 0; goto out; next_ref: @@ -4656,7 +5354,7 @@ extref: 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) + if (*index_ret != (u64)-1 && *index_ret != ref_index) goto next_extref; if (parent != dir_id) @@ -4678,6 +5376,7 @@ extref: if (len != namelen || strncmp(ref_namebuf, name, len)) goto next_extref; + *index_ret = ref_index; ret = 0; goto out; @@ -4692,25 +5391,172 @@ out: return ret; } +static void print_dir_item_err(struct btrfs_root *root, struct btrfs_key *key, + u64 ino, u64 index, const char *namebuf, + int name_len, u8 filetype, int err) +{ + if (err & (DIR_ITEM_MISMATCH | DIR_ITEM_MISSING)) { + error("root %llu DIR ITEM[%llu %llu] name %s filetype %d %s", + root->objectid, key->objectid, key->offset, namebuf, + filetype, + err & DIR_ITEM_MISMATCH ? "mismath" : "missing"); + } + + if (err & (DIR_INDEX_MISMATCH | DIR_INDEX_MISSING)) { + error("root %llu DIR INDEX[%llu %llu] name %s filetype %d %s", + root->objectid, key->objectid, index, namebuf, filetype, + err & DIR_ITEM_MISMATCH ? "mismath" : "missing"); + } + + if (err & (INODE_ITEM_MISSING | INODE_ITEM_MISMATCH)) { + error( + "root %llu INODE_ITEM[%llu] index %llu name %s filetype %d %s", + root->objectid, ino, index, namebuf, filetype, + err & INODE_ITEM_MISMATCH ? "mismath" : "missing"); + } + + if (err & INODE_REF_MISSING) + error( + "root %llu INODE REF[%llu, %llu] name %s filetype %u missing", + root->objectid, ino, key->objectid, namebuf, filetype); + +} + +/* + * Call repair_inode_item_missing and repair_ternary_lowmem to repair + * + * Returns error after repair + */ +static int repair_dir_item(struct btrfs_root *root, u64 dirid, u64 ino, + u64 index, u8 filetype, char *namebuf, u32 name_len, + int err) +{ + int ret; + + if (err & INODE_ITEM_MISSING) { + ret = repair_inode_item_missing(root, ino, filetype); + if (!ret) + err &= ~(INODE_ITEM_MISMATCH | INODE_ITEM_MISSING); + } + + if (err & ~(INODE_ITEM_MISMATCH | INODE_ITEM_MISSING)) { + ret = repair_ternary_lowmem(root, dirid, ino, index, namebuf, + name_len, filetype, err); + if (!ret) { + err &= ~(DIR_INDEX_MISMATCH | DIR_INDEX_MISSING); + err &= ~(DIR_ITEM_MISMATCH | DIR_ITEM_MISSING); + err &= ~(INODE_REF_MISSING); + } + } + return err; +} + +static int __count_dir_isize(struct btrfs_root *root, u64 ino, int type, + u64 *size_ret) +{ + struct btrfs_key key; + struct btrfs_path path; + u32 len; + struct btrfs_dir_item *di; + int ret; + int cur = 0; + int total = 0; + + ASSERT(size_ret); + *size_ret = 0; + + key.objectid = ino; + key.type = type; + key.offset = (u64)-1; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) { + ret = -EIO; + goto out; + } + /* if found, go to spacial case */ + if (ret == 0) + goto special_case; + +loop: + ret = btrfs_previous_item(root, &path, ino, type); + + if (ret) { + ret = 0; + goto out; + } + +special_case: + di = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_dir_item); + cur = 0; + total = btrfs_item_size_nr(path.nodes[0], path.slots[0]); + + while (cur < total) { + len = btrfs_dir_name_len(path.nodes[0], di); + if (len > BTRFS_NAME_LEN) + len = BTRFS_NAME_LEN; + *size_ret += len; + + len += btrfs_dir_data_len(path.nodes[0], di); + len += sizeof(*di); + di = (struct btrfs_dir_item *)((char *)di + len); + cur += len; + } + goto loop; + +out: + btrfs_release_path(&path); + return ret; +} + +static int count_dir_isize(struct btrfs_root *root, u64 ino, u64 *size) +{ + u64 item_size; + u64 index_size; + int ret; + + ASSERT(size); + ret = __count_dir_isize(root, ino, BTRFS_DIR_ITEM_KEY, &item_size); + if (ret) + goto out; + + ret = __count_dir_isize(root, ino, BTRFS_DIR_INDEX_KEY, &index_size); + if (ret) + goto out; + + *size = item_size + index_size; + +out: + if (ret) + error("failed to count root %llu INODE[%llu] root size", + root->objectid, ino); + 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 + * @path: the path * @size: the st_size of the INODE_ITEM * @ext_ref: the EXTENDED_IREF feature * * Return 0 if no error occurred. + * Return DIR_COUNT_AGAIN if the isize of the inode should be recalculated. */ -static int check_dir_item(struct btrfs_root *root, struct btrfs_key *key, - struct extent_buffer *node, int slot, u64 *size, +static int check_dir_item(struct btrfs_root *root, struct btrfs_key *di_key, + struct btrfs_path *path, u64 *size, unsigned int ext_ref) { struct btrfs_dir_item *di; struct btrfs_inode_item *ii; - struct btrfs_path path; + struct btrfs_key key; struct btrfs_key location; + struct extent_buffer *node; + int slot; char namebuf[BTRFS_NAME_LEN] = {0}; u32 total; u32 cur = 0; @@ -4718,118 +5564,190 @@ static int check_dir_item(struct btrfs_root *root, struct btrfs_key *key, u32 name_len; u32 data_len; u8 filetype; - u32 mode; + u32 mode = 0; u64 index; int ret; - int err = 0; + int err; + int tmp_err; + int need_research = 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; + if (di_key->type == BTRFS_DIR_INDEX_KEY) + index = di_key->offset; + else + index = (u64)-1; +begin: + err = 0; + cur = 0; + + /* since after repair, path and the dir item may be changed */ + if (need_research) { + need_research = 0; + err |= DIR_COUNT_AGAIN; + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, di_key, path, 0, 0); + /* the item was deleted, let path point the last checked item */ + if (ret > 0) { + if (path->slots[0] == 0) + btrfs_prev_leaf(root, path); + else + path->slots[0]--; + } + if (ret) + goto out; + } + + 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); + memset(namebuf, 0, sizeof(namebuf) / sizeof(*namebuf)); while (cur < total) { data_len = btrfs_dir_data_len(node, di); + tmp_err = 0; 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); + root->objectid, + di_key->type == BTRFS_DIR_ITEM_KEY ? "DIR_ITEM" : "DIR_INDEX", + di_key->objectid, di_key->offset, data_len); name_len = btrfs_dir_name_len(node, di); - if (cur + sizeof(*di) + name_len > total || - name_len > BTRFS_NAME_LEN) { + if (name_len <= BTRFS_NAME_LEN) { + len = name_len; + } else { + 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; + di_key->type == BTRFS_DIR_ITEM_KEY ? "DIR_ITEM" : "DIR_INDEX", + di_key->objectid, di_key->offset); } (*size) += name_len; - - read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len); + read_extent_buffer(node, namebuf, (unsigned long)(di + 1), + len); filetype = btrfs_dir_type(node, di); - if (key->type == BTRFS_DIR_ITEM_KEY && - key->offset != btrfs_name_hash(namebuf, len)) { + if (di_key->type == BTRFS_DIR_ITEM_KEY && + di_key->offset != btrfs_name_hash(namebuf, len)) { err |= -EIO; error("root %llu DIR_ITEM[%llu %llu] name %s namelen %u filetype %u mismatch with its hash, wanted %llu have %llu", - root->objectid, key->objectid, key->offset, - namebuf, len, filetype, key->offset, - btrfs_name_hash(namebuf, len)); + root->objectid, di_key->objectid, di_key->offset, + namebuf, len, filetype, di_key->offset, + btrfs_name_hash(namebuf, len)); } - 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; + btrfs_release_path(path); /* Check relative INODE_ITEM(existence/filetype) */ - ret = btrfs_search_slot(NULL, root, &location, &path, 0, 0); + 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); + tmp_err |= INODE_ITEM_MISSING; goto next; } - ii = btrfs_item_ptr(path.nodes[0], path.slots[0], + ii = btrfs_item_ptr(path->nodes[0], path->slots[0], struct btrfs_inode_item); - mode = btrfs_inode_mode(path.nodes[0], ii); - + 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); + tmp_err |= INODE_ITEM_MISMATCH; + goto next; } /* 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); + key.objectid = location.objectid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = di_key->objectid; + tmp_err |= find_inode_ref(root, &key, namebuf, len, + &index, ext_ref); + + /* check relative INDEX/ITEM */ + key.objectid = di_key->objectid; + if (key.type == BTRFS_DIR_ITEM_KEY) { + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = index; + } else { + key.type = BTRFS_DIR_ITEM_KEY; + key.offset = btrfs_name_hash(namebuf, name_len); + } + tmp_err |= find_dir_item(root, &key, &location, namebuf, + name_len, filetype); + /* find_dir_item may find index */ + if (key.type == BTRFS_DIR_INDEX_KEY) + index = key.offset; next: - btrfs_release_path(&path); + + if (tmp_err && repair) { + ret = repair_dir_item(root, di_key->objectid, + location.objectid, index, + imode_to_type(mode), namebuf, + name_len, tmp_err); + if (ret != tmp_err) { + need_research = 1; + goto begin; + } + } + btrfs_release_path(path); + print_dir_item_err(root, di_key, location.objectid, index, + namebuf, name_len, filetype, tmp_err); + err |= tmp_err; 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) { + if (di_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); + root->objectid, di_key->objectid, + di_key->offset); break; } } - +out: + /* research path */ + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, di_key, path, 0, 0); + if (ret) + err |= ret > 0 ? -ENOENT : ret; return err; } /* + * Wrapper function of btrfs_punch_hole. + * + * Returns 0 means success. + * Returns not 0 means error. + */ +static int punch_extent_hole(struct btrfs_root *root, u64 ino, u64 start, + u64 len) +{ + struct btrfs_trans_handle *trans; + int ret = 0; + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) + return PTR_ERR(trans); + + ret = btrfs_punch_hole(trans, root, ino, start, len); + if (ret) + error("failed to add hole [%llu, %llu] in inode [%llu]", + start, len, ino); + else + printf("Add a hole [%llu, %llu] in inode [%llu]\n", start, len, + ino); + + btrfs_commit_transaction(trans, root); + return ret; +} + +/* * Check file extent datasum/hole, update the size of the file extents, * check and update the last offset of the file extent. * @@ -4944,9 +5862,15 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_key *fkey, /* 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); + if (repair) + ret = punch_extent_hole(root, fkey->objectid, + *end, fkey->offset - *end); + if (!repair || ret) { + err |= FILE_EXTENT_ERROR; + error( + "root %llu EXTENT_DATA[%llu %llu] interrupt, should start at %llu", + root->objectid, fkey->objectid, fkey->offset, *end); + } } *end += extent_num_bytes; @@ -5018,6 +5942,194 @@ out: } /* + * Set directory inode isize to @isize. + * + * Returns 0 on success. + * Returns != 0 on error. + */ +static int repair_dir_isize_lowmem(struct btrfs_root *root, + struct btrfs_path *path, + u64 ino, u64 isize) +{ + struct btrfs_trans_handle *trans; + struct btrfs_inode_item *ii; + struct btrfs_key key; + struct btrfs_key research_key; + int ret; + int err = 0; + + btrfs_item_key_to_cpu(path->nodes[0], &research_key, path->slots[0]); + + key.objectid = ino; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + err |= ret; + goto out; + } + + btrfs_release_path(path); + ret = btrfs_search_slot(trans, root, &key, path, 0, 1); + if (ret > 0) + ret = -ENOENT; + if (ret) { + err |= ret; + goto fail; + } + + ii = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_inode_item); + btrfs_set_inode_size(path->nodes[0], ii, isize); + btrfs_mark_buffer_dirty(path->nodes[0]); +fail: + btrfs_commit_transaction(trans, root); +out: + if (ret) + error("failed to set isize in inode %llu root %llu", + ino, root->root_key.objectid); + else + printf("Set isize in inode %llu root %llu to %llu\n", + ino, root->root_key.objectid, isize); + + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, &research_key, path, 0, 0); + err |= ret; + + return err; +} + +/* + * Wrapper function for btrfs_add_orphan_item(). + * + * Returns 0 on success. + * Returns != 0 on error. + */ +static int repair_inode_orphan_item_lowmem(struct btrfs_root *root, + struct btrfs_path *path, u64 ino) +{ + struct btrfs_trans_handle *trans; + struct btrfs_key research_key; + int ret; + int err = 0; + + btrfs_item_key_to_cpu(path->nodes[0], &research_key, path->slots[0]); + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + err |= ret; + goto out; + } + + btrfs_release_path(path); + ret = btrfs_add_orphan_item(trans, root, path, ino); + err |= ret; + btrfs_commit_transaction(trans, root); +out: + if (ret) + error("failed to add inode %llu as orphan item root %llu", + ino, root->root_key.objectid); + else + printf("Added inode %llu as orphan item root %llu\n", + ino, root->root_key.objectid); + + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, &research_key, path, 0, 0); + err |= ret; + + return err; +} + +/* Set inode_item nlink to @ref_count. + * If @ref_count == 0, move it to "lost+found" and increase @ref_count. + * + * Returns 0 on success + */ +static int repair_inode_nlinks_lowmem(struct btrfs_root *root, + struct btrfs_path *path, u64 ino, + const char *name, u32 namelen, + u64 ref_count, u8 filetype, u64 *nlink) +{ + struct btrfs_trans_handle *trans; + struct btrfs_inode_item *ii; + struct btrfs_key key; + struct btrfs_key old_key; + char namebuf[BTRFS_NAME_LEN] = {0}; + int name_len; + int ret; + int ret2; + + /* save the key */ + btrfs_item_key_to_cpu(path->nodes[0], &old_key, path->slots[0]); + + if (name && namelen) { + ASSERT(namelen <= BTRFS_NAME_LEN); + memcpy(namebuf, name, namelen); + name_len = namelen; + } else { + sprintf(namebuf, "%llu", ino); + name_len = count_digits(ino); + printf("Can't find file name for inode %llu, use %s instead\n", + ino, namebuf); + } + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + goto out; + } + + btrfs_release_path(path); + /* if refs is 0, put it into lostfound */ + if (ref_count == 0) { + ret = link_inode_to_lostfound(trans, root, path, ino, namebuf, + name_len, filetype, &ref_count); + if (ret) + goto fail; + } + + /* reset inode_item's nlink to ref_count */ + key.objectid = ino; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; + + btrfs_release_path(path); + ret = btrfs_search_slot(trans, root, &key, path, 0, 1); + if (ret > 0) + ret = -ENOENT; + if (ret) + goto fail; + + ii = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_inode_item); + btrfs_set_inode_nlink(path->nodes[0], ii, ref_count); + btrfs_mark_buffer_dirty(path->nodes[0]); + + if (nlink) + *nlink = ref_count; +fail: + btrfs_commit_transaction(trans, root); +out: + if (ret) + error( + "fail to repair nlink of inode %llu root %llu name %s filetype %u", + root->objectid, ino, namebuf, filetype); + else + printf("Fixed nlink of inode %llu root %llu name %s filetype %u\n", + root->objectid, ino, namebuf, filetype); + + /* research */ + btrfs_release_path(path); + ret2 = btrfs_search_slot(NULL, root, &old_key, path, 0, 0); + if (ret2 < 0) + return ret |= ret2; + return ret; +} + +/* * Check INODE_ITEM and related ITEMs (the same inode number) * 1. check link count * 2. check inode ref/extref @@ -5034,6 +6146,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, struct extent_buffer *node; struct btrfs_inode_item *ii; struct btrfs_key key; + struct btrfs_key last_key; u64 inode_id; u32 mode; u64 nlink; @@ -5048,6 +6161,8 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, int slot; int ret; int err = 0; + char namebuf[BTRFS_NAME_LEN] = {0}; + u32 name_len = 0; node = path->nodes[0]; slot = path->slots[0]; @@ -5071,6 +6186,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, nodatasum = btrfs_inode_flags(node, ii) & BTRFS_INODE_NODATASUM; while (1) { + btrfs_item_key_to_cpu(path->nodes[0], &last_key, path->slots[0]); ret = btrfs_next_item(root, path); if (ret < 0) { /* out will fill 'err' rusing current statistics */ @@ -5088,8 +6204,8 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, switch (key.type) { case BTRFS_INODE_REF_KEY: - ret = check_inode_ref(root, &key, node, slot, &refs, - mode); + ret = check_inode_ref(root, &key, path, namebuf, + &name_len, &refs, mode); err |= ret; break; case BTRFS_INODE_EXTREF_KEY: @@ -5109,8 +6225,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, imode_to_type(mode), key.objectid, key.offset); } - ret = check_dir_item(root, &key, node, slot, &size, - ext_ref); + ret = check_dir_item(root, &key, path, &size, ext_ref); err |= ret; break; case BTRFS_EXTENT_DATA_KEY: @@ -5133,8 +6248,26 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path, } out: + if (err & LAST_ITEM) { + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, &last_key, path, 0, 0); + if (ret) + return err; + } + /* verify INODE_ITEM nlink/isize/nbytes */ if (dir) { + if (repair && (err & DIR_COUNT_AGAIN)) { + err &= ~DIR_COUNT_AGAIN; + count_dir_isize(root, inode_id, &size); + } + + if ((nlink != 1 || refs != 1) && repair) { + ret = repair_inode_nlinks_lowmem(root, path, inode_id, + namebuf, name_len, refs, imode_to_type(mode), + &nlink); + } + if (nlink != 1) { err |= LINK_COUNT_ERROR; error("root %llu DIR INODE[%llu] shouldn't have more than one link(%llu)", @@ -5152,23 +6285,49 @@ out: } if (isize != size) { - err |= ISIZE_ERROR; - error("root %llu DIR INODE [%llu] size(%llu) not equal to %llu", - root->objectid, inode_id, isize, size); + if (repair) + ret = repair_dir_isize_lowmem(root, path, + inode_id, size); + if (!repair || ret) { + 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); + if (repair) + ret = repair_inode_nlinks_lowmem(root, path, + inode_id, namebuf, name_len, refs, + imode_to_type(mode), &nlink); + if (!repair || ret) { + 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 (repair) + ret = repair_inode_orphan_item_lowmem(root, + path, inode_id); + if (!repair || ret) { + err |= ORPHAN_ITEM; + error("root %llu INODE[%llu] is orphan item", + root->objectid, inode_id); + } } 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 (repair) + ret = punch_extent_hole(root, inode_id, + extent_end, isize - extent_end); + if (!repair || ret) { + 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) { @@ -5185,13 +6344,89 @@ out: } } + if (err & LAST_ITEM) + btrfs_next_item(root, path); + return err; +} + +/* + * Insert the missing inode item and inode ref. + * + * Normal INODE_ITEM_MISSING and INODE_REF_MISSING are handled in backref * dir. + * Root dir should be handled specially because root dir is the root of fs. + * + * returns err (>0 or 0) after repair + */ +static int repair_fs_first_inode(struct btrfs_root *root, int err) +{ + struct btrfs_trans_handle *trans; + struct btrfs_key key; + struct btrfs_path path; + int filetype = BTRFS_FT_DIR; + int ret = 0; + + btrfs_init_path(&path); + + if (err & INODE_REF_MISSING) { + key.objectid = BTRFS_FIRST_FREE_OBJECTID; + key.type = BTRFS_INODE_REF_KEY; + key.offset = BTRFS_FIRST_FREE_OBJECTID; + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + goto out; + } + + btrfs_release_path(&path); + ret = btrfs_search_slot(trans, root, &key, &path, 1, 1); + if (ret) + goto trans_fail; + + ret = btrfs_insert_inode_ref(trans, root, "..", 2, + BTRFS_FIRST_FREE_OBJECTID, + BTRFS_FIRST_FREE_OBJECTID, 0); + if (ret) + goto trans_fail; + + printf("Add INODE_REF[%llu %llu] name %s\n", + BTRFS_FIRST_FREE_OBJECTID, BTRFS_FIRST_FREE_OBJECTID, + ".."); + err &= ~INODE_REF_MISSING; +trans_fail: + if (ret) + error("fail to insert first inode's ref"); + btrfs_commit_transaction(trans, root); + } + + if (err & INODE_ITEM_MISSING) { + ret = repair_inode_item_missing(root, + BTRFS_FIRST_FREE_OBJECTID, filetype); + if (ret) + goto out; + err &= ~INODE_ITEM_MISSING; + } +out: + if (ret) + error("fail to repair first inode"); + btrfs_release_path(&path); return err; } +/* + * check first root dir's inode_item and inode_ref + * + * returns 0 means no error + * returns >0 means error + * returns <0 means fatal error + */ static int check_fs_first_inode(struct btrfs_root *root, unsigned int ext_ref) { struct btrfs_path path; struct btrfs_key key; + struct btrfs_inode_item *ii; + u64 index; + u32 mode; int err = 0; int ret; @@ -5202,28 +6437,48 @@ static int check_fs_first_inode(struct btrfs_root *root, unsigned int ext_ref) /* 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) + BTRFS_FIRST_FREE_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); + } else { + 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) != BTRFS_FT_DIR) + err |= INODE_ITEM_MISMATCH; } - err |= check_inode_item(root, &path, ext_ref); - err &= ~LAST_ITEM; - if (err && !ret) - ret = -EIO; + /* lookup first inode ref */ + key.offset = BTRFS_FIRST_FREE_OBJECTID; + key.type = BTRFS_INODE_REF_KEY; + /* special index value */ + index = 0; + + ret = find_inode_ref(root, &key, "..", strlen(".."), &index, ext_ref); + if (ret < 0) + goto out; + err |= ret; + out: btrfs_release_path(&path); - return ret; + + if (err && repair) + err = repair_fs_first_inode(root, err); + + if (err & (INODE_ITEM_MISSING | INODE_ITEM_MISMATCH)) + error("root dir INODE_ITEM is %s", + err & INODE_ITEM_MISMATCH ? "mismatch" : "missing"); + if (err & INODE_REF_MISSING) + error("root dir INODE_REF is missing"); + + return ret < 0 ? ret : err; } static struct tree_backref *find_tree_backref(struct extent_record *rec, @@ -5286,15 +6541,21 @@ static struct data_backref *find_data_backref(struct extent_record *rec, return back; } /* - * Iterate all item on the tree and call check_inode_item() to check. + * This function calls walk_down_tree_v2 and walk_up_tree_v2 to check tree + * blocks and integrity of fs tree items. * - * @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. + * @root: the root of the tree to be checked. + * @ext_ref feature EXTENDED_IREF is enable or not. + * @account if NOT 0 means check the tree (including tree)'s treeblocks. + * otherwise means check fs tree(s) items relationship and + * @root MUST be a fs tree root. + * Returns 0 represents OK. + * Returns not 0 represents error. */ -static int check_fs_root_v2(struct btrfs_root *root, unsigned int ext_ref) +static int check_btrfs_root(struct btrfs_trans_handle *trans, + struct btrfs_root *root, unsigned int ext_ref, + int check_all) + { struct btrfs_path path; struct node_refs nrefs; @@ -5303,17 +6564,20 @@ static int check_fs_root_v2(struct btrfs_root *root, unsigned int ext_ref) 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)); + if (!check_all) { + /* + * 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 skip it forever. + */ + ret = check_fs_first_inode(root, ext_ref); + if (ret < 0) + return ret; + } + + level = btrfs_header_level(root->node); btrfs_init_path(&path); @@ -5335,7 +6599,9 @@ static int check_fs_root_v2(struct btrfs_root *root, unsigned int ext_ref) } while (1) { - ret = walk_down_tree_v2(root, &path, &level, &nrefs, ext_ref); + ret = walk_down_tree_v2(trans, root, &path, &level, &nrefs, + ext_ref, check_all); + err |= !!ret; /* if ret is negative, walk shall stop */ @@ -5357,6 +6623,23 @@ out: return ret; } +static int pin_metadata_blocks(struct btrfs_fs_info *fs_info); + +/* + * Iterate all items in 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) +{ + reset_cached_block_groups(root->fs_info); + return check_btrfs_root(NULL, root, ext_ref, 0); +} + /* * Find the relative ref for root_ref and root_backref. * @@ -9547,6 +10830,7 @@ static int check_extent_refs(struct btrfs_root *root, struct cache_extent *cache; int ret = 0; int had_dups = 0; + int err = 0; if (repair) { /* @@ -9690,6 +10974,7 @@ static int check_extent_refs(struct btrfs_root *root, cur_err = 1; } + err = cur_err; remove_cache_extent(extent_cache, cache); free_all_extent_backrefs(rec); if (!init_extent_tree && repair && (!cur_err || fix)) @@ -9722,7 +11007,10 @@ repair_abort: } return ret; } - return 0; + + if (err) + err = -EIO; + return err; } u64 calc_stripe_length(u64 type, u64 length, int num_stripes) @@ -9951,6 +11239,66 @@ static int check_device_used(struct device_record *dev_rec, } } +/* + * Extra (optional) check for dev_item size to report possbile problem on a new + * kernel. + */ +static void check_dev_size_alignment(u64 devid, u64 total_bytes, u32 sectorsize) +{ + if (!IS_ALIGNED(total_bytes, sectorsize)) { + warning( +"unaligned total_bytes detected for devid %llu, have %llu should be aligned to %u", + devid, total_bytes, sectorsize); + warning( +"this is OK for older kernel, but may cause kernel warning for newer kernels"); + warning("this can be fixed by 'btrfs rescue fix-device-size'"); + } +} + +/* + * Unlike device size alignment check above, some super total_bytes check + * failure can lead to mount failure for newer kernel. + * + * So this function will return the error for a fatal super total_bytes problem. + */ +static bool is_super_size_valid(struct btrfs_fs_info *fs_info) +{ + struct btrfs_device *dev; + struct list_head *dev_list = &fs_info->fs_devices->devices; + u64 total_bytes = 0; + u64 super_bytes = btrfs_super_total_bytes(fs_info->super_copy); + + list_for_each_entry(dev, dev_list, dev_list) + total_bytes += dev->total_bytes; + + /* Important check, which can cause unmountable fs */ + if (super_bytes < total_bytes) { + error("super total bytes %llu smaller than real device(s) size %llu", + super_bytes, total_bytes); + error("mounting this fs may fail for newer kernels"); + error("this can be fixed by 'btrfs rescue fix-device-size'"); + return false; + } + + /* + * Optional check, just to make everything aligned and match with each + * other. + * + * For a btrfs-image restored fs, we don't need to check it anyway. + */ + if (btrfs_super_flags(fs_info->super_copy) & + (BTRFS_SUPER_FLAG_METADUMP | BTRFS_SUPER_FLAG_METADUMP_V2)) + return true; + if (!IS_ALIGNED(super_bytes, fs_info->sectorsize) || + !IS_ALIGNED(total_bytes, fs_info->sectorsize) || + super_bytes != total_bytes) { + warning("minor unaligned/mismatch device size detected"); + warning( + "recommended to use 'btrfs rescue fix-device-size' to fix it"); + } + return true; +} + /* check btrfs_dev_item -> btrfs_dev_extent */ static int check_devices(struct rb_root *dev_cache, struct device_extent_tree *dev_extent_cache) @@ -9968,6 +11316,8 @@ static int check_devices(struct rb_root *dev_cache, if (err) ret = err; + check_dev_size_alignment(dev_rec->devid, dev_rec->total_byte, + global_info->sectorsize); dev_node = rb_next(dev_node); } list_for_each_entry(dext_rec, &dev_extent_cache->no_device_orphans, @@ -10293,6 +11643,29 @@ loop: goto again; } +static int check_extent_inline_ref(struct extent_buffer *eb, + struct btrfs_key *key, struct btrfs_extent_inline_ref *iref) +{ + int ret; + u8 type = btrfs_extent_inline_ref_type(eb, iref); + + switch (type) { + case BTRFS_TREE_BLOCK_REF_KEY: + case BTRFS_EXTENT_DATA_REF_KEY: + case BTRFS_SHARED_BLOCK_REF_KEY: + case BTRFS_SHARED_DATA_REF_KEY: + ret = 0; + break; + default: + error("extent[%llu %u %llu] has unknown ref type: %d", + key->objectid, key->type, key->offset, type); + ret = UNKNOWN_TYPE; + break; + } + + return ret; +} + /* * Check backrefs of a tree block given by @bytenr or @eb. * @@ -10307,7 +11680,7 @@ loop: */ static int check_tree_block_ref(struct btrfs_root *root, struct extent_buffer *eb, u64 bytenr, - int level, u64 owner) + int level, u64 owner, struct node_refs *nrefs) { struct btrfs_key key; struct btrfs_root *extent_root = root->fs_info->extent_root; @@ -10319,6 +11692,7 @@ static int check_tree_block_ref(struct btrfs_root *root, unsigned long ptr; int slot; int skinny_level; + int root_level = btrfs_header_level(root->node); int type; u32 nodesize = root->fs_info->nodesize; u32 item_size; @@ -10327,11 +11701,12 @@ static int check_tree_block_ref(struct btrfs_root *root, int found_ref = 0; int err = 0; int ret; + int strict = 1; + int parent = 0; 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, SKINNY_METADATA)) @@ -10369,10 +11744,19 @@ static int check_tree_block_ref(struct btrfs_root *root, iref = (struct btrfs_extent_inline_ref *)(info + 1); } + if (eb) { u64 header_gen; u64 extent_gen; + /* + * Due to the feature of shared tree blocks, if the upper node + * is a fs root or shared node, the extent of checked node may + * not be updated until the next CoW. + */ + if (nrefs) + strict = should_check_extent_strictly(root, nrefs, + level); if (!(btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)) { error( @@ -10410,25 +11794,38 @@ static int check_tree_block_ref(struct btrfs_root *root, item_size = btrfs_item_size_nr(leaf, slot); ptr = (unsigned long)iref; end = (unsigned long)ei + item_size; + while (ptr < end) { iref = (struct btrfs_extent_inline_ref *)ptr; type = btrfs_extent_inline_ref_type(leaf, iref); offset = btrfs_extent_inline_ref_offset(leaf, iref); - if (type == BTRFS_TREE_BLOCK_REF_KEY && - (offset == root->objectid || offset == owner)) { - found_ref = 1; + ret = check_extent_inline_ref(leaf, &key, iref); + if (ret) { + err |= ret; + break; + } + if (type == BTRFS_TREE_BLOCK_REF_KEY) { + if (offset == root->objectid) + found_ref = 1; + if (!strict && owner == offset) + 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) + 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); + } else { + /* + * Check if the backref points to valid + * referencer + */ + found_ref = !check_tree_block_ref( root, NULL, + offset, level + 1, owner, + NULL); + } } if (found_ref) @@ -10454,9 +11851,129 @@ static int check_tree_block_ref(struct btrfs_root *root, err |= BACKREF_MISSING; out: btrfs_release_path(&path); + if (nrefs && strict && + level < root_level && nrefs->full_backref[level + 1]) + parent = nrefs->bytenr[level + 1]; if (eb && (err & BACKREF_MISSING)) - error("extent[%llu %u] backref lost (owner: %llu, level: %u)", - bytenr, nodesize, owner, level); + error( + "extent[%llu %u] backref lost (owner: %llu, level: %u) %s %llu", + bytenr, nodesize, owner, level, + parent ? "parent" : "root", + parent ? parent : root->objectid); + return err; +} + +/* + * If @err contains BACKREF_MISSING then add extent of the + * file_extent_data_item. + * + * Returns error bits after reapir. + */ +static int repair_extent_data_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *pathp, + struct node_refs *nrefs, + int err) +{ + struct btrfs_file_extent_item *fi; + struct btrfs_key fi_key; + struct btrfs_key key; + struct btrfs_extent_item *ei; + struct btrfs_path path; + struct btrfs_root *extent_root = root->fs_info->extent_root; + struct extent_buffer *eb; + u64 size; + u64 disk_bytenr; + u64 num_bytes; + u64 parent; + u64 offset; + u64 extent_offset; + u64 file_offset; + int generation; + int slot; + int ret = 0; + + eb = pathp->nodes[0]; + slot = pathp->slots[0]; + btrfs_item_key_to_cpu(eb, &fi_key, slot); + fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item); + + if (btrfs_file_extent_type(eb, fi) == BTRFS_FILE_EXTENT_INLINE || + btrfs_file_extent_disk_bytenr(eb, fi) == 0) + return err; + + file_offset = fi_key.offset; + generation = btrfs_file_extent_generation(eb, fi); + disk_bytenr = btrfs_file_extent_disk_bytenr(eb, fi); + num_bytes = btrfs_file_extent_disk_num_bytes(eb, fi); + extent_offset = btrfs_file_extent_offset(eb, fi); + offset = file_offset - extent_offset; + + /* now repair only adds backref */ + if ((err & BACKREF_MISSING) == 0) + return err; + + /* search extent item */ + key.objectid = disk_bytenr; + key.type = BTRFS_EXTENT_ITEM_KEY; + key.offset = num_bytes; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0); + if (ret < 0) { + ret = -EIO; + goto out; + } + + /* insert an extent item */ + if (ret > 0) { + key.objectid = disk_bytenr; + key.type = BTRFS_EXTENT_ITEM_KEY; + key.offset = num_bytes; + size = sizeof(*ei); + + btrfs_release_path(&path); + ret = btrfs_insert_empty_item(trans, extent_root, &path, &key, + size); + if (ret) + goto out; + eb = path.nodes[0]; + ei = btrfs_item_ptr(eb, path.slots[0], struct btrfs_extent_item); + + btrfs_set_extent_refs(eb, ei, 0); + btrfs_set_extent_generation(eb, ei, generation); + btrfs_set_extent_flags(eb, ei, BTRFS_EXTENT_FLAG_DATA); + + btrfs_mark_buffer_dirty(eb); + ret = btrfs_update_block_group(trans, extent_root, disk_bytenr, + num_bytes, 1, 0); + btrfs_release_path(&path); + } + + if (nrefs->full_backref[0]) + parent = btrfs_header_bytenr(eb); + else + parent = 0; + + ret = btrfs_inc_extent_ref(trans, root, disk_bytenr, num_bytes, parent, + root->objectid, + parent ? BTRFS_FIRST_FREE_OBJECTID : fi_key.objectid, + offset); + if (ret) { + error( + "failed to increase extent data backref[%llu %llu] root %llu", + disk_bytenr, num_bytes, root->objectid); + goto out; + } else { + printf("Add one extent data backref [%llu %llu]\n", + disk_bytenr, num_bytes); + } + + err &= ~BACKREF_MISSING; +out: + if (ret) + error("can't repair root %llu extent data item[%llu %llu]", + root->objectid, disk_bytenr, num_bytes); return err; } @@ -10467,9 +11984,11 @@ out: * Return 0 for no error found */ static int check_extent_data_item(struct btrfs_root *root, - struct extent_buffer *eb, int slot) + struct btrfs_path *pathp, + struct node_refs *nrefs, int account_bytes) { struct btrfs_file_extent_item *fi; + struct extent_buffer *eb = pathp->nodes[0]; struct btrfs_path path; struct btrfs_root *extent_root = root->fs_info->extent_root; struct btrfs_key fi_key; @@ -10489,8 +12008,10 @@ static int check_extent_data_item(struct btrfs_root *root, int type; u64 ref_root; int found_dbackref = 0; + int slot = pathp->slots[0]; int err = 0; int ret; + int strict; btrfs_item_key_to_cpu(eb, &fi_key, slot); fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item); @@ -10511,7 +12032,7 @@ static int check_extent_data_item(struct btrfs_root *root, fi_key.objectid, fi_key.offset, disk_num_bytes, root->fs_info->sectorsize); err |= BYTES_UNALIGNED; - } else { + } else if (account_bytes) { data_bytes_allocated += disk_num_bytes; } if (!IS_ALIGNED(extent_num_bytes, root->fs_info->sectorsize)) { @@ -10520,7 +12041,7 @@ static int check_extent_data_item(struct btrfs_root *root, fi_key.objectid, fi_key.offset, extent_num_bytes, root->fs_info->sectorsize); err |= BYTES_UNALIGNED; - } else { + } else if (account_bytes) { data_bytes_referenced += extent_num_bytes; } owner = btrfs_header_owner(eb); @@ -10554,19 +12075,28 @@ static int check_extent_data_item(struct btrfs_root *root, iref = (struct btrfs_extent_inline_ref *)(ei + 1); ptr = (unsigned long)iref; end = (unsigned long)ei + item_size; + strict = should_check_extent_strictly(root, nrefs, -1); + while (ptr < end) { iref = (struct btrfs_extent_inline_ref *)ptr; type = btrfs_extent_inline_ref_type(leaf, iref); dref = (struct btrfs_extent_data_ref *)(&iref->offset); + ret = check_extent_inline_ref(leaf, &dbref_key, iref); + if (ret) { + err |= ret; + break; + } if (type == BTRFS_EXTENT_DATA_REF_KEY) { ref_root = btrfs_extent_data_ref_root(leaf, dref); - if (ref_root == owner || ref_root == root->objectid) + if (ref_root == root->objectid) + found_dbackref = 1; + else if (!strict && owner == ref_root) found_dbackref = 1; } else if (type == BTRFS_SHARED_DATA_REF_KEY) { found_dbackref = !check_tree_block_ref(root, NULL, btrfs_extent_inline_ref_offset(leaf, iref), - 0, owner); + 0, owner, NULL); } if (found_dbackref) @@ -10938,7 +12468,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)) + if (slot >= btrfs_header_nritems(leaf) || + btrfs_header_owner(leaf) != root_id) goto next; btrfs_item_key_to_cpu(leaf, &key, slot); if (key.objectid != objectid || key.type != BTRFS_EXTENT_DATA_KEY) @@ -11015,24 +12546,77 @@ out: } /* + * Only delete backref if REFERENCER_MISSING now + * + * Returns <0 the extent was deleted + * Returns >0 the backref was deleted but extent still exists, returned value + * means error after repair + * Returns 0 nothing happened + */ +static int repair_extent_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_path *path, + u64 bytenr, u64 num_bytes, u64 parent, u64 root_objectid, + u64 owner, u64 offset, int err) +{ + struct btrfs_key old_key; + int freed = 0; + int ret; + + btrfs_item_key_to_cpu(path->nodes[0], &old_key, path->slots[0]); + + if (err & (REFERENCER_MISSING | REFERENCER_MISMATCH)) { + /* delete the backref */ + ret = btrfs_free_extent(trans, root->fs_info->fs_root, bytenr, + num_bytes, parent, root_objectid, owner, offset); + if (!ret) { + freed = 1; + err &= ~REFERENCER_MISSING; + printf("Delete backref in extent [%llu %llu]\n", + bytenr, num_bytes); + } else { + error("fail to delete backref in extent [%llu %llu]", + bytenr, num_bytes); + } + } + + /* btrfs_free_extent may delete the extent */ + btrfs_release_path(path); + ret = btrfs_search_slot(NULL, root, &old_key, path, 0, 0); + + if (ret) + ret = -ENOENT; + else if (freed) + ret = err; + return ret; +} + +/* * This function will check a given extent item, including its backref and * itself (like crossing stripe boundary and type) * * Since we don't use extent_record anymore, introduce new error bit */ -static int check_extent_item(struct btrfs_fs_info *fs_info, - struct extent_buffer *eb, int slot) +static int check_extent_item(struct btrfs_trans_handle *trans, + struct btrfs_fs_info *fs_info, + struct btrfs_path *path) { struct btrfs_extent_item *ei; struct btrfs_extent_inline_ref *iref; struct btrfs_extent_data_ref *dref; + struct extent_buffer *eb = path->nodes[0]; unsigned long end; unsigned long ptr; + int slot = path->slots[0]; int type; u32 nodesize = btrfs_super_nodesize(fs_info->super_copy); u32 item_size = btrfs_item_size_nr(eb, slot); u64 flags; u64 offset; + u64 parent; + u64 num_bytes; + u64 root_objectid; + u64 owner; + u64 owner_offset; int metadata = 0; int level; struct btrfs_key key; @@ -11040,10 +12624,13 @@ static int check_extent_item(struct btrfs_fs_info *fs_info, int err = 0; btrfs_item_key_to_cpu(eb, &key, slot); - if (key.type == BTRFS_EXTENT_ITEM_KEY) + if (key.type == BTRFS_EXTENT_ITEM_KEY) { bytes_used += key.offset; - else + num_bytes = key.offset; + } else { bytes_used += nodesize; + num_bytes = nodesize; + } if (item_size < sizeof(*ei)) { /* @@ -11095,42 +12682,63 @@ next: goto out; } + parent = 0; + root_objectid = 0; + owner = 0; + owner_offset = 0; /* Now check every backref in this extent item */ iref = (struct btrfs_extent_inline_ref *)ptr; type = btrfs_extent_inline_ref_type(eb, iref); offset = btrfs_extent_inline_ref_offset(eb, iref); switch (type) { case BTRFS_TREE_BLOCK_REF_KEY: + root_objectid = offset; + owner = level; ret = check_tree_block_backref(fs_info, offset, key.objectid, level); err |= ret; break; case BTRFS_SHARED_BLOCK_REF_KEY: + parent = offset; ret = check_shared_block_backref(fs_info, offset, key.objectid, level); err |= ret; break; case BTRFS_EXTENT_DATA_REF_KEY: dref = (struct btrfs_extent_data_ref *)(&iref->offset); - ret = check_extent_data_backref(fs_info, - btrfs_extent_data_ref_root(eb, dref), - btrfs_extent_data_ref_objectid(eb, dref), - btrfs_extent_data_ref_offset(eb, dref), - key.objectid, key.offset, - btrfs_extent_data_ref_count(eb, dref)); + root_objectid = btrfs_extent_data_ref_root(eb, dref); + owner = btrfs_extent_data_ref_objectid(eb, dref); + owner_offset = btrfs_extent_data_ref_offset(eb, dref); + ret = check_extent_data_backref(fs_info, root_objectid, owner, + owner_offset, key.objectid, key.offset, + btrfs_extent_data_ref_count(eb, dref)); err |= ret; break; case BTRFS_SHARED_DATA_REF_KEY: + parent = offset; ret = check_shared_data_backref(fs_info, offset, key.objectid); err |= ret; break; default: error("extent[%llu %d %llu] has unknown ref type: %d", key.objectid, key.type, key.offset, type); - err |= UNKNOWN_TYPE; + ret = UNKNOWN_TYPE; + err |= ret; goto out; } + if (err && repair) { + ret = repair_extent_item(trans, fs_info->extent_root, path, + key.objectid, num_bytes, parent, root_objectid, + owner, owner_offset, ret); + if (ret < 0) + goto out; + if (ret) { + goto next; + err = ret; + } + } + ptr += btrfs_extent_inline_ref_size(type); goto next; @@ -11213,6 +12821,7 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, struct btrfs_path path; struct btrfs_key key; struct btrfs_dev_extent *ptr; + u64 total_bytes; u64 dev_id; u64 used; u64 total = 0; @@ -11221,6 +12830,7 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, dev_item = btrfs_item_ptr(eb, slot, struct btrfs_dev_item); dev_id = btrfs_device_id(eb, dev_item); used = btrfs_device_bytes_used(eb, dev_item); + total_bytes = btrfs_device_total_bytes(eb, dev_item); key.objectid = dev_id; key.type = BTRFS_DEV_EXTENT_KEY; @@ -11265,6 +12875,8 @@ next: BTRFS_DEV_EXTENT_KEY, dev_id); return ACCOUNTING_MISMATCH; } + check_dev_size_alignment(dev_id, total_bytes, fs_info->sectorsize); + return 0; } @@ -11392,8 +13004,51 @@ out: error( "block group[%llu %llu] used %llu but extent items used %llu", bg_key.objectid, bg_key.offset, used, total); - err |= ACCOUNTING_MISMATCH; + err |= BG_ACCOUNTING_ERROR; + } + return err; +} + +/* + * Add block group item to the extent tree if @err contains REFERENCER_MISSING. + * FIXME: We still need to repair error of dev_item. + * + * Returns error after repair. + */ +static int repair_chunk_item(struct btrfs_trans_handle *trans, + struct btrfs_root *chunk_root, + struct btrfs_path *path, int err) +{ + struct btrfs_chunk *chunk; + struct btrfs_key chunk_key; + struct extent_buffer *eb = path->nodes[0]; + u64 length; + int slot = path->slots[0]; + u64 type; + int ret = 0; + + btrfs_item_key_to_cpu(eb, &chunk_key, slot); + if (chunk_key.type != BTRFS_CHUNK_ITEM_KEY) + return err; + chunk = btrfs_item_ptr(eb, slot, struct btrfs_chunk); + type = btrfs_chunk_type(path->nodes[0], chunk); + length = btrfs_chunk_length(eb, chunk); + + if (err & REFERENCER_MISSING) { + ret = btrfs_make_block_group(trans, chunk_root->fs_info, 0, + type, chunk_key.objectid, chunk_key.offset, length); + if (ret) { + error("fail to add block group item[%llu %llu]", + chunk_key.offset, length); + goto out; + } else { + err &= ~REFERENCER_MISSING; + printf("Added block group item[%llu %llu]\n", + chunk_key.offset, length); + } } + +out: return err; } @@ -11502,30 +13157,83 @@ out: return err; } +static int delete_extent_tree_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *path) +{ + struct btrfs_key key; + int ret = 0; + + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + btrfs_release_path(path); + ret = btrfs_search_slot(trans, root, &key, path, -1, 1); + if (ret) { + ret = -ENOENT; + goto out; + } + + ret = btrfs_del_item(trans, root, path); + if (ret) + goto out; + + if (path->slots[0] == 0) + btrfs_prev_leaf(root, path); + else + path->slots[0]--; +out: + if (ret) + error("failed to delete root %llu item[%llu, %u, %llu]", + root->objectid, key.objectid, key.type, key.offset); + else + printf("Deleted root %llu item[%llu, %u, %llu]\n", + root->objectid, key.objectid, key.type, key.offset); + return ret; +} + /* * Main entry function to check known items and update related accounting info */ -static int check_leaf_items(struct btrfs_root *root, struct extent_buffer *eb) +static int check_leaf_items(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_path *path, + struct node_refs *nrefs, int account_bytes) { struct btrfs_fs_info *fs_info = root->fs_info; struct btrfs_key key; - int slot = 0; + struct extent_buffer *eb; + int slot; int type; struct btrfs_extent_data_ref *dref; - int ret; + int ret = 0; int err = 0; -next: +again: + eb = path->nodes[0]; + slot = path->slots[0]; + if (slot >= btrfs_header_nritems(eb)) { + if (slot == 0) { + error("empty leaf [%llu %u] root %llu", eb->start, + root->fs_info->nodesize, root->objectid); + err |= EIO; + } + goto out; + } + btrfs_item_key_to_cpu(eb, &key, slot); type = key.type; switch (type) { case BTRFS_EXTENT_DATA_KEY: - ret = check_extent_data_item(root, eb, slot); + ret = check_extent_data_item(root, path, nrefs, account_bytes); + if (repair && ret) + ret = repair_extent_data_item(trans, root, path, nrefs, + ret); err |= ret; break; case BTRFS_BLOCK_GROUP_ITEM_KEY: ret = check_block_group_item(fs_info, eb, slot); + if (repair && + ret & REFERENCER_MISSING) + ret = delete_extent_tree_item(trans, root, path); err |= ret; break; case BTRFS_DEV_ITEM_KEY: @@ -11534,6 +13242,8 @@ next: break; case BTRFS_CHUNK_ITEM_KEY: ret = check_chunk_item(fs_info, eb, slot); + if (repair && ret) + ret = repair_chunk_item(trans, root, path, ret); err |= ret; break; case BTRFS_DEV_EXTENT_KEY: @@ -11542,15 +13252,19 @@ next: break; case BTRFS_EXTENT_ITEM_KEY: case BTRFS_METADATA_ITEM_KEY: - ret = check_extent_item(fs_info, eb, slot); + ret = check_extent_item(trans, fs_info, path); err |= ret; break; case BTRFS_EXTENT_CSUM_KEY: total_csum_bytes += btrfs_item_size_nr(eb, slot); + err |= ret; break; case BTRFS_TREE_BLOCK_REF_KEY: ret = check_tree_block_backref(fs_info, key.offset, key.objectid, -1); + if (repair && + ret & (REFERENCER_MISMATCH | REFERENCER_MISSING)) + ret = delete_extent_tree_item(trans, root, path); err |= ret; break; case BTRFS_EXTENT_DATA_REF_KEY: @@ -11561,203 +13275,47 @@ next: btrfs_extent_data_ref_offset(eb, dref), key.objectid, 0, btrfs_extent_data_ref_count(eb, dref)); + if (repair && + ret & (REFERENCER_MISMATCH | REFERENCER_MISSING)) + ret = delete_extent_tree_item(trans, root, path); err |= ret; break; case BTRFS_SHARED_BLOCK_REF_KEY: ret = check_shared_block_backref(fs_info, key.offset, key.objectid, -1); + if (repair && + ret & (REFERENCER_MISMATCH | REFERENCER_MISSING)) + ret = delete_extent_tree_item(trans, root, path); err |= ret; break; case BTRFS_SHARED_DATA_REF_KEY: ret = check_shared_data_backref(fs_info, key.offset, key.objectid); + if (repair && + ret & (REFERENCER_MISMATCH | REFERENCER_MISSING)) + ret = delete_extent_tree_item(trans, root, path); err |= ret; break; default: break; } - if (++slot < btrfs_header_nritems(eb)) - goto next; - + ++path->slots[0]; + goto again; +out: return err; } -/* - * Helper function for later fs/subvol tree check. To determine if a tree - * block should be checked. - * This function will ensure only the direct referencer with lowest rootid to - * check a fs/subvolume tree block. - * - * Backref check at extent tree would detect errors like missing subvolume - * tree, so we can do aggressive check to reduce duplicated checks. - */ -static int should_check(struct btrfs_root *root, struct extent_buffer *eb) -{ - struct btrfs_root *extent_root = root->fs_info->extent_root; - struct btrfs_key key; - struct btrfs_path path; - struct extent_buffer *leaf; - int slot; - struct btrfs_extent_item *ei; - unsigned long ptr; - unsigned long end; - int type; - u32 item_size; - u64 offset; - struct btrfs_extent_inline_ref *iref; - int ret; - - btrfs_init_path(&path); - key.objectid = btrfs_header_bytenr(eb); - key.type = BTRFS_METADATA_ITEM_KEY; - key.offset = (u64)-1; - - /* - * Any failure in backref resolving means we can't determine - * whom the tree block belongs to. - * So in that case, we need to check that tree block - */ - ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0); - if (ret < 0) - goto need_check; - - ret = btrfs_previous_extent_item(extent_root, &path, - btrfs_header_bytenr(eb)); - if (ret) - goto need_check; - - leaf = path.nodes[0]; - slot = path.slots[0]; - btrfs_item_key_to_cpu(leaf, &key, slot); - ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item); - - if (key.type == BTRFS_METADATA_ITEM_KEY) { - iref = (struct btrfs_extent_inline_ref *)(ei + 1); - } else { - struct btrfs_tree_block_info *info; - - info = (struct btrfs_tree_block_info *)(ei + 1); - iref = (struct btrfs_extent_inline_ref *)(info + 1); - } - - item_size = btrfs_item_size_nr(leaf, slot); - ptr = (unsigned long)iref; - end = (unsigned long)ei + item_size; - while (ptr < end) { - iref = (struct btrfs_extent_inline_ref *)ptr; - type = btrfs_extent_inline_ref_type(leaf, iref); - offset = btrfs_extent_inline_ref_offset(leaf, iref); - - /* - * We only check the tree block if current root is - * the lowest referencer of it. - */ - if (type == BTRFS_TREE_BLOCK_REF_KEY && - offset < root->objectid) { - btrfs_release_path(&path); - return 0; - } - - ptr += btrfs_extent_inline_ref_size(type); - } - /* - * Normally we should also check keyed tree block ref, but that may be - * very time consuming. Inlined ref should already make us skip a lot - * of refs now. So skip search keyed tree block ref. - */ - -need_check: - btrfs_release_path(&path); - return 1; -} - -/* - * Traversal function for tree block. We will do: - * 1) Skip shared fs/subvolume tree blocks - * 2) Update related bytes accounting - * 3) Pre-order traversal - */ -static int traverse_tree_block(struct btrfs_root *root, - struct extent_buffer *node) -{ - struct extent_buffer *eb; - struct btrfs_key key; - struct btrfs_key drop_key; - int level; - u64 nr; - int i; - int err = 0; - int ret; - - /* - * Skip shared fs/subvolume tree block, in that case they will - * be checked by referencer with lowest rootid - */ - if (is_fstree(root->objectid) && !should_check(root, node)) - return 0; - - /* Update bytes accounting */ - total_btree_bytes += node->len; - if (fs_root_objectid(btrfs_header_owner(node))) - total_fs_tree_bytes += node->len; - if (btrfs_header_owner(node) == BTRFS_EXTENT_TREE_OBJECTID) - total_extent_tree_bytes += node->len; - - /* pre-order tranversal, check itself first */ - level = btrfs_header_level(node); - ret = check_tree_block_ref(root, node, btrfs_header_bytenr(node), - btrfs_header_level(node), - btrfs_header_owner(node)); - err |= ret; - if (err) - error( - "check %s failed root %llu bytenr %llu level %d, force continue check", - level ? "node":"leaf", root->objectid, - btrfs_header_bytenr(node), btrfs_header_level(node)); - - if (!level) { - btree_space_waste += btrfs_leaf_free_space(root, node); - ret = check_leaf_items(root, node); - err |= ret; - return err; - } - - nr = btrfs_header_nritems(node); - btrfs_disk_key_to_cpu(&drop_key, &root->root_item.drop_progress); - btree_space_waste += (BTRFS_NODEPTRS_PER_BLOCK(root) - nr) * - sizeof(struct btrfs_key_ptr); - - /* Then check all its children */ - for (i = 0; i < nr; i++) { - u64 blocknr = btrfs_node_blockptr(node, i); - - btrfs_node_key_to_cpu(node, &key, i); - if (level == root->root_item.drop_level && - is_dropped_key(&key, &drop_key)) - continue; - - /* - * 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->fs_info, blocknr, 0); - if (extent_buffer_uptodate(eb)) { - ret = traverse_tree_block(root, eb); - err |= ret; - } - free_extent_buffer(eb); - } - - return err; -} +static int pin_metadata_blocks(struct btrfs_fs_info *fs_info); /* * Low memory usage version check_chunks_and_extents. */ static int check_chunks_and_extents_v2(struct btrfs_fs_info *fs_info) { + struct btrfs_trans_handle *trans = NULL; struct btrfs_path path; + struct btrfs_key old_key; struct btrfs_key key; struct btrfs_root *root1; struct btrfs_root *root; @@ -11767,12 +13325,26 @@ static int check_chunks_and_extents_v2(struct btrfs_fs_info *fs_info) root = fs_info->fs_root; + if (repair) { + /* pin every tree block to avoid extent overwrite */ + ret = pin_metadata_blocks(fs_info); + if (ret) { + error("failed to pin metadata blocks"); + return ret; + } + trans = btrfs_start_transaction(fs_info->extent_root, 1); + if (IS_ERR(trans)) { + error("failed to start transaction before check"); + return PTR_ERR(trans); + } + } + root1 = root->fs_info->chunk_root; - ret = traverse_tree_block(root1, root1->node); + ret = check_btrfs_root(trans, root1, 0, 1); err |= ret; root1 = root->fs_info->tree_root; - ret = traverse_tree_block(root1, root1->node); + ret = check_btrfs_root(trans, root1, 0, 1); err |= ret; btrfs_init_path(&path); @@ -11782,7 +13354,7 @@ static int check_chunks_and_extents_v2(struct btrfs_fs_info *fs_info) ret = btrfs_search_slot(NULL, root1, &key, &path, 0, 0); if (ret) { - error("cannot find extent treet in tree_root"); + error("cannot find extent tree in tree_root"); goto out; } @@ -11790,6 +13362,7 @@ static int check_chunks_and_extents_v2(struct btrfs_fs_info *fs_info) btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); if (key.type != BTRFS_ROOT_ITEM_KEY) goto next; + old_key = key; key.offset = (u64)-1; if (key.objectid == BTRFS_TREE_RELOC_OBJECTID) @@ -11802,19 +13375,38 @@ static int check_chunks_and_extents_v2(struct btrfs_fs_info *fs_info) goto next; } - ret = traverse_tree_block(cur_root, cur_root->node); + ret = check_btrfs_root(trans, cur_root, 0, 1); err |= ret; if (key.objectid == BTRFS_TREE_RELOC_OBJECTID) btrfs_free_fs_root(cur_root); + + btrfs_release_path(&path); + ret = btrfs_search_slot(NULL, root->fs_info->tree_root, + &old_key, &path, 0, 0); + if (ret) + goto out; next: ret = btrfs_next_item(root1, &path); if (ret) goto out; } - out: + + /* if repair, update block accounting */ + if (repair) { + ret = btrfs_fix_block_accounting(trans, root); + if (ret) + err |= ret; + else + err &= ~BG_ACCOUNTING_ERROR; + } + + if (trans) + btrfs_commit_transaction(trans, root->fs_info->extent_root); + btrfs_release_path(&path); + return err; } @@ -11829,6 +13421,12 @@ static int do_check_chunks_and_extents(struct btrfs_fs_info *fs_info) else ret = check_chunks_and_extents(fs_info); + /* Also repair device size related problems */ + if (repair && !ret) { + ret = btrfs_fix_device_and_super_size(fs_info); + if (ret > 0) + ret = 0; + } return ret; } @@ -13320,31 +14918,36 @@ int cmd_check(int argc, char **argv) goto close_out; } + if (!init_extent_tree) { + ret = repair_root_items(info); + if (ret < 0) { + err = !!ret; + error("failed to repair root items: %s", strerror(-ret)); + goto close_out; + } + if (repair) { + fprintf(stderr, "Fixed %d roots.\n", ret); + ret = 0; + } else if (ret > 0) { + fprintf(stderr, + "Found %d roots with an outdated root item.\n", + ret); + fprintf(stderr, + "Please run a filesystem check with the option --repair to fix them.\n"); + ret = 1; + err |= ret; + goto close_out; + } + } + ret = do_check_chunks_and_extents(info); err |= !!ret; if (ret) error( "errors found in extent allocation tree or chunk allocation"); - ret = repair_root_items(info); - 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; - } else if (ret > 0) { - fprintf(stderr, - "Found %d roots with an outdated root item.\n", - ret); - fprintf(stderr, - "Please run a filesystem check with the option --repair to fix them.\n"); - ret = 1; - err |= !!ret; - goto close_out; - } + /* Only re-check super size after we checked and repaired the fs */ + err |= !is_super_size_valid(info); if (!ctx.progress_enabled) { if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE))