/*******************************************************************************
 * Copyright IBM Corp. and others 2001
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which accompanies this
 * distribution and is available at https://www.eclipse.org/legal/epl-2.0/
 * or the Apache License, Version 2.0 which accompanies this distribution
 * and is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License, v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception [1] and GNU General Public
 * License, version 2 with the OpenJDK Assembly Exception [2].
 *
 * [1] https://www.gnu.org/software/classpath/license.html
 * [2] https://openjdk.org/legal/assembly-exception.html
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0
 *******************************************************************************/

/**
 * @file
 * @ingroup PortTest
 * @brief Verify port library core file creation.
 *
 * Exercise the API for port library virtual memory management operations.  These functions
 * can be found in the file @ref omrosdump.c
 *
 *
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#if defined(AIXPPC)
#include <sys/types.h>
#include <sys/var.h>
#endif /* defined(AIXPPC) */

#include <signal.h>

#if defined(OMR_OS_WINDOWS)
/* for getcwd() */
#include <direct.h>
#define getcwd _getcwd
#endif /* defined(OMR_OS_WINDOWS) */

#include "testHelpers.hpp"
#include "omrport.h"

#define allocNameSize 64 /**< @internal buffer size for name function */

static uintptr_t simpleHandlerFunction(struct OMRPortLibrary *portLibrary, uint32_t gpType, void *gpInfo, void *handler_arg);
static void removeDump(OMRPortLibrary *portLib, const char *filename, const char *testName);

typedef struct simpleHandlerInfo {
	char *coreFileName;
	const char *testName;
} simpleHandlerInfo;

static void
removeDump(OMRPortLibrary *portLib, const char *filename, const char *testName)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portLib);
	bool removeDumpSucceeded = true;
	portTestEnv->changeIndent(1);

	/* Delete the file if possible. */
#if defined(J9ZOS390)
	char deleteCore[EsMaxPath] = {0};
	sprintf(deleteCore, "tso delete %s", (strstr(filename, ".") + 1));

	char *ending = strstr(deleteCore, ".X&DS");
	if (NULL != ending) {
		strncpy(ending, ".X001", 5);
	}
	if (-1 == system(deleteCore)) {
		removeDumpSucceeded = false;
	}
#else /* defined(J9ZOS390) */
	if (0 != remove(filename)) {
		removeDumpSucceeded = false;
	}
#endif /* defined(J9ZOS390) */
	if (removeDumpSucceeded) {
		portTestEnv->log("removed: %s\n", filename);
	} else {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "\tfailed to remove %s\n", filename);
	}
	portTestEnv->changeIndent(-1);
}

/**
 * Verify port library memory management.
 *
 * Ensure the port library is properly setup to run core file generation operations.
 */
TEST(PortDumpTest, dump_verify_functiontable_slots)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portTestEnv->getPortLibrary());
	const char *testName = "omrdump_verify_functiontable_slots";

	reportTestEntry(OMRPORTLIB, testName);


	/* omrmem_test2 */
	if (NULL == OMRPORTLIB->dump_create) {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "portLibrary->vmem_reserve_memory is NULL\n");
	}
	reportTestExit(OMRPORTLIB, testName);
}

/**
 * Call omrdump_create() without passing in core file name. This does not actually test that a core file was actually created.
 */
TEST(PortDumpTest, dump_test_create_dump_with_NO_name)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portTestEnv->getPortLibrary());
	const char *testName = "omrdump_test_create_dump_with_NO_name";
	uintptr_t rc = 99;
	char coreFileName[EsMaxPath];
	BOOLEAN doFileVerification = FALSE;
#if defined(AIXPPC)
	struct vario myvar;
	int sys_parmRC;
#endif /* defined(AIXPPC) */

	reportTestEntry(OMRPORTLIB, testName);

	coreFileName[0] = '\0';

#if 0
	/* try out a NULL test (turns out this crashes) */
	rc = omrdump_create(NULL, NULL, NULL); /* this crashes */
#endif /* 0 */

	/* try out a more sane NULL test */
	portTestEnv->log("calling omrdump_create with empty filename\n");

