bcachefs: Split out check_unreachable_inodes() pass

With inode backpointers, we can write a very simple
check_unreachable_inodes() pass that only looks for non-unlinked inodes
that are missing backpointers, and reattaches them.

This simplifies check_directory_structure() so that it's now only
checking for directory structure loops,

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2024-09-29 23:40:28 -04:00
parent bf4baaa087
commit bade9711e0
3 changed files with 67 additions and 35 deletions

View File

@ -1292,6 +1292,58 @@ int bch2_check_inodes(struct bch_fs *c)
return ret;
}
static int check_unreachable_inode(struct btree_trans *trans,
struct btree_iter *iter,
struct bkey_s_c k)
{
struct bch_fs *c = trans->c;
struct printbuf buf = PRINTBUF;
int ret = 0;
if (!bkey_is_inode(k.k))
return 0;
struct bch_inode_unpacked inode;
BUG_ON(bch2_inode_unpack(k, &inode));
if (inode.bi_subvol)
return 0;
if (inode.bi_flags & BCH_INODE_unlinked)
return 0;
if (fsck_err_on(!inode.bi_dir,
trans, inode_unreachable,
"unreachable inode:\n%s",
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, k),
buf.buf)))
ret = reattach_inode(trans, &inode);
fsck_err:
printbuf_exit(&buf);
return ret;
}
/*
* Reattach unreachable (but not unlinked) inodes
*
* Run after check_inodes() and check_dirents(), so we node that inode
* backpointer fields point to valid dirents, and every inode that has a dirent
* that points to it has its backpointer field set - so we're just looking for
* non-unlinked inodes without backpointers:
*/
int bch2_check_unreachable_inodes(struct bch_fs *c)
{
int ret = bch2_trans_run(c,
for_each_btree_key_commit(trans, iter, BTREE_ID_inodes,
POS_MIN,
BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k,
NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
check_unreachable_inode(trans, &iter, k)));
bch_err_fn(c, ret);
return ret;
}
static inline bool btree_matches_i_mode(enum btree_id btree, unsigned mode)
{
switch (btree) {
@ -2450,22 +2502,6 @@ static int check_subvol_path(struct btree_trans *trans, struct btree_iter *iter,
if (ret)
break;
/*
* We've checked that inode backpointers point to valid dirents;
* here, it's sufficient to check that the subvolume root has a
* dirent:
*/
if (fsck_err_on(!subvol_root.bi_dir,
trans, subvol_unreachable,
"unreachable subvolume %s",
(bch2_bkey_val_to_text(&buf, c, s.s_c),
prt_newline(&buf),
bch2_inode_unpacked_to_text(&buf, &subvol_root),
buf.buf))) {
ret = reattach_subvol(trans, s);
break;
}
u32 parent = le32_to_cpu(s.v->fs_path_parent);
if (darray_u32_has(&subvol_path, parent)) {
@ -2526,12 +2562,6 @@ static bool path_is_dup(pathbuf *p, u64 inum, u32 snapshot)
return false;
}
/*
* Check that a given inode is reachable from its subvolume root - we already
* verified subvolume connectivity:
*
* XXX: we should also be verifying that inodes are in the right subvolumes
*/
static int check_path(struct btree_trans *trans, pathbuf *p, struct bkey_s_c inode_k)
{
struct bch_fs *c = trans->c;
@ -2545,6 +2575,9 @@ static int check_path(struct btree_trans *trans, pathbuf *p, struct bkey_s_c ino
BUG_ON(bch2_inode_unpack(inode_k, &inode));
if (!S_ISDIR(inode.bi_mode))
return 0;
while (!inode.bi_subvol) {
struct btree_iter dirent_iter;
struct bkey_s_c_dirent d;
@ -2559,21 +2592,15 @@ static int check_path(struct btree_trans *trans, pathbuf *p, struct bkey_s_c ino
bch2_trans_iter_exit(trans, &dirent_iter);
if (bch2_err_matches(ret, ENOENT)) {
ret = 0;
if (fsck_err(trans, inode_unreachable,
"unreachable inode\n%s",
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, inode_k),
buf.buf)))
ret = reattach_inode(trans, &inode);
printbuf_reset(&buf);
bch2_bkey_val_to_text(&buf, c, inode_k);
bch_err(c, "unreachable inode in check_directory_structure: %s\n%s",
bch2_err_str(ret), buf.buf);
goto out;
}
bch2_trans_iter_exit(trans, &dirent_iter);
if (!S_ISDIR(inode.bi_mode))
break;
ret = darray_push(p, ((struct pathbuf_entry) {
.inum = inode.bi_inum,
.snapshot = snapshot,
@ -2626,9 +2653,8 @@ fsck_err:
}
/*
* Check for unreachable inodes, as well as loops in the directory structure:
* After bch2_check_dirents(), if an inode backpointer doesn't exist that means it's
* unreachable:
* Check for loops in the directory structure: all other connectivity issues
* have been fixed by prior passes
*/
int bch2_check_directory_structure(struct bch_fs *c)
{
@ -2756,6 +2782,10 @@ static int check_nlinks_find_hardlinks(struct bch_fs *c,
if (S_ISDIR(u.bi_mode))
continue;
/*
* Previous passes ensured that bi_nlink is nonzero if
* it had multiple hardlinks:
*/
if (!u.bi_nlink)
continue;

View File

@ -9,6 +9,7 @@ int bch2_check_dirents(struct bch_fs *);
int bch2_check_xattrs(struct bch_fs *);
int bch2_check_root(struct bch_fs *);
int bch2_check_subvolume_structure(struct bch_fs *);
int bch2_check_unreachable_inodes(struct bch_fs *);
int bch2_check_directory_structure(struct bch_fs *);
int bch2_check_nlinks(struct bch_fs *);
int bch2_fix_reflink_p(struct bch_fs *);

View File

@ -46,6 +46,7 @@
x(check_dirents, 27, PASS_FSCK) \
x(check_xattrs, 28, PASS_FSCK) \
x(check_root, 29, PASS_ONLINE|PASS_FSCK) \
x(check_unreachable_inodes, 40, PASS_ONLINE|PASS_FSCK) \
x(check_subvolume_structure, 36, PASS_ONLINE|PASS_FSCK) \
x(check_directory_structure, 30, PASS_ONLINE|PASS_FSCK) \
x(check_nlinks, 31, PASS_FSCK) \