bcachefs: Run check_dirents second time if required

If we move a key backwards, we'll need a second pass to run the rest of
the fsck checks.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2025-05-31 19:12:25 -04:00
parent a4907d7f33
commit c72def5237
4 changed files with 49 additions and 27 deletions

View file

@ -705,8 +705,9 @@ int bch2_readdir(struct bch_fs *c, subvol_inum inum,
subvol_inum target;
bool need_second_pass = false;
int ret2 = bch2_str_hash_check_key(trans, NULL, &bch2_dirent_hash_desc,
hash_info, &iter, k) ?:
hash_info, &iter, k, &need_second_pass) ?:
bch2_dirent_read_target(trans, inum, dirent, &target);
if (ret2 > 0)
continue;

View file

@ -2141,7 +2141,8 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
struct bch_hash_info *hash_info,
struct inode_walker *dir,
struct inode_walker *target,
struct snapshots_seen *s)
struct snapshots_seen *s,
bool *need_second_pass)
{
struct bch_fs *c = trans->c;
struct inode_walker_entry *i;
@ -2183,7 +2184,8 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
*hash_info = bch2_hash_info_init(c, &i->inode);
dir->first_this_inode = false;
ret = bch2_str_hash_check_key(trans, s, &bch2_dirent_hash_desc, hash_info, iter, k);
ret = bch2_str_hash_check_key(trans, s, &bch2_dirent_hash_desc, hash_info,
iter, k, need_second_pass);
if (ret < 0)
goto err;
if (ret) {
@ -2228,7 +2230,8 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
STR_HASH_must_create) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc);
/* might need another check_dirents pass */
if (dir_offset < k.k->p.offset)
*need_second_pass = true;
goto out;
}
@ -2296,7 +2299,6 @@ out:
err:
fsck_err:
printbuf_exit(&buf);
bch_err_fn(c, ret);
return ret;
}
@ -2310,17 +2312,30 @@ int bch2_check_dirents(struct bch_fs *c)
struct inode_walker target = inode_walker_init();
struct snapshots_seen s;
struct bch_hash_info hash_info;
bool need_second_pass = false, did_second_pass = false;
snapshots_seen_init(&s);
again:
int ret = bch2_trans_run(c,
for_each_btree_key_commit(trans, iter, BTREE_ID_dirents,
POS(BCACHEFS_ROOT_INO, 0),
BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k,
NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
check_dirent(trans, &iter, k, &hash_info, &dir, &target, &s)) ?:
check_dirent(trans, &iter, k, &hash_info, &dir, &target, &s,
&need_second_pass)) ?:
check_subdir_count_notnested(trans, &dir));
if (!ret && need_second_pass && !did_second_pass) {
bch_info(c, "check_dirents requires second pass");
swap(did_second_pass, need_second_pass);
goto again;
}
if (!ret && need_second_pass) {
bch_err(c, "dirents not repairing");
ret = -EINVAL;
}
snapshots_seen_exit(&s);
inode_walker_exit(&dir);
inode_walker_exit(&target);
@ -2334,16 +2349,14 @@ static int check_xattr(struct btree_trans *trans, struct btree_iter *iter,
struct inode_walker *inode)
{
struct bch_fs *c = trans->c;
struct inode_walker_entry *i;
int ret;
ret = bch2_check_key_has_snapshot(trans, iter, k);
int ret = bch2_check_key_has_snapshot(trans, iter, k);
if (ret < 0)
return ret;
if (ret)
return 0;
i = walk_inode(trans, inode, k);
struct inode_walker_entry *i = walk_inode(trans, inode, k);
ret = PTR_ERR_OR_ZERO(i);
if (ret)
return ret;
@ -2359,9 +2372,9 @@ static int check_xattr(struct btree_trans *trans, struct btree_iter *iter,
*hash_info = bch2_hash_info_init(c, &i->inode);
inode->first_this_inode = false;
ret = bch2_str_hash_check_key(trans, NULL, &bch2_xattr_hash_desc, hash_info, iter, k);
bch_err_fn(c, ret);
return ret;
bool need_second_pass = false;
return bch2_str_hash_check_key(trans, NULL, &bch2_xattr_hash_desc, hash_info,
iter, k, &need_second_pass);
}
/*

View file

@ -35,7 +35,8 @@ static noinline int fsck_rename_dirent(struct btree_trans *trans,
struct snapshots_seen *s,
const struct bch_hash_desc desc,
struct bch_hash_info *hash_info,
struct bkey_s_c_dirent old)
struct bkey_s_c_dirent old,
bool *updated_before_k_pos)
{
struct qstr old_name = bch2_dirent_get_name(old);
struct bkey_i_dirent *new = bch2_trans_kmalloc(trans, bkey_bytes(old.k) + 32);
@ -62,16 +63,15 @@ static noinline int fsck_rename_dirent(struct btree_trans *trans,
old.k->p.snapshot, &new->k_i,
BTREE_UPDATE_internal_snapshot_node);
if (ret && !bch2_err_matches(ret, EEXIST))
goto err;
if (!ret)
break;
if (!ret) {
if (bpos_lt(new->k.p, old.k->p))
*updated_before_k_pos = true;
break;
}
}
if (ret)
goto err;
ret = bch2_fsck_update_backpointers(trans, s, desc, hash_info, &new->k_i);
err:
ret = ret ?: bch2_fsck_update_backpointers(trans, s, desc, hash_info, &new->k_i);
bch_err_fn(trans->c, ret);
return ret;
}
@ -230,7 +230,8 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
struct snapshots_seen *s,
const struct bch_hash_desc *desc,
struct bch_hash_info *hash_info,
struct btree_iter *k_iter, struct bkey_s_c hash_k)
struct btree_iter *k_iter, struct bkey_s_c hash_k,
bool *updated_before_k_pos)
{
struct bch_fs *c = trans->c;
struct btree_iter iter = {};
@ -310,6 +311,9 @@ bad_hash:
if (k.k)
goto duplicate_entries;
if (bpos_lt(new->k.p, k.k->p))
*updated_before_k_pos = true;
ret = bch2_insert_snapshot_whiteouts(trans, desc->btree_id,
k_iter->pos, new->k.p) ?:
bch2_hash_delete_at(trans, *desc, hash_info, k_iter,
@ -345,7 +349,8 @@ duplicate_entries:
ret = bch2_hash_delete_at(trans, *desc, hash_info, &iter, 0);
break;
case 2:
ret = fsck_rename_dirent(trans, s, *desc, hash_info, bkey_s_c_to_dirent(hash_k)) ?:
ret = fsck_rename_dirent(trans, s, *desc, hash_info, bkey_s_c_to_dirent(hash_k),
updated_before_k_pos) ?:
bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
goto out;
}

View file

@ -402,13 +402,15 @@ int __bch2_str_hash_check_key(struct btree_trans *,
struct snapshots_seen *,
const struct bch_hash_desc *,
struct bch_hash_info *,
struct btree_iter *, struct bkey_s_c);
struct btree_iter *, struct bkey_s_c,
bool *);
static inline int bch2_str_hash_check_key(struct btree_trans *trans,
struct snapshots_seen *s,
const struct bch_hash_desc *desc,
struct bch_hash_info *hash_info,
struct btree_iter *k_iter, struct bkey_s_c hash_k)
struct btree_iter *k_iter, struct bkey_s_c hash_k,
bool *updated_before_k_pos)
{
if (hash_k.k->type != desc->key_type)
return 0;
@ -416,7 +418,8 @@ static inline int bch2_str_hash_check_key(struct btree_trans *trans,
if (likely(desc->hash_bkey(hash_info, hash_k) == hash_k.k->p.offset))
return 0;
return __bch2_str_hash_check_key(trans, s, desc, hash_info, k_iter, hash_k);
return __bch2_str_hash_check_key(trans, s, desc, hash_info, k_iter, hash_k,
updated_before_k_pos);
}
#endif /* _BCACHEFS_STR_HASH_H */