mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-12-10 00:00:24 +03:00
296 lines
7.7 KiB
C
296 lines
7.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include "bcachefs.h"
|
|
|
|
#include "fs/check.h"
|
|
#include "fs/namei.h"
|
|
|
|
#include "init/progress.h"
|
|
|
|
static int dirent_points_to_inode(struct bch_fs *c,
|
|
struct bkey_s_c_dirent dirent,
|
|
struct bch_inode_unpacked *inode)
|
|
{
|
|
int ret = dirent_points_to_inode_nowarn(c, dirent, inode);
|
|
if (ret) {
|
|
CLASS(printbuf, buf)();
|
|
bch2_dirent_inode_mismatch_msg(&buf, c, dirent, inode);
|
|
bch_warn(c, "%s", buf.buf);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int remove_backpointer(struct btree_trans *trans,
|
|
struct bch_inode_unpacked *inode)
|
|
{
|
|
if (!bch2_inode_has_backpointer(inode))
|
|
return 0;
|
|
|
|
u32 snapshot = inode->bi_snapshot;
|
|
|
|
if (inode->bi_parent_subvol)
|
|
try(bch2_subvolume_get_snapshot(trans, inode->bi_parent_subvol, &snapshot));
|
|
|
|
struct bch_fs *c = trans->c;
|
|
CLASS(btree_iter_uninit, iter)(trans);
|
|
struct bkey_s_c_dirent d = bkey_try(dirent_get_by_pos(trans, &iter,
|
|
SPOS(inode->bi_dir, inode->bi_dir_offset, snapshot)));
|
|
|
|
try(dirent_points_to_inode(c, d, inode));
|
|
try(bch2_fsck_remove_dirent(trans, d.k->p));
|
|
return 0;
|
|
}
|
|
|
|
static int reattach_subvol(struct btree_trans *trans, struct bkey_s_c_subvolume s)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
|
|
struct bch_inode_unpacked inode;
|
|
try(bch2_inode_find_by_inum_trans(trans,
|
|
(subvol_inum) { s.k->p.offset, le64_to_cpu(s.v->inode) },
|
|
&inode));
|
|
|
|
int ret = remove_backpointer(trans, &inode);
|
|
if (!bch2_err_matches(ret, ENOENT))
|
|
bch_err_msg(c, ret, "removing dirent");
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = bch2_reattach_inode(trans, &inode);
|
|
bch_err_msg(c, ret, "reattaching inode %llu", inode.bi_inum);
|
|
return ret;
|
|
}
|
|
|
|
static int check_subvol_path(struct btree_trans *trans, struct btree_iter *iter, struct bkey_s_c k)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
CLASS(darray_u32, subvol_path)();
|
|
CLASS(printbuf, buf)();
|
|
int ret = 0;
|
|
|
|
if (k.k->type != KEY_TYPE_subvolume)
|
|
return 0;
|
|
|
|
CLASS(btree_iter, parent_iter)(trans, BTREE_ID_subvolumes, POS_MIN, 0);
|
|
|
|
subvol_inum start = {
|
|
.subvol = k.k->p.offset,
|
|
.inum = le64_to_cpu(bkey_s_c_to_subvolume(k).v->inode),
|
|
};
|
|
|
|
while (k.k->p.offset != BCACHEFS_ROOT_SUBVOL) {
|
|
try(darray_push(&subvol_path, k.k->p.offset));
|
|
|
|
struct bkey_s_c_subvolume s = bkey_s_c_to_subvolume(k);
|
|
|
|
struct bch_inode_unpacked subvol_root;
|
|
ret = bch2_inode_find_by_inum_trans(trans,
|
|
(subvol_inum) { s.k->p.offset, le64_to_cpu(s.v->inode) },
|
|
&subvol_root);
|
|
if (ret)
|
|
break;
|
|
|
|
u32 parent = le32_to_cpu(s.v->fs_path_parent);
|
|
|
|
if (darray_find(subvol_path, parent)) {
|
|
printbuf_reset(&buf);
|
|
prt_printf(&buf, "subvolume loop: ");
|
|
|
|
try(bch2_inum_to_path(trans, start, &buf));
|
|
|
|
if (fsck_err(trans, subvol_loop, "%s", buf.buf))
|
|
ret = reattach_subvol(trans, s);
|
|
break;
|
|
}
|
|
|
|
bch2_btree_iter_set_pos(&parent_iter, POS(0, parent));
|
|
k = bkey_try(bch2_btree_iter_peek_slot(&parent_iter));
|
|
|
|
if (fsck_err_on(k.k->type != KEY_TYPE_subvolume,
|
|
trans, subvol_unreachable,
|
|
"unreachable subvolume %s",
|
|
(printbuf_reset(&buf),
|
|
bch2_bkey_val_to_text(&buf, c, s.s_c),
|
|
buf.buf))) {
|
|
return reattach_subvol(trans, s);
|
|
}
|
|
}
|
|
fsck_err:
|
|
return ret;
|
|
}
|
|
|
|
int bch2_check_subvolume_structure(struct bch_fs *c)
|
|
{
|
|
CLASS(btree_trans, trans)(c);
|
|
|
|
struct progress_indicator progress;
|
|
bch2_progress_init(&progress, __func__, c, BIT_ULL(BTREE_ID_subvolumes), 0);
|
|
|
|
return for_each_btree_key_commit(trans, iter,
|
|
BTREE_ID_subvolumes, POS_MIN, BTREE_ITER_prefetch, k,
|
|
NULL, NULL, BCH_TRANS_COMMIT_no_enospc, ({
|
|
bch2_progress_update_iter(trans, &progress, &iter) ?:
|
|
check_subvol_path(trans, &iter, k);
|
|
}));
|
|
}
|
|
|
|
static int bch2_bi_depth_renumber_one(struct btree_trans *trans,
|
|
u64 inum, u32 snapshot,
|
|
u32 new_depth)
|
|
{
|
|
CLASS(btree_iter, iter)(trans, BTREE_ID_inodes, SPOS(0, inum, snapshot), 0);
|
|
struct bkey_s_c k = bkey_try(bch2_btree_iter_peek_slot(&iter));
|
|
|
|
try(!bkey_is_inode(k.k) ? -BCH_ERR_ENOENT_inode : 0);
|
|
|
|
struct bch_inode_unpacked inode;
|
|
try(bch2_inode_unpack(k, &inode));
|
|
|
|
if (inode.bi_depth != new_depth) {
|
|
inode.bi_depth = new_depth;
|
|
return __bch2_fsck_write_inode(trans, &inode) ?:
|
|
bch2_trans_commit(trans, NULL, NULL, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bch2_bi_depth_renumber(struct btree_trans *trans, darray_u64 *path,
|
|
u32 snapshot, u32 new_bi_depth)
|
|
{
|
|
u32 restart_count = trans->restart_count;
|
|
int ret = 0;
|
|
|
|
darray_for_each_reverse(*path, i) {
|
|
ret = nested_lockrestart_do(trans,
|
|
bch2_bi_depth_renumber_one(trans, *i, snapshot, new_bi_depth));
|
|
bch_err_fn(trans->c, ret);
|
|
if (ret)
|
|
break;
|
|
|
|
new_bi_depth++;
|
|
}
|
|
|
|
return ret ?: trans_was_restarted(trans, restart_count);
|
|
}
|
|
|
|
static int check_path_loop(struct btree_trans *trans, struct bkey_s_c inode_k)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
CLASS(darray_u64, path)();
|
|
CLASS(printbuf, buf)();
|
|
u32 snapshot = inode_k.k->p.snapshot;
|
|
bool redo_bi_depth = false;
|
|
u32 min_bi_depth = U32_MAX;
|
|
int ret = 0;
|
|
|
|
struct bpos start = inode_k.k->p;
|
|
|
|
struct bch_inode_unpacked inode;
|
|
try(bch2_inode_unpack(inode_k, &inode));
|
|
|
|
CLASS(btree_iter, inode_iter)(trans, BTREE_ID_inodes, POS_MIN, 0);
|
|
|
|
/*
|
|
* If we're running full fsck, check_dirents() will have already ran,
|
|
* and we shouldn't see any missing alloc/backpointers.here - otherwise that's
|
|
* handled separately, by check_unreachable_inodes
|
|
*/
|
|
while (!inode.bi_subvol &&
|
|
bch2_inode_has_backpointer(&inode)) {
|
|
CLASS(btree_iter_uninit, dirent_iter)(trans);
|
|
struct bkey_s_c_dirent d = dirent_get_by_pos(trans, &dirent_iter,
|
|
SPOS(inode.bi_dir, inode.bi_dir_offset, snapshot));
|
|
ret = bkey_err(d.s_c);
|
|
if (ret && !bch2_err_matches(ret, ENOENT))
|
|
return ret;
|
|
|
|
if (bch2_err_matches(ret, ENOENT)) {
|
|
printbuf_reset(&buf);
|
|
bch2_bkey_val_to_text(&buf, c, inode_k);
|
|
bch_err(c, "unreachable inode in check_directory_structure: %s\n%s",
|
|
bch2_err_str(ret), buf.buf);
|
|
return ret;
|
|
}
|
|
|
|
try(darray_push(&path, inode.bi_inum));
|
|
|
|
bch2_btree_iter_set_pos(&inode_iter, SPOS(0, inode.bi_dir, snapshot));
|
|
inode_k = bch2_btree_iter_peek_slot(&inode_iter);
|
|
|
|
struct bch_inode_unpacked parent_inode;
|
|
ret = bkey_err(inode_k) ?:
|
|
!bkey_is_inode(inode_k.k) ? -BCH_ERR_ENOENT_inode
|
|
: bch2_inode_unpack(inode_k, &parent_inode);
|
|
if (ret) {
|
|
/* Should have been caught in dirents pass */
|
|
bch_err_msg(c, ret, "error looking up parent directory");
|
|
return ret;
|
|
}
|
|
|
|
min_bi_depth = parent_inode.bi_depth;
|
|
|
|
if (parent_inode.bi_depth < inode.bi_depth &&
|
|
min_bi_depth < U16_MAX)
|
|
break;
|
|
|
|
inode = parent_inode;
|
|
redo_bi_depth = true;
|
|
|
|
if (darray_find(path, inode.bi_inum)) {
|
|
printbuf_reset(&buf);
|
|
prt_printf(&buf, "directory structure loop in snapshot %u: ",
|
|
snapshot);
|
|
|
|
try(bch2_inum_snapshot_to_path(trans, start.offset, start.snapshot, NULL, &buf));
|
|
|
|
if (c->opts.verbose) {
|
|
prt_newline(&buf);
|
|
darray_for_each(path, i)
|
|
prt_printf(&buf, "%llu ", *i);
|
|
}
|
|
|
|
if (fsck_err(trans, dir_loop, "%s", buf.buf)) {
|
|
ret = remove_backpointer(trans, &inode);
|
|
bch_err_msg(c, ret, "removing dirent");
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = bch2_reattach_inode(trans, &inode);
|
|
bch_err_msg(c, ret, "reattaching inode %llu", inode.bi_inum);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (inode.bi_subvol)
|
|
min_bi_depth = 0;
|
|
|
|
if (redo_bi_depth)
|
|
try(bch2_bi_depth_renumber(trans, &path, snapshot, min_bi_depth));
|
|
fsck_err:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Check for loops in the directory structure: all other connectivity issues
|
|
* have been fixed by prior passes
|
|
*/
|
|
int bch2_check_directory_structure(struct bch_fs *c)
|
|
{
|
|
CLASS(btree_trans, trans)(c);
|
|
return for_each_btree_key_reverse_commit(trans, iter, BTREE_ID_inodes, POS_MIN,
|
|
BTREE_ITER_intent|
|
|
BTREE_ITER_prefetch|
|
|
BTREE_ITER_all_snapshots, k,
|
|
NULL, NULL, BCH_TRANS_COMMIT_no_enospc, ({
|
|
if (!S_ISDIR(bkey_inode_mode(k)))
|
|
continue;
|
|
|
|
if (bch2_inode_flags(k) & BCH_INODE_unlinked)
|
|
continue;
|
|
|
|
check_path_loop(trans, k);
|
|
}));
|
|
}
|