/****************************************************************
 *								*
 *	Copyright 2001, 2012 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_stdlib.h"
#include "gtm_unistd.h"
#include <errno.h>
#include "gtm_stat.h"
#include "gtm_stdio.h"
#include "gtm_string.h"
#include "gtm_permissions.h"

#ifdef __MVS__
#include "gtm_zos_io.h"
#endif
#include "gdsroot.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsblk.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "gtmio.h"
#include "mupipbckup.h"
#include "copy.h"
#include "eintr_wrappers.h"
#include "sleep_cnt.h"
#include "util.h"
#include "gtmmsg.h"
#include "wcs_sleep.h"
#include "gtm_file_stat.h"
#include "gtm_tempnam.h"
#include "gds_blk_downgrade.h"
#include "shmpool.h"
#include "wcs_phase2_commit_wait.h"

#define TMPDIR_ACCESS_MODE	R_OK | W_OK | X_OK
#define	COMMAND_ARRAY_SIZE	1024
#define	MV_CMD			"mv "
#define	CP_CMD			"cp "

#define	FREE_COMMAND_STR_IF_NEEDED		\
{						\
	if (command != &cmdarray[0])		\
	{					\
		free(command);			\
		command = &cmdarray[0];		\
	}					\
}

#define	CLEANUP_AND_RETURN_FALSE								\
{												\
	int	rc;										\
												\
	if (FD_INVALID != backup_fd)								\
		CLOSEFILE_RESET(backup_fd, rc);	/* resets "backup_fd" to FD_INVALID */		\
	if (!debug_mupip)									\
		UNLINK(tempfilename);								\
	return FALSE;										\
}

GBLREF	bool			file_backed_up;
GBLREF	gd_region		*gv_cur_region;
GBLREF	bool			record;
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	sgmnt_data_ptr_t	cs_data;
GBLREF	bool			online;
GBLREF	uint4			process_id;
GBLREF	boolean_t		debug_mupip;

error_def(ERR_BCKUPBUFLUSH);
error_def(ERR_COMMITWAITSTUCK);
error_def(ERR_DBCCERR);
error_def(ERR_DBROLLEDBACK);
error_def(ERR_ERRCALL);
error_def(ERR_PERMGENFAIL);
error_def(ERR_TEXT);
error_def(ERR_TMPFILENOCRE);
ZOS_ONLY(error_def(ERR_BADTAG);)

