Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Kent Overstreet 6556 99.54% 144 96.00%
Justin Husted 11 0.17% 1 0.67%
Youling Tang 8 0.12% 1 0.67%
Guoyu Ou 6 0.09% 1 0.67%
Nathan Chancellor 2 0.03% 1 0.67%
Dan Robertson 2 0.03% 1 0.67%
Al Viro 1 0.02% 1 0.67%
Total 6586 150

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
// SPDX-License-Identifier: GPL-2.0

#include "bcachefs.h"
#include "btree_key_cache.h"
#include "btree_write_buffer.h"
#include "bkey_methods.h"
#include "btree_update.h"
#include "buckets.h"
#include "compress.h"
#include "dirent.h"
#include "disk_accounting.h"
#include "error.h"
#include "extents.h"
#include "extent_update.h"
#include "fs.h"
#include "inode.h"
#include "str_hash.h"
#include "snapshot.h"
#include "subvolume.h"
#include "varint.h"

#include <linux/random.h>

#include <linux/unaligned.h>

#define x(name, ...)	#name,
const char * const bch2_inode_opts[] = {
	BCH_INODE_OPTS()
	NULL,
};

static const char * const bch2_inode_flag_strs[] = {
	BCH_INODE_FLAGS()
	NULL
};
#undef  x

static int delete_ancestor_snapshot_inodes(struct btree_trans *, struct bpos);

static const u8 byte_table[8] = { 1, 2, 3, 4, 6, 8, 10, 13 };

static int inode_decode_field(const u8 *in, const u8 *end,
			      u64 out[2], unsigned *out_bits)
{
	__be64 be[2] = { 0, 0 };
	unsigned bytes, shift;
	u8 *p;

	if (in >= end)
		return -1;

	if (!*in)
		return -1;

	/*
	 * position of highest set bit indicates number of bytes:
	 * shift = number of bits to remove in high byte:
	 */
	shift	= 8 - __fls(*in); /* 1 <= shift <= 8 */
	bytes	= byte_table[shift - 1];

	if (in + bytes > end)
		return -1;

	p = (u8 *) be + 16 - bytes;
	memcpy(p, in, bytes);
	*p ^= (1 << 8) >> shift;

	out[0] = be64_to_cpu(be[0]);
	out[1] = be64_to_cpu(be[1]);
	*out_bits = out[0] ? 64 + fls64(out[0]) : fls64(out[1]);

	return bytes;
}

static inline void bch2_inode_pack_inlined(struct bkey_inode_buf *packed,
					   const struct bch_inode_unpacked *inode)
{
	struct bkey_i_inode_v3 *k = &packed->inode;
	u8 *out = k->v.fields;
	u8 *end = (void *) &packed[1];
	u8 *last_nonzero_field = out;
	unsigned nr_fields = 0, last_nonzero_fieldnr = 0;
	unsigned bytes;
	int ret;

	bkey_inode_v3_init(&packed->inode.k_i);
	packed->inode.k.p.offset	= inode->bi_inum;
	packed->inode.v.bi_journal_seq	= cpu_to_le64(inode->bi_journal_seq);
	packed->inode.v.bi_hash_seed	= inode->bi_hash_seed;
	packed->inode.v.bi_flags	= cpu_to_le64(inode->bi_flags);
	packed->inode.v.bi_sectors	= cpu_to_le64(inode->bi_sectors);
	packed->inode.v.bi_size		= cpu_to_le64(inode->bi_size);
	packed->inode.v.bi_version	= cpu_to_le64(inode->bi_version);
	SET_INODEv3_MODE(&packed->inode.v, inode->bi_mode);
	SET_INODEv3_FIELDS_START(&packed->inode.v, INODEv3_FIELDS_START_CUR);


#define x(_name, _bits)							\
	nr_fields++;							\
									\
	if (inode->_name) {						\
		ret = bch2_varint_encode_fast(out, inode->_name);	\
		out += ret;						\
									\
		if (_bits > 64)						\
			*out++ = 0;					\
									\
		last_nonzero_field = out;				\
		last_nonzero_fieldnr = nr_fields;			\
	} else {							\
		*out++ = 0;						\
									\
		if (_bits > 64)						\
			*out++ = 0;					\
	}

	BCH_INODE_FIELDS_v3()
#undef  x
	BUG_ON(out > end);

	out = last_nonzero_field;
	nr_fields = last_nonzero_fieldnr;

	bytes = out - (u8 *) &packed->inode.v;
	set_bkey_val_bytes(&packed->inode.k, bytes);
	memset_u64s_tail(&packed->inode.v, 0, bytes);

	SET_INODEv3_NR_FIELDS(&k->v, nr_fields);

	if (IS_ENABLED(CONFIG_BCACHEFS_DEBUG)) {
		struct bch_inode_unpacked unpacked;

		ret = bch2_inode_unpack(bkey_i_to_s_c(&packed->inode.k_i), &unpacked);
		BUG_ON(ret);
		BUG_ON(unpacked.bi_inum		!= inode->bi_inum);
		BUG_ON(unpacked.bi_hash_seed	!= inode->bi_hash_seed);
		BUG_ON(unpacked.bi_sectors	!= inode->bi_sectors);
		BUG_ON(unpacked.bi_size		!= inode->bi_size);
		BUG_ON(unpacked.bi_version	!= inode->bi_version);
		BUG_ON(unpacked.bi_mode		!= inode->bi_mode);

#define x(_name, _bits)	if (unpacked._name != inode->_name)		\
			panic("unpacked %llu should be %llu",		\
			      (u64) unpacked._name, (u64) inode->_name);
		BCH_INODE_FIELDS_v3()
#undef  x
	}
}

void bch2_inode_pack(struct bkey_inode_buf *packed,
		     const struct bch_inode_unpacked *inode)
{
	bch2_inode_pack_inlined(packed, inode);
}

