]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bcachefs: Fix subvol to missing root repair
authorKent Overstreet <kent.overstreet@linux.dev>
Mon, 2 Jun 2025 23:48:27 +0000 (19:48 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Wed, 4 Jun 2025 20:45:41 +0000 (16:45 -0400)
We had a bug where the root inode of a subvolume was erronously deleted:
bch2_evict_inode() called bch2_inode_rm(), meaning the VFS inode's
i_nlink was somehow set to 0 when it shouldn't have - the inode in the
btree indicated it clearly was not unlinked.

This has been addressed with additional safety checks in
bch2_inode_rm() - pulling in the safety checks we already were doing
when deleting unlinked inodes in recovery - but the really disastrous
bug was in check_subvols(), which on finding a dangling subvol (subvol
with a missing root inode) would delete the subvolume.

I assume this bug dates from early check_directory_structure() code,
which originally handled subvolumes and normal paths - the idea being
that still live contents of the subvolume would get reattached
somewhere.

But that's incorrect, and disastrously so; deleting a subvolume triggers
deleting the snapshot ID it points to, deleting the entire contents.

The correct way to repair is to recreate the root inode if it's missing;
then any contents will get reattached under that subvolume's lost+found.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/subvolume.c

index 60c10365c285741f5a7db695860bd106cdba5470..020587449123b1e93915724aef0a24e24306d9a4 100644 (file)
@@ -130,10 +130,20 @@ static int check_subvol(struct btree_trans *trans,
                             "subvolume %llu points to missing subvolume root %llu:%u",
                             k.k->p.offset, le64_to_cpu(subvol.v->inode),
                             le32_to_cpu(subvol.v->snapshot))) {
-                       ret = bch2_subvolume_delete(trans, iter->pos.offset);
-                       bch_err_msg(c, ret, "deleting subvolume %llu", iter->pos.offset);
-                       ret = ret ?: -BCH_ERR_transaction_restart_nested;
-                       goto err;
+                       /*
+                        * Recreate - any contents that are still disconnected
+                        * will then get reattached under lost+found
+                        */
+                       bch2_inode_init_early(c, &inode);
+                       bch2_inode_init_late(c, &inode, bch2_current_time(c),
+                                            0, 0, S_IFDIR|0700, 0, NULL);
+                       inode.bi_inum                   = le64_to_cpu(subvol.v->inode);
+                       inode.bi_snapshot               = le32_to_cpu(subvol.v->snapshot);
+                       inode.bi_subvol                 = k.k->p.offset;
+                       inode.bi_parent_subvol          = le32_to_cpu(subvol.v->fs_path_parent);
+                       ret = __bch2_fsck_write_inode(trans, &inode);
+                       if (ret)
+                               goto err;
                }
        } else {
                goto err;