#if defined(J9ZOS390)
	rc = omrdump_create(coreFileName, "IEATDUMP", NULL);
#else /* defined(J9ZOS390) */
	rc = omrdump_create(coreFileName, NULL, NULL);
#endif /* defined(J9ZOS390) */

	if (0 == rc) {
		uintptr_t verifyFileRC = 99;

		portTestEnv->log("omrdump_create claims to have written a core file to: %s\n", coreFileName);


#if defined(AIXPPC)
		/* We defer to fork abort on AIX machines that don't have "Enable full CORE dump" enabled in smit,
		 * in which case omrdump_create will not return the filename.
		 * So, only check for a specific filename if we are getting full core dumps */
		sys_parmRC = sys_parm(SYSP_GET, SYSP_V_FULLCORE, &myvar);

		if ((0 == sys_parmRC) && (1 == myvar.v.v_fullcore.value)) {

			doFileVerification = TRUE;
		}
#else /* defined(AIXPPC) */
		doFileVerification = TRUE;
#endif /* defined(AIXPPC) */
		if (doFileVerification) {
			verifyFileRC = verifyFileExists(PORTTEST_ERROR_ARGS, coreFileName);
			if (0 == verifyFileRC) {
				removeDump(OMRPORTLIB, coreFileName, testName);
			}
		}
	} else {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "omrdump_create returned: %u, with filename: %s", rc, coreFileName);
	}
	reportTestExit(OMRPORTLIB, testName);
}



#if !defined(J9ZOS390)
/**
 * Test omrdump_create(), this time passing in core file name,
 * but with no path. It should get created in the current
 * working directory.
 * Verify that the returned core file name actually exists.
 * This test hasn't been made to run on z/OS yet - it must be
 * possible because omrdump_create with a NULL filename already
 * does the job of generating a data set name with no path, but
 * it would probably require someone with knowledge of the
 * platform to determine what an acceptable name would be.
 */
TEST(PortDumpTest, dump_test_create_dump_with_no_path)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portTestEnv->getPortLibrary());
	const char *testName = "dump_test_create_dump_with_no_path";
	uintptr_t rc = 99;
	char coreFileName[EsMaxPath];

	reportTestEntry(OMRPORTLIB, testName);

	strcpy(coreFileName, testName);

	portTestEnv->log("calling omrdump_create with filename: %s\n", coreFileName);
	rc = omrdump_create(coreFileName, NULL, NULL);

	if (0 == rc) {
		uintptr_t verifyFileRC = 99;

		portTestEnv->log("omrdump_create claims to have written a core file to: %s\n", coreFileName);
		verifyFileRC = verifyFileExists(PORTTEST_ERROR_ARGS, coreFileName);
		if (0 == verifyFileRC) {
			removeDump(OMRPORTLIB, coreFileName, testName);
		}
	} else {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "omrdump_create returned: %u, with filename: %s\n", rc, coreFileName);
	}
	reportTestExit(OMRPORTLIB, testName);
}
#endif /* !defined(J9ZOS390) */



/**
 * Test omrdump_create(), this time passing in core file name
 * Verify that the returned core file name actually exists.
 *
 * @note this only tests IEATDUMP generation on z/OS
 */
TEST(PortDumpTest, dump_test_create_dump_with_name)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portTestEnv->getPortLibrary());
	const char *testName = "omrdump_test_create_dump_with_name";
	uintptr_t rc = 99;
	char buff[EsMaxPath];
	char *coreFileName = NULL;

	reportTestEntry(OMRPORTLIB, testName);

#if defined(J9ZOS390) && !defined(OMR_EBCDIC)
	coreFileName = atoe_getcwd(buff, EsMaxPath);
#else /* defined(J9ZOS390) && !defined(OMR_EBCDIC) */
	coreFileName = getcwd(buff, EsMaxPath);
#endif /* defined(J9ZOS390) && !defined(OMR_EBCDIC) */

	strncat(coreFileName, "/", EsMaxPath);	/* make sure the directory ends with a slash */
	strncat(coreFileName, testName, EsMaxPath);

	portTestEnv->log("calling omrdump_create with filename: %s\n", coreFileName);
