bcachefs: fsck: Improve check_key_has_inode()

Print out more info when we find a key (extent, dirent, xattr) for a
missing inode - was there a good inode in an older snapshot, full(ish)
list of keys for that missing inode, so we can make better decisions on
how to repair.

If it looks like it should've been deleted, autofix it. If we ever hit
the non-autofix cases, we'll want to write more repair code (possibly
reconstituting the inode).

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet
2025-06-15 12:09:24 -04:00
parent 03208bd06a
commit f2a701fd94
2 changed files with 88 additions and 22 deletions
+87 -21
View File
@@ -1436,6 +1436,7 @@ static int check_key_has_inode(struct btree_trans *trans,
{
struct bch_fs *c = trans->c;
struct printbuf buf = PRINTBUF;
struct btree_iter iter2 = {};
int ret = PTR_ERR_OR_ZERO(i);
if (ret)
return ret;
@@ -1445,40 +1446,105 @@ static int check_key_has_inode(struct btree_trans *trans,
bool have_inode = i && !i->whiteout;
if (!have_inode && (c->sb.btrees_lost_data & BIT_ULL(BTREE_ID_inodes))) {
ret = reconstruct_inode(trans, iter->btree_id, k.k->p.snapshot, k.k->p.inode) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc);
if (ret)
goto err;
if (!have_inode && (c->sb.btrees_lost_data & BIT_ULL(BTREE_ID_inodes)))
goto reconstruct;
inode->last_pos.inode--;
ret = bch_err_throw(c, transaction_restart_nested);
goto err;
if (have_inode && btree_matches_i_mode(iter->btree_id, i->inode.bi_mode))
goto out;
prt_printf(&buf, ", ");
bool have_old_inode = false;
darray_for_each(inode->inodes, i2)
if (!i2->whiteout &&
bch2_snapshot_is_ancestor(c, k.k->p.snapshot, i2->inode.bi_snapshot) &&
btree_matches_i_mode(iter->btree_id, i2->inode.bi_mode)) {
prt_printf(&buf, "but found good inode in older snapshot\n");
bch2_inode_unpacked_to_text(&buf, &i2->inode);
prt_newline(&buf);
have_old_inode = true;
break;
}
struct bkey_s_c k2;
unsigned nr_keys = 0;
prt_printf(&buf, "found keys:\n");
for_each_btree_key_max_norestart(trans, iter2, iter->btree_id,
SPOS(k.k->p.inode, 0, k.k->p.snapshot),
POS(k.k->p.inode, U64_MAX),
0, k2, ret) {
nr_keys++;
if (nr_keys <= 10) {
bch2_bkey_val_to_text(&buf, c, k2);
prt_newline(&buf);
}
if (nr_keys >= 100)
break;
}
if (fsck_err_on(!have_inode,
trans, key_in_missing_inode,
"key in missing inode:\n%s",
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, k), buf.buf)))
goto delete;
if (ret)
goto err;
if (fsck_err_on(have_inode && !btree_matches_i_mode(iter->btree_id, i->inode.bi_mode),
trans, key_in_wrong_inode_type,
"key for wrong inode mode %o:\n%s",
i->inode.bi_mode,
(printbuf_reset(&buf),
bch2_bkey_val_to_text(&buf, c, k), buf.buf)))
goto delete;
if (nr_keys > 100)
prt_printf(&buf, "found > %u keys for this missing inode\n", nr_keys);
else if (nr_keys > 10)
prt_printf(&buf, "found %u keys for this missing inode\n", nr_keys);
if (!have_inode) {
if (fsck_err_on(!have_inode,
trans, key_in_missing_inode,
"key in missing inode%s", buf.buf)) {
/*
* Maybe a deletion that raced with data move, or something
* weird like that? But if we know the inode was deleted, or
* it's just a few keys, we can safely delete them.
*
* If it's many keys, we should probably recreate the inode
*/
if (have_old_inode || nr_keys <= 2)
goto delete;
else
goto reconstruct;
}
} else {
/*
* not autofix, this one would be a giant wtf - bit error in the
* inode corrupting i_mode?
*
* may want to try repairing inode instead of deleting
*/
if (fsck_err_on(!btree_matches_i_mode(iter->btree_id, i->inode.bi_mode),
trans, key_in_wrong_inode_type,
"key for wrong inode mode %o%s",
i->inode.bi_mode, buf.buf))
goto delete;
}
out:
err:
fsck_err:
bch2_trans_iter_exit(trans, &iter2);
printbuf_exit(&buf);
bch_err_fn(c, ret);
return ret;
delete:
/*
* XXX: print out more info
* count up extents for this inode, check if we have different inode in
* an older snapshot version, perhaps decide if we want to reconstitute
*/
ret = bch2_btree_delete_at(trans, iter, BTREE_UPDATE_internal_snapshot_node);
goto out;
reconstruct:
ret = reconstruct_inode(trans, iter->btree_id, k.k->p.snapshot, k.k->p.inode) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc);
if (ret)
goto err;
inode->last_pos.inode--;
ret = bch_err_throw(c, transaction_restart_nested);
goto out;
}
static int check_i_sectors_notnested(struct btree_trans *trans, struct inode_walker *w)
+1 -1
View File
@@ -251,7 +251,7 @@ enum bch_fsck_flags {
x(deleted_inode_not_unlinked, 214, FSCK_AUTOFIX) \
x(deleted_inode_has_child_snapshots, 288, FSCK_AUTOFIX) \
x(extent_overlapping, 215, 0) \
x(key_in_missing_inode, 216, 0) \
x(key_in_missing_inode, 216, FSCK_AUTOFIX) \
x(key_in_wrong_inode_type, 217, 0) \
x(extent_past_end_of_inode, 218, FSCK_AUTOFIX) \
x(dirent_empty_name, 219, 0) \