/****************************************************************
 *								*
 *	Copyright 2009, 2010 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_fcntl.h"
#include "gtm_stat.h"
#include "gtm_unistd.h"
#include "gtm_permissions.h"
#include "gtm_string.h"
#include "gtm_stdio.h"
#include "gtm_stdlib.h"
#include "gtm_tempnam.h"
#include "gtm_time.h"

#include "gdsroot.h"
#include "gdskill.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsblk.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "iosp.h"
#include "error.h"
#include "cli.h"
#include "gtmio.h"
#include "send_msg.h"
#include "shmpool.h"
#include "db_snapshot.h"

GBLREF	uint4		process_id;

boolean_t	ss_write_block(sgmnt_addrs *csa,
				block_id blk,
				cache_rec_ptr_t cr,
				sm_uc_ptr_t mm_blk_ptr,
				snapshot_context_ptr_t lcl_ss_ctx)
{
	sgmnt_data_ptr_t	csd;
	node_local_ptr_t	cnl;
	sm_uc_ptr_t		blk_ptr;
	shm_snapshot_ptr_t	ss_shm_ptr;
	int			blk_size, pwrite_res, status, size;
	off_t			blk_offset;
	boolean_t		is_bg;
	DEBUG_ONLY(
		blk_hdr_ptr_t	save_blk_ptr;
	)

	error_def(ERR_SSFILOPERR);
	assert(NULL != lcl_ss_ctx);
	csd = csa->hdr;
	cnl = csa->nl;
	is_bg = (dba_bg == csd->acc_meth);
	assert(is_bg || (dba_mm == csd->acc_meth));
	assert((is_bg && (NULL != cr) && (NULL == mm_blk_ptr)) || (!is_bg && (NULL == cr) && (NULL != mm_blk_ptr)));
	ss_shm_ptr = lcl_ss_ctx->ss_shm_ptr;
	DBG_ENSURE_PTR_WITHIN_SS_BOUNDS(csa, (sm_uc_ptr_t)ss_shm_ptr);
	if (ss_shm_ptr->failure_errno)
	{
		/* A prior GT.M process has encountered an error and hence the snapshot is invalid and hence there is
		 * no use in continuing with the before image writes. So, do early return.
		 */
		return FALSE;
	}
	assert(cnl->ss_shmid == lcl_ss_ctx->attach_shmid);
	assert(ss_shm_ptr->ss_info.ss_shmid == lcl_ss_ctx->attach_shmid);
	/* If this block falls outside the total number of blocks that were present during the time of snapshot initiation, then
	 * we should not be writing the before image as the shadow bitmap might not have that much space. Hence do an early return
	 * to let GT.M continue as if the block was already written to the shadow file.
	 */
	if (blk >= lcl_ss_ctx->total_blks)
		return TRUE;
	/* ss_release (function that invalidates a snapshot and announces GT.M not to write any more
	 * before images) waits for the active phase 2 commits to complete and hence the below
	 * assert is safe to be used.
	 */
	assert(ss_shm_ptr->in_use && SNAPSHOTS_IN_PROG(csa));
	assert(!is_bg || ((NULL != cr) && cr->in_cw_set)); /* ensure the buffer has been pinned (from preemption in db_csh_getn) */
	blk_size = csd->blk_size;
	if (is_bg)
		blk_ptr = GDS_ANY_REL2ABS(csa, cr->buffaddr);
	else
		blk_ptr = mm_blk_ptr;
#	ifdef GTM_CRYPT
	/* If the database is encrypted, the old_block will be in the encrypted twin buffer. Logic similar to the one
	 * done in backup_block.c
	 */
	if (csd->is_encrypted)
	{
		assert(is_bg);
		DBG_ENSURE_PTR_IS_VALID_GLOBUFF(csa, csd, blk_ptr);
		DEBUG_ONLY(save_blk_ptr = (blk_hdr_ptr_t)blk_ptr;)
		blk_ptr = GDS_ANY_ENCRYPTGLOBUF(blk_ptr, csa);
		/* Ensure that the unencrypted buffer (save_blk_ptr) and the encrypted twin buffer (blk_ptr) are indeed
		 * holding the same block
		 */
		assert(save_blk_ptr->tn == ((blk_hdr_ptr_t)blk_ptr)->tn);
		assert(save_blk_ptr->bsiz == ((blk_hdr_ptr_t)blk_ptr)->bsiz);
		assert(save_blk_ptr->levl == ((blk_hdr_ptr_t)blk_ptr)->levl);
		DBG_ENSURE_PTR_IS_VALID_ENCTWINGLOBUFF(csa, csd, blk_ptr);
	}
#	endif
	assert(NULL != blk_ptr);
	size = ((blk_hdr_ptr_t)blk_ptr)->bsiz;
	if (csa->do_fullblockwrites)
		size = (int)(ROUND_UP(size, csa->fullblockwrite_len));
	/* If the block is FREE and block size is zero, we don't want to issue an empty write below. Instead write block of size
	 * equal to the database block size.
	 */
	if (!size || (size > csd->blk_size))
		size = csd->blk_size;
	assert(size <= ss_shm_ptr->ss_info.db_blk_size);
	assert(((blk_hdr_ptr_t)blk_ptr)->tn < ss_shm_ptr->ss_info.snapshot_tn);
	blk_offset = ((off_t)(lcl_ss_ctx->shadow_vbn - 1) * DISK_BLOCK_SIZE + (off_t)blk * blk_size);
	/* Note: If a FREE block is being written here, then we could avoid the write below: if the underlying file system
	 * is guaranteed to give us all zeros for a block and if the block header is empty
	 */
	assert(-1 != lcl_ss_ctx->shdw_fd);
	LSEEKWRITE(lcl_ss_ctx->shdw_fd, blk_offset, blk_ptr, size, pwrite_res);
	if ((0 != pwrite_res) && SNAPSHOTS_IN_PROG(cnl))
	{
		send_msg(VARLSTCNT(7) ERR_SSFILOPERR, 4, LEN_AND_LIT("write"),
			LEN_AND_STR(lcl_ss_ctx->shadow_file),
			pwrite_res);
		ss_shm_ptr->failed_pid = process_id;
		ss_shm_ptr->failure_errno = pwrite_res;
		return FALSE;
	}
	/* Mark the block as before imaged in the bitmap */
	ss_set_shdw_bitmap(csa, lcl_ss_ctx, blk);
	return TRUE;
}
