// SPDX-License-Identifier: GPL-2.0
/*
 * io_misc.c - fallocate, fpunch, truncate:
 */

#include "bcachefs.h"

#include "alloc/buckets.h"
#include "alloc/foreground.h"

#include "btree/bkey_buf.h"
#include "btree/update.h"

#include "data/extents.h"
#include "data/extent_update.h"
#include "data/io_misc.h"
#include "data/reconcile/trigger.h"
#include "data/write.h"

#include "fs/inode.h"
#include "fs/logged_ops.h"

#include "init/error.h"

#include "snapshots/subvolume.h"

#include "util/clock.h"

/* Overwrites whatever was present with zeroes: */
int bch2_extent_fallocate(struct btree_trans *trans,
			  subvol_inum inum,
			  struct btree_iter *iter,
			  u64 sectors,
			  struct bch_inode_opts opts,
			  s64 *i_sectors_delta,
			  struct write_point_specifier write_point)
{
	struct bch_fs *c = trans->c;
	CLASS(disk_reservation, res)(c);
	struct open_buckets open_buckets = { 0 };
	unsigned sectors_allocated = 0, new_replicas;
	bool unwritten = opts.nocow &&
	    c->sb.version >= bcachefs_metadata_version_unwritten_extents;
	int ret;

	struct bkey_buf old __cleanup(bch2_bkey_buf_exit);
	bch2_bkey_buf_init(&old);
	struct bkey_buf new __cleanup(bch2_bkey_buf_exit);
	bch2_bkey_buf_init(&new);

	CLASS(closure_stack, cl)();

	struct bkey_s_c k = bkey_try(bch2_btree_iter_peek_slot(iter));

	sectors = min_t(u64, sectors, k.k->p.offset - iter->pos.offset);
	new_replicas = max(0, (int) opts.data_replicas -
			   (int) bch2_bkey_nr_ptrs_fully_allocated(c, k));

	/*
	 * Get a disk reservation before (in the nocow case) calling
	 * into the allocator:
	 */
	ret = bch2_disk_reservation_get(c, &res.r, sectors, new_replicas,
				       BCH_DISK_RESERVATION_PARTIAL);
	if (unlikely(!res.r.sectors && new_replicas)) {
		ret = bch_err_throw(c, ENOSPC_disk_reservation);
		goto err_noprint;
	}

	if (new_replicas)
		sectors = res.r.sectors / new_replicas;

	bch2_bkey_buf_reassemble(&old, k);

	if (!unwritten) {
		struct bkey_i_reservation *reservation;

		bch2_bkey_buf_realloc(&new, sizeof(*reservation) / sizeof(u64));
		reservation = bkey_reservation_init(new.k);
		reservation->k.p = iter->pos;
		bch2_key_resize(&reservation->k, sectors);
		reservation->v.nr_replicas = opts.data_replicas;
	} else {
		struct bkey_i_extent *e;
		struct bch_devs_list devs_have;
		struct write_point *wp;

		devs_have.nr = 0;

		bch2_bkey_buf_realloc(&new, BKEY_EXTENT_U64s_MAX);

		e = bkey_extent_init(new.k);
		e->k.p = iter->pos;

		struct alloc_request *req;
		ret = PTR_ERR_OR_ZERO(req = alloc_request_get(trans,
						opts.foreground_target,
						false,
						&devs_have,
						opts.data_replicas,
						opts.data_replicas,
						BCH_WATERMARK_normal,
						0, &cl)) ?:
			bch2_alloc_sectors_req(trans, req, write_point, &wp);
		if (bch2_err_matches(ret, BCH_ERR_operation_blocked)) {
			bch2_trans_unlock_long(trans);
			bch2_wait_on_allocator(c, req, ret, &cl);
			ret = bch_err_throw(c, transaction_restart_nested);
		}
		if (ret)
			goto err;

		sectors = min_t(u64, sectors, wp->sectors_free);
		sectors_allocated = sectors;

		bch2_key_resize(&e->k, sectors);

		bch2_open_bucket_get(c, wp, &open_buckets);
		bch2_alloc_sectors_append_ptrs(c, wp, &e->k_i, sectors, false);
		bch2_alloc_sectors_done(c, wp);

		extent_for_each_ptr(extent_i_to_s(e), ptr)
			ptr->unwritten = true;
	}

