bcachefs: Fix dirent_casefold_mismatch repair
authorKent Overstreet <kent.overstreet@linux.dev>
Sat, 31 May 2025 04:11:52 +0000 (00:11 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Wed, 4 Jun 2025 20:45:41 +0000 (16:45 -0400)
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>
fs/bcachefs/dirent.c
fs/bcachefs/dirent.h
fs/bcachefs/fsck.c
fs/bcachefs/str_hash.c
fs/bcachefs/str_hash.h

index 36bccd2..300f7cc 100644 (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;
index 31848b5..70fb0b5 100644 (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,
index 4a72dbd..68ed69a 100644 (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);
+
+               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;
 
-               ret =   bch2_hash_delete_at(trans,
+               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;
        }
 
index 7904bf1..71b735a 100644 (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 @@ bad_hash:
                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);
-
-               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,
+       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;
+
+               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 @@ bad_hash:
                        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;
+       } else {
+duplicate_entries:
+               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, 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, 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:
-       goto out;
-duplicate_entries:
-       ret = hash_pick_winner(trans, *desc, hash_info, hash_k, k);
-       if (ret < 0)
-               goto out;
+       bch2_trans_iter_exit(trans, dup_iter);
+       printbuf_exit(&buf);
+       if (free_snapshots_seen)
+               darray_exit(&s->ids);
+       return ret;
+}
 
-       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;
+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;
 
-       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;
+       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;
 
-       ret = bch2_trans_commit(trans, NULL, NULL, 0) ?:
-               -BCH_ERR_transaction_restart_commit;
+       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;
 }
index 2d31bc2..79d51ae 100644 (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 *,