/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _BCACHEFS_SUBVOLUME_H
#define _BCACHEFS_SUBVOLUME_H

#include "util/darray.h"
#include "snapshots/types.h"

int bch2_check_subvols(struct bch_fs *);
int bch2_check_subvol_children(struct bch_fs *);

int bch2_subvolume_validate(struct bch_fs *, struct bkey_s_c,
			    const struct bkey_validate_context *);
void bch2_subvolume_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c);
int bch2_subvolume_trigger(struct btree_trans *, struct btree_trigger_op);

#define bch2_bkey_ops_subvolume ((struct bkey_ops) {		\
	.key_validate	= bch2_subvolume_validate,		\
	.val_to_text	= bch2_subvolume_to_text,		\
	.trigger	= bch2_subvolume_trigger,		\
	.min_val_size	= 16,					\
})

int bch2_subvol_has_children(struct btree_trans *, u32);
int bch2_subvolume_get(struct btree_trans *, unsigned,
		       bool, struct bch_subvolume *);
int __bch2_subvolume_get_snapshot(struct btree_trans *, u32,
				  u32 *, bool);
int bch2_subvolume_get_snapshot(struct btree_trans *, u32, u32 *);

int bch2_subvol_is_ro_trans(struct btree_trans *, u32, u32 *);
int bch2_subvol_is_ro(struct bch_fs *, u32);

static inline struct bkey_s_c
bch2_btree_iter_peek_in_subvolume_max_type(struct btree_iter *iter, struct bpos end,
					   u32 subvolid, u32 *snapshot, unsigned flags)
{
	int ret = bch2_trans_relock(iter->trans);
	if (ret)
		return bkey_s_c_err(ret);

	if (!*snapshot) {
		ret = bch2_subvolume_get_snapshot(iter->trans, subvolid, snapshot);
		if (ret)
			return bkey_s_c_err(ret);

		bch2_btree_iter_set_snapshot(iter, *snapshot);
	}
	return bch2_btree_iter_peek_max_type(iter, end, flags);
}

#define for_each_btree_key_in_subvolume_max_continue(_trans, _iter,		\
					 _end, _subvolid, _flags, _k, _do)	\
({										\
	int _ret3 = 0;								\
										\
	do {									\
		u32 _restart_count = bch2_trans_begin(_trans);			\
		u32 _snapshot = 0;						\
		_ret3 = 0;							\
										\
		struct bkey_s_c _k = bch2_btree_iter_peek_in_subvolume_max_type(&(_iter),\
					_end, _subvolid, &_snapshot, (_flags));	\
		if (!(_k).k)							\
			break;							\
										\
		_ret3 = bkey_err(_k) ?: (_do);					\
		if (!_ret3)							\
			bch2_trans_verify_not_restarted(_trans, _restart_count);\
	} while (bch2_err_matches(_ret3, BCH_ERR_transaction_restart) ||	\
		 (!_ret3 && bch2_btree_iter_advance(&(_iter))));		\
										\
	_ret3;									\
})

static inline bool
febk_in_subvolume_in_trans_cond(struct btree_trans *trans,
				struct btree_iter *iter,
				u32 *restart_count,
				u32 *snapshot,
				int *ret)
{
	if (!*ret) {
		bch2_trans_verify_not_restarted(trans, *restart_count);
		return bch2_btree_iter_advance(iter);
	}

	if (bch2_err_matches(*ret, BCH_ERR_transaction_restart)) {
		*restart_count = bch2_trans_begin(trans);
		*snapshot = 0;
		*ret = 0;
		return true;
	}

	return false;
}

#define for_each_btree_key_in_subvolume_max_continue_in_trans(_trans, _iter,	\
					 _end, _subvolid, _flags, _k, _do)	\
({										\
	u32 _restart_count = (_trans)->restart_count;				\
	u32 _snapshot = 0;							\
	int _ret3 = 0;								\
										\
	do {									\
		struct bkey_s_c _k = bch2_btree_iter_peek_in_subvolume_max_type(&(_iter),\
					_end, _subvolid, &_snapshot, (_flags));	\
		if (!(_k).k)							\
			break;							\
										\
		_ret3 = bkey_err(_k) ?: (_do);					\
	} while (febk_in_subvolume_in_trans_cond(_trans, &(_iter),		\
				 &_restart_count, &_snapshot, &_ret3));		\
										\
	_ret3;									\
})

#define for_each_btree_key_in_subvolume_max(_trans, _iter, _btree_id,		\
				_start, _end, _subvolid, _flags, _k, _do)	\
({										\
	CLASS(btree_iter, _iter)((_trans), (_btree_id), (_start), (_flags));	\
										\
	for_each_btree_key_in_subvolume_max_continue(_trans, _iter,		\
					_end, _subvolid, _flags, _k, _do);	\
})

#define for_each_btree_key_in_subvolume_max_in_trans(_trans, _iter, _btree_id,	\
				_start, _end, _subvolid, _flags, _k, _do)	\
({										\
	CLASS(btree_iter, _iter)((_trans), (_btree_id), (_start), (_flags));	\
										\
	for_each_btree_key_in_subvolume_max_continue_in_trans(_trans, _iter,		\
					_end, _subvolid, _flags, _k, _do);	\
})

int bch2_subvolume_unlink(struct btree_trans *, u32);
int bch2_subvolume_create(struct btree_trans *, u64, u32, u32, u32 *, u32 *,
			  struct bch_subvolume *, bool);

int bch2_initialize_subvolumes(struct bch_fs *);
int bch2_fs_upgrade_for_subvolumes(struct bch_fs *);

void bch2_fs_subvolumes_init_early(struct bch_fs *);

#endif /* _BCACHEFS_SUBVOLUME_H */