	ret = bch2_extent_update(trans, inum, iter, new.k, &res.r,
				 0, i_sectors_delta, true, 0);
err:
	if (!ret && sectors_allocated)
		bch2_increment_clock(c, sectors_allocated, WRITE);
	if (should_print_err(ret)) {
		CLASS(printbuf, buf)();
		bch2_inum_offset_err_msg_trans(trans, &buf, inum.subvol, iter->pos);
		prt_printf(&buf, "fallocate error: %s", bch2_err_str(ret));
		bch_err_ratelimited(c, "%s", buf.buf);
	}
err_noprint:
	bch2_open_buckets_put(c, &open_buckets);

	return ret;
}

/* For fsck */
int bch2_fpunch_snapshot(struct btree_trans *trans, struct bpos start, struct bpos end)
{
	u32 restart_count = trans->restart_count;
	struct bch_fs *c = trans->c;
	CLASS(disk_reservation, res)(c);
	unsigned max_sectors	= KEY_SIZE_MAX & (~0 << c->block_bits);

	return for_each_btree_key_max_commit(trans, iter, BTREE_ID_extents,
			start, end, 0, k,
			&res.r, NULL, BCH_TRANS_COMMIT_no_enospc, ({
		struct bkey_i delete;
		bkey_init(&delete.k);
		delete.k.p = iter.pos;

		/* create the biggest key we can */
		bch2_key_resize(&delete.k, max_sectors);
		bch2_cut_back(end, &delete);

		bch2_extent_trim_atomic(trans, &iter, &delete) ?:
		bch2_trans_update(trans, &iter, &delete, 0);
	})) ?: trans_was_restarted(trans, restart_count);
}

/*
 * Returns -BCH_ERR_transacton_restart if we had to drop locks:
 */
int bch2_fpunch_at(struct btree_trans *trans, struct btree_iter *iter,
		   subvol_inum inum, u64 end,
		   s64 *i_sectors_delta)
{
	struct bch_fs *c	= trans->c;
	unsigned max_sectors	= KEY_SIZE_MAX & (~0 << c->block_bits);
	struct bpos end_pos = POS(inum.inum, end);
	struct bkey_s_c k;
	int ret = 0, ret2 = 0;
	u32 snapshot;

	while (!ret ||
	       bch2_err_matches(ret, BCH_ERR_transaction_restart)) {
		struct disk_reservation disk_res =
			bch2_disk_reservation_init(c, 0);
		struct bkey_i delete;

		if (ret)
			ret2 = ret;

		bch2_trans_begin(trans);

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

		bch2_btree_iter_set_snapshot(iter, snapshot);

		/*
		 * peek_max() doesn't have ideal semantics for extents:
		 */
		k = bch2_btree_iter_peek_max(iter, end_pos);
		if (!k.k)
			break;

		ret = bkey_err(k);
		if (ret)
			continue;

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

		/* create the biggest key we can */
		bch2_key_resize(&delete.k, max_sectors);
		bch2_cut_back(end_pos, &delete);

		ret = bch2_extent_update(trans, inum, iter, &delete,
				&disk_res, 0, i_sectors_delta, false, 0);
		bch2_disk_reservation_put(c, &disk_res);
	}

	return ret ?: ret2;
}

int bch2_fpunch(struct bch_fs *c, subvol_inum inum, u64 start, u64 end,
		s64 *i_sectors_delta)
{
	CLASS(btree_trans, trans)(c);
	CLASS(btree_iter, iter)(trans, BTREE_ID_extents, POS(inum.inum, start),
				BTREE_ITER_intent);

	int ret = bch2_fpunch_at(trans, &iter, inum, end, i_sectors_delta);

	return bch2_err_matches(ret, BCH_ERR_transaction_restart) ? 0 : ret;
}

