summaryrefslogtreecommitdiff
path: root/cmds-check.c
diff options
context:
space:
mode:
authorJosef Bacik <jbacik@fb.com>2014-10-10 16:57:13 -0400
committerDavid Sterba <dsterba@suse.cz>2014-10-14 10:45:03 +0200
commitb25a40651d15c8ca795ab7a4f1b8150128f09e3c (patch)
tree7e3eaec4a384d5d14b3b8676168405fe7b83444c /cmds-check.c
parent994ce2672dbf89fe5a7a18557b15449f0f83af65 (diff)
Btrfs-progs: delete bogus dir indexes
We may run across dir indexes that are corrupt in such a way that it makes them useless, such as having a bad location key or a bad name. In this case we can just delete dir indexes that don't show up properly and then re-create what we need. When we delete dir indexes however we need to restart scanning the fs tree as we could have greated bogus inode recs if the location key was bad, so set it up so that if we had to delete an dir index we go ahead and free up our inode recs and return -EAGAIN to check_fs_roots so it knows to restart the loop. Thanks, Signed-off-by: Josef Bacik <jbacik@fb.com> Signed-off-by: David Sterba <dsterba@suse.cz>
Diffstat (limited to 'cmds-check.c')
-rw-r--r--cmds-check.c137
1 files changed, 119 insertions, 18 deletions
diff --git a/cmds-check.c b/cmds-check.c
index 4f400032..2219e758 100644
--- a/cmds-check.c
+++ b/cmds-check.c
@@ -1650,35 +1650,99 @@ static int add_missing_dir_index(struct btrfs_root *root,
return 0;
}
+static int delete_dir_index(struct btrfs_root *root,
+ struct cache_tree *inode_cache,
+ struct inode_record *rec,
+ struct inode_backref *backref)
+{
+ struct btrfs_trans_handle *trans;
+ struct btrfs_dir_item *di;
+ struct btrfs_path *path;
+ int ret = 0;
+
+ 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);
+ }
+
+
+ fprintf(stderr, "Deleting bad dir index [%llu,%u,%llu] root %llu\n",
+ (unsigned long long)backref->dir,
+ BTRFS_DIR_INDEX_KEY, (unsigned long long)backref->index,
+ (unsigned long long)root->objectid);
+
+ di = btrfs_lookup_dir_index(trans, root, path, backref->dir,
+ backref->name, backref->namelen,
+ backref->index, -1);
+ if (IS_ERR(di)) {
+ ret = PTR_ERR(di);
+ btrfs_free_path(path);
+ btrfs_commit_transaction(trans, root);
+ if (ret == -ENOENT)
+ return 0;
+ return ret;
+ }
+
+ if (!di)
+ ret = btrfs_del_item(trans, root, path);
+ else
+ ret = btrfs_delete_one_dir_name(trans, root, path, di);
+ BUG_ON(ret);
+ btrfs_free_path(path);
+ btrfs_commit_transaction(trans, root);
+ return ret;
+}
+
static int repair_inode_backrefs(struct btrfs_root *root,
struct inode_record *rec,
- struct cache_tree *inode_cache)
+ struct cache_tree *inode_cache,
+ int delete)
{
struct inode_backref *tmp, *backref;
u64 root_dirid = btrfs_root_dirid(&root->root_item);
int ret = 0;
+ int repaired = 0;
list_for_each_entry_safe(backref, tmp, &rec->backrefs, list) {
/* Index 0 for root dir's are special, don't mess with it */
if (rec->ino == root_dirid && backref->index == 0)
continue;
- if (!backref->found_dir_index && backref->found_inode_ref) {
- ret = add_missing_dir_index(root, inode_cache, rec,
- backref);
+ if (delete && backref->found_dir_index &&
+ !backref->found_inode_ref) {
+ ret = delete_dir_index(root, inode_cache, rec, backref);
if (ret)
break;
+ repaired++;
+ list_del(&backref->list);
+ free(backref);
}
- if (backref->found_dir_item && backref->found_dir_index) {
- if (!backref->errors && backref->found_inode_ref) {
- list_del(&backref->list);
- free(backref);
+ if (!delete && !backref->found_dir_index &&
+ backref->found_dir_item && backref->found_inode_ref) {
+ ret = add_missing_dir_index(root, inode_cache, rec,
+ backref);
+ if (ret)
+ break;
+ repaired++;
+ if (backref->found_dir_item &&
+ backref->found_dir_index &&
+ backref->found_dir_index) {
+ if (!backref->errors &&
+ backref->found_inode_ref) {
+ list_del(&backref->list);
+ free(backref);
+ }
}
}
- }
- return ret;
+ }
+ return ret ? ret : repaired;
}
static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
@@ -1716,7 +1780,9 @@ static int check_inode_recs(struct btrfs_root *root,
struct ptr_node *node;
struct inode_record *rec;
struct inode_backref *backref;
+ int stage = 0;
int ret;
+ int err = 0;
u64 error = 0;
u64 root_dirid = btrfs_root_dirid(&root->root_item);
@@ -1730,22 +1796,52 @@ static int check_inode_recs(struct btrfs_root *root,
* We need to repair backrefs first because we could change some of the
* errors in the inode recs.
*
+ * We also need to go through and delete invalid backrefs first and then
+ * add the correct ones second. We do this because we may get EEXIST
+ * when adding back the correct index because we hadn't yet deleted the
+ * invalid index.
+ *
* For example, if we were missing a dir index then the directories
* isize would be wrong, so if we fixed the isize to what we thought it
* would be and then fixed the backref we'd still have a invalid fs, so
* we need to add back the dir index and then check to see if the isize
* is still wrong.
*/
- cache = search_cache_extent(inode_cache, 0);
- while (repair && cache) {
- node = container_of(cache, struct ptr_node, cache);
- rec = node->data;
- cache = next_cache_extent(cache);
+ while (stage < 3) {
+ stage++;
+ if (stage == 3 && !err)
+ break;
- if (list_empty(&rec->backrefs))
- continue;
- repair_inode_backrefs(root, rec, inode_cache);
+ cache = search_cache_extent(inode_cache, 0);
+ while (repair && cache) {
+ node = container_of(cache, struct ptr_node, cache);
+ rec = node->data;
+ cache = next_cache_extent(cache);
+
+ /* Need to free everything up and rescan */
+ if (stage == 3) {
+ remove_cache_extent(inode_cache, &node->cache);
+ free(node);
+ free_inode_rec(rec);
+ continue;
+ }
+
+ if (list_empty(&rec->backrefs))
+ continue;
+
+ ret = repair_inode_backrefs(root, rec, inode_cache,
+ stage == 1);
+ if (ret < 0) {
+ err = ret;
+ stage = 2;
+ break;
+ } if (ret > 0) {
+ err = -EAGAIN;
+ }
+ }
}
+ if (err)
+ return err;
rec = get_inode_rec(inode_cache, root_dirid, 0);
if (rec) {
@@ -2295,6 +2391,11 @@ again:
goto next;
}
ret = check_fs_root(tmp_root, root_cache, &wc);
+ if (ret == -EAGAIN) {
+ free_root_recs_tree(root_cache);
+ btrfs_release_path(&path);
+ goto again;
+ }
if (ret)
err = 1;
if (key.objectid == BTRFS_TREE_RELOC_OBJECTID)