From 55fea7186ca8aa7faf442d3117478d44a4ec0eb4 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 9 Dec 2014 16:27:32 +0800 Subject: [PATCH] btrfs-progs: Add inode item rebuild function. Add a basic inode item rebuild function for I_ERR_NO_INODE_ITEM. The main use case is to repair btrfs which fs root has corrupted leaf, but it is already working for case if the corrupteed fs root leaf/node contains no inode extent_data. The repair needs 3 elements for inode rebuild: 1. inode number This is quite easy, existing inode_record codes will detect it quite well. 2. inode type This is the trick part. The only reliable method is to recovery it from parent's dir_index/item. The remaining method will search for regular file extent for FILE type or child's backref for DIR(todo). Fallback will be FILE. Inode name(inode_ref) will be recoverd by nlink repair function. This is just a fundamental implement, some advanced recovery can be improved later with btrfs-progs infrastructure change. Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- cmds-check.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++-- ctree.h | 2 + inode.c | 5 +- 3 files changed, 160 insertions(+), 7 deletions(-) diff --git a/cmds-check.c b/cmds-check.c index 719ec698..fee29ec1 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -1905,6 +1905,12 @@ static int find_file_type(struct inode_record *rec, u8 *type) { struct inode_backref *backref; + /* For inode item recovered case */ + if (rec->found_inode_item) { + *type = imode_to_type(rec->imode); + return 0; + } + list_for_each_entry(backref, &rec->backrefs, list) { if (backref->found_dir_index || backref->found_dir_item) { *type = backref->filetype; @@ -2098,6 +2104,149 @@ out: return ret; } +/* + * Check if there is any normal(reg or prealloc) file extent for given + * ino. + * This is used to determine the file type when neither its dir_index/item or + * inode_item exists. + * + * This will *NOT* report error, if any error happens, just consider it does + * not have any normal file extent. + */ +static int find_normal_file_extent(struct btrfs_root *root, u64 ino) +{ + struct btrfs_path *path; + struct btrfs_key key; + struct btrfs_key found_key; + struct btrfs_file_extent_item *fi; + u8 type; + int ret = 0; + + path = btrfs_alloc_path(); + if (!path) + goto out; + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = 0; + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + if (ret < 0) { + ret = 0; + goto out; + } + if (ret && path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret) { + ret = 0; + goto out; + } + } + while (1) { + btrfs_item_key_to_cpu(path->nodes[0], &found_key, + path->slots[0]); + if (found_key.objectid != ino || + found_key.type != BTRFS_EXTENT_DATA_KEY) + break; + fi = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_file_extent_item); + type = btrfs_file_extent_type(path->nodes[0], fi); + if (type != BTRFS_FILE_EXTENT_INLINE) + ret = 1; + goto out; + } +out: + btrfs_free_path(path); + return ret; +} + +static u32 btrfs_type_to_imode(u8 type) +{ + static u32 imode_by_btrfs_type[] = { + [BTRFS_FT_REG_FILE] = S_IFREG, + [BTRFS_FT_DIR] = S_IFDIR, + [BTRFS_FT_CHRDEV] = S_IFCHR, + [BTRFS_FT_BLKDEV] = S_IFBLK, + [BTRFS_FT_FIFO] = S_IFIFO, + [BTRFS_FT_SOCK] = S_IFSOCK, + [BTRFS_FT_SYMLINK] = S_IFLNK, + }; + + return imode_by_btrfs_type[(type)]; +} + +static int repair_inode_no_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *path, + struct inode_record *rec) +{ + u8 filetype; + u32 mode = 0700; + int type_recovered = 0; + int ret = 0; + + /* + * TODO: + * 1. salvage data from existing file extent and + * punch hole to keep fi ext consistent. + * 2. salvage data from extent tree + */ + printf("Trying to rebuild inode:%llu\n", rec->ino); + + type_recovered = !find_file_type(rec, &filetype); + + /* + * Try to determine inode type if type not found. + * + * For found regular file extent, it must be FILE. + * For found dir_item/index, it must be DIR. + * + * For undetermined one, use FILE as fallback. + * + * TODO: + * 1. If found extent belong to it in extent tree, it must be FILE + * Need extra hook in extent tree scan. + * 2. If found backref(inode_index/item is already handled) to it, + * it must be DIR. + * Need new inode-inode ref structure to allow search for that. + */ + if (!type_recovered) { + if (rec->found_file_extent && + find_normal_file_extent(root, rec->ino)) { + type_recovered = 1; + filetype = BTRFS_FT_REG_FILE; + } else if (rec->found_dir_item) { + type_recovered = 1; + filetype = BTRFS_FT_DIR; + } else { + printf("Can't determint the filetype for inode %llu, assume it is a normal file\n", + rec->ino); + type_recovered = 1; + filetype = BTRFS_FT_REG_FILE; + } + } + + ret = btrfs_new_inode(trans, root, rec->ino, + mode | btrfs_type_to_imode(filetype)); + if (ret < 0) + goto out; + + /* + * Here inode rebuild is done, we only rebuild the inode item, + * don't repair the nlink(like move to lost+found). + * That is the job of nlink repair. + * + * We just fill the record and return + */ + rec->found_dir_item = 1; + rec->imode = mode | btrfs_type_to_imode(filetype); + rec->nlink = 0; + rec->errors &= ~I_ERR_NO_INODE_ITEM; + /* Ensure the inode_nlinks repair function will be called */ + rec->errors |= I_ERR_LINK_COUNT_WRONG; +out: + return ret; +} + static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec) { struct btrfs_trans_handle *trans; @@ -2106,7 +2255,8 @@ static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec) if (!(rec->errors & (I_ERR_DIR_ISIZE_WRONG | I_ERR_NO_ORPHAN_ITEM | - I_ERR_LINK_COUNT_WRONG))) + I_ERR_LINK_COUNT_WRONG | + I_ERR_NO_INODE_ITEM))) return rec->errors; path = btrfs_alloc_path(); @@ -2126,7 +2276,9 @@ static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec) return PTR_ERR(trans); } - if (rec->errors & I_ERR_DIR_ISIZE_WRONG) + if (rec->errors & I_ERR_NO_INODE_ITEM) + ret = repair_inode_no_item(trans, root, path, rec); + if (!ret && rec->errors & I_ERR_DIR_ISIZE_WRONG) ret = repair_inode_isize(trans, root, path, rec); if (!ret && rec->errors & I_ERR_NO_ORPHAN_ITEM) ret = repair_inode_orphan_item(trans, root, path, rec); @@ -2282,6 +2434,8 @@ static int check_inode_recs(struct btrfs_root *root, } } + if (!rec->found_inode_item) + rec->errors |= I_ERR_NO_INODE_ITEM; if (rec->found_link != rec->nlink) rec->errors |= I_ERR_LINK_COUNT_WRONG; if (repair) { @@ -2294,8 +2448,6 @@ static int check_inode_recs(struct btrfs_root *root, } error++; - if (!rec->found_inode_item) - rec->errors |= I_ERR_NO_INODE_ITEM; print_inode_error(root, rec); list_for_each_entry(backref, &rec->backrefs, list) { if (!backref->found_dir_item) diff --git a/ctree.h b/ctree.h index eadae76d..7861940f 100644 --- a/ctree.h +++ b/ctree.h @@ -2445,6 +2445,8 @@ static inline int is_fstree(u64 rootid) /* inode.c */ int check_dir_conflict(struct btrfs_root *root, char *name, int namelen, u64 dir, u64 index); +int btrfs_new_inode(struct btrfs_trans_handle *trans, struct btrfs_root *root, + u64 ino, u32 mode); int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 ino, u64 parent_ino, char *name, int namelen, u8 type, u64 *index, int add_backref); diff --git a/inode.c b/inode.c index ff1be48c..a37f7f33 100644 --- a/inode.c +++ b/inode.c @@ -458,9 +458,8 @@ static void fill_inode_item(struct btrfs_trans_handle *trans, * its backref. * The backref is added by btrfs_add_link(). */ -static int btrfs_new_inode(struct btrfs_trans_handle *trans, - struct btrfs_root *root, - u64 ino, u32 mode) +int btrfs_new_inode(struct btrfs_trans_handle *trans, struct btrfs_root *root, + u64 ino, u32 mode) { struct btrfs_inode_item inode_item = {0}; int ret = 0;