btrfs-progs: Add inode item rebuild function.
authorQu Wenruo <quwenruo@cn.fujitsu.com>
Tue, 9 Dec 2014 08:27:32 +0000 (16:27 +0800)
committerDavid Sterba <dsterba@suse.cz>
Wed, 10 Dec 2014 12:52:28 +0000 (13:52 +0100)
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 <quwenruo@cn.fujitsu.com>
Signed-off-by: David Sterba <dsterba@suse.cz>
cmds-check.c
ctree.h
inode.c

index 719ec69..fee29ec 100644 (file)
@@ -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 eadae76..7861940 100644 (file)
--- 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 ff1be48..a37f7f3 100644 (file)
--- 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;