/* truncate: */

void bch2_logged_op_truncate_to_text(struct printbuf *out, struct bch_fs *c, struct bkey_s_c k)
{
	struct bkey_s_c_logged_op_truncate op = bkey_s_c_to_logged_op_truncate(k);

	prt_printf(out, "subvol=%u", le32_to_cpu(op.v->subvol));
	prt_printf(out, " inum=%llu", le64_to_cpu(op.v->inum));
	prt_printf(out, " new_i_size=%llu", le64_to_cpu(op.v->new_i_size));
}

static int truncate_set_isize(struct btree_trans *trans,
			      subvol_inum inum,
			      u64 new_i_size,
			      bool warn)
{
	CLASS(btree_iter_uninit, iter)(trans);
	struct bch_inode_unpacked inode_u;

	return __bch2_inode_peek(trans, &iter, &inode_u, inum, BTREE_ITER_intent,
				 warn ? __func__ : NULL) ?:
		(inode_u.bi_size = new_i_size, 0) ?:
		bch2_inode_write(trans, &iter, &inode_u);
}

static int __bch2_resume_logged_op_truncate(struct btree_trans *trans,
					    struct bkey_i *op_k,
					    u64 *i_sectors_delta)
{
	struct bch_fs *c = trans->c;
	struct bkey_i_logged_op_truncate *op = bkey_i_to_logged_op_truncate(op_k);
	subvol_inum inum = { le32_to_cpu(op->v.subvol), le64_to_cpu(op->v.inum) };
	u64 new_i_size = le64_to_cpu(op->v.new_i_size);
	bool warn_errors = i_sectors_delta != NULL;
	int ret;

	ret = commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
			truncate_set_isize(trans, inum, new_i_size, i_sectors_delta != NULL));
	if (ret)
		goto err;

	{
		CLASS(btree_iter, fpunch_iter)(trans, BTREE_ID_extents,
					       POS(inum.inum, round_up(new_i_size, block_bytes(c)) >> 9),
					       BTREE_ITER_intent);
		ret = bch2_fpunch_at(trans, &fpunch_iter, inum, U64_MAX, i_sectors_delta);

		if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
			ret = 0;
	}
err:
	if (warn_errors)
		bch_err_fn(c, ret);
	return ret;
}

int bch2_resume_logged_op_truncate(struct btree_trans *trans, struct bkey_i *op_k)
{
	return __bch2_resume_logged_op_truncate(trans, op_k, NULL);
}

int bch2_truncate(struct bch_fs *c, subvol_inum inum, u64 new_i_size, u64 *i_sectors_delta)
{
	struct bkey_i_logged_op_truncate op;

	bkey_logged_op_truncate_init(&op.k_i);
	op.v.subvol	= cpu_to_le32(inum.subvol);
	op.v.inum	= cpu_to_le64(inum.inum);
	op.v.new_i_size	= cpu_to_le64(new_i_size);

	/*
	 * Logged ops aren't atomic w.r.t. snapshot creation: creating a
	 * snapshot while they're in progress, then crashing, will result in the
	 * resume only proceeding in one of the snapshots
	 */
	guard(rwsem_read)(&c->snapshots.create_lock);
	CLASS(btree_trans, trans)(c);
	try(bch2_logged_op_start(trans, &op.k_i));
	int ret = __bch2_resume_logged_op_truncate(trans, &op.k_i, i_sectors_delta);
	ret = bch2_logged_op_finish(trans, &op.k_i) ?: ret;
	return ret;
}

/* finsert/fcollapse: */

void bch2_logged_op_finsert_to_text(struct printbuf *out, struct bch_fs *c, struct bkey_s_c k)
{
	struct bkey_s_c_logged_op_finsert op = bkey_s_c_to_logged_op_finsert(k);

	prt_printf(out, "subvol=%u",		le32_to_cpu(op.v->subvol));
	prt_printf(out, " inum=%llu",		le64_to_cpu(op.v->inum));
	prt_printf(out, " dst_offset=%lli",	le64_to_cpu(op.v->dst_offset));
	prt_printf(out, " src_offset=%llu",	le64_to_cpu(op.v->src_offset));
}