bool	mubfilcpy (backup_reg_list *list)
{
	mstr			*file, tempfile;
	unsigned char		cmdarray[COMMAND_ARRAY_SIZE], *command = &cmdarray[0];
	sgmnt_data_ptr_t	header_cpy;
	int4			backup_fd = FD_INVALID, size, vbn, counter, hdrsize, rsize, ntries;
	ssize_t                 status;
	int4			adjust, blk_num, cmdlen, rv, save_errno, temp, tempfilelen, tmplen;
	struct stat		stat_buf;
	off_t			filesize, handled, offset;
	char 			*inbuf, *zero_blk, *ptr, *errptr;
	boolean_t		done;
	char			tempfilename[MAX_FN_LEN + 1], tempdir[MAX_FN_LEN], prefix[MAX_FN_LEN];
	int                     fstat_res;
	uint4			ustatus;
	muinc_blk_hdr_ptr_t	sblkh_p;
	ZOS_ONLY(int		realfiletag;)
	int			group_id;
	int			perm;
	struct perm_diag_data	pdd;

	file = &(list->backup_file);
	file->addr[file->len] = '\0';
	header_cpy = list->backup_hdr;
	hdrsize = (int4)ROUND_UP(SIZEOF_FILE_HDR(header_cpy), DISK_BLOCK_SIZE);

	/* the temporary file should be located in the destination directory */
	ptr = file->addr + file->len - 1;
	while (('/' != *ptr) && (ptr > file->addr))
		ptr--;
	if (ptr > file->addr)
	{
		memcpy(tempdir, file->addr, ptr - file->addr + 1);
		tempdir[ptr - file->addr + 1] = '\0';
	}
	else
	{
		tempdir[0] = '.';
		tempdir[1] = '\0';
	}
	memset(prefix, 0, MAX_FN_LEN);
	memcpy(prefix, gv_cur_region->rname, gv_cur_region->rname_len);
	SPRINTF(&prefix[gv_cur_region->rname_len], "_%d_", process_id);

	/* verify that we have access to the temporary directory to avoid /tmp */
	if (0 != ACCESS(tempdir, TMPDIR_ACCESS_MODE))
	{
		save_errno = errno;
		errptr = (char *)STRERROR(save_errno);
		util_out_print("access : !AZ", TRUE, errptr);
		if (online)
			cs_addrs->nl->nbb = BACKUP_NOT_IN_PROGRESS;
		util_out_print("ERROR: Do NOT have full access to directory !AD", TRUE,
			       LEN_AND_STR(tempdir));
		return FALSE;
	}

	/* make this cp a two step process ==> cp followed by mv */
	ntries = 0;
	fstat_res = FILE_NOT_FOUND;
	/* do go into the loop for the first time, irrespective of fstat_res*/
	while ((FILE_NOT_FOUND != fstat_res) || (!ntries))
	{
		gtm_tempnam(tempdir, prefix, tempfilename);
		tempfile.addr = tempfilename;
		tempfile.len = STRLEN(tempfilename);
		if ((FILE_STAT_ERROR == (fstat_res = gtm_file_stat(&tempfile, NULL, NULL, FALSE, &ustatus))) ||
		    (ntries > MAX_TEMP_OPEN_TRY))
		{
			if (FILE_STAT_ERROR != fstat_res)
				gtm_putmsg(VARLSTCNT(8) ERR_TMPFILENOCRE, 2, tempfile.len, tempfile.addr,
					   ERR_TEXT, 2, LEN_AND_LIT("Tried a maximum number of times, clean-up temporary files " \
								    "in backup directory and retry."));
			else
				gtm_putmsg(VARLSTCNT(5) ERR_TMPFILENOCRE, 2, tempfile.len, tempfile.addr, ustatus);
			return FALSE;
		}
		ntries++;
	}
	/* Calculate total line length for "cp" command. If cannot fit in local variable array, malloc space */
	tmplen = gv_cur_region->dyn.addr->fname_len;
	tempfilelen = STRLEN(tempfilename);
	cmdlen = STR_LIT_LEN(CP_CMD) + tmplen + 1 /* space */ + tempfilelen + 1 /* terminating NULL byte */;
	if (cmdlen > SIZEOF(cmdarray))
		command = malloc(cmdlen);	/* allocate memory and use that instead of local array "cmdarray" */
	MEMCPY_LIT(command, CP_CMD);
	cmdlen = STR_LIT_LEN(CP_CMD);
	memcpy(&command[cmdlen], gv_cur_region->dyn.addr->fname, tmplen);
	cmdlen += tmplen;
	command[cmdlen++] = ' ';
	memcpy(&command[cmdlen], tempfilename, tempfilelen);
	cmdlen += tempfilelen;
	command[cmdlen] = 0;
	if (debug_mupip)
		util_out_print("!/MUPIP INFO:   !AD", TRUE, cmdlen, command);
	if (0 != (rv = SYSTEM((char *)command)))
	{
		if (-1 == rv)
		{
			save_errno = errno;
			errptr = (char *)STRERROR(save_errno);
			util_out_print("system : !AZ", TRUE, errptr);
		}
		if (online)
			cs_addrs->nl->nbb = BACKUP_NOT_IN_PROGRESS;
		util_out_print("Error doing !AD", TRUE, cmdlen, command);
		FREE_COMMAND_STR_IF_NEEDED;
		CLEANUP_AND_RETURN_FALSE;
	}
	FREE_COMMAND_STR_IF_NEEDED;
	assert(command == &cmdarray[0]);

	/* give temporary files the group and permissions as other shared resources - like journal files */
	OPENFILE(tempfilename, O_RDWR, backup_fd);
	if (FD_INVALID == backup_fd)
	{
		save_errno = errno;
		errptr = (char *)STRERROR(save_errno);
		util_out_print("open : !AZ", TRUE, errptr);
		if (online)
			cs_addrs->nl->nbb = BACKUP_NOT_IN_PROGRESS;
		util_out_print("Error opening backup file !AD.", TRUE, file->len, file->addr);
		util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
		CLEANUP_AND_RETURN_FALSE;
	}
	FSTAT_FILE(((unix_db_info *)(gv_cur_region->dyn.addr->file_cntl->file_info))->fd, &stat_buf, fstat_res);
	if (-1 != fstat_res)
		if (gtm_set_group_and_perm(&stat_buf, &group_id, &perm, PERM_FILE, &pdd) < 0)
		{
			send_msg(VARLSTCNT(6+PERMGENDIAG_ARG_COUNT)
				ERR_PERMGENFAIL, 4, RTS_ERROR_STRING("backup file"),
				RTS_ERROR_STRING(((unix_db_info *)(gv_cur_region->dyn.addr->file_cntl->file_info))->fn),
				PERMGENDIAG_ARGS(pdd));
			gtm_putmsg(VARLSTCNT(6+PERMGENDIAG_ARG_COUNT)
				ERR_PERMGENFAIL, 4, RTS_ERROR_STRING("backup file"),
				RTS_ERROR_STRING(((unix_db_info *)(gv_cur_region->dyn.addr->file_cntl->file_info))->fn),
				PERMGENDIAG_ARGS(pdd));
			if (online)
				cs_addrs->nl->nbb = BACKUP_NOT_IN_PROGRESS;
			CLEANUP_AND_RETURN_FALSE;
		}
	/* setup new group and permissions if indicated by the security rules.
	 */
	if ((-1 == fstat_res) || (-1 == FCHMOD(backup_fd, perm)) || ((-1 != group_id) && (-1 == fchown(backup_fd, -1, group_id))))
	{
		save_errno = errno;
		errptr = (char *)STRERROR(save_errno);
		util_out_print("system : !AZ", TRUE, errptr);
		if (online)
			cs_addrs->nl->nbb = BACKUP_NOT_IN_PROGRESS;
		CLEANUP_AND_RETURN_FALSE;
	}
#if defined(__MVS__)
	if (-1 == gtm_zos_tag_to_policy(backup_fd, TAG_BINARY, &realfiletag))
		TAG_POLICY_GTM_PUTMSG( tempfilename, realfiletag, TAG_BINARY, errno);
#endif
	if (online)
	{
		cs_addrs->nl->nbb = BACKUP_NOT_IN_PROGRESS;
		if ((cs_addrs->onln_rlbk_cycle != cs_addrs->nl->onln_rlbk_cycle) ||
			(0 != cs_addrs->nl->onln_rlbk_pid))
		{	/* A concurrent online rollback happened since we did the gvcst_init. The backup is not reliable.
			 * Cleanup and exit
			 */
			gtm_putmsg(VARLSTCNT(1) ERR_DBROLLEDBACK);
			CLEANUP_AND_RETURN_FALSE;
		}
		/* if there has been an extend, truncate it */
		FSTAT_FILE(backup_fd, &stat_buf, fstat_res);
		if (-1 == fstat_res)
		{
			save_errno = errno;
			errptr = (char *)STRERROR(save_errno);
			util_out_print("fstat : !AZ", TRUE, errptr);
			util_out_print("Error obtaining status of backup file !AD.", TRUE, file->len, file->addr);
			util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
			CLEANUP_AND_RETURN_FALSE;
		}
		filesize = header_cpy->start_vbn * DISK_BLOCK_SIZE +
			(off_t)header_cpy->trans_hist.total_blks * header_cpy->blk_size;
		if (filesize != stat_buf.st_size)
		{	/* file has been extended, so truncate it and set the end of database block */
			int ftruncate_res;
			FTRUNCATE(backup_fd, filesize, ftruncate_res);
			if (-1 == ftruncate_res)
			{
				save_errno = errno;
				errptr = (char *)STRERROR(save_errno);
				util_out_print("ftruncate : !AZ", TRUE, errptr);
				util_out_print("Error truncating backup file !AD.", TRUE, file->len, file->addr);
				util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
				CLEANUP_AND_RETURN_FALSE;
			}
			zero_blk = (char *)malloc(DISK_BLOCK_SIZE);
			memset(zero_blk, 0, DISK_BLOCK_SIZE);
			LSEEKWRITE(backup_fd, filesize - DISK_BLOCK_SIZE, zero_blk, DISK_BLOCK_SIZE, status);
			if (0 != status)
			{
				util_out_print("Error writing the last block in database !AD.", TRUE, file->len, file->addr);
				util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
				CLEANUP_AND_RETURN_FALSE;
			}
			free(zero_blk);
		}
		/* By getting crit here, we ensure that there is no process still in transaction logic that sees
		   (nbb != BACKUP_NOT_IN_PRORESS). After rel_crit(), any process that enters transaction logic will
		   see (nbb == BACKUP_NOT_IN_PRORESS) because we just set it to that value. At this point, backup
		   buffer is complete and there will not be any more new entries in the backup buffer until the next
		   backup.
		*/
		assert(!cs_addrs->hold_onto_crit);	/* this ensures we can safely do unconditional grab_crit and rel_crit */
		grab_crit(gv_cur_region);
		assert(cs_data == cs_addrs->hdr);
		if (dba_bg == cs_data->acc_meth)
		{	/* Now that we have crit, wait for any pending phase2 updates to finish. Since phase2 updates happen
			 * outside of crit, we dont want them to keep writing to the backup temporary file even after the
			 * backup is complete and the temporary file has been deleted.
			 */
			if (cs_addrs->nl->wcs_phase2_commit_pidcnt && !wcs_phase2_commit_wait(cs_addrs, NULL))
			{
				assert(FALSE);
				gtm_putmsg(VARLSTCNT(7) ERR_COMMITWAITSTUCK, 5, process_id, 1,
					cs_addrs->nl->wcs_phase2_commit_pidcnt, DB_LEN_STR(gv_cur_region));
				rel_crit(gv_cur_region);
				CLEANUP_AND_RETURN_FALSE;
			}
		}
		if (debug_mupip)
		{
			util_out_print("MUPIP INFO:   Current Transaction # at end of backup is 0x!16@XQ", TRUE,
				&cs_data->trans_hist.curr_tn);
		}
		rel_crit(gv_cur_region);
		counter = 0;
		while ((0 != cs_addrs->shmpool_buffer->backup_cnt) && (0 == cs_addrs->shmpool_buffer->failed))
		{
			backup_buffer_flush(gv_cur_region);
			if (++counter > MAX_BACKUP_FLUSH_TRY)
			{
				gtm_putmsg(VARLSTCNT(1) ERR_BCKUPBUFLUSH);
				CLEANUP_AND_RETURN_FALSE;
			}
			if (counter & 0xF)
				wcs_sleep(counter);
			else
			{	/* Force recovery every few retries - this should not be happening */
				if (FALSE == shmpool_lock_hdr(gv_cur_region))
				{
					assert(FALSE);
					gtm_putmsg(VARLSTCNT(9) ERR_DBCCERR, 2, REG_LEN_STR(gv_cur_region),
						   ERR_ERRCALL, 3, CALLFROM);
					CLEANUP_AND_RETURN_FALSE;
				}
				shmpool_abandoned_blk_chk(gv_cur_region, TRUE);
				shmpool_unlock_hdr(gv_cur_region);
			}
		}

		if (0 != cs_addrs->shmpool_buffer->failed)
		{
			assert(EACCES == cs_addrs->shmpool_buffer->backup_errno);
			util_out_print("Process !UL encountered the following error.", TRUE, cs_addrs->shmpool_buffer->failed);
			if (0 != cs_addrs->shmpool_buffer->backup_errno)
				gtm_putmsg(VARLSTCNT(1) cs_addrs->shmpool_buffer->backup_errno);
			util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
			CLEANUP_AND_RETURN_FALSE;
		}

		FSTAT_FILE(list->backup_fd, &stat_buf, fstat_res);
		if (-1 == fstat_res)
		{
			save_errno = errno;
			errptr = (char *)STRERROR(save_errno);
			util_out_print("fstat : !AZ", TRUE, errptr);
			util_out_print("Error obtaining status of temporary file !AD.",
				       TRUE, LEN_AND_STR(list->backup_tempfile));
			util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
			CLEANUP_AND_RETURN_FALSE;
		}

		if (0 < (filesize = stat_buf.st_size))
		{
			rsize = (int4)(SIZEOF(muinc_blk_hdr) + header_cpy->blk_size);
			sblkh_p = (muinc_blk_hdr_ptr_t)malloc(rsize);
			/* Do not use LSEEKREAD macro here because of dependence on setting filepointer for
			   subsequent reads.
			*/
			if (-1 != (status = (ssize_t)lseek(list->backup_fd, 0, SEEK_SET)))
			{
				DOREADRC(list->backup_fd, (sm_uc_ptr_t)sblkh_p, rsize, status);
			} else
				status = errno;
			if (0 != status)
			{
				if (0 < status)
				{
					errptr = (char *)STRERROR((int)status);
					util_out_print("read : ", TRUE, errptr);
					util_out_print("Error reading the temporary file !AD.",
						       TRUE, LEN_AND_STR(list->backup_tempfile));
				}
				else
					util_out_print("Premature end of temporary file !AD.",
						       TRUE, LEN_AND_STR(list->backup_tempfile));
				util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
				free(sblkh_p);
				CLEANUP_AND_RETURN_FALSE;
			}
			while (TRUE)
			{
				assert(sblkh_p->valid_data);
				blk_num = sblkh_p->blkid;
				if (debug_mupip)
					util_out_print("MUPIP INFO:     Restoring block 0x!XL from temporary file.",
						       TRUE, blk_num);
				if (blk_num < header_cpy->trans_hist.total_blks)
				{
					inbuf = (char_ptr_t)(sblkh_p + 1);
					/* If the incoming block has an ondisk version of V4, convert it back to that
					   version before writing it out so it is the same as the block in the original
					   database.
					*/
					if (GDSV4 == sblkh_p->use.bkup.ondsk_blkver)
					{	/* Need to downgrade this block back to a previous format. Downgrade in place. */
						gds_blk_downgrade((v15_blk_hdr_ptr_t)inbuf, (blk_hdr_ptr_t)inbuf);
						size = (((v15_blk_hdr_ptr_t)inbuf)->bsiz + 1) & ~1;
					} else
						size = (((blk_hdr_ptr_t)inbuf)->bsiz + 1) & ~1;

					if (cs_addrs->do_fullblockwrites)
						size = (int4)ROUND_UP(size, cs_addrs->fullblockwrite_len);
					assert(cs_addrs->hdr->blk_size >= size);
					offset = (header_cpy->start_vbn - 1) * DISK_BLOCK_SIZE
						+ ((off_t)header_cpy->blk_size * blk_num);
					LSEEKWRITE(backup_fd,
						   offset,
						   inbuf,
						   size,
						   save_errno);
					if (0 != save_errno)
					{
						util_out_print("Error accessing output file !AD. Aborting restore.",
							       TRUE, file->len, file->addr);
						errptr = (char *)STRERROR(save_errno);
						util_out_print("write : !AZ", TRUE, errptr);
						free(sblkh_p);
						CLEANUP_AND_RETURN_FALSE;
					}
				} /* Else ignore block that is larger than file was at time backup initiated */
				DOREADRC(list->backup_fd, (sm_uc_ptr_t)sblkh_p, rsize, save_errno);
				if (0 != save_errno)
				{
					if (0 < save_errno)
					{
						util_out_print("Error accessing temporary file !AD.",
							       TRUE, LEN_AND_STR(list->backup_tempfile));
						errptr = (char *)STRERROR(save_errno);
						util_out_print("read : !AZ", TRUE, errptr);
						free(sblkh_p);
						CLEANUP_AND_RETURN_FALSE;
					} else
						/* End of file .. Note this does not detect the difference between
						   clean end of file and partial record end of file.
						 */
						break;
				}
			}
			free(sblkh_p);
		}
	}

	LSEEKWRITE(backup_fd, 0, header_cpy, hdrsize, save_errno);
	if (0 != save_errno)
	{
		errptr = (char *)STRERROR(save_errno);
		util_out_print("write : !AZ", TRUE, errptr);
		util_out_print("Error writing data to backup file !AD.", TRUE, file->len, file->addr);
		util_out_print("WARNING: backup file !AD is not valid.", TRUE, file->len, file->addr);
		CLEANUP_AND_RETURN_FALSE;
	}
	CLOSEFILE_RESET(backup_fd, status);	/* resets "backup_fd" to FD_INVALID */

	/* mv it to destination */
	/* Calculate total line length for "mv" command. If cannot fit in local variable array, malloc space */
	assert(command == &cmdarray[0]);
	tmplen = file->len;
	cmdlen = STR_LIT_LEN(MV_CMD) + tempfilelen + 1 /* space */ + tmplen + 1 /* terminating NULL byte */;
	if (cmdlen > SIZEOF(cmdarray))
		command = malloc(cmdlen);	/* allocate memory and use that instead of local array "cmdarray" */
	MEMCPY_LIT(command, MV_CMD);
	cmdlen = STR_LIT_LEN(MV_CMD);
	memcpy(&command[cmdlen], tempfilename, tempfilelen);
	cmdlen += tempfilelen;
	command[cmdlen++] = ' ';
	memcpy(&command[cmdlen], file->addr, tmplen);
	cmdlen += tmplen;
	command[cmdlen] = 0;
	if (debug_mupip)
		util_out_print("MUPIP INFO:   !AD", TRUE, cmdlen, command);
	if (0 != (rv = SYSTEM((char *)command)))
	{
		if (-1 == rv)
		{
			save_errno = errno;
			errptr = (char *)STRERROR(save_errno);
			util_out_print("system : !AZ", TRUE, errptr);
		}
		util_out_print("Error executing command : !AD", TRUE, cmdlen, command);
		FREE_COMMAND_STR_IF_NEEDED;
		CLEANUP_AND_RETURN_FALSE;
	}
	FREE_COMMAND_STR_IF_NEEDED;
	assert(command == &cmdarray[0]);
	util_out_print("DB file !AD backed up in file !AD", TRUE, gv_cur_region->dyn.addr->fname_len,
		       gv_cur_region->dyn.addr->fname, file->len, file->addr);
	util_out_print("Transactions up to 0x!16@XQ are backed up.", TRUE, &header_cpy->trans_hist.curr_tn);
	cs_addrs->hdr->last_com_backup = header_cpy->trans_hist.curr_tn;
	cs_addrs->hdr->last_com_bkup_last_blk = header_cpy->trans_hist.total_blks;
	if (record)
	{
		cs_addrs->hdr->last_rec_backup = header_cpy->trans_hist.curr_tn;
		cs_addrs->hdr->last_rec_bkup_last_blk = header_cpy->trans_hist.total_blks;
	}
	file_backed_up = TRUE;
	return TRUE;
}