static noinline int bch2_inode_unpack_v1(struct bkey_s_c_inode inode,
				struct bch_inode_unpacked *unpacked)
{
	const u8 *in = inode.v->fields;
	const u8 *end = bkey_val_end(inode);
	u64 field[2];
	unsigned fieldnr = 0, field_bits;
	int ret;

#define x(_name, _bits)							\
	if (fieldnr++ == INODEv1_NR_FIELDS(inode.v)) {			\
		unsigned offset = offsetof(struct bch_inode_unpacked, _name);\
		memset((void *) unpacked + offset, 0,			\
		       sizeof(*unpacked) - offset);			\
		return 0;						\
	}								\
									\
	ret = inode_decode_field(in, end, field, &field_bits);		\
	if (ret < 0)							\
		return ret;						\
									\
	if (field_bits > sizeof(unpacked->_name) * 8)			\
		return -1;						\
									\
	unpacked->_name = field[1];					\
	in += ret;

	BCH_INODE_FIELDS_v2()
#undef  x

	/* XXX: signal if there were more fields than expected? */
	return 0;
}

static int bch2_inode_unpack_v2(struct bch_inode_unpacked *unpacked,
				const u8 *in, const u8 *end,
				unsigned nr_fields)
{
	unsigned fieldnr = 0;
	int ret;
	u64 v[2];

#define x(_name, _bits)							\
	if (fieldnr < nr_fields) {					\
		ret = bch2_varint_decode_fast(in, end, &v[0]);		\
		if (ret < 0)						\
			return ret;					\
		in += ret;						\
									\
		if (_bits > 64) {					\
			ret = bch2_varint_decode_fast(in, end, &v[1]);	\
			if (ret < 0)					\
				return ret;				\
			in += ret;					\
		} else {						\
			v[1] = 0;					\
		}							\
	} else {							\
		v[0] = v[1] = 0;					\
	}								\
									\
	unpacked->_name = v[0];						\
	if (v[1] || v[0] != unpacked->_name)				\
		return -1;						\
	fieldnr++;

	BCH_INODE_FIELDS_v2()
#undef  x

	/* XXX: signal if there were more fields than expected? */
	return 0;
}

static int bch2_inode_unpack_v3(struct bkey_s_c k,
				struct bch_inode_unpacked *unpacked)
{
	struct bkey_s_c_inode_v3 inode = bkey_s_c_to_inode_v3(k);
	const u8 *in = inode.v->fields;
	const u8 *end = bkey_val_end(inode);
	unsigned nr_fields = INODEv3_NR_FIELDS(inode.v);
	unsigned fieldnr = 0;
	int ret;
	u64 v[2];

	unpacked->bi_inum	= inode.k->p.offset;
	unpacked->bi_journal_seq= le64_to_cpu(inode.v->bi_journal_seq);
	unpacked->bi_hash_seed	= inode.v->bi_hash_seed;
	unpacked->bi_flags	= le64_to_cpu(inode.v->bi_flags);
	unpacked->bi_sectors	= le64_to_cpu(inode.v->bi_sectors);
	unpacked->bi_size	= le64_to_cpu(inode.v->bi_size);
	unpacked->bi_version	= le64_to_cpu(inode.v->bi_version);
	unpacked->bi_mode	= INODEv3_MODE(inode.v);

#define x(_name, _bits)							\
	if (fieldnr < nr_fields) {					\
		ret = bch2_varint_decode_fast(in, end, &v[0]);		\
		if (ret < 0)						\
			return ret;					\
		in += ret;						\
									\
		if (_bits > 64) {					\
			ret = bch2_varint_decode_fast(in, end, &v[1]);	\
			if (ret < 0)					\
				return ret;				\
			in += ret;					\
		} else {						\
			v[1] = 0;					\
		}							\
	} else {							\
		v[0] = v[1] = 0;					\
	}								\
									\
	unpacked->_name = v[0];						\
	if (v[1] || v[0] != unpacked->_name)				\
		return -1;						\
	fieldnr++;

	BCH_INODE_FIELDS_v3()
#undef  x

	/* XXX: signal if there were more fields than expected? */
	return 0;
}

static noinline int bch2_inode_unpack_slowpath(struct bkey_s_c k,
					       struct bch_inode_unpacked *unpacked)
{
	memset(unpacked, 0, sizeof(*unpacked));

	unpacked->bi_snapshot = k.k->p.snapshot;

	switch (k.k->type) {
	case KEY_TYPE_inode: {
		struct bkey_s_c_inode inode = bkey_s_c_to_inode(k);

		unpacked->bi_inum	= inode.k->p.offset;
		unpacked->bi_journal_seq= 0;
		unpacked->bi_hash_seed	= inode.v->bi_hash_seed;
		unpacked->bi_flags	= le32_to_cpu(inode.v->bi_flags);
		unpacked->bi_mode	= le16_to_cpu(inode.v->bi_mode);

		if (INODEv1_NEW_VARINT(inode.v)) {
			return bch2_inode_unpack_v2(unpacked, inode.v->fields,
						    bkey_val_end(inode),
						    INODEv1_NR_FIELDS(inode.v));
		} else {
			return bch2_inode_unpack_v1(inode, unpacked);
		}
		break;
	}
	case KEY_TYPE_inode_v2: {
		struct bkey_s_c_inode_v2 inode = bkey_s_c_to_inode_v2(k);

		unpacked->bi_inum	= inode.k->p.offset;
		unpacked->bi_journal_seq= le64_to_cpu(inode.v->bi_journal_seq);
		unpacked->bi_hash_seed	= inode.v->bi_hash_seed;
		unpacked->bi_flags	= le64_to_cpu(inode.v->bi_flags);
		unpacked->bi_mode	= le16_to_cpu(inode.v->bi_mode);

		return bch2_inode_unpack_v2(unpacked, inode.v->fields,
					    bkey_val_end(inode),
					    INODEv2_NR_FIELDS(inode.v));
	}
	default:
		BUG();
	}
}

int bch2_inode_unpack(struct bkey_s_c k,
		      struct bch_inode_unpacked *unpacked)
{
	unpacked->bi_snapshot = k.k->p.snapshot;

	return likely(k.k->type == KEY_TYPE_inode_v3)
		? bch2_inode_unpack_v3(k, unpacked)
		: bch2_inode_unpack_slowpath(k, unpacked);
}

