bcachefs: Fix dirent_casefold_mismatch repair

Instead of simply recreating a mis-casefolded dirent, use the str_hash
repair code, which will rename it if necessary - the dirent might have
been created again with the correct casefolding.

Factor out out bch2_str_hash_repair key() from
__bch2_str_hash_check_key() for the new path to use, and export
bch2_dirent_create_key() as well.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2025-05-31 00:11:52 -04:00
parent b938d3c970
commit 2bf380c005
5 changed files with 162 additions and 118 deletions

View File

@ -288,7 +288,7 @@ int bch2_dirent_init_name(struct bkey_i_dirent *dirent,
return 0;
}
static struct bkey_i_dirent *dirent_create_key(struct btree_trans *trans,
struct bkey_i_dirent *bch2_dirent_create_key(struct btree_trans *trans,
const struct bch_hash_info *hash_info,
subvol_inum dir,
u8 type,
@ -332,7 +332,7 @@ int bch2_dirent_create_snapshot(struct btree_trans *trans,
struct bkey_i_dirent *dirent;
int ret;
dirent = dirent_create_key(trans, hash_info, dir_inum, type, name, NULL, dst_inum);
dirent = bch2_dirent_create_key(trans, hash_info, dir_inum, type, name, NULL, dst_inum);
ret = PTR_ERR_OR_ZERO(dirent);
if (ret)
return ret;
@ -356,7 +356,7 @@ int bch2_dirent_create(struct btree_trans *trans, subvol_inum dir,
struct bkey_i_dirent *dirent;
int ret;
dirent = dirent_create_key(trans, hash_info, dir, type, name, NULL, dst_inum);
dirent = bch2_dirent_create_key(trans, hash_info, dir, type, name, NULL, dst_inum);
ret = PTR_ERR_OR_ZERO(dirent);
if (ret)
return ret;
@ -461,8 +461,8 @@ int bch2_dirent_rename(struct btree_trans *trans,
*src_offset = dst_iter.pos.offset;
/* Create new dst key: */
new_dst = dirent_create_key(trans, dst_hash, dst_dir, 0, dst_name,
dst_hash->cf_encoding ? &dst_name_lookup : NULL, 0);
new_dst = bch2_dirent_create_key(trans, dst_hash, dst_dir, 0, dst_name,
dst_hash->cf_encoding ? &dst_name_lookup : NULL, 0);
ret = PTR_ERR_OR_ZERO(new_dst);
if (ret)
goto out;
@ -472,8 +472,8 @@ int bch2_dirent_rename(struct btree_trans *trans,
/* Create new src key: */
if (mode == BCH_RENAME_EXCHANGE) {
new_src = dirent_create_key(trans, src_hash, src_dir, 0, src_name,
src_hash->cf_encoding ? &src_name_lookup : NULL, 0);
new_src = bch2_dirent_create_key(trans, src_hash, src_dir, 0, src_name,
src_hash->cf_encoding ? &src_name_lookup : NULL, 0);
ret = PTR_ERR_OR_ZERO(new_src);
if (ret)
goto out;

View File

@ -63,6 +63,9 @@ int bch2_dirent_init_name(struct bkey_i_dirent *,
const struct bch_hash_info *,
const struct qstr *,
const struct qstr *);
struct bkey_i_dirent *bch2_dirent_create_key(struct btree_trans *,
const struct bch_hash_info *, subvol_inum, u8,
const struct qstr *, const struct qstr *, u64);
int bch2_dirent_create_snapshot(struct btree_trans *, u32, u64, u32,
const struct bch_hash_info *, u8,

View File

@ -2210,32 +2210,34 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, k),
buf.buf))) {
struct qstr name = bch2_dirent_get_name(d);
u32 subvol = d.v->d_type == DT_SUBVOL
? le32_to_cpu(d.v->d_parent_subvol)
: 0;
subvol_inum dir_inum = { .subvol = d.v->d_type == DT_SUBVOL
? le32_to_cpu(d.v->d_parent_subvol)
: 0,
};
u64 target = d.v->d_type == DT_SUBVOL
? le32_to_cpu(d.v->d_child_subvol)
: le64_to_cpu(d.v->d_inum);
u64 dir_offset;
struct qstr name = bch2_dirent_get_name(d);
ret = bch2_hash_delete_at(trans,
struct bkey_i_dirent *new_d =
bch2_dirent_create_key(trans, hash_info, dir_inum,
d.v->d_type, &name, NULL, target);
ret = PTR_ERR_OR_ZERO(new_d);
if (ret)
goto out;
new_d->k.p.inode = d.k->p.inode;
new_d->k.p.snapshot = d.k->p.snapshot;
struct btree_iter dup_iter = {};
ret = bch2_hash_delete_at(trans,
bch2_dirent_hash_desc, hash_info, iter,
BTREE_UPDATE_internal_snapshot_node) ?:
bch2_dirent_create_snapshot(trans, subvol,
d.k->p.inode, d.k->p.snapshot,
hash_info,
d.v->d_type,
&name,
target,
&dir_offset,
BTREE_ITER_with_updates|
BTREE_UPDATE_internal_snapshot_node|
STR_HASH_must_create) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc);
if (dir_offset < k.k->p.offset)
*need_second_pass = true;
bch2_str_hash_repair_key(trans, s,
&bch2_dirent_hash_desc, hash_info,
iter, bkey_i_to_s_c(&new_d->k_i),
&dup_iter, bkey_s_c_null,
need_second_pass);
goto out;
}

View File

@ -31,12 +31,12 @@ static int bch2_dirent_has_target(struct btree_trans *trans, struct bkey_s_c_dir
}
}
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,
bool *updated_before_k_pos)
static int bch2_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,
bool *updated_before_k_pos)
{
struct qstr old_name = bch2_dirent_get_name(old);
struct bkey_i_dirent *new = bch2_trans_kmalloc(trans, BKEY_U64s_MAX * sizeof(u64));
@ -233,54 +233,20 @@ static noinline int check_inode_hash_info_matches_root(struct btree_trans *trans
return ret;
}
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,
bool *updated_before_k_pos)
/* Put a str_hash key in its proper location, checking for duplicates */
int bch2_str_hash_repair_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 k,
struct btree_iter *dup_iter, struct bkey_s_c dup_k,
bool *updated_before_k_pos)
{
struct bch_fs *c = trans->c;
struct btree_iter iter = {};
struct printbuf buf = PRINTBUF;
struct bkey_s_c k;
bool free_snapshots_seen = false;
int ret = 0;
u64 hash = desc->hash_bkey(hash_info, hash_k);
if (hash_k.k->p.offset < hash)
goto bad_hash;
for_each_btree_key_norestart(trans, iter, desc->btree_id,
SPOS(hash_k.k->p.inode, hash, hash_k.k->p.snapshot),
BTREE_ITER_slots|
BTREE_ITER_with_updates, k, ret) {
if (bkey_eq(k.k->p, hash_k.k->p))
break;
if (k.k->type == desc->key_type &&
!desc->cmp_bkey(k, hash_k))
goto duplicate_entries;
if (bkey_deleted(k.k)) {
bch2_trans_iter_exit(trans, &iter);
goto bad_hash;
}
}
out:
bch2_trans_iter_exit(trans, &iter);
printbuf_exit(&buf);
if (free_snapshots_seen)
darray_exit(&s->ids);
return ret;
bad_hash:
/*
* Before doing any repair, check hash_info itself:
*/
ret = check_inode_hash_info_matches_root(trans, hash_k.k->p.inode, hash_info);
if (ret)
goto out;
if (!s) {
s = bch2_trans_kmalloc(trans, sizeof(*s));
ret = PTR_ERR_OR_ZERO(s);
@ -297,25 +263,22 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
free_snapshots_seen = true;
}
if (fsck_err(trans, hash_table_key_wrong_offset,
"hash table key at wrong offset: btree %s inode %llu offset %llu, hashed to %llu\n%s",
bch2_btree_id_str(desc->btree_id), hash_k.k->p.inode, hash_k.k->p.offset, hash,
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, hash_k), buf.buf))) {
struct bkey_i *new = bch2_bkey_make_mut_noupdate(trans, hash_k);
if (IS_ERR(new))
return PTR_ERR(new);
if (!dup_k.k) {
struct bkey_i *new = bch2_bkey_make_mut_noupdate(trans, k);
ret = PTR_ERR_OR_ZERO(new);
if (ret)
goto out;
k = bch2_hash_set_or_get_in_snapshot(trans, &iter, *desc, hash_info,
(subvol_inum) { 0, hash_k.k->p.inode },
hash_k.k->p.snapshot, new,
dup_k = bch2_hash_set_or_get_in_snapshot(trans, dup_iter, *desc, hash_info,
(subvol_inum) { 0, new->k.p.inode },
new->k.p.snapshot, new,
STR_HASH_must_create|
BTREE_ITER_with_updates|
BTREE_UPDATE_internal_snapshot_node);
ret = bkey_err(k);
ret = bkey_err(dup_k);
if (ret)
goto out;
if (k.k)
if (dup_k.k)
goto duplicate_entries;
if (bpos_lt(new->k.p, k.k->p))
@ -329,40 +292,108 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
bch2_fsck_update_backpointers(trans, s, *desc, hash_info, new) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc) ?:
-BCH_ERR_transaction_restart_commit;
goto out;
}
fsck_err:
goto out;
} else {
duplicate_entries:
ret = hash_pick_winner(trans, *desc, hash_info, hash_k, k);
if (ret < 0)
goto out;
ret = hash_pick_winner(trans, *desc, hash_info, k, dup_k);
if (ret < 0)
goto out;
if (!fsck_err(trans, hash_table_key_duplicate,
"duplicate hash table keys%s:\n%s",
ret != 2 ? "" : ", both point to valid inodes",
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, hash_k),
prt_newline(&buf),
bch2_bkey_val_to_text(&buf, c, k),
buf.buf)))
goto out;
if (!fsck_err(trans, hash_table_key_duplicate,
"duplicate hash table keys%s:\n%s",
ret != 2 ? "" : ", both point to valid inodes",
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, k),
prt_newline(&buf),
bch2_bkey_val_to_text(&buf, c, dup_k),
buf.buf)))
goto out;
switch (ret) {
case 0:
ret = bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
break;
case 1:
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),
updated_before_k_pos) ?:
bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
goto out;
switch (ret) {
case 0:
ret = bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
break;
case 1:
ret = bch2_hash_delete_at(trans, *desc, hash_info, dup_iter, 0);
break;
case 2:
ret = bch2_fsck_rename_dirent(trans, s, *desc, hash_info,
bkey_s_c_to_dirent(k),
updated_before_k_pos) ?:
bch2_hash_delete_at(trans, *desc, hash_info, k_iter,
BTREE_ITER_with_updates);
goto out;
}
ret = bch2_trans_commit(trans, NULL, NULL, 0) ?:
-BCH_ERR_transaction_restart_commit;
}
out:
fsck_err:
bch2_trans_iter_exit(trans, dup_iter);
printbuf_exit(&buf);
if (free_snapshots_seen)
darray_exit(&s->ids);
return ret;
}
ret = bch2_trans_commit(trans, NULL, NULL, 0) ?:
-BCH_ERR_transaction_restart_commit;
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,
bool *updated_before_k_pos)
{
struct bch_fs *c = trans->c;
struct btree_iter iter = {};
struct printbuf buf = PRINTBUF;
struct bkey_s_c k;
int ret = 0;
u64 hash = desc->hash_bkey(hash_info, hash_k);
if (hash_k.k->p.offset < hash)
goto bad_hash;
for_each_btree_key_norestart(trans, iter, desc->btree_id,
SPOS(hash_k.k->p.inode, hash, hash_k.k->p.snapshot),
BTREE_ITER_slots|
BTREE_ITER_with_updates, k, ret) {
if (bkey_eq(k.k->p, hash_k.k->p))
break;
if (k.k->type == desc->key_type &&
!desc->cmp_bkey(k, hash_k)) {
ret = check_inode_hash_info_matches_root(trans, hash_k.k->p.inode,
hash_info) ?:
bch2_str_hash_repair_key(trans, s, desc, hash_info,
k_iter, hash_k,
&iter, k, updated_before_k_pos);
break;
}
if (bkey_deleted(k.k))
goto bad_hash;
}
bch2_trans_iter_exit(trans, &iter);
out:
fsck_err:
printbuf_exit(&buf);
return ret;
bad_hash:
bch2_trans_iter_exit(trans, &iter);
/*
* Before doing any repair, check hash_info itself:
*/
ret = check_inode_hash_info_matches_root(trans, hash_k.k->p.inode, hash_info);
if (ret)
goto out;
if (fsck_err(trans, hash_table_key_wrong_offset,
"hash table key at wrong offset: should be at %llu\n%s",
hash,
(bch2_bkey_val_to_text(&buf, c, hash_k), buf.buf)))
ret = bch2_str_hash_repair_key(trans, s, desc, hash_info,
k_iter, hash_k,
&iter, bkey_s_c_null,
updated_before_k_pos);
goto out;
}

View File

@ -398,6 +398,14 @@ int bch2_hash_delete(struct btree_trans *trans,
int bch2_repair_inode_hash_info(struct btree_trans *, struct bch_inode_unpacked *);
struct snapshots_seen;
int bch2_str_hash_repair_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 *);
int __bch2_str_hash_check_key(struct btree_trans *,
struct snapshots_seen *,
const struct bch_hash_desc *,