static int adjust_i_size(struct btree_trans *trans, subvol_inum inum,
			 u64 offset, s64 len, bool warn)
{
	offset	<<= 9;
	len	<<= 9;

	CLASS(btree_iter_uninit, iter)(trans);
	struct bch_inode_unpacked inode_u;

	try(__bch2_inode_peek(trans, &iter, &inode_u, inum, BTREE_ITER_intent,
			      warn ? __func__ : NULL));

	if (len > 0) {
		if (MAX_LFS_FILESIZE - inode_u.bi_size < len)
			return -EFBIG;

		if (offset >= inode_u.bi_size)
			return bch_err_throw(trans->c, EINVAL_finsert_offset_past_eof);
	}

	inode_u.bi_size += len;
	inode_u.bi_mtime = inode_u.bi_ctime = bch2_current_time(trans->c);

	return bch2_inode_write(trans, &iter, &inode_u);
}

static int __bch2_resume_logged_op_finsert(struct btree_trans *trans,
					   struct bkey_i *op_k,
					   u64 *i_sectors_delta)
{
	struct bch_fs *c = trans->c;
	struct bkey_i_logged_op_finsert *op = bkey_i_to_logged_op_finsert(op_k);
	subvol_inum inum = { le32_to_cpu(op->v.subvol), le64_to_cpu(op->v.inum) };
	u64 dst_offset = le64_to_cpu(op->v.dst_offset);
	u64 src_offset = le64_to_cpu(op->v.src_offset);
	s64 shift = dst_offset - src_offset;
	u64 len = abs(shift);
	u64 pos = le64_to_cpu(op->v.pos);
	bool insert = shift > 0;
	u32 snapshot;
	bool warn_errors = i_sectors_delta != NULL;
	int ret = 0;

	/*
	 * check for missing subvolume before fpunch, as in resume we don't want
	 * it to be a fatal error
	 */
	try(lockrestart_do(trans, __bch2_subvolume_get_snapshot(trans, inum.subvol, &snapshot, warn_errors)));

	CLASS(btree_iter, iter)(trans, BTREE_ID_extents, POS(inum.inum, 0), BTREE_ITER_intent);

	switch (op->v.state) {
case LOGGED_OP_FINSERT_start:
	op->v.state = LOGGED_OP_FINSERT_shift_extents;

	if (insert) {
		ret = commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
				adjust_i_size(trans, inum, src_offset, len, warn_errors) ?:
				bch2_logged_op_update(trans, &op->k_i));
		if (ret)
			goto err;
	} else {
		bch2_btree_iter_set_pos(&iter, POS(inum.inum, src_offset));

		ret = bch2_fpunch_at(trans, &iter, inum, src_offset + len, i_sectors_delta);
		if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
			goto err;

		ret = commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
				bch2_logged_op_update(trans, &op->k_i));
	}

	fallthrough;
