diff options
-rw-r--r-- | drivers/md/bcache/fs-gc.c | 107 |
1 files changed, 96 insertions, 11 deletions
diff --git a/drivers/md/bcache/fs-gc.c b/drivers/md/bcache/fs-gc.c index 160d364c3d98..8c3cb023e67f 100644 --- a/drivers/md/bcache/fs-gc.c +++ b/drivers/md/bcache/fs-gc.c @@ -219,11 +219,11 @@ static int check_lostfound(struct cache_set *c, struct bkey_i_inode *lostfound_inode) { struct qstr lostfound = QSTR("lost+found"); - struct bch_hash_info root_str_hash = bch_hash_info_init(&root_inode->v); + struct bch_hash_info root_hash_info = bch_hash_info_init(&root_inode->v); u64 inum; int ret; - inum = bch_dirent_lookup(c, BCACHE_ROOT_INO, &root_str_hash, + inum = bch_dirent_lookup(c, BCACHE_ROOT_INO, &root_hash_info, &lostfound); if (!inum) { bch_notice(c, "creating lost+found"); @@ -259,7 +259,7 @@ create_lostfound: if (ret) return ret; - ret = bch_dirent_create(c, BCACHE_ROOT_INO, &root_str_hash, DT_DIR, + ret = bch_dirent_create(c, BCACHE_ROOT_INO, &root_hash_info, DT_DIR, &lostfound, lostfound_inode->k.p.inode, NULL, 0); if (ret) return ret; @@ -329,6 +329,65 @@ static int path_down(struct pathbuf *p, u64 inum) return 0; } +static int detach_dir(struct cache_set *c, struct btree_iter *iter, + struct bkey_s_c_dirent dirent) +{ + struct qstr name; + struct bkey_i_inode dir_inode; + struct bch_hash_info dir_hash_info; + u64 dir_inum = dirent.k->p.inode; + int ret; + char *buf; + + name.len = bch_dirent_name_bytes(dirent); + buf = kmalloc(name.len + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, dirent.v->d_name, name.len); + buf[name.len] = '\0'; + name.name = buf; + + /* Unlock iter so we don't deadlock, after copying name: */ + bch_btree_iter_unlock(iter); + + ret = bch_inode_find_by_inum(c, dir_inum, &dir_inode); + if (ret) + goto err; + + dir_hash_info = bch_hash_info_init(&dir_inode.v); + + ret = bch_dirent_delete(c, dir_inum, &dir_hash_info, &name, NULL); +err: + kfree(buf); + return ret; +} + +static int reattach_dir(struct cache_set *c, + struct bkey_i_inode *lostfound_inode, + u64 inum) +{ + struct bch_hash_info lostfound_hash_info = + bch_hash_info_init(&lostfound_inode->v); + char name_buf[20]; + struct qstr name; + int ret; + + snprintf(name_buf, sizeof(name_buf), "%llu", inum); + name = (struct qstr) QSTR(name_buf); + + le32_add_cpu(&lostfound_inode->v.i_nlink, 1); + + ret = bch_btree_insert(c, BTREE_ID_INODES, &lostfound_inode->k_i, + NULL, NULL, NULL, 0); + if (ret) + return ret; + + return bch_dirent_create(c, lostfound_inode->k.p.inode, + &lostfound_hash_info, + DT_DIR, &name, inum, NULL, 0); +} + noinline_for_stack static int check_directory_structure(struct cache_set *c) { @@ -339,6 +398,7 @@ static int check_directory_structure(struct cache_set *c) struct btree_iter iter; struct bkey_s_c k; struct bkey_s_c_dirent dirent; + bool had_unreachable; u64 d_inum; int ret = 0; @@ -351,7 +411,7 @@ static int check_directory_structure(struct cache_set *c) return ret; /* DFS: */ - +restart_dfs: ret = inode_bitmap_set(&dirs_done, BCACHE_ROOT_INO); if (ret) goto err; @@ -361,7 +421,7 @@ static int check_directory_structure(struct cache_set *c) return ret; while (path.nr) { -down: +next: e = &path.entries[path.nr - 1]; if (e->offset == U64_MAX) @@ -384,8 +444,13 @@ down: d_inum = le64_to_cpu(dirent.v->d_inum); - unfixable_fsck_err_on(inode_bitmap_test(&dirs_done, d_inum), c, - "directory with multiple hardlinks"); + if (fsck_err_on(inode_bitmap_test(&dirs_done, d_inum), c, + "directory with multiple hardlinks")) { + ret = detach_dir(c, &iter, dirent); + if (ret) + goto err; + continue; + } ret = inode_bitmap_set(&dirs_done, d_inum); if (ret) @@ -396,7 +461,7 @@ down: goto err; bch_btree_iter_unlock(&iter); - goto down; + goto next; } ret = bch_btree_iter_unlock(&iter); if (ret) @@ -405,18 +470,38 @@ up: path.nr--; } + had_unreachable = false; + for_each_btree_key(&iter, c, BTREE_ID_INODES, POS_MIN, k) { if (k.k->type != BCH_INODE_FS || !S_ISDIR(le16_to_cpu(bkey_s_c_to_inode(k).v->i_mode))) continue; - unfixable_fsck_err_on(!inode_bitmap_test(&dirs_done, k.k->p.inode), c, - "unreachable directory found (inum %llu)", - k.k->p.inode); + if (fsck_err_on(!inode_bitmap_test(&dirs_done, k.k->p.inode), c, + "unreachable directory found (inum %llu)", + k.k->p.inode)) { + bch_btree_iter_unlock(&iter); + + ret = reattach_dir(c, &lostfound_inode, k.k->p.inode); + if (ret) + goto err; + + had_unreachable = true; + } } ret = bch_btree_iter_unlock(&iter); if (ret) goto err; + + if (had_unreachable) { + bch_info(c, "reattached unreachable directories, restarting pass to check for loops"); + kfree(dirs_done.bits); + kfree(path.entries); + memset(&dirs_done, 0, sizeof(dirs_done)); + memset(&path, 0, sizeof(path)); + goto restart_dfs; + } + out: kfree(dirs_done.bits); kfree(path.entries); |