int __bch2_inode_peek(struct btree_trans *trans,
		      struct btree_iter *iter,
		      struct bch_inode_unpacked *inode,
		      subvol_inum inum, unsigned flags,
		      bool warn)
{
	u32 snapshot;
	int ret = __bch2_subvolume_get_snapshot(trans, inum.subvol, &snapshot, warn);
	if (ret)
		return ret;

	struct bkey_s_c k = bch2_bkey_get_iter(trans, iter, BTREE_ID_inodes,
					       SPOS(0, inum.inum, snapshot),
					       flags|BTREE_ITER_cached);
	ret = bkey_err(k);
	if (ret)
		return ret;

	ret = bkey_is_inode(k.k) ? 0 : -BCH_ERR_ENOENT_inode;
	if (ret)
		goto err;

	ret = bch2_inode_unpack(k, inode);
	if (ret)
		goto err;

	return 0;
err:
	if (warn)
		bch_err_msg(trans->c, ret, "looking up inum %llu:%llu:", inum.subvol, inum.inum);
	bch2_trans_iter_exit(trans, iter);
	return ret;
}

int bch2_inode_write_flags(struct btree_trans *trans,
		     struct btree_iter *iter,
		     struct bch_inode_unpacked *inode,
		     enum btree_iter_update_trigger_flags flags)
{
	struct bkey_inode_buf *inode_p;

	inode_p = bch2_trans_kmalloc(trans, sizeof(*inode_p));
	if (IS_ERR(inode_p))
		return PTR_ERR(inode_p);

	bch2_inode_pack_inlined(inode_p, inode);
	inode_p->inode.k.p.snapshot = iter->snapshot;
	return bch2_trans_update(trans, iter, &inode_p->inode.k_i, flags);
}

int __bch2_fsck_write_inode(struct btree_trans *trans, struct bch_inode_unpacked *inode)
{
	struct bkey_inode_buf *inode_p =
		bch2_trans_kmalloc(trans, sizeof(*inode_p));

	if (IS_ERR(inode_p))
		return PTR_ERR(inode_p);

	bch2_inode_pack(inode_p, inode);
	inode_p->inode.k.p.snapshot = inode->bi_snapshot;

	return bch2_btree_insert_nonextent(trans, BTREE_ID_inodes,
				&inode_p->inode.k_i,
				BTREE_UPDATE_internal_snapshot_node);
}

int bch2_fsck_write_inode(struct btree_trans *trans, struct bch_inode_unpacked *inode)
{
	int ret = commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
			    __bch2_fsck_write_inode(trans, inode));
	bch_err_fn(trans->c, ret);
	return ret;
}

struct bkey_i *bch2_inode_to_v3(struct btree_trans *trans, struct bkey_i *k)
{
	struct bch_inode_unpacked u;
	struct bkey_inode_buf *inode_p;
	int ret;

	if (!bkey_is_inode(&k->k))
		return ERR_PTR(-ENOENT);

	inode_p = bch2_trans_kmalloc(trans, sizeof(*inode_p));
	if (IS_ERR(inode_p))
		return ERR_CAST(inode_p);

	ret = bch2_inode_unpack(bkey_i_to_s_c(k), &u);
	if (ret)
		return ERR_PTR(ret);

	bch2_inode_pack(inode_p, &u);
	return &inode_p->inode.k_i;
}

static int __bch2_inode_validate(struct bch_fs *c, struct bkey_s_c k,
				 enum bch_validate_flags flags)
{
	struct bch_inode_unpacked unpacked;
	int ret = 0;

	bkey_fsck_err_on(k.k->p.inode,
			 c, inode_pos_inode_nonzero,
			 "nonzero k.p.inode");

	bkey_fsck_err_on(k.k->p.offset < BLOCKDEV_INODE_MAX,
			 c, inode_pos_blockdev_range,
			 "fs inode in blockdev range");

	bkey_fsck_err_on(bch2_inode_unpack(k, &unpacked),
			 c, inode_unpack_error,
			 "invalid variable length fields");

	bkey_fsck_err_on(unpacked.bi_data_checksum >= BCH_CSUM_OPT_NR + 1,
			 c, inode_checksum_type_invalid,
			 "invalid data checksum type (%u >= %u",
			 unpacked.bi_data_checksum, BCH_CSUM_OPT_NR + 1);

	bkey_fsck_err_on(unpacked.bi_compression &&
			 !bch2_compression_opt_valid(unpacked.bi_compression - 1),
			 c, inode_compression_type_invalid,
			 "invalid compression opt %u", unpacked.bi_compression - 1);

	bkey_fsck_err_on((unpacked.bi_flags & BCH_INODE_unlinked) &&
			 unpacked.bi_nlink != 0,
			 c, inode_unlinked_but_nlink_nonzero,
			 "flagged as unlinked but bi_nlink != 0");

	bkey_fsck_err_on(unpacked.bi_subvol && !S_ISDIR(unpacked.bi_mode),
			 c, inode_subvol_root_but_not_dir,
			 "subvolume root but not a directory");
fsck_err:
	return ret;
}

int bch2_inode_validate(struct bch_fs *c, struct bkey_s_c k,
			enum bch_validate_flags flags)
{
	struct bkey_s_c_inode inode = bkey_s_c_to_inode(k);
	int ret = 0;

	bkey_fsck_err_on(INODEv1_STR_HASH(inode.v) >= BCH_STR_HASH_NR,
			 c, inode_str_hash_invalid,
			 "invalid str hash type (%llu >= %u)",
			 INODEv1_STR_HASH(inode.v), BCH_STR_HASH_NR);

	ret = __bch2_inode_validate(c, k, flags);
fsck_err:
	return ret;
}

int bch2_inode_v2_validate(struct bch_fs *c, struct bkey_s_c k,
			   enum bch_validate_flags flags)
{
	struct bkey_s_c_inode_v2 inode = bkey_s_c_to_inode_v2(k);
	int ret = 0;

	bkey_fsck_err_on(INODEv2_STR_HASH(inode.v) >= BCH_STR_HASH_NR,
			 c, inode_str_hash_invalid,
			 "invalid str hash type (%llu >= %u)",
			 INODEv2_STR_HASH(inode.v), BCH_STR_HASH_NR);

	ret = __bch2_inode_validate(c, k, flags);
fsck_err:
	return ret;
}

int bch2_inode_v3_validate(struct bch_fs *c, struct bkey_s_c k,
			   enum bch_validate_flags flags)
{
	struct bkey_s_c_inode_v3 inode = bkey_s_c_to_inode_v3(k);
	int ret = 0;

