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; subvol_inum target;
bool need_second_pass = false;
int ret2 = bch2_str_hash_check_key(trans, NULL, &bch2_dirent_hash_desc, 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); bch2_dirent_read_target(trans, inum, dirent, &target);
if (ret2 > 0) if (ret2 > 0)
continue; 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 bch_hash_info *hash_info,
struct inode_walker *dir, struct inode_walker *dir,
struct inode_walker *target, struct inode_walker *target,
struct snapshots_seen *s) struct snapshots_seen *s,
bool *need_second_pass)
{ {
struct bch_fs *c = trans->c; struct bch_fs *c = trans->c;
struct inode_walker_entry *i; 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); *hash_info = bch2_hash_info_init(c, &i->inode);
dir->first_this_inode = false; 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) if (ret < 0)
goto err; goto err;
if (ret) { if (ret) {
@ -2228,7 +2230,8 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
STR_HASH_must_create) ?: STR_HASH_must_create) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc); 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; goto out;
} }
@ -2296,7 +2299,6 @@ out:
err: err:
fsck_err: fsck_err:
printbuf_exit(&buf); printbuf_exit(&buf);
bch_err_fn(c, ret);
return ret; return ret;
} }
@ -2310,17 +2312,30 @@ int bch2_check_dirents(struct bch_fs *c)
struct inode_walker target = inode_walker_init(); struct inode_walker target = inode_walker_init();
struct snapshots_seen s; struct snapshots_seen s;
struct bch_hash_info hash_info; struct bch_hash_info hash_info;
bool need_second_pass = false, did_second_pass = false;
snapshots_seen_init(&s); snapshots_seen_init(&s);
again:
int ret = bch2_trans_run(c, int ret = bch2_trans_run(c,
for_each_btree_key_commit(trans, iter, BTREE_ID_dirents, for_each_btree_key_commit(trans, iter, BTREE_ID_dirents,
POS(BCACHEFS_ROOT_INO, 0), POS(BCACHEFS_ROOT_INO, 0),
BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k, BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k,
NULL, NULL, BCH_TRANS_COMMIT_no_enospc, 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)); 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); snapshots_seen_exit(&s);
inode_walker_exit(&dir); inode_walker_exit(&dir);
inode_walker_exit(&target); 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 inode_walker *inode)
{ {
struct bch_fs *c = trans->c; 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) if (ret < 0)
return ret; return ret;
if (ret) if (ret)
return 0; return 0;
i = walk_inode(trans, inode, k); struct inode_walker_entry *i = walk_inode(trans, inode, k);
ret = PTR_ERR_OR_ZERO(i); ret = PTR_ERR_OR_ZERO(i);
if (ret) if (ret)
return 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); *hash_info = bch2_hash_info_init(c, &i->inode);
inode->first_this_inode = false; inode->first_this_inode = false;
ret = bch2_str_hash_check_key(trans, NULL, &bch2_xattr_hash_desc, hash_info, iter, k); bool need_second_pass = false;
bch_err_fn(c, ret); return bch2_str_hash_check_key(trans, NULL, &bch2_xattr_hash_desc, hash_info,
return ret; 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, struct snapshots_seen *s,
const struct bch_hash_desc desc, const struct bch_hash_desc desc,
struct bch_hash_info *hash_info, 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 qstr old_name = bch2_dirent_get_name(old);
struct bkey_i_dirent *new = bch2_trans_kmalloc(trans, bkey_bytes(old.k) + 32); 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, old.k->p.snapshot, &new->k_i,
BTREE_UPDATE_internal_snapshot_node); BTREE_UPDATE_internal_snapshot_node);
if (ret && !bch2_err_matches(ret, EEXIST)) if (ret && !bch2_err_matches(ret, EEXIST))
goto err; break;
if (!ret) if (!ret) {
if (bpos_lt(new->k.p, old.k->p))
*updated_before_k_pos = true;
break; break;
} }
}
if (ret) ret = ret ?: bch2_fsck_update_backpointers(trans, s, desc, hash_info, &new->k_i);
goto err;
ret = bch2_fsck_update_backpointers(trans, s, desc, hash_info, &new->k_i);
err:
bch_err_fn(trans->c, ret); bch_err_fn(trans->c, ret);
return ret; return ret;
} }
@ -230,7 +230,8 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
struct snapshots_seen *s, struct snapshots_seen *s,
const struct bch_hash_desc *desc, const struct bch_hash_desc *desc,
struct bch_hash_info *hash_info, 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 bch_fs *c = trans->c;
struct btree_iter iter = {}; struct btree_iter iter = {};
@ -310,6 +311,9 @@ bad_hash:
if (k.k) if (k.k)
goto duplicate_entries; 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, ret = bch2_insert_snapshot_whiteouts(trans, desc->btree_id,
k_iter->pos, new->k.p) ?: k_iter->pos, new->k.p) ?:
bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 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); ret = bch2_hash_delete_at(trans, *desc, hash_info, &iter, 0);
break; break;
case 2: 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); bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
goto out; goto out;
} }

View file

@ -402,13 +402,15 @@ int __bch2_str_hash_check_key(struct btree_trans *,
struct snapshots_seen *, struct snapshots_seen *,
const struct bch_hash_desc *, const struct bch_hash_desc *,
struct bch_hash_info *, 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, static inline int bch2_str_hash_check_key(struct btree_trans *trans,
struct snapshots_seen *s, struct snapshots_seen *s,
const struct bch_hash_desc *desc, const struct bch_hash_desc *desc,
struct bch_hash_info *hash_info, 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) if (hash_k.k->type != desc->key_type)
return 0; 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)) if (likely(desc->hash_bkey(hash_info, hash_k) == hash_k.k->p.offset))
return 0; 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 */ #endif /* _BCACHEFS_STR_HASH_H */