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;
}