/****************************************************************
 *								*
 * Copyright (c) 2001-2021 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	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_unistd.h"
#include "gtm_stdio.h"
#include "gtm_string.h"
#include <errno.h>

#include <rtnhdr.h>
#include "compiler.h"
#include "urx.h"
#include "objlabel.h"	/* needed for masscomp.h */
#include "masscomp.h"
#include "gtmio.h"
#include "incr_link.h"
#include "min_max.h"	/* MIDENT_CMP needs MIN */
#include "cmd_qlf.h"	/* needed for CQ_UTF8 */
#include "gtm_text_alloc.h"

/* INCR_LINK - read and process a mumps object module. Link said module to currently executing image */

LITREF char gtm_release_name[];
LITREF int4 gtm_release_name_len;

static char 		*code;
GBLREF mident_fixed 	zlink_mname;
GBLREF  boolean_t	gtm_utf8_mode;

error_def(ERR_INVOBJFILE);
error_def(ERR_LOADRUNNING);
error_def(ERR_TEXT);

#define RELREAD 50	/* number of relocation entries to buffer */
typedef struct res_list_struct
{
	struct res_list_struct *next, *list;
	unsigned int	addr, symnum;
} res_list;

void res_free(res_list *root);
bool addr_fix(int file, struct exec *fhead, urx_rtnref *urx_lcl, rhdtyp *code);
void zl_error(int4 *file, int4 err, int4 len, char *addr, int4 err2, int4 len2, char *addr2);