	bkey_fsck_err_on(INODEv3_FIELDS_START(inode.v) < INODEv3_FIELDS_START_INITIAL ||
			 INODEv3_FIELDS_START(inode.v) > bkey_val_u64s(inode.k),
			 c, inode_v3_fields_start_bad,
			 "invalid fields_start (got %llu, min %u max %zu)",
			 INODEv3_FIELDS_START(inode.v),
			 INODEv3_FIELDS_START_INITIAL,
			 bkey_val_u64s(inode.k));

	bkey_fsck_err_on(INODEv3_STR_HASH(inode.v) >= BCH_STR_HASH_NR,
			 c, inode_str_hash_invalid,
			 "invalid str hash type (%llu >= %u)",
			 INODEv3_STR_HASH(inode.v), BCH_STR_HASH_NR);

	ret = __bch2_inode_validate(c, k, flags);
fsck_err:
	return ret;
}

static void __bch2_inode_unpacked_to_text(struct printbuf *out,
					  struct bch_inode_unpacked *inode)
{
	prt_printf(out, "\n");
	printbuf_indent_add(out, 2);
	prt_printf(out, "mode=%o\n", inode->bi_mode);

	prt_str(out, "flags=");
	prt_bitflags(out, bch2_inode_flag_strs, inode->bi_flags & ((1U << 20) - 1));
	prt_printf(out, "(%x)\n", inode->bi_flags);

	prt_printf(out, "journal_seq=%llu\n",	inode->bi_journal_seq);
	prt_printf(out, "hash_seed=%llx\n",	inode->bi_hash_seed);
	prt_printf(out, "hash_type=");
	bch2_prt_str_hash_type(out, INODE_STR_HASH(inode));
	prt_newline(out);
	prt_printf(out, "bi_size=%llu\n",	inode->bi_size);
	prt_printf(out, "bi_sectors=%llu\n",	inode->bi_sectors);
	prt_printf(out, "bi_version=%llu\n",	inode->bi_version);

#define x(_name, _bits)						\
	prt_printf(out, #_name "=%llu\n", (u64) inode->_name);
	BCH_INODE_FIELDS_v3()
#undef  x

	bch2_printbuf_strip_trailing_newline(out);
	printbuf_indent_sub(out, 2);
}

void bch2_inode_unpacked_to_text(struct printbuf *out, struct bch_inode_unpacked *inode)
{
	prt_printf(out, "inum: %llu:%u ", inode->bi_inum, inode->bi_snapshot);
	__bch2_inode_unpacked_to_text(out, inode);
}

void bch2_inode_to_text(struct printbuf *out, struct bch_fs *c, struct bkey_s_c k)
{
	struct bch_inode_unpacked inode;

	if (bch2_inode_unpack(k, &inode)) {
		prt_printf(out, "(unpack error)");
		return;
	}

	__bch2_inode_unpacked_to_text(out, &inode);
}

static inline u64 bkey_inode_flags(struct bkey_s_c k)
{
	switch (k.k->type) {
	case KEY_TYPE_inode:
		return le32_to_cpu(bkey_s_c_to_inode(k).v->bi_flags);
	case KEY_TYPE_inode_v2:
		return le64_to_cpu(bkey_s_c_to_inode_v2(k).v->bi_flags);
	case KEY_TYPE_inode_v3:
		return le64_to_cpu(bkey_s_c_to_inode_v3(k).v->bi_flags);
	default:
		return 0;
	}
}

static inline void bkey_inode_flags_set(struct bkey_s k, u64 f)
{
	switch (k.k->type) {
	case KEY_TYPE_inode:
		bkey_s_to_inode(k).v->bi_flags = cpu_to_le32(f);
		return;
	case KEY_TYPE_inode_v2:
		bkey_s_to_inode_v2(k).v->bi_flags = cpu_to_le64(f);
		return;
	case KEY_TYPE_inode_v3:
		bkey_s_to_inode_v3(k).v->bi_flags = cpu_to_le64(f);
		return;
	default:
		BUG();
	}
}

static inline bool bkey_is_unlinked_inode(struct bkey_s_c k)
{
	unsigned f = bkey_inode_flags(k) & BCH_INODE_unlinked;

	return (f & BCH_INODE_unlinked) && !(f & BCH_INODE_has_child_snapshot);
}

static struct bkey_s_c
bch2_bkey_get_iter_snapshot_parent(struct btree_trans *trans, struct btree_iter *iter,
				   enum btree_id btree, struct bpos pos,
				   unsigned flags)
{
	struct bch_fs *c = trans->c;
	struct bkey_s_c k;
	int ret = 0;

	for_each_btree_key_upto_norestart(trans, *iter, btree,
					  bpos_successor(pos),
					  SPOS(pos.inode, pos.offset, U32_MAX),
					  flags|BTREE_ITER_all_snapshots, k, ret)
		if (bch2_snapshot_is_ancestor(c, pos.snapshot, k.k->p.snapshot))
			return k;

	bch2_trans_iter_exit(trans, iter);
	return ret ? bkey_s_c_err(ret) : bkey_s_c_null;
}

static struct bkey_s_c
bch2_inode_get_iter_snapshot_parent(struct btree_trans *trans, struct btree_iter *iter,
				    struct bpos pos, unsigned flags)
{
	struct bkey_s_c k;
again:
	k = bch2_bkey_get_iter_snapshot_parent(trans, iter, BTREE_ID_inodes, pos, flags);
	if (!k.k ||
	    bkey_err(k) ||
	    bkey_is_inode(k.k))
		return k;

