mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-12-09 00:00:17 +03:00
779 lines
22 KiB
C
779 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include "bcachefs.h"
|
|
|
|
#include "alloc/background.h"
|
|
#include "alloc/check.h"
|
|
#include "alloc/lru.h"
|
|
|
|
#include "btree/cache.h"
|
|
#include "btree/update.h"
|
|
#include "btree/write_buffer.h"
|
|
|
|
#include "data/ec.h"
|
|
|
|
#include "init/error.h"
|
|
#include "init/progress.h"
|
|
|
|
/*
|
|
* This synthesizes deleted extents for holes, similar to BTREE_ITER_slots for
|
|
* extents style btrees, but works on non-extents btrees:
|
|
*/
|
|
static struct bkey_s_c bch2_get_key_or_hole(struct btree_iter *iter, struct bpos end, struct bkey *hole)
|
|
{
|
|
struct bkey_s_c k = bch2_btree_iter_peek_slot(iter);
|
|
|
|
if (bkey_err(k))
|
|
return k;
|
|
|
|
if (k.k->type) {
|
|
return k;
|
|
} else {
|
|
CLASS(btree_iter_copy, iter2)(iter);
|
|
|
|
struct btree_path *path = btree_iter_path(iter->trans, iter);
|
|
if (!bpos_eq(path->l[0].b->key.k.p, SPOS_MAX))
|
|
end = bkey_min(end, bpos_nosnap_successor(path->l[0].b->key.k.p));
|
|
|
|
end = bkey_min(end, POS(iter->pos.inode, iter->pos.offset + U32_MAX - 1));
|
|
|
|
/*
|
|
* btree node min/max is a closed interval, upto takes a half
|
|
* open interval:
|
|
*/
|
|
k = bch2_btree_iter_peek_max(&iter2, end);
|
|
if (bkey_err(k))
|
|
return k;
|
|
|
|
struct bpos next = iter2.pos;
|
|
BUG_ON(next.offset >= iter->pos.offset + U32_MAX);
|
|
|
|
bkey_init(hole);
|
|
hole->p = iter->pos;
|
|
|
|
bch2_key_resize(hole, next.offset - iter->pos.offset);
|
|
return (struct bkey_s_c) { hole, NULL };
|
|
}
|
|
}
|
|
|
|
static bool next_bucket(struct bch_fs *c, struct bch_dev **ca, struct bpos *bucket)
|
|
{
|
|
if (*ca) {
|
|
if (bucket->offset < (*ca)->mi.first_bucket)
|
|
bucket->offset = (*ca)->mi.first_bucket;
|
|
|
|
if (bucket->offset < (*ca)->mi.nbuckets)
|
|
return true;
|
|
|
|
bch2_dev_put(*ca);
|
|
*ca = NULL;
|
|
bucket->inode++;
|
|
bucket->offset = 0;
|
|
}
|
|
|
|
guard(rcu)();
|
|
*ca = __bch2_next_dev_idx(c, bucket->inode, NULL);
|
|
if (*ca) {
|
|
*bucket = POS((*ca)->dev_idx, (*ca)->mi.first_bucket);
|
|
bch2_dev_get(*ca);
|
|
}
|
|
|
|
return *ca != NULL;
|
|
}
|
|
|
|
static struct bkey_s_c bch2_get_key_or_real_bucket_hole(struct btree_iter *iter,
|
|
struct bch_dev **ca, struct bkey *hole)
|
|
{
|
|
while (true) {
|
|
struct bch_fs *c = iter->trans->c;
|
|
struct bkey_s_c k = bch2_get_key_or_hole(iter, POS_MAX, hole);
|
|
if (bkey_err(k))
|
|
return k;
|
|
|
|
*ca = bch2_dev_iterate_noerror(c, *ca, k.k->p.inode);
|
|
|
|
if (!k.k->type) {
|
|
struct bpos hole_start = bkey_start_pos(k.k);
|
|
|
|
if (!*ca || !bucket_valid(*ca, hole_start.offset)) {
|
|
if (!next_bucket(c, ca, &hole_start))
|
|
return bkey_s_c_null;
|
|
|
|
bch2_btree_iter_set_pos(iter, hole_start);
|
|
continue;
|
|
}
|
|
|
|
if (k.k->p.offset > (*ca)->mi.nbuckets)
|
|
bch2_key_resize(hole, (*ca)->mi.nbuckets - hole_start.offset);
|
|
}
|
|
|
|
return k;
|
|
}
|
|
}
|
|
|
|
int bch2_need_discard_or_freespace_err(struct btree_trans *trans,
|
|
struct bkey_s_c alloc_k,
|
|
bool set, bool discard, bool repair)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
enum bch_fsck_flags flags = FSCK_CAN_IGNORE|(repair ? FSCK_CAN_FIX : 0);
|
|
enum bch_sb_error_id err_id = discard
|
|
? BCH_FSCK_ERR_need_discard_key_wrong
|
|
: BCH_FSCK_ERR_freespace_key_wrong;
|
|
enum btree_id btree = discard ? BTREE_ID_need_discard : BTREE_ID_freespace;
|
|
CLASS(printbuf, buf)();
|
|
|
|
bch2_bkey_val_to_text(&buf, c, alloc_k);
|
|
|
|
int ret = __bch2_fsck_err(NULL, trans, flags, err_id,
|
|
"bucket incorrectly %sset in %s btree\n%s",
|
|
set ? "" : "un",
|
|
bch2_btree_id_str(btree),
|
|
buf.buf);
|
|
if (bch2_err_matches(ret, BCH_ERR_fsck_ignore) ||
|
|
bch2_err_matches(ret, BCH_ERR_fsck_errors_not_fixed))
|
|
ret = 0;
|
|
return ret;
|
|
}
|
|
|
|
static noinline_for_stack
|
|
int bch2_check_alloc_key(struct btree_trans *trans,
|
|
struct bkey_s_c alloc_k,
|
|
struct btree_iter *alloc_iter,
|
|
struct btree_iter *discard_iter,
|
|
struct btree_iter *freespace_iter,
|
|
struct btree_iter *bucket_gens_iter)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct bch_alloc_v4 a_convert;
|
|
const struct bch_alloc_v4 *a;
|
|
unsigned gens_offset;
|
|
struct bkey_s_c k;
|
|
CLASS(printbuf, buf)();
|
|
int ret = 0;
|
|
|
|
CLASS(bch2_dev_bucket_tryget_noerror, ca)(c, alloc_k.k->p);
|
|
if (ret_fsck_err_on(!ca,
|
|
trans, alloc_key_to_missing_dev_bucket,
|
|
"alloc key for invalid device:bucket %llu:%llu",
|
|
alloc_k.k->p.inode, alloc_k.k->p.offset))
|
|
try(bch2_btree_delete_at(trans, alloc_iter, 0));
|
|
if (!ca)
|
|
return 0;
|
|
|
|
if (!ca->mi.freespace_initialized)
|
|
return 0;
|
|
|
|
a = bch2_alloc_to_v4(alloc_k, &a_convert);
|
|
|
|
bch2_btree_iter_set_pos(discard_iter, alloc_k.k->p);
|
|
k = bkey_try(bch2_btree_iter_peek_slot(discard_iter));
|
|
|
|
bool is_discarded = a->data_type == BCH_DATA_need_discard;
|
|
if (need_discard_or_freespace_err_on(!!k.k->type != is_discarded,
|
|
trans, alloc_k, !is_discarded, true, true))
|
|
try(bch2_btree_bit_mod_iter(trans, discard_iter, is_discarded));
|
|
|
|
bch2_btree_iter_set_pos(freespace_iter, alloc_freespace_pos(alloc_k.k->p, *a));
|
|
k = bkey_try(bch2_btree_iter_peek_slot(freespace_iter));
|
|
|
|
bool is_free = a->data_type == BCH_DATA_free;
|
|
if (need_discard_or_freespace_err_on(!!k.k->type != is_free,
|
|
trans, alloc_k, !is_free, false, true))
|
|
try(bch2_btree_bit_mod_iter(trans, freespace_iter, is_free));
|
|
|
|
bch2_btree_iter_set_pos(bucket_gens_iter, alloc_gens_pos(alloc_k.k->p, &gens_offset));
|
|
k = bkey_try(bch2_btree_iter_peek_slot(bucket_gens_iter));
|
|
|
|
if (ret_fsck_err_on(a->gen != alloc_gen(k, gens_offset),
|
|
trans, bucket_gens_key_wrong,
|
|
"incorrect gen in bucket_gens btree (got %u should be %u)\n%s",
|
|
alloc_gen(k, gens_offset), a->gen,
|
|
(printbuf_reset(&buf),
|
|
bch2_bkey_val_to_text(&buf, c, alloc_k), buf.buf))) {
|
|
struct bkey_i_bucket_gens *g =
|
|
errptr_try(bch2_trans_kmalloc(trans, sizeof(*g)));
|
|
|
|
if (k.k->type == KEY_TYPE_bucket_gens) {
|
|
bkey_reassemble(&g->k_i, k);
|
|
} else {
|
|
bkey_bucket_gens_init(&g->k_i);
|
|
g->k.p = alloc_gens_pos(alloc_k.k->p, &gens_offset);
|
|
}
|
|
|
|
g->v.gens[gens_offset] = a->gen;
|
|
|
|
try(bch2_trans_update(trans, bucket_gens_iter, &g->k_i, 0));
|
|
}
|
|
fsck_err:
|
|
return ret;
|
|
}
|
|
|
|
static noinline_for_stack
|
|
int bch2_check_alloc_hole_freespace(struct btree_trans *trans,
|
|
struct bch_dev *ca,
|
|
struct bpos start,
|
|
struct bpos *end,
|
|
struct btree_iter *freespace_iter)
|
|
{
|
|
CLASS(printbuf, buf)();
|
|
|
|
if (!ca->mi.freespace_initialized)
|
|
return 0;
|
|
|
|
bch2_btree_iter_set_pos(freespace_iter, start);
|
|
|
|
struct bkey_s_c k = bkey_try(bch2_btree_iter_peek_slot(freespace_iter));
|
|
|
|
*end = bkey_min(k.k->p, *end);
|
|
|
|
if (ret_fsck_err_on(k.k->type != KEY_TYPE_set,
|
|
trans, freespace_hole_missing,
|
|
"hole in alloc btree missing in freespace btree\n"
|
|
"device %llu buckets %llu-%llu",
|
|
freespace_iter->pos.inode,
|
|
freespace_iter->pos.offset,
|
|
end->offset)) {
|
|
struct bkey_i *update =
|
|
errptr_try(bch2_trans_kmalloc(trans, sizeof(*update)));
|
|
|
|
bkey_init(&update->k);
|
|
update->k.type = KEY_TYPE_set;
|
|
update->k.p = freespace_iter->pos;
|
|
bch2_key_resize(&update->k,
|
|
min_t(u64, U32_MAX, end->offset -
|
|
freespace_iter->pos.offset));
|
|
|
|
try(bch2_trans_update(trans, freespace_iter, update, 0));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static noinline_for_stack
|
|
int bch2_check_alloc_hole_bucket_gens(struct btree_trans *trans,
|
|
struct bpos start,
|
|
struct bpos *end,
|
|
struct btree_iter *bucket_gens_iter)
|
|
{
|
|
CLASS(printbuf, buf)();
|
|
unsigned gens_offset, gens_end_offset;
|
|
|
|
bch2_btree_iter_set_pos(bucket_gens_iter, alloc_gens_pos(start, &gens_offset));
|
|
|
|
struct bkey_s_c k = bkey_try(bch2_btree_iter_peek_slot(bucket_gens_iter));
|
|
|
|
if (bkey_cmp(alloc_gens_pos(start, &gens_offset),
|
|
alloc_gens_pos(*end, &gens_end_offset)))
|
|
gens_end_offset = KEY_TYPE_BUCKET_GENS_NR;
|
|
|
|
if (k.k->type == KEY_TYPE_bucket_gens) {
|
|
struct bkey_i_bucket_gens g;
|
|
bool need_update = false;
|
|
|
|
bkey_reassemble(&g.k_i, k);
|
|
|
|
for (unsigned i = gens_offset; i < gens_end_offset; i++) {
|
|
if (ret_fsck_err_on(g.v.gens[i], trans,
|
|
bucket_gens_hole_wrong,
|
|
"hole in alloc btree at %llu:%llu with nonzero gen in bucket_gens btree (%u)",
|
|
bucket_gens_pos_to_alloc(k.k->p, i).inode,
|
|
bucket_gens_pos_to_alloc(k.k->p, i).offset,
|
|
g.v.gens[i])) {
|
|
g.v.gens[i] = 0;
|
|
need_update = true;
|
|
}
|
|
}
|
|
|
|
if (need_update) {
|
|
struct bkey_i *u = errptr_try(bch2_trans_kmalloc(trans, sizeof(g)));
|
|
|
|
memcpy(u, &g, sizeof(g));
|
|
|
|
try(bch2_trans_update(trans, bucket_gens_iter, u, 0));
|
|
}
|
|
}
|
|
|
|
*end = bkey_min(*end, bucket_gens_pos_to_alloc(bpos_nosnap_successor(k.k->p), 0));
|
|
return 0;
|
|
}
|
|
|
|
struct check_discard_freespace_key_async {
|
|
struct work_struct work;
|
|
struct bch_fs *c;
|
|
struct bbpos pos;
|
|
};
|
|
|
|
static int bch2_recheck_discard_freespace_key(struct btree_trans *trans, struct bbpos pos)
|
|
{
|
|
CLASS(btree_iter, iter)(trans, pos.btree, pos.pos, 0);
|
|
struct bkey_s_c k = bkey_try(bch2_btree_iter_peek_slot(&iter));
|
|
|
|
u8 gen;
|
|
return k.k->type != KEY_TYPE_set
|
|
? __bch2_check_discard_freespace_key(trans, &iter, &gen, FSCK_ERR_SILENT)
|
|
: 0;
|
|
}
|
|
|
|
static void check_discard_freespace_key_work(struct work_struct *work)
|
|
{
|
|
struct check_discard_freespace_key_async *w =
|
|
container_of(work, struct check_discard_freespace_key_async, work);
|
|
|
|
bch2_trans_do(w->c, bch2_recheck_discard_freespace_key(trans, w->pos));
|
|
enumerated_ref_put(&w->c->writes, BCH_WRITE_REF_check_discard_freespace_key);
|
|
kfree(w);
|
|
}
|
|
|
|
static int delete_discard_freespace_key(struct btree_trans *trans,
|
|
struct btree_iter *iter,
|
|
bool async_repair)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
|
|
if (!async_repair) {
|
|
try(bch2_btree_bit_mod_iter(trans, iter, false));
|
|
try(bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc));
|
|
return bch_err_throw(c, transaction_restart_commit);
|
|
} else {
|
|
/*
|
|
* We can't repair here when called from the allocator path: the
|
|
* commit will recurse back into the allocator
|
|
*
|
|
* Returning 1 indicates to the caller of
|
|
* check_discard_freespace_key() - "don't allocate this bucket"
|
|
*/
|
|
struct check_discard_freespace_key_async *w = kzalloc(sizeof(*w), GFP_KERNEL);
|
|
if (!w)
|
|
return 1;
|
|
|
|
if (!enumerated_ref_tryget(&c->writes, BCH_WRITE_REF_check_discard_freespace_key)) {
|
|
kfree(w);
|
|
return 1;
|
|
}
|
|
|
|
INIT_WORK(&w->work, check_discard_freespace_key_work);
|
|
w->c = c;
|
|
w->pos = BBPOS(iter->btree_id, iter->pos);
|
|
queue_work(c->write_ref_wq, &w->work);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int __bch2_check_discard_freespace_key(struct btree_trans *trans, struct btree_iter *iter, u8 *gen,
|
|
enum bch_fsck_flags fsck_flags)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
enum bch_data_type state = iter->btree_id == BTREE_ID_need_discard
|
|
? BCH_DATA_need_discard
|
|
: BCH_DATA_free;
|
|
CLASS(printbuf, buf)();
|
|
int ret = 0;
|
|
|
|
bool async_repair = fsck_flags & FSCK_ERR_NO_LOG;
|
|
fsck_flags |= FSCK_CAN_FIX|FSCK_CAN_IGNORE;
|
|
|
|
struct bpos bucket = iter->pos;
|
|
bucket.offset &= ~(~0ULL << 56);
|
|
u64 genbits = iter->pos.offset & (~0ULL << 56);
|
|
|
|
CLASS(btree_iter, alloc_iter)(trans, BTREE_ID_alloc, bucket,
|
|
async_repair ? BTREE_ITER_cached : 0);
|
|
struct bkey_s_c alloc_k = bkey_try(bch2_btree_iter_peek_slot(&alloc_iter));
|
|
|
|
if (!bch2_dev_bucket_exists(c, bucket)) {
|
|
if (__fsck_err(trans, fsck_flags,
|
|
need_discard_freespace_key_to_invalid_dev_bucket,
|
|
"entry in %s btree for nonexistant dev:bucket %llu:%llu",
|
|
bch2_btree_id_str(iter->btree_id), bucket.inode, bucket.offset))
|
|
ret = delete_discard_freespace_key(trans, iter, async_repair);
|
|
else
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
|
|
struct bch_alloc_v4 a_convert;
|
|
const struct bch_alloc_v4 *a = bch2_alloc_to_v4(alloc_k, &a_convert);
|
|
|
|
if (a->data_type != state ||
|
|
(state == BCH_DATA_free &&
|
|
genbits != alloc_freespace_genbits(*a))) {
|
|
if (__fsck_err(trans, fsck_flags,
|
|
need_discard_freespace_key_bad,
|
|
"%s\nincorrectly set at %s:%llu:%llu:0 (free %u, genbits %llu should be %llu)",
|
|
(bch2_bkey_val_to_text(&buf, c, alloc_k), buf.buf),
|
|
bch2_btree_id_str(iter->btree_id),
|
|
iter->pos.inode,
|
|
iter->pos.offset,
|
|
a->data_type == state,
|
|
genbits >> 56, alloc_freespace_genbits(*a) >> 56))
|
|
ret = delete_discard_freespace_key(trans, iter, async_repair);
|
|
else
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
|
|
*gen = a->gen;
|
|
out:
|
|
fsck_err:
|
|
bch2_set_btree_iter_dontneed(&alloc_iter);
|
|
return ret;
|
|
}
|
|
|
|
static int bch2_check_discard_freespace_key(struct btree_trans *trans, struct btree_iter *iter)
|
|
{
|
|
u8 gen;
|
|
int ret = __bch2_check_discard_freespace_key(trans, iter, &gen, 0);
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
/*
|
|
* We've already checked that generation numbers in the bucket_gens btree are
|
|
* valid for buckets that exist; this just checks for keys for nonexistent
|
|
* buckets.
|
|
*/
|
|
static noinline_for_stack
|
|
int bch2_check_bucket_gens_key(struct btree_trans *trans,
|
|
struct btree_iter *iter,
|
|
struct bkey_s_c k)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct bkey_i_bucket_gens g;
|
|
u64 start = bucket_gens_pos_to_alloc(k.k->p, 0).offset;
|
|
u64 end = bucket_gens_pos_to_alloc(bpos_nosnap_successor(k.k->p), 0).offset;
|
|
u64 b;
|
|
bool need_update = false;
|
|
CLASS(printbuf, buf)();
|
|
|
|
BUG_ON(k.k->type != KEY_TYPE_bucket_gens);
|
|
bkey_reassemble(&g.k_i, k);
|
|
|
|
CLASS(bch2_dev_tryget_noerror, ca)(c, k.k->p.inode);
|
|
if (!ca) {
|
|
if (ret_fsck_err(trans, bucket_gens_to_invalid_dev,
|
|
"bucket_gens key for invalid device:\n%s",
|
|
(bch2_bkey_val_to_text(&buf, c, k), buf.buf)))
|
|
return bch2_btree_delete_at(trans, iter, 0);
|
|
return 0;
|
|
}
|
|
|
|
if (ret_fsck_err_on(end <= ca->mi.first_bucket ||
|
|
start >= ca->mi.nbuckets,
|
|
trans, bucket_gens_to_invalid_buckets,
|
|
"bucket_gens key for invalid buckets:\n%s",
|
|
(bch2_bkey_val_to_text(&buf, c, k), buf.buf))) {
|
|
return bch2_btree_delete_at(trans, iter, 0);
|
|
}
|
|
|
|
for (b = start; b < ca->mi.first_bucket; b++)
|
|
if (ret_fsck_err_on(g.v.gens[b & KEY_TYPE_BUCKET_GENS_MASK],
|
|
trans, bucket_gens_nonzero_for_invalid_buckets,
|
|
"bucket_gens key has nonzero gen for invalid bucket")) {
|
|
g.v.gens[b & KEY_TYPE_BUCKET_GENS_MASK] = 0;
|
|
need_update = true;
|
|
}
|
|
|
|
for (b = ca->mi.nbuckets; b < end; b++)
|
|
if (ret_fsck_err_on(g.v.gens[b & KEY_TYPE_BUCKET_GENS_MASK],
|
|
trans, bucket_gens_nonzero_for_invalid_buckets,
|
|
"bucket_gens key has nonzero gen for invalid bucket")) {
|
|
g.v.gens[b & KEY_TYPE_BUCKET_GENS_MASK] = 0;
|
|
need_update = true;
|
|
}
|
|
|
|
if (need_update) {
|
|
struct bkey_i *u = errptr_try(bch2_trans_kmalloc(trans, sizeof(g)));
|
|
|
|
memcpy(u, &g, sizeof(g));
|
|
try(bch2_trans_update(trans, iter, u, 0));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_btree_alloc_iter(struct btree_trans *trans,
|
|
struct bch_dev **ca,
|
|
struct btree_iter *iter,
|
|
struct btree_iter *discard_iter,
|
|
struct btree_iter *freespace_iter,
|
|
struct btree_iter *bucket_gens_iter,
|
|
struct progress_indicator *progress)
|
|
{
|
|
struct bkey hole;
|
|
struct bkey_s_c k = bkey_try(bch2_get_key_or_real_bucket_hole(iter, ca, &hole));
|
|
|
|
if (!k.k)
|
|
return 1;
|
|
|
|
try(progress_update_iter(trans, progress, iter));
|
|
|
|
struct bpos next;
|
|
if (k.k->type) {
|
|
next = bpos_nosnap_successor(k.k->p);
|
|
|
|
try(bch2_check_alloc_key(trans, k, iter,
|
|
discard_iter,
|
|
freespace_iter,
|
|
bucket_gens_iter));
|
|
} else {
|
|
next = k.k->p;
|
|
|
|
try(bch2_check_alloc_hole_freespace(trans, *ca,
|
|
bkey_start_pos(k.k),
|
|
&next,
|
|
freespace_iter));
|
|
try(bch2_check_alloc_hole_bucket_gens(trans,
|
|
bkey_start_pos(k.k),
|
|
&next,
|
|
bucket_gens_iter));
|
|
}
|
|
|
|
try(bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc));
|
|
|
|
bch2_btree_iter_set_pos(iter, next);
|
|
return 0;
|
|
}
|
|
|
|
static int check_btree_alloc(struct btree_trans *trans)
|
|
{
|
|
struct progress_indicator progress;
|
|
bch2_progress_init(&progress, trans->c, BIT_ULL(BTREE_ID_alloc));
|
|
|
|
CLASS(btree_iter, iter)(trans, BTREE_ID_alloc, POS_MIN, BTREE_ITER_prefetch);
|
|
CLASS(btree_iter, discard_iter)(trans, BTREE_ID_need_discard, POS_MIN, BTREE_ITER_prefetch);
|
|
CLASS(btree_iter, freespace_iter)(trans, BTREE_ID_freespace, POS_MIN, BTREE_ITER_prefetch);
|
|
CLASS(btree_iter, bucket_gens_iter)(trans, BTREE_ID_bucket_gens, POS_MIN, BTREE_ITER_prefetch);
|
|
|
|
struct bch_dev *ca __free(bch2_dev_put) = NULL;
|
|
int ret = 0;
|
|
|
|
while (!(ret = lockrestart_do(trans,
|
|
check_btree_alloc_iter(trans, &ca, &iter,
|
|
&discard_iter,
|
|
&freespace_iter,
|
|
&bucket_gens_iter,
|
|
&progress))))
|
|
;
|
|
|
|
return min(0, ret);
|
|
}
|
|
|
|
int bch2_check_alloc_info(struct bch_fs *c)
|
|
{
|
|
CLASS(btree_trans, trans)(c);
|
|
|
|
try(check_btree_alloc(trans));
|
|
|
|
try(for_each_btree_key(trans, iter,
|
|
BTREE_ID_need_discard, POS_MIN,
|
|
BTREE_ITER_prefetch, k,
|
|
bch2_check_discard_freespace_key(trans, &iter)));
|
|
|
|
{
|
|
/*
|
|
* Check freespace btree: we're iterating over every individual
|
|
* pos of the freespace keys
|
|
*/
|
|
CLASS(btree_iter, iter)(trans, BTREE_ID_freespace, POS_MIN,
|
|
BTREE_ITER_prefetch);
|
|
while (1) {
|
|
bch2_trans_begin(trans);
|
|
struct bkey_s_c k = bch2_btree_iter_peek(&iter);
|
|
if (!k.k)
|
|
break;
|
|
|
|
int ret = bkey_err(k) ?:
|
|
bch2_check_discard_freespace_key(trans, &iter);
|
|
if (bch2_err_matches(ret, BCH_ERR_transaction_restart)) {
|
|
ret = 0;
|
|
continue;
|
|
}
|
|
if (ret) {
|
|
CLASS(printbuf, buf)();
|
|
bch2_bkey_val_to_text(&buf, c, k);
|
|
bch_err(c, "while checking %s", buf.buf);
|
|
return ret;
|
|
}
|
|
|
|
bch2_btree_iter_set_pos(&iter, bpos_nosnap_successor(iter.pos));
|
|
}
|
|
}
|
|
|
|
try(for_each_btree_key_commit(trans, iter,
|
|
BTREE_ID_bucket_gens, POS_MIN,
|
|
BTREE_ITER_prefetch, k,
|
|
NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
|
|
bch2_check_bucket_gens_key(trans, &iter, k)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bch2_check_alloc_to_lru_ref(struct btree_trans *trans,
|
|
struct btree_iter *alloc_iter,
|
|
struct wb_maybe_flush *last_flushed)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct bch_alloc_v4 a_convert;
|
|
const struct bch_alloc_v4 *a;
|
|
CLASS(printbuf, buf)();
|
|
|
|
struct bkey_s_c alloc_k = bkey_try(bch2_btree_iter_peek(alloc_iter));
|
|
if (!alloc_k.k)
|
|
return 0;
|
|
|
|
CLASS(bch2_dev_tryget_noerror, ca)(c, alloc_k.k->p.inode);
|
|
if (!ca)
|
|
return 0;
|
|
|
|
a = bch2_alloc_to_v4(alloc_k, &a_convert);
|
|
|
|
u64 lru_idx = alloc_lru_idx_fragmentation(*a, ca);
|
|
if (lru_idx)
|
|
try(bch2_lru_check_set(trans, BCH_LRU_BUCKET_FRAGMENTATION,
|
|
bucket_to_u64(alloc_k.k->p),
|
|
lru_idx, alloc_k, last_flushed));
|
|
|
|
if (a->data_type == BCH_DATA_cached) {
|
|
if (ret_fsck_err_on(!a->io_time[READ],
|
|
trans, alloc_key_cached_but_read_time_zero,
|
|
"cached bucket with read_time 0\n%s",
|
|
(printbuf_reset(&buf),
|
|
bch2_bkey_val_to_text(&buf, c, alloc_k), buf.buf))) {
|
|
struct bkey_i_alloc_v4 *a_mut =
|
|
errptr_try(bch2_alloc_to_v4_mut(trans, alloc_k));
|
|
|
|
a_mut->v.io_time[READ] = bch2_current_io_time(c, READ);
|
|
try(bch2_trans_update(trans, alloc_iter,
|
|
&a_mut->k_i, BTREE_TRIGGER_norun));
|
|
|
|
a = &a_mut->v;
|
|
}
|
|
|
|
try(bch2_lru_check_set(trans, alloc_k.k->p.inode,
|
|
bucket_to_u64(alloc_k.k->p),
|
|
a->io_time[READ],
|
|
alloc_k, last_flushed));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bch2_check_alloc_to_lru_refs(struct bch_fs *c)
|
|
{
|
|
struct wb_maybe_flush last_flushed __cleanup(wb_maybe_flush_exit);
|
|
wb_maybe_flush_init(&last_flushed);
|
|
|
|
struct progress_indicator progress;
|
|
bch2_progress_init(&progress, c, BIT_ULL(BTREE_ID_alloc));
|
|
|
|
CLASS(btree_trans, trans)(c);
|
|
return for_each_btree_key_commit(trans, iter, BTREE_ID_alloc,
|
|
POS_MIN, BTREE_ITER_prefetch, k,
|
|
NULL, NULL, BCH_TRANS_COMMIT_no_enospc, ({
|
|
progress_update_iter(trans, &progress, &iter) ?:
|
|
wb_maybe_flush_inc(&last_flushed) ?:
|
|
bch2_check_alloc_to_lru_ref(trans, &iter, &last_flushed);
|
|
}))?: bch2_check_stripe_to_lru_refs(trans);
|
|
}
|
|
|
|
static int dev_freespace_init_iter(struct btree_trans *trans, struct bch_dev *ca,
|
|
struct btree_iter *iter, struct bpos end)
|
|
{
|
|
struct bkey hole;
|
|
struct bkey_s_c k = bkey_try(bch2_get_key_or_hole(iter, end, &hole));
|
|
|
|
if (k.k->type) {
|
|
/*
|
|
* We process live keys in the alloc btree one at a
|
|
* time:
|
|
*/
|
|
struct bch_alloc_v4 a_convert;
|
|
const struct bch_alloc_v4 *a = bch2_alloc_to_v4(k, &a_convert);
|
|
|
|
try(bch2_bucket_do_index(trans, ca, k, a, true));
|
|
try(bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc));
|
|
|
|
bch2_btree_iter_advance(iter);
|
|
} else {
|
|
struct bkey_i *freespace = errptr_try(bch2_trans_kmalloc(trans, sizeof(*freespace)));
|
|
|
|
bkey_init(&freespace->k);
|
|
freespace->k.type = KEY_TYPE_set;
|
|
freespace->k.p = k.k->p;
|
|
freespace->k.size = k.k->size;
|
|
|
|
try(bch2_btree_insert_trans(trans, BTREE_ID_freespace, freespace, 0));
|
|
try(bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc));
|
|
|
|
bch2_btree_iter_set_pos(iter, k.k->p);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bch2_dev_freespace_init(struct bch_fs *c, struct bch_dev *ca,
|
|
u64 bucket_start, u64 bucket_end)
|
|
{
|
|
struct bpos end = POS(ca->dev_idx, bucket_end);
|
|
unsigned long last_updated = jiffies;
|
|
|
|
BUG_ON(bucket_start > bucket_end);
|
|
BUG_ON(bucket_end > ca->mi.nbuckets);
|
|
|
|
CLASS(btree_trans, trans)(c);
|
|
CLASS(btree_iter, iter)(trans, BTREE_ID_alloc,
|
|
POS(ca->dev_idx, max_t(u64, ca->mi.first_bucket, bucket_start)),
|
|
BTREE_ITER_prefetch);
|
|
/*
|
|
* Scan the alloc btree for every bucket on @ca, and add buckets to the
|
|
* freespace/need_discard/need_gc_gens btrees as needed:
|
|
*/
|
|
while (bkey_lt(iter.pos, end)) {
|
|
if (time_after(jiffies, last_updated + HZ * 10)) {
|
|
bch_info(ca, "%s: currently at %llu/%llu",
|
|
__func__, iter.pos.offset, ca->mi.nbuckets);
|
|
last_updated = jiffies;
|
|
}
|
|
|
|
try(lockrestart_do(trans, dev_freespace_init_iter(trans, ca, &iter, end)));
|
|
}
|
|
|
|
scoped_guard(mutex, &c->sb_lock) {
|
|
struct bch_member *m = bch2_members_v2_get_mut(c->disk_sb.sb, ca->dev_idx);
|
|
SET_BCH_MEMBER_FREESPACE_INITIALIZED(m, true);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bch2_fs_freespace_init(struct bch_fs *c)
|
|
{
|
|
if (c->sb.features & BIT_ULL(BCH_FEATURE_small_image))
|
|
return 0;
|
|
|
|
/*
|
|
* We can crash during the device add path, so we need to check this on
|
|
* every mount:
|
|
*/
|
|
|
|
bool doing_init = false;
|
|
for_each_member_device(c, ca) {
|
|
if (ca->mi.freespace_initialized)
|
|
continue;
|
|
|
|
if (!doing_init) {
|
|
bch_info(c, "initializing freespace");
|
|
doing_init = true;
|
|
}
|
|
|
|
try(bch2_dev_freespace_init(c, ca, 0, ca->mi.nbuckets));
|
|
}
|
|
|
|
if (doing_init) {
|
|
guard(mutex)(&c->sb_lock);
|
|
bch2_write_super(c);
|
|
bch_verbose(c, "done initializing freespace");
|
|
}
|
|
|
|
return 0;
|
|
}
|