#if !defined(J9ZOS390)
	rc = omrdump_create(coreFileName, NULL, NULL);
#else /* !defined(J9ZOS390) */
	rc = omrdump_create(coreFileName, "IEATDUMP", NULL);
#endif /* !defined(J9ZOS390) */

	if (0 == rc) {
		uintptr_t verifyFileRC = 99;

		portTestEnv->log("omrdump_create claims to have written a core file to: %s\n", coreFileName);
		verifyFileRC = verifyFileExists(PORTTEST_ERROR_ARGS, coreFileName);
		if (0 == verifyFileRC) {
			removeDump(OMRPORTLIB, coreFileName, testName);
		}
	} else {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "omrdump_create returned: %u, with filename: %s\n", rc, coreFileName);
	}
	reportTestExit(OMRPORTLIB, testName);
}



/**
 * Test calling omrdump_create() from a signal handler.
 *
 * Verify that the returned core file name actually exists.
 */
TEST(PortDumpTest, dump_test_create_dump_from_signal_handler)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portTestEnv->getPortLibrary());
	const char *testName = "omrdump_test_create_dump_from_signal_handler";
	char buff[EsMaxPath];
	char *coreFileName = NULL;
	uintptr_t result;
	uint32_t protectResult;
	simpleHandlerInfo handlerInfo;
	uint32_t sig_protectFlags;

#if defined(J9ZOS390) && !defined(OMR_EBCDIC)
	coreFileName = atoe_getcwd(buff, EsMaxPath);
#else /* defined(J9ZOS390) && !defined(OMR_EBCDIC) */
	coreFileName = getcwd(buff, EsMaxPath);
#endif /* defined(J9ZOS390) && !defined(OMR_EBCDIC) */

	strncat(coreFileName, "/", EsMaxPath);	/* make sure the directory ends with a slash */
	strncat(coreFileName, testName, EsMaxPath);

	sig_protectFlags = OMRPORT_SIG_FLAG_SIGSEGV | OMRPORT_SIG_FLAG_MAY_RETURN;

	reportTestEntry(OMRPORTLIB, testName);

	if (!omrsig_can_protect(sig_protectFlags)) {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "portLibrary->sig_protect does not offer adequate protection for test\n");
	} else {
		handlerInfo.coreFileName = coreFileName;
		handlerInfo.testName = testName;
		protectResult = omrsig_protect(
							raiseSEGV, NULL,
							simpleHandlerFunction, &handlerInfo,
							sig_protectFlags,
							&result);

		if (protectResult != OMRPORT_SIG_EXCEPTION_OCCURRED) {
			outputErrorMessage(PORTTEST_ERROR_ARGS, "portLibrary->sig_protect -- expected 0 return value\n");
		}
	}
	reportTestExit(OMRPORTLIB, testName);
}


static uintptr_t
simpleHandlerFunction(struct OMRPortLibrary *portLibrary, uint32_t gpType, void *gpInfo, void *handler_arg)
{
	OMRPORT_ACCESS_FROM_OMRPORT(portLibrary);
	simpleHandlerInfo *info = (simpleHandlerInfo *)handler_arg;
	const char *testName = info->testName;
	uintptr_t rc;

	portTestEnv->log("calling omrdump_create with filename: %s\n", info->coreFileName);

#if defined(J9ZOS390)
	rc = omrdump_create(info->coreFileName, "IEATDUMP", NULL);
#else /* defined(J9ZOS390) */
	rc = omrdump_create(info->coreFileName, NULL, NULL);
#endif /* defined(J9ZOS390) */

	if (0 == rc) {
		uintptr_t verifyFileRC = 99;

		portTestEnv->log("omrdump_create claims to have written a core file to: %s\n", info->coreFileName);
		verifyFileRC = verifyFileExists(PORTTEST_ERROR_ARGS, info->coreFileName);
		if (0 == verifyFileRC) {
			removeDump(OMRPORTLIB, info->coreFileName, testName);
		}
	} else {
		outputErrorMessage(PORTTEST_ERROR_ARGS, "omrdump_create returned: %u, with filename: %s", rc, info->coreFileName);
	}

	return OMRPORT_SIG_EXCEPTION_RETURN;
}