	bch2_trans_iter_exit(trans, iter);
	pos = k.k->p;
	goto again;
}

int __bch2_inode_has_child_snapshots(struct btree_trans *trans, struct bpos pos)
{
	struct bch_fs *c = trans->c;
	struct btree_iter iter;
	struct bkey_s_c k;
	int ret = 0;

	for_each_btree_key_upto_norestart(trans, iter,
			BTREE_ID_inodes, POS(0, pos.offset), bpos_predecessor(pos),
			BTREE_ITER_all_snapshots|
			BTREE_ITER_with_updates, k, ret)
		if (bch2_snapshot_is_ancestor(c, k.k->p.snapshot, pos.snapshot) &&
		    bkey_is_inode(k.k)) {
			ret = 1;
			break;
		}
	bch2_trans_iter_exit(trans, &iter);
	return ret;
}

static int update_inode_has_children(struct btree_trans *trans,
				     struct bkey_s k,
				     bool have_child)
{
	if (!have_child) {
		int ret = bch2_inode_has_child_snapshots(trans, k.k->p);
		if (ret)
			return ret < 0 ? ret : 0;
	}

	u64 f = bkey_inode_flags(k.s_c);
	if (have_child != !!(f & BCH_INODE_has_child_snapshot))
		bkey_inode_flags_set(k, f ^ BCH_INODE_has_child_snapshot);

	return 0;
}

static int update_parent_inode_has_children(struct btree_trans *trans, struct bpos pos,
					    bool have_child)
{
	struct btree_iter iter;
	struct bkey_s_c k = bch2_inode_get_iter_snapshot_parent(trans,
						&iter, pos, BTREE_ITER_with_updates);
	int ret = bkey_err(k);
	if (ret)
		return ret;
	if (!k.k)
		return 0;

	if (!have_child) {
		ret = bch2_inode_has_child_snapshots(trans, k.k->p);
		if (ret) {
			ret = ret < 0 ? ret : 0;
			goto err;
		}
	}

	u64 f = bkey_inode_flags(k);
	if (have_child != !!(f & BCH_INODE_has_child_snapshot)) {
		struct bkey_i *update = bch2_bkey_make_mut(trans, &iter, &k,
					     BTREE_UPDATE_internal_snapshot_node);
		ret = PTR_ERR_OR_ZERO(update);
		if (ret)
			goto err;

		bkey_inode_flags_set(bkey_i_to_s(update), f ^ BCH_INODE_has_child_snapshot);
	}
err:
	bch2_trans_iter_exit(trans, &iter);
	return ret;
}

int bch2_trigger_inode(struct btree_trans *trans,
		       enum btree_id btree_id, unsigned level,
		       struct bkey_s_c old,
		       struct bkey_s new,
		       enum btree_iter_update_trigger_flags flags)
{
	struct bch_fs *c = trans->c;

	if ((flags & BTREE_TRIGGER_atomic) && (flags & BTREE_TRIGGER_insert)) {
		BUG_ON(!trans->journal_res.seq);
		bkey_s_to_inode_v3(new).v->bi_journal_seq = cpu_to_le64(trans->journal_res.seq);
	}

	s64 nr = bkey_is_inode(new.k) - bkey_is_inode(old.k);
	if ((flags & (BTREE_TRIGGER_transactional|BTREE_TRIGGER_gc)) && nr) {
		struct disk_accounting_pos acc = { .type = BCH_DISK_ACCOUNTING_nr_inodes };
		int ret = bch2_disk_accounting_mod(trans, &acc, &nr, 1, flags & BTREE_TRIGGER_gc);
		if (ret)
			return ret;
	}

	if (flags & BTREE_TRIGGER_transactional) {
		int unlinked_delta =	(int) bkey_is_unlinked_inode(new.s_c) -
					(int) bkey_is_unlinked_inode(old);
		if (unlinked_delta) {
			int ret = bch2_btree_bit_mod_buffered(trans, BTREE_ID_deleted_inodes,
							      new.k->p, unlinked_delta > 0);
			if (ret)
				return ret;
		}

		/*
		 * If we're creating or deleting an inode at this snapshot ID,
		 * and there might be an inode in a parent snapshot ID, we might
		 * need to set or clear the has_child_snapshot flag on the
		 * parent.
		 */
		int deleted_delta = (int) bkey_is_inode(new.k) -
				    (int) bkey_is_inode(old.k);
		if (deleted_delta &&
		    bch2_snapshot_parent(c, new.k->p.snapshot)) {
			int ret = update_parent_inode_has_children(trans, new.k->p,
								   deleted_delta > 0);
			if (ret)
				return ret;
		}

		/*
		 * When an inode is first updated in a new snapshot, we may need
		 * to clear has_child_snapshot
		 */
		if (deleted_delta > 0) {
			int ret = update_inode_has_children(trans, new, false);
			if (ret)
				return ret;
		}
	}

	return 0;
}

int bch2_inode_generation_validate(struct bch_fs *c, struct bkey_s_c k,
				   enum bch_validate_flags flags)
{
	int ret = 0;

	bkey_fsck_err_on(k.k->p.inode,
			 c, inode_pos_inode_nonzero,
			 "nonzero k.p.inode");
fsck_err:
	return ret;
}

void bch2_inode_generation_to_text(struct printbuf *out, struct bch_fs *c,
				   struct bkey_s_c k)
{
	struct bkey_s_c_inode_generation gen = bkey_s_c_to_inode_generation(k);

	prt_printf(out, "generation: %u", le32_to_cpu(gen.v->bi_generation));
}

void bch2_inode_init_early(struct bch_fs *c,
			   struct bch_inode_unpacked *inode_u)
{
	enum bch_str_hash_type str_hash =
		bch2_str_hash_opt_to_type(c, c->opts.str_hash);

	memset(inode_u, 0, sizeof(*inode_u));

	SET_INODE_STR_HASH(inode_u, str_hash);
	get_random_bytes(&inode_u->bi_hash_seed, sizeof(inode_u->bi_hash_seed));
}

void bch2_inode_init_late(struct bch_inode_unpacked *inode_u, u64 now,
			  uid_t uid, gid_t gid, umode_t mode, dev_t rdev,
			  struct bch_inode_unpacked *parent)
{
	inode_u->bi_mode	= mode;
	inode_u->bi_uid		= uid;
	inode_u->bi_gid		= gid;
	inode_u->bi_dev		= rdev;
	inode_u->bi_atime	= now;
	inode_u->bi_mtime	= now;
	inode_u->bi_ctime	= now;
	inode_u->bi_otime	= now;

	if (parent && parent->bi_mode & S_ISGID) {
		inode_u->bi_gid = parent->bi_gid;
		if (S_ISDIR(mode))
			inode_u->bi_mode |= S_ISGID;
	}