boolean_t incr_link(int *file_desc, zro_ent *dummy, uint4 fname_len, char *fname)
{
	rhdtyp		*hdr, *old_rhead;
	int 		code_size, save_errno, cnt;
	int4 		rhd_diff, read_size;
	char		*literal_ptr;
	var_tabent	*curvar;
	char		module_name[SIZEOF(mident_fixed)];
	lab_tabent	*lbt_ent, *lbt_bot, *lbt_top, *olbt_ent, *olbt_bot, *olbt_top, *curlab;
	urx_rtnref	urx_lcl_anchor;
	int		order;
	struct exec 	file_hdr;

	assert(file_desc && (FD_INVALID != *file_desc));
	urx_lcl_anchor.len = 0;
	urx_lcl_anchor.addr = 0;
	urx_lcl_anchor.lab = 0;
	urx_lcl_anchor.next = 0;
	code = NULL;
	DOREADRL(*file_desc, &file_hdr, SIZEOF(file_hdr), read_size);
	if (read_size != SIZEOF(file_hdr))
	{
		if (-1 == read_size)
		{
			save_errno = errno;
			zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, strlen(STRERROR(save_errno)),
				 STRERROR(save_errno));
		} else
			zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, RTS_ERROR_TEXT("reading file header"));
	} else if (OMAGIC != file_hdr.a_magic)
		zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, RTS_ERROR_TEXT("bad magic"));
	else if (OBJ_LABEL != file_hdr.a_stamp)
		return IL_RECOMPILE;	/* wrong version */
	assert(0 == file_hdr.a_bss);
	code_size = file_hdr.a_text + file_hdr.a_data;
	code = GTM_TEXT_ALLOC(code_size);
	DOREADRL(*file_desc, code, code_size, read_size);
	if (read_size != code_size)
	{
		if (-1 == read_size)
		{
			save_errno = errno;
			zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, strlen(STRERROR(save_errno)),
				 STRERROR(save_errno));
		} else
			zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, RTS_ERROR_TEXT("reading code"));
	}
	hdr = (rhdtyp *)code;
	if (memcmp(&hdr->jsb[0], "GTM_CODE", SIZEOF(hdr->jsb)))
		zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, RTS_ERROR_TEXT("missing GTM_CODE"));
	if ((hdr->compiler_qlf & CQ_UTF8) && !gtm_utf8_mode)
		zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT,
			RTS_ERROR_TEXT("Object compiled with CHSET=UTF-8 which is different from $ZCHSET"));
	if (!(hdr->compiler_qlf & CQ_UTF8) && gtm_utf8_mode)
		zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT,
			RTS_ERROR_TEXT("Object compiled with CHSET=M which is different from $ZCHSET"));
	literal_ptr = code + file_hdr.a_text;
	hdr->routine_source_offset += (uint4)literal_ptr; /* now an absolute pointer, not an offset */
	for (cnt = hdr->vartab_len, curvar = VARTAB_ADR(hdr); cnt; --cnt, ++curvar)
	{ /* relocate the variable table */
		assert(0 < curvar->var_name.len);
		curvar->var_name.addr += (uint4)literal_ptr;
	}
	for (cnt = hdr->labtab_len, curlab = LABTAB_ADR(hdr); cnt; --cnt, ++curlab)
		/* relocate the label table */
		curlab->lab_name.addr += (uint4)literal_ptr;
	if (!addr_fix(*file_desc, &file_hdr, &urx_lcl_anchor, hdr))
	{
		urx_free(&urx_lcl_anchor);
		zl_error(file_desc, ERR_INVOBJFILE, fname_len, fname, ERR_TEXT, RTS_ERROR_TEXT("address fixup failure"));
	}
	if (!zlput_rname(hdr))
	{
		urx_free(&urx_lcl_anchor);
		/* Copy routine name to local variable because zl_error free's it.  */
		memcpy(&module_name[0], hdr->routine_name.addr, hdr->routine_name.len);
		zl_error(file_desc, ERR_LOADRUNNING, hdr->routine_name.len, &module_name[0], 0, 0, NULL);
	}
	urx_add(&urx_lcl_anchor);
	old_rhead = (rhdtyp *)hdr->old_rhead_ptr;
	lbt_bot = (lab_tabent *)((char *)hdr + hdr->labtab_ptr);
	lbt_top = lbt_bot + hdr->labtab_len;
	while (old_rhead)
	{
		lbt_ent = lbt_bot;
		olbt_bot = (lab_tabent *)((char *)old_rhead + old_rhead->labtab_ptr);
		olbt_top = olbt_bot + old_rhead->labtab_len;
		for (olbt_ent = olbt_bot; olbt_ent < olbt_top; olbt_ent++)
		{
			for (; lbt_ent < lbt_top; lbt_ent++)
			{
				MIDENT_CMP(&olbt_ent->lab_name, &lbt_ent->lab_name, order);
				if (order <= 0)
					break;
			}
			if ((lbt_ent < lbt_top) && !order)
			{
				olbt_ent->lab_ln_ptr = lbt_ent->lab_ln_ptr;
				olbt_ent->has_parms = lbt_ent->has_parms;
			} else
				olbt_ent->lab_ln_ptr = 0;
		}
		rhd_diff = (char *)hdr - (char *)old_rhead;
		old_rhead->src_full_name = hdr->src_full_name;
		old_rhead->routine_name = hdr->routine_name;
		old_rhead->vartab_len = hdr->vartab_len;
		old_rhead->vartab_ptr = hdr->vartab_ptr + rhd_diff;
		old_rhead->ptext_ptr = hdr->ptext_ptr + rhd_diff;
		old_rhead->current_rhead_ptr = rhd_diff;
		old_rhead->temp_mvals = hdr->temp_mvals;
		old_rhead->temp_size = hdr->temp_size;
		old_rhead = (rhdtyp *) old_rhead->old_rhead_ptr;
	}
	urx_resolve(hdr, lbt_bot, lbt_top);
	return IL_DONE;
}

