bcachefs: Run check_dirents second time if required
authorKent Overstreet <kent.overstreet@linux.dev>
Sat, 31 May 2025 23:12:25 +0000 (19:12 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Mon, 2 Jun 2025 16:16:36 +0000 (12:16 -0400)
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>
fs/bcachefs/dirent.c
fs/bcachefs/fsck.c
fs/bcachefs/str_hash.c
fs/bcachefs/str_hash.h

index 62bb8825086159e4255bc82bb9fe026bc9c91f3a..0d77b7e3632c399d41ba181bcbe39ddff486f09f 100644 (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;
index a7bf949df816c09815e6e07dea7ac7488478f836..458f61c3395821fb920b5417bcfe825e72b37544 100644 (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);
 }
 
 /*
index 36d7ca7b0b1ac19565897148f5890e7c31bd8ed1..e6ecc8a549ba8ace310b6a76a4f8f0dc225c4f04 100644 (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;
        }
index ebcc006825cd5e58a956aff0a2fdebd602b8cadf..2d31bc2358d0ca6926805f1b39c65f7a910a1f56 100644 (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 */