	if (parent) {
#define x(_name, ...)	inode_u->bi_##_name = parent->bi_##_name;
		BCH_INODE_OPTS()
#undef x
	}
}

void bch2_inode_init(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
		     uid_t uid, gid_t gid, umode_t mode, dev_t rdev,
		     struct bch_inode_unpacked *parent)
{
	bch2_inode_init_early(c, inode_u);
	bch2_inode_init_late(inode_u, bch2_current_time(c),
			     uid, gid, mode, rdev, parent);
}

static inline u32 bkey_generation(struct bkey_s_c k)
{
	switch (k.k->type) {
	case KEY_TYPE_inode:
	case KEY_TYPE_inode_v2:
		BUG();
	case KEY_TYPE_inode_generation:
		return le32_to_cpu(bkey_s_c_to_inode_generation(k).v->bi_generation);
	default:
		return 0;
	}
}

/*
 * This just finds an empty slot:
 */
int bch2_inode_create(struct btree_trans *trans,
		      struct btree_iter *iter,
		      struct bch_inode_unpacked *inode_u,
		      u32 snapshot, u64 cpu)
{
	struct bch_fs *c = trans->c;
	struct bkey_s_c k;
	u64 min, max, start, pos, *hint;
	int ret = 0;
	unsigned bits = (c->opts.inodes_32bit ? 31 : 63);

	if (c->opts.shard_inode_numbers) {
		bits -= c->inode_shard_bits;

		min = (cpu << bits);
		max = (cpu << bits) | ~(ULLONG_MAX << bits);

		min = max_t(u64, min, BLOCKDEV_INODE_MAX);
		hint = c->unused_inode_hints + cpu;
	} else {
		min = BLOCKDEV_INODE_MAX;
		max = ~(ULLONG_MAX << bits);
		hint = c->unused_inode_hints;
	}

	start = READ_ONCE(*hint);

	if (start >= max || start < min)
		start = min;

	pos = start;
	bch2_trans_iter_init(trans, iter, BTREE_ID_inodes, POS(0, pos),
			     BTREE_ITER_all_snapshots|
			     BTREE_ITER_intent);
again:
	while ((k = bch2_btree_iter_peek(iter)).k &&
	       !(ret = bkey_err(k)) &&
	       bkey_lt(k.k->p, POS(0, max))) {
		if (pos < iter->pos.offset)
			goto found_slot;

		/*
		 * We don't need to iterate over keys in every snapshot once
		 * we've found just one:
		 */
		pos = iter->pos.offset + 1;
		bch2_btree_iter_set_pos(iter, POS(0, pos));
	}

	if (!ret && pos < max)
		goto found_slot;

	if (!ret && start == min)
		ret = -BCH_ERR_ENOSPC_inode_create;

	if (ret) {
		bch2_trans_iter_exit(trans, iter);
		return ret;
	}

	/* Retry from start */
	pos = start = min;
	bch2_btree_iter_set_pos(iter, POS(0, pos));
	goto again;
found_slot:
	bch2_btree_iter_set_pos(iter, SPOS(0, pos, snapshot));
	k = bch2_btree_iter_peek_slot(iter);
	ret = bkey_err(k);
	if (ret) {
		bch2_trans_iter_exit(trans, iter);
		return ret;
	}

	*hint			= k.k->p.offset;
	inode_u->bi_inum	= k.k->p.offset;
	inode_u->bi_generation	= bkey_generation(k);
	return 0;
}

static int bch2_inode_delete_keys(struct btree_trans *trans,
				  subvol_inum inum, enum btree_id id)
{
	struct btree_iter iter;
	struct bkey_s_c k;
	struct bkey_i delete;
	struct bpos end = POS(inum.inum, U64_MAX);
	u32 snapshot;
	int ret = 0;

	/*
	 * We're never going to be deleting partial extents, no need to use an
	 * extent iterator:
	 */
	bch2_trans_iter_init(trans, &iter, id, POS(inum.inum, 0),
			     BTREE_ITER_intent);

	while (1) {
		bch2_trans_begin(trans);

		ret = bch2_subvolume_get_snapshot(trans, inum.subvol, &snapshot);
		if (ret)
			goto err;

		bch2_btree_iter_set_snapshot(&iter, snapshot);

		k = bch2_btree_iter_peek_upto(&iter, end);
		ret = bkey_err(k);
		if (ret)
			goto err;

		if (!k.k)
			break;

		bkey_init(&delete.k);
		delete.k.p = iter.pos;

		if (iter.flags & BTREE_ITER_is_extents)
			bch2_key_resize(&delete.k,
					bpos_min(end, k.k->p).offset -
					iter.pos.offset);

		ret = bch2_trans_update(trans, &iter, &delete, 0) ?:
		      bch2_trans_commit(trans, NULL, NULL,
					BCH_TRANS_COMMIT_no_enospc);
err:
		if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
			break;
	}

	bch2_trans_iter_exit(trans, &iter);
	return ret;
}

int bch2_inode_rm(struct bch_fs *c, subvol_inum inum)
{
	struct btree_trans *trans = bch2_trans_get(c);
	struct btree_iter iter = { NULL };
	struct bkey_i_inode_generation delete;
	struct bch_inode_unpacked inode_u;
	struct bkey_s_c k;
	u32 snapshot;
	int ret;

	/*
	 * If this was a directory, there shouldn't be any real dirents left -
	 * but there could be whiteouts (from hash collisions) that we should
	 * delete:
	 *
	 * XXX: the dirent could ideally would delete whiteouts when they're no
	 * longer needed
	 */
	ret   = bch2_inode_delete_keys(trans, inum, BTREE_ID_extents) ?:
		bch2_inode_delete_keys(trans, inum, BTREE_ID_xattrs) ?:
		bch2_inode_delete_keys(trans, inum, BTREE_ID_dirents);
	if (ret)
		goto err;
retry:
	bch2_trans_begin(trans);

	ret = bch2_subvolume_get_snapshot(trans, inum.subvol, &snapshot);
	if (ret)
		goto err;

	k = bch2_bkey_get_iter(trans, &iter, BTREE_ID_inodes,
			       SPOS(0, inum.inum, snapshot),
			       BTREE_ITER_intent|BTREE_ITER_cached);
	ret = bkey_err(k);
	if (ret)
		goto err;

	if (!bkey_is_inode(k.k)) {
		bch2_fs_inconsistent(c,
				     "inode %llu:%u not found when deleting",
				     inum.inum, snapshot);
		ret = -EIO;
		goto err;
	}

	bch2_inode_unpack(k, &inode_u);

	bkey_inode_generation_init(&delete.k_i);
	delete.k.p = iter.pos;
	delete.v.bi_generation = cpu_to_le32(inode_u.bi_generation + 1);

	ret   = bch2_trans_update(trans, &iter, &delete.k_i, 0) ?:
		bch2_trans_commit(trans, NULL, NULL,
				BCH_TRANS_COMMIT_no_enospc);
err:
	bch2_trans_iter_exit(trans, &iter);
	if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
		goto retry;

	if (ret)
		goto err2;

	ret = delete_ancestor_snapshot_inodes(trans, SPOS(0, inum.inum, snapshot));
err2:
	bch2_trans_put(trans);
	return ret;
}

int bch2_inode_find_by_inum_nowarn_trans(struct btree_trans *trans,
				  subvol_inum inum,
				  struct bch_inode_unpacked *inode)
{
	struct btree_iter iter;
	int ret;