bool addr_fix(int file, struct exec *fhead, urx_rtnref *urx_lcl, rhdtyp *code)
{
	res_list 	*res_root, *new_res, *res_temp, *res_temp1;
	char 		*symbols, *sym_temp, *sym_temp1, *symtop, *res_addr;
	struct relocation_info rel[RELREAD];
	int		numrel, rel_read, i, string_size;
	unsigned int	sym_size;
	size_t		status;
	mident_fixed	rtnid, labid;
	mstr		rtn_str;
	rhdtyp		*rtn;
	lab_tabent	*label, *labtop;
	bool		labsym;
	urx_rtnref	*urx_rp;
	urx_addr	*urx_tmpaddr;

	res_root = 0;
	numrel = (fhead->a_trsize + fhead->a_drsize) / SIZEOF(struct relocation_info);
	if (numrel * SIZEOF(struct relocation_info) != fhead->a_trsize + fhead->a_drsize)
		return FALSE;
	for ( ; numrel;)
	{
		rel_read = numrel < RELREAD ? numrel : RELREAD;
		DOREADRC(file, rel, rel_read * SIZEOF(struct relocation_info), status);
                if (0 != status)
		{
			res_free(res_root);
			return FALSE;
		}
		numrel -= rel_read;
		for (i = 0; i < rel_read; i++)
		{
			if (rel[i].r_extern)
			{
				new_res = (res_list *)malloc(SIZEOF(*new_res));
				new_res->symnum = rel[i].r_symbolnum;
				new_res->addr = rel[i].r_address;
				new_res->next = new_res->list = 0;
				/* Insert the relocation entry in symbol number order on the unresolved chain */
				if (!res_root)
					res_root = new_res;
				else
				{	res_temp = res_root;
					res_temp1 = 0;
					while (res_temp)
					{
						if (res_temp->symnum >= new_res->symnum)
							break;
						res_temp1 = res_temp;
						res_temp = res_temp->next;
					}
					if (res_temp)
					{	if (res_temp->symnum == new_res->symnum)
						{
							new_res->list = res_temp->list;
							res_temp->list = new_res;
						} else
						{	if (res_temp1)
							{
								new_res->next = res_temp1->next;
								res_temp1->next = new_res;
							} else
							{
								assert(res_temp == res_root);
								new_res->next = res_root;
								res_root = new_res;
							}
						}
					} else
						res_temp1->next = new_res;
				}
			} else
				*(unsigned int *)(((char *)code) + rel[i].r_address) += (unsigned int)code;
		}
	}
	/* All relocations within the routine should have been done, so copy the routine_name */
	assert(code->routine_name.len < SIZEOF(zlink_mname.c));
	memcpy(&zlink_mname.c[0], code->routine_name.addr, code->routine_name.len);
	zlink_mname.c[code->routine_name.len] = 0;
	if (!res_root)
		return TRUE;
	if ((off_t)-1 == lseek(file, (off_t)fhead->a_syms, SEEK_CUR))
	{	res_free(res_root);
		return FALSE;
	}
	DOREADRC(file, &string_size, SIZEOF(string_size), status);
	if (0 != status)
	{
		res_free(res_root);
		return FALSE;
	}
	string_size -= SIZEOF(string_size);
	symbols = malloc(string_size);
	DOREADRC(file, symbols, string_size, status);
	if (0 != status)
	{
		free(symbols);
		res_free(res_root);
		return FALSE;
	}
	/* Match up unresolved entries with the null terminated symbol name entries from the
	 * symbol text pool we just read in.
	 */
	sym_temp = sym_temp1 = symbols;
	symtop = symbols + string_size;
	for (i = 0; res_root; i++)
	{
		while (i < res_root->symnum)
		{	/* Forward symbol space until our symnum index (i) matches the symbol we are processing in res_root */
			while (*sym_temp)
			{
				if (sym_temp >= symtop)
				{
					free(symbols);
					res_free(res_root);
					return FALSE;
				}
				sym_temp++;
			}
			sym_temp++;
			sym_temp1 = sym_temp;
			i++;
		}
		assert (i == res_root->symnum);
		/* Find end of routine name that we care about */
		while (('.' != *sym_temp1) && *sym_temp1)
		{	if (sym_temp1 >= symtop)
			{
				free(symbols);
				res_free(res_root);
				return FALSE;
			}
			sym_temp1++;
		}
		assert(sym_temp1 >= sym_temp);
		sym_size = (unsigned int)(sym_temp1 - sym_temp);
		assert(sym_size <= MAX_MIDENT_LEN);
		memcpy(&rtnid.c[0], sym_temp, sym_size);
		rtnid.c[sym_size] = 0;
		if ('_' == rtnid.c[0])
			rtnid.c[0] = '%';
		assert((sym_size != mid_len(&zlink_mname)) || (0 != memcmp(&zlink_mname.c[0], &rtnid.c[0], sym_size)));
		rtn_str.addr = &rtnid.c[0];
		rtn_str.len = (mstr_len_t)sym_size;
		rtn = find_rtn_hdr(&rtn_str); /* Routine already resolved? */
		sym_size = 0;
		labsym = FALSE;
		if (*sym_temp1 == '.')
		{	/* If symbol is for a label, find the end of the label name */
			sym_temp1++;
			sym_temp = sym_temp1;
			while (*sym_temp1)
			{
				if (sym_temp1 >= symtop)
				{
					free(symbols);
					res_free(res_root);
					return FALSE;
				}
				sym_temp1++;
			}
			assert(sym_temp1 >= sym_temp);
			sym_size = (unsigned int)(sym_temp1 - sym_temp);
			assert(sym_size <= MAX_MIDENT_LEN);
			memcpy(&labid.c[0], sym_temp, sym_size);
			labid.c[sym_size] = 0;
			if ('_' == labid.c[0])
				labid.c[0] = '%';
			labsym = TRUE;
		}
		sym_temp1++;
		sym_temp = sym_temp1;
		if (rtn)
		{	/* The routine part at least is known */
			if (labsym)
			{	/* Look our target label up in the routines label table */
				label = (lab_tabent *)((char *)rtn + rtn->labtab_ptr);
				labtop = label + rtn->labtab_len;
				for (; label < labtop && ((sym_size != label->lab_name.len)
					|| memcmp(&labid.c[0], label->lab_name.addr, sym_size)); label++)
					;
				if (label < labtop)
					res_addr = (char *)&label->LABENT_LNR_OFFSET;
				else
					res_addr = 0;
			} else
				res_addr = (char *)rtn;
			if (res_addr)
			{	/* The external symbol definition is available. Resolve all references to it */
				res_temp = res_root->next;
				while (res_root)
				{
					*(uint4 *)(((char *)code) + res_root->addr) = (unsigned int)res_addr;
					res_temp1 = res_root->list;
					free(res_root);
					res_root = res_temp1;
				}
				res_root = res_temp;
				continue;
			}
		}
		/* This symbol is unknown. Put on the (local) unresolved extern chain -- either for labels or routines */
		urx_rp = urx_putrtn(rtn_str.addr, rtn_str.len, urx_lcl);
		res_temp = res_root->next;
		while (res_root)
		{
			if (labsym)
				urx_putlab(&labid.c[0], sym_size, urx_rp, ((char *)code) + res_root->addr);
			else
			{	urx_tmpaddr = (urx_addr *)malloc(SIZEOF(urx_addr));
				urx_tmpaddr->next = urx_rp->addr;
				urx_tmpaddr->addr = (INTPTR_T *)(((char *)code) + res_root->addr);
				urx_rp->addr = urx_tmpaddr;
			}
			res_temp1 = res_root->list;
			free(res_root);
			res_root = res_temp1;
		}
		res_root = res_temp;
	}
	free(symbols);
	return TRUE;
}

void res_free(res_list *root)
{
	res_list *temp;

	while (root)
	{	while (root->list)
		{	temp = root->list->list;
			free(root->list);
			root->list = temp;
		}
		temp = root->next;
		free(root);
		root = temp;
	}
}

/* ZL_ERROR - perform cleanup and signal errors found in zlinking a mumps object module
 * err - an error code that accepts no arguments and
 * err2 - an error code that accepts two arguments (!AD)
 */
void zl_error(int4 *file, int4 err, int4 len, char *addr, int4 err2, int4 len2, char *addr2)
{
	int rc;

	if (code)
	{
		GTM_TEXT_FREE(code);
		code = NULL;
	}
	if (0 < *file)
	{
		CLOSEFILE_RESET(*file, rc);	/* resets "*file" to FD_INVALID */
	} else
		*file = FD_INVALID;
	if (0 != err2)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) err, 2, len, addr, err2, 2, len2, addr2);
	else
		rts_error_csa(CSA_ARG(NULL)  VARLSTCNT(4) err, 2, len, addr);
}
