Btrfs-progs: allow fsck to fix directory isize errors
authorJosef Bacik <jbacik@fusionio.com>
Mon, 9 Sep 2013 20:41:55 +0000 (16:41 -0400)
committerChris Mason <chris.mason@fusionio.com>
Wed, 16 Oct 2013 12:20:43 +0000 (08:20 -0400)
A user reported a problem where he was unable to rmdir an empty directory.  This
is because his isize was wrong.  This patch will fix this sort of corruption and
allow him to rmdir his directory.  Thanks

Signed-off-by: Josef Bacik <jbacik@fusionio.com>
Signed-off-by: David Sterba <dsterba@suse.cz>
Signed-off-by: Chris Mason <chris.mason@fusionio.com>
cmds-check.c

index b23353e6b9b51f920f21be05d301c2c53dac7b15..0cba4cc8fb886aea0195d07b44ff2ce61b7ddd83 100644 (file)
@@ -221,6 +221,8 @@ struct walk_control {
        int root_level;
 };
 
+static void reset_cached_block_groups(struct btrfs_fs_info *fs_info);
+
 static u8 imode_to_type(u32 imode)
 {
 #define S_SHIFT 12
@@ -1344,8 +1346,64 @@ out:
        return ret;
 }
 
+static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
+{
+       struct btrfs_trans_handle *trans;
+       struct btrfs_path *path;
+       struct btrfs_inode_item *ei;
+       struct btrfs_key key;
+       int ret;
+
+       /* So far we just fix dir isize wrong */
+       if (!(rec->errors & I_ERR_DIR_ISIZE_WRONG))
+               return 1;
+
+       path = btrfs_alloc_path();
+       if (!path)
+               return -ENOMEM;
+
+       trans = btrfs_start_transaction(root, 1);
+       if (IS_ERR(trans)) {
+               btrfs_free_path(path);
+               return PTR_ERR(trans);
+       }
+
+       key.objectid = rec->ino;
+       key.type = BTRFS_INODE_ITEM_KEY;
+       key.offset = (u64)-1;
+
+       ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+       if (ret < 0)
+               goto out;
+       if (ret) {
+               if (!path->slots[0]) {
+                       ret = -ENOENT;
+                       goto out;
+               }
+               path->slots[0]--;
+               ret = 0;
+       }
+       btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
+       if (key.objectid != rec->ino) {
+               ret = -ENOENT;
+               goto out;
+       }
+
+       ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
+                           struct btrfs_inode_item);
+       btrfs_set_inode_size(path->nodes[0], ei, rec->found_size);
+       btrfs_mark_buffer_dirty(path->nodes[0]);
+       rec->errors &= ~I_ERR_DIR_ISIZE_WRONG;
+       printf("reset isize for dir %Lu root %Lu\n", rec->ino,
+              root->root_key.objectid);
+out:
+       btrfs_commit_transaction(trans, root);
+       btrfs_free_path(path);
+       return ret;
+}
+
 static int check_inode_recs(struct btrfs_root *root,
-                           struct cache_tree *inode_cache)
+                           struct cache_tree *inode_cache, int repair)
 {
        struct cache_extent *cache;
        struct ptr_node *node;
@@ -1400,6 +1458,15 @@ static int check_inode_recs(struct btrfs_root *root,
                        }
                }
 
+               if (repair) {
+                       ret = try_repair_inode(root, rec);
+                       if (ret == 0 && can_free_inode_rec(rec)) {
+                               free_inode_rec(rec);
+                               continue;
+                       }
+                       ret = 0;
+               }
+
                error++;
                if (!rec->found_inode_item)
                        rec->errors |= I_ERR_NO_INODE_ITEM;
@@ -1741,7 +1808,7 @@ static int process_root_ref(struct extent_buffer *eb, int slot,
 
 static int check_fs_root(struct btrfs_root *root,
                         struct cache_tree *root_cache,
-                        struct walk_control *wc)
+                        struct walk_control *wc, int repair)
 {
        int ret = 0;
        int wret;
@@ -1811,7 +1878,7 @@ static int check_fs_root(struct btrfs_root *root,
                                root_node.current);
        }
 
-       ret = check_inode_recs(root, &root_node.inode_cache);
+       ret = check_inode_recs(root, &root_node.inode_cache, repair);
        return ret;
 }
 
@@ -1827,7 +1894,8 @@ static int fs_root_objectid(u64 objectid)
 }
 
 static int check_fs_roots(struct btrfs_root *root,
-                         struct cache_tree *root_cache)
+                         struct cache_tree *root_cache,
+                         int repair)
 {
        struct btrfs_path path;
        struct btrfs_key key;
@@ -1838,6 +1906,12 @@ static int check_fs_roots(struct btrfs_root *root,
        int ret;
        int err = 0;
 
+       /*
+        * Just in case we made any changes to the extent tree that weren't
+        * reflected into the free space cache yet.
+        */
+       if (repair)
+               reset_cached_block_groups(root->fs_info);
        memset(&wc, 0, sizeof(wc));
        cache_tree_init(&wc.shared);
        btrfs_init_path(&path);
@@ -1864,7 +1938,7 @@ static int check_fs_roots(struct btrfs_root *root,
                                err = 1;
                                goto next;
                        }
-                       ret = check_fs_root(tmp_root, root_cache, &wc);
+                       ret = check_fs_root(tmp_root, root_cache, &wc, repair);
                        if (ret)
                                err = 1;
                        btrfs_free_fs_root(tmp_root);
@@ -5879,7 +5953,7 @@ int cmd_check(int argc, char **argv)
                goto out;
 
        fprintf(stderr, "checking fs roots\n");
-       ret = check_fs_roots(root, &root_cache);
+       ret = check_fs_roots(root, &root_cache, repair);
        if (ret)
                goto out;