	ret = bch2_inode_peek_nowarn(trans, &iter, inode, inum, 0);
	if (!ret)
		bch2_trans_iter_exit(trans, &iter);
	return ret;
}

int bch2_inode_find_by_inum_trans(struct btree_trans *trans,
				  subvol_inum inum,
				  struct bch_inode_unpacked *inode)
{
	struct btree_iter iter;
	int ret;

	ret = bch2_inode_peek(trans, &iter, inode, inum, 0);
	if (!ret)
		bch2_trans_iter_exit(trans, &iter);
	return ret;
}

int bch2_inode_find_by_inum(struct bch_fs *c, subvol_inum inum,
			    struct bch_inode_unpacked *inode)
{
	return bch2_trans_do(c, bch2_inode_find_by_inum_trans(trans, inum, inode));
}

int bch2_inode_nlink_inc(struct bch_inode_unpacked *bi)
{
	if (bi->bi_flags & BCH_INODE_unlinked)
		bi->bi_flags &= ~BCH_INODE_unlinked;
	else {
		if (bi->bi_nlink == U32_MAX)
			return -EINVAL;

		bi->bi_nlink++;
	}

	return 0;
}

void bch2_inode_nlink_dec(struct btree_trans *trans, struct bch_inode_unpacked *bi)
{
	if (bi->bi_nlink && (bi->bi_flags & BCH_INODE_unlinked)) {
		bch2_trans_inconsistent(trans, "inode %llu unlinked but link count nonzero",
					bi->bi_inum);
		return;
	}

	if (bi->bi_flags & BCH_INODE_unlinked) {
		bch2_trans_inconsistent(trans, "inode %llu link count underflow", bi->bi_inum);
		return;
	}

	if (bi->bi_nlink)
		bi->bi_nlink--;
	else
		bi->bi_flags |= BCH_INODE_unlinked;
}

struct bch_opts bch2_inode_opts_to_opts(struct bch_inode_unpacked *inode)
{
	struct bch_opts ret = { 0 };
#define x(_name, _bits)							\
	if (inode->bi_##_name)						\
		opt_set(ret, _name, inode->bi_##_name - 1);
	BCH_INODE_OPTS()
#undef x
	return ret;
}

void bch2_inode_opts_get(struct bch_io_opts *opts, struct bch_fs *c,
			 struct bch_inode_unpacked *inode)
{
#define x(_name, _bits)		opts->_name = inode_opt_get(c, inode, _name);
	BCH_INODE_OPTS()
#undef x

	if (opts->nocow)
		opts->compression = opts->background_compression = opts->data_checksum = opts->erasure_code = 0;
}

int bch2_inum_opts_get(struct btree_trans *trans, subvol_inum inum, struct bch_io_opts *opts)
{
	struct bch_inode_unpacked inode;
	int ret = lockrestart_do(trans, bch2_inode_find_by_inum_trans(trans, inum, &inode));

	if (ret)
		return ret;

	bch2_inode_opts_get(opts, trans->c, &inode);
	return 0;
}

static noinline int __bch2_inode_rm_snapshot(struct btree_trans *trans, u64 inum, u32 snapshot)
{
	struct bch_fs *c = trans->c;
	struct btree_iter iter = { NULL };
	struct bkey_i_inode_generation delete;
	struct bch_inode_unpacked inode_u;
	struct bkey_s_c k;
	int ret;

	do {
		ret   = bch2_btree_delete_range_trans(trans, BTREE_ID_extents,
						      SPOS(inum, 0, snapshot),
						      SPOS(inum, U64_MAX, snapshot),
						      0, NULL) ?:
			bch2_btree_delete_range_trans(trans, BTREE_ID_dirents,
						      SPOS(inum, 0, snapshot),
						      SPOS(inum, U64_MAX, snapshot),
						      0, NULL) ?:
			bch2_btree_delete_range_trans(trans, BTREE_ID_xattrs,
						      SPOS(inum, 0, snapshot),
						      SPOS(inum, U64_MAX, snapshot),
						      0, NULL);
	} while (ret == -BCH_ERR_transaction_restart_nested);
	if (ret)
		goto err;
retry:
	bch2_trans_begin(trans);

	k = bch2_bkey_get_iter(trans, &iter, BTREE_ID_inodes,
			       SPOS(0, inum, snapshot), BTREE_ITER_intent);
	ret = bkey_err(k);
	if (ret)
		goto err;

	if (!bkey_is_inode(k.k)) {
		bch2_fs_inconsistent(c,
				     "inode %llu:%u not found when deleting",
				     inum, snapshot);
		ret = -EIO;
		goto err;
	}

	bch2_inode_unpack(k, &inode_u);

	/* Subvolume root? */
	if (inode_u.bi_subvol)
		bch_warn(c, "deleting inode %llu marked as unlinked, but also a subvolume root!?", inode_u.bi_inum);

	bkey_inode_generation_init(&delete.k_i);
	delete.k.p = iter.pos;
	delete.v.bi_generation = cpu_to_le32(inode_u.bi_generation + 1);

	ret   = bch2_trans_update(trans, &iter, &delete.k_i, 0) ?:
		bch2_trans_commit(trans, NULL, NULL,
				BCH_TRANS_COMMIT_no_enospc);
err:
	bch2_trans_iter_exit(trans, &iter);
	if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
		goto retry;

	return ret ?: -BCH_ERR_transaction_restart_nested;
}

/*
 * After deleting an inode, there may be versions in older snapshots that should
 * also be deleted - if they're not referenced by sibling snapshots and not open
 * in other subvolumes:
 */
static int delete_ancestor_snapshot_inodes(struct btree_trans *trans, struct bpos pos)
{
	struct btree_iter iter;
	struct bkey_s_c k;
	int ret;
next_parent:
	ret = lockrestart_do(trans,
		bkey_err(k = bch2_inode_get_iter_snapshot_parent(trans, &iter, pos, 0)));
	if (ret || !k.k)
		return ret;

	bool unlinked = bkey_is_unlinked_inode(k);
	pos = k.k->p;
	bch2_trans_iter_exit(trans, &iter);

	if (!unlinked)
		return 0;

	ret = lockrestart_do(trans, bch2_inode_or_descendents_is_open(trans, pos));
	if (ret)
		return ret < 0 ? ret : 0;

	ret = __bch2_inode_rm_snapshot(trans, pos.offset, pos.snapshot);
	if (ret)
		return ret;
	goto next_parent;
}

int bch2_inode_rm_snapshot(struct btree_trans *trans, u64 inum, u32 snapshot)
{
	return __bch2_inode_rm_snapshot(trans, inum, snapshot) ?:
		delete_ancestor_snapshot_inodes(trans, SPOS(0, inum, snapshot));
}

static int may_delete_deleted_inode(struct btree_trans *trans,
				    struct btree_iter *iter,
				    struct bpos pos,
				    bool *need_another_pass)
{
	struct bch_fs *c = trans->c;
	struct btree_iter inode_iter;
	struct bkey_s_c k;
	struct bch_inode_unpacked inode;
	struct printbuf buf = PRINTBUF;
	int ret;

	k = bch2_bkey_get_iter(trans, &inode_iter, BTREE_ID_inodes, pos, BTREE_ITER_cached);
	ret = bkey_err(k);
	if (ret)
		return ret;

	ret = bkey_is_inode(k.k) ? 0 : -BCH_ERR_ENOENT_inode;
	if (fsck_err_on(!bkey_is_inode(k.k),
			trans, deleted_inode_missing,
			"nonexistent inode %llu:%u in deleted_inodes btree",
			pos.offset, pos.snapshot))
		goto delete;

	ret = bch2_inode_unpack(k, &inode);
	if (ret)
		goto out;

	if (S_ISDIR(inode.bi_mode)) {
		ret = bch2_empty_dir_snapshot(trans, pos.offset, 0, pos.snapshot);
		if (fsck_err_on(bch2_err_matches(ret, ENOTEMPTY),
				trans, deleted_inode_is_dir,
				"non empty directory %llu:%u in deleted_inodes btree",
				pos.offset, pos.snapshot))
			goto delete;
		if (ret)
			goto out;
	}

	if (fsck_err_on(!(inode.bi_flags & BCH_INODE_unlinked),
			trans, deleted_inode_not_unlinked,
			"non-deleted inode %llu:%u in deleted_inodes btree",
			pos.offset, pos.snapshot))
		goto delete;

	if (fsck_err_on(inode.bi_flags & BCH_INODE_has_child_snapshot,
			trans, deleted_inode_has_child_snapshots,
			"inode with child snapshots %llu:%u in deleted_inodes btree",
			pos.offset, pos.snapshot))
		goto delete;

	ret = bch2_inode_has_child_snapshots(trans, k.k->p);
	if (ret < 0)
		goto out;

	if (ret) {
		if (fsck_err(trans, inode_has_child_snapshots_wrong,
			     "inode has_child_snapshots flag wrong (should be set)\n%s",
			     (printbuf_reset(&buf),
			      bch2_inode_unpacked_to_text(&buf, &inode),
			      buf.buf))) {
			inode.bi_flags |= BCH_INODE_has_child_snapshot;
			ret = __bch2_fsck_write_inode(trans, &inode);
			if (ret)
				goto out;
		}
		goto delete;

	}

	if (test_bit(BCH_FS_clean_recovery, &c->flags) &&
	    !fsck_err(trans, deleted_inode_but_clean,
		      "filesystem marked as clean but have deleted inode %llu:%u",
		      pos.offset, pos.snapshot)) {
		ret = 0;
		goto out;
	}

	ret = 1;
out:
fsck_err:
	bch2_trans_iter_exit(trans, &inode_iter);
	printbuf_exit(&buf);
	return ret;
delete:
	ret = bch2_btree_bit_mod_buffered(trans, BTREE_ID_deleted_inodes, pos, false);
	goto out;
}

int bch2_delete_dead_inodes(struct bch_fs *c)
{
	struct btree_trans *trans = bch2_trans_get(c);
	bool need_another_pass;
	int ret;
again:
	/*
	 * if we ran check_inodes() unlinked inodes will have already been
	 * cleaned up but the write buffer will be out of sync; therefore we
	 * alway need a write buffer flush
	 */
	ret = bch2_btree_write_buffer_flush_sync(trans);
	if (ret)
		goto err;

	need_another_pass = false;

	/*
	 * Weird transaction restart handling here because on successful delete,
	 * bch2_inode_rm_snapshot() will return a nested transaction restart,
	 * but we can't retry because the btree write buffer won't have been
	 * flushed and we'd spin:
	 */
	ret = for_each_btree_key_commit(trans, iter, BTREE_ID_deleted_inodes, POS_MIN,
					BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k,
					NULL, NULL, BCH_TRANS_COMMIT_no_enospc, ({
		ret = may_delete_deleted_inode(trans, &iter, k.k->p, &need_another_pass);
		if (ret > 0) {
			bch_verbose(c, "deleting unlinked inode %llu:%u", k.k->p.offset, k.k->p.snapshot);

			ret = bch2_inode_rm_snapshot(trans, k.k->p.offset, k.k->p.snapshot);
			/*
			 * We don't want to loop here: a transaction restart
			 * error here means we handled a transaction restart and
			 * we're actually done, but if we loop we'll retry the
			 * same key because the write buffer hasn't been flushed
			 * yet
			 */
			if (bch2_err_matches(ret, BCH_ERR_transaction_restart)) {
				ret = 0;
				continue;
			}
		}

		ret;
	}));

	if (!ret && need_another_pass)
		goto again;
err:
	bch2_trans_put(trans);
	return ret;
}