case LOGGED_OP_FINSERT_shift_extents:
	while (1) {
		struct disk_reservation disk_res =
			bch2_disk_reservation_init(c, 0);
		struct bkey_i delete, *copy;
		struct bkey_s_c k;
		struct bpos src_pos = POS(inum.inum, src_offset);

		bch2_trans_begin(trans);

		ret = __bch2_subvolume_get_snapshot(trans, inum.subvol, &snapshot,
						    warn_errors);
		if (ret)
			goto btree_err;

		bch2_btree_iter_set_snapshot(&iter, snapshot);
		bch2_btree_iter_set_pos(&iter, SPOS(inum.inum, pos, snapshot));

		k = insert
			? bch2_btree_iter_peek_prev_min(&iter, POS(inum.inum, 0))
			: bch2_btree_iter_peek_max(&iter, POS(inum.inum, U64_MAX));
		if ((ret = bkey_err(k)))
			goto btree_err;

		if (!k.k ||
		    k.k->p.inode != inum.inum ||
		    bkey_le(k.k->p, POS(inum.inum, src_offset)))
			break;

		copy = bch2_bkey_make_mut_noupdate(trans, k);
		if ((ret = PTR_ERR_OR_ZERO(copy)))
			goto btree_err;

		if (snapshot != k.k->p.snapshot) {
			ret = bch2_disk_reservation_add(c, &disk_res,
					copy->k.size *
					bch2_bkey_nr_ptrs_allocated(c, bkey_i_to_s_c(copy)),
					0);
			if (ret)
				goto btree_err;
		} else if (insert &&
			   bkey_lt(bkey_start_pos(k.k), src_pos)) {
			bch2_cut_front(c, src_pos, copy);

			/* Splitting compressed extent? */
			bch2_disk_reservation_add(c, &disk_res,
					copy->k.size *
					bch2_bkey_nr_ptrs_allocated(c, bkey_i_to_s_c(copy)),
					BCH_DISK_RESERVATION_NOFAIL);
		}

		bkey_init(&delete.k);
		delete.k.p = copy->k.p;
		delete.k.p.snapshot = snapshot;
		delete.k.size = copy->k.size;

		copy->k.p.offset += shift;
		copy->k.p.snapshot = snapshot;

		op->v.pos = cpu_to_le64(insert ? bkey_start_offset(&delete.k) : delete.k.p.offset);

		ret =   bch2_btree_insert_trans(trans, BTREE_ID_extents, &delete, 0) ?:
			bch2_btree_insert_trans(trans, BTREE_ID_extents, copy, 0) ?:
			bch2_logged_op_update(trans, &op->k_i) ?:
			bch2_trans_commit(trans, &disk_res, NULL, BCH_TRANS_COMMIT_no_enospc);
btree_err:
		bch2_disk_reservation_put(c, &disk_res);

		if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
			continue;
		if (ret)
			goto err;

		pos = le64_to_cpu(op->v.pos);
	}

	op->v.state = LOGGED_OP_FINSERT_finish;

	if (!insert) {
		ret = commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
				adjust_i_size(trans, inum, src_offset, shift, warn_errors) ?:
				bch2_logged_op_update(trans, &op->k_i));
	} else {
		/* We need an inode update to update bi_journal_seq for fsync: */
		ret = commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
				adjust_i_size(trans, inum, 0, 0, warn_errors) ?:
				bch2_logged_op_update(trans, &op->k_i));
	}

	break;
case LOGGED_OP_FINSERT_finish:
	break;
	}
err:
	if (warn_errors)
		bch_err_fn(c, ret);
	return ret;
}

int bch2_resume_logged_op_finsert(struct btree_trans *trans, struct bkey_i *op_k)
{
	return __bch2_resume_logged_op_finsert(trans, op_k, NULL);
}

int bch2_fcollapse_finsert(struct bch_fs *c, subvol_inum inum,
			   u64 offset, u64 len, bool insert,
			   s64 *i_sectors_delta)
{
	struct bkey_i_logged_op_finsert op;
	s64 shift = insert ? len : -len;

	bkey_logged_op_finsert_init(&op.k_i);
	op.v.subvol	= cpu_to_le32(inum.subvol);
	op.v.inum	= cpu_to_le64(inum.inum);
	op.v.dst_offset	= cpu_to_le64(offset + shift);
	op.v.src_offset	= cpu_to_le64(offset);
	op.v.pos	= cpu_to_le64(insert ? U64_MAX : offset);

	/*
	 * Logged ops aren't atomic w.r.t. snapshot creation: creating a
	 * snapshot while they're in progress, then crashing, will result in the
	 * resume only proceeding in one of the snapshots
	 */
	guard(rwsem_read)(&c->snapshots.create_lock);
	CLASS(btree_trans, trans)(c);
	try(bch2_logged_op_start(trans, &op.k_i));
	int ret = __bch2_resume_logged_op_finsert(trans, &op.k_i, i_sectors_delta);
	ret = bch2_logged_op_finish(trans, &op.k_i) ?: ret;
	return ret;
}
