/*
 * Copyright (C) 1994-2018 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * PBS Pro is free software. You can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * PBS Pro is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * For a copy of the commercial license terms and conditions,
 * go to: (http://www.pbspro.com/UserArea/agreement.html)
 * or contact the Altair Legal Department.
 *
 * Altair’s dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of PBS Pro and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair’s trademarks, including but not limited to "PBS™",
 * "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
 * trademark licensing policies.
 *
 */

/**
 * @file	pbs_python_external.c
 * @brief
 *  This file contains shared routines that can be used by any of the PBS
 *  infrastructure daemons (Server,MOM or Scheduler). This file basically
 *  provides all the implementation for external interface routines found
 *  in pbs_python.h
 *
 */

#include <pbs_config.h>

/* --- BEGIN PYTHON DEPENDENCIES --- */

#ifdef PYTHON

#include <pbs_python_private.h> /* private python file  */
#include <eval.h>               /* For PyEval_EvalCode  */
#include <pythonrun.h>          /* For Py_SetPythonHome */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

extern void init_pbs_ifl(void);

static struct _inittab pbs_python_inittab_modules[] = {
	{PBS_PYTHON_V1_MODULE_EXTENSION_NAME, pbs_v1_module_inittab},
	{"_pbs_ifl", init_pbs_ifl},
	{NULL, NULL}                    /* sentinel */
};

static PyObject *
_pbs_python_compile_file(const char *file_name,
	const char *compiled_code_file_name);
extern int pbs_python_setup_namespace_dict(PyObject *globals);

#endif      /* PYTHON */

#include <pbs_python.h>

/* --- END PYTHON DEPENDENCIES --- */

/*
 * GLOBAL
 */
/* TODO make it autoconf? */
char *pbs_python_daemon_name;

/*
 * ===================   BEGIN   EXTERNAL ROUTINES  ===================
 */


/**
 *
 * @brief
 *	Start the Python interpreter.
 *
 * @param[in/out] interp_data - has some prefilled information about
 *				the python interpreter to start, like python
 *				daemon name. This will also get filled in
 *				with new information such as status of
 *				of the python start.
 * @note
 *	If  called by pbs_python command, then any log messages are logged as
 *	DEBUG3; otherwise, DEBUG2.
 */

void
pbs_python_ext_start_interpreter(
	struct python_interpreter_data *interp_data)
{

#ifdef	PYTHON           /* -- BEGIN ONLY IF PYTHON IS CONFIGURED -- */

	struct stat sbuf;
	char pbs_python_home[MAXPATHLEN+1];
	char pbs_python_destlib[MAXPATHLEN+1];
	int  evtype;
	int  rc;

	/*
	 * initialize the convenience global pbs_python_daemon_name, as it is
	 * used everywhere
	 */

	pbs_python_daemon_name = interp_data->daemon_name;

	/* Need to make logging less verbose if pbs_python command */
	/* used, since it can get called many times in a pbs daemon, */
	/* and it would litter that daemon's logs */
	if (IS_PBS_PYTHON_CMD(pbs_python_daemon_name))
		evtype = PBSEVENT_DEBUG3;
	else
		evtype = PBSEVENT_DEBUG2;

	memset((char *)pbs_python_home, '\0', MAXPATHLEN+1);
	memset((char *)pbs_python_destlib, '\0', MAXPATHLEN+1);

	snprintf(pbs_python_home, MAXPATHLEN, "%s/python",
		pbs_conf.pbs_exec_path);
	snprintf(pbs_python_destlib, MAXPATHLEN, "%s/lib64/python/altair",
		pbs_conf.pbs_exec_path);
	rc = stat(pbs_python_destlib, &sbuf);
	if (rc != 0) {
		snprintf(pbs_python_destlib, MAXPATHLEN, "%s/lib/python/altair",
			pbs_conf.pbs_exec_path);
		rc = stat(pbs_python_destlib, &sbuf);
	}
	if (rc != 0) {
		log_err(-1, __func__,
			"--> PBS Python library directory not found <--");
		return;
	}
	if (!S_ISDIR(sbuf.st_mode)) {
		log_err(-1, __func__,
			"--> PBS Python library path is not a directory <--");
		return;
	}

	if (interp_data) {
		interp_data->init_interpreter_data(interp_data); /* to be safe */
		if (interp_data->interp_started) {
			log_event(evtype, PBS_EVENTCLASS_SERVER,
				LOG_INFO, interp_data->daemon_name,
				"--> Python interpreter already started <--");
			return;
		}
	} else { /* we need to allocate memory */
		log_err(-1, __func__,
			"--> Passed NULL for interpreter data <--");
		return;
	}

	Py_NoSiteFlag = 1;
	Py_FrozenFlag = 1;
	Py_OptimizeFlag = 2;            /* TODO make this a compile flag variable */
	Py_IgnoreEnvironmentFlag = 1;   /* ignore PYTHONPATH and PYTHONHOME */
	if (file_exists(pbs_python_home))
		Py_SetPythonHome(pbs_python_home);

	/* we make sure our top level module is initialized */
	if ((PyImport_ExtendInittab(pbs_python_inittab_modules) != 0)) {
		log_err(-1, "PyImport_ExtendInittab",
			"--> Failed to initialize Python interpreter <--");
		return;
	}


	Py_InitializeEx(1);  /* arg '1' means to not skip init of signals -    */
	/* we want signals to propagate to the executing  */
	/* Python script to be able to interrupt it       */

	if (Py_IsInitialized()) {
		char *msgbuf;

		interp_data->interp_started = 1; /* mark python as initialized */
		/* print only the first five characters, TODO check for NULL? */
		pbs_asprintf(&msgbuf,
			"--> Python Interpreter started, compiled with version:'%s' <--",
			Py_GetVersion());
		log_event(evtype, PBS_EVENTCLASS_SERVER,
			LOG_INFO, interp_data->daemon_name, msgbuf);
		free(msgbuf);
	} else {
		log_err(-1, "Py_InitializeEx",
			"--> Failed to initialize Python interpreter <--");
		goto ERROR_EXIT;
	}
	/*
	 * Add Altair python module directory to sys path. NOTE:
	 *  PBS_PYTHON_MODULE_DIR is a command line define, also insert
	 * standard required python modules
	 */
	if (pbs_python_modify_syspath(pbs_python_destlib, -1) == -1) {
		snprintf(log_buffer, LOG_BUF_SIZE-1,
			"could not insert %s into sys.path shutting down",
			pbs_python_destlib);
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		log_err(-1, __func__, log_buffer);
		goto ERROR_EXIT;
	}

	/*
	 * At this point it is safe to load the available server types from
	 * the python modules. since the syspath is setup correctly
	 */
	if ((pbs_python_load_python_types(interp_data) == -1)) {
		log_err(-1, __func__, "could not load python types into the interpreter");
		goto ERROR_EXIT;
	}

	interp_data->pbs_python_types_loaded = 1; /* just in case */
	return;

ERROR_EXIT:
	pbs_python_ext_shutdown_interpreter(interp_data);
	return;
#else  /* !PYTHON */
	log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
		PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
		LOG_INFO, "start_python",
		"--> Python interpreter not built in <--");
	return;
#endif /* PYTHON */
}

/**
 *
 * @brief
 *	Shuts down the Python interpreter.
 *
 * @param[in/out] interp_data - has some prefilled information about
 *				the python interpreter to shutdown,
 *				This will also get filled in
 *				with new information such as status of
 *				of the python shutdown.
 * @note
 *	If  called by pbs_python command, then any log messages are logged as
 *	DEBUG3; otherwise, DEBUG2.
 */


void
pbs_python_ext_shutdown_interpreter(struct python_interpreter_data *interp_data)
{
#ifdef	PYTHON           /* -- BEGIN ONLY IF PYTHON IS CONFIGURED -- */
	int	evtype;

	if (IS_PBS_PYTHON_CMD(pbs_python_daemon_name))
		evtype = PBSEVENT_DEBUG3;
	else
		evtype = PBSEVENT_DEBUG2;

	if (interp_data) {
		if (interp_data->interp_started) {
			log_event(evtype, PBS_EVENTCLASS_SERVER,
				LOG_INFO, interp_data->daemon_name,
				"--> Stopping Python interpreter <--");

			/* before finalize clear global python objects */
			pbs_python_event_unset();  /* clear Python event object */
			pbs_python_unload_python_types(interp_data);
			Py_Finalize();
		}
		interp_data->destroy_interpreter_data(interp_data);
		/* reset so that we do not have a problem */
		pbs_python_daemon_name = NULL;
	}

#endif /* PYTHON */

}

/**
 * @brief
 * 	pbs_python_ext_quick_start_interpreter - the basic startup without loading
 *	of PBS attributes and resources into Python.
 */

void
pbs_python_ext_quick_start_interpreter(void)
{

#ifdef	PYTHON           /* -- BEGIN ONLY IF PYTHON IS CONFIGURED -- */

	char pbs_python_home[MAXPATHLEN+1];
	char pbs_python_destlib[MAXPATHLEN+1];

	memset((char *)pbs_python_home, '\0', MAXPATHLEN+1);
	memset((char *)pbs_python_destlib, '\0', MAXPATHLEN+1);

	snprintf(pbs_python_home, MAXPATHLEN, "%s/python",
		pbs_conf.pbs_exec_path);
	snprintf(pbs_python_destlib, MAXPATHLEN, "%s/lib/python/altair",
		pbs_conf.pbs_exec_path);

	Py_NoSiteFlag = 1;
	Py_FrozenFlag = 1;
	Py_OptimizeFlag = 2;            /* TODO make this a compile flag variable */
	Py_IgnoreEnvironmentFlag = 1;   /* ignore PYTHONPATH and PYTHONHOME */
	if (file_exists(pbs_python_home))
		Py_SetPythonHome(pbs_python_home);

	/* we make sure our top level module is initialized */
	if ((PyImport_ExtendInittab(pbs_python_inittab_modules) != 0)) {
		log_err(-1, "PyImport_ExtendInittab",
			"--> Failed to initialize Python interpreter <--");
		return;
	}

	Py_InitializeEx(0);  /* SKIP initialization of signals */

	if (Py_IsInitialized()) {
		char *msgbuf;

		pbs_asprintf(&msgbuf,
			"--> Python Interpreter quick started, compiled with version:'%s' <--",
			Py_GetVersion());
		log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
			PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
			LOG_INFO, __func__, msgbuf);
		free(msgbuf);
	} else {
		log_err(-1, "Py_InitializeEx",
			"--> Failed to quick initialize Python interpreter <--");
		goto ERROR_EXIT;
	}
	/*
	 * Add Altair python module directory to sys path. NOTE:
	 *  PBS_PYTHON_MODULE_DIR is a command line define, also insert
	 * standard required python modules
	 */
	if (pbs_python_modify_syspath(pbs_python_destlib, -1) == -1) {
		snprintf(log_buffer, LOG_BUF_SIZE-1,
			"could not insert %s into sys.path shutting down",
			pbs_python_destlib);
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		log_err(-1, __func__, log_buffer);
		goto ERROR_EXIT;
	}

	snprintf(log_buffer, LOG_BUF_SIZE-1,
		"--> Inserted Altair PBS Python modules dir '%s' <--",
		pbs_python_destlib);
	log_buffer[LOG_BUF_SIZE-1] = '\0';
	log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
		PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
		LOG_INFO, __func__, log_buffer);

	return;

ERROR_EXIT:
	pbs_python_ext_quick_shutdown_interpreter();
	return;
#else   /* !PYTHON */
	log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
		PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
		LOG_INFO, "start_python",
		"--> Python interpreter not built in <--");
	return;
#endif  /* PYTHON */

}

/**
 * @brief
 * 	pbs_python_ext_quick_shutdown_interpreter
 */

void
pbs_python_ext_quick_shutdown_interpreter(void)
{
#ifdef	PYTHON           /* -- BEGIN ONLY IF PYTHON IS CONFIGURED -- */
	log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
		PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
		LOG_INFO, "pbs_python_ext_quick_shutdown_interpreter",
		"--> Stopping Python interpreter <--");
	Py_Finalize();
#endif /* PYTHON */

}

void
pbs_python_ext_free_python_script(
	struct python_script *py_script)
{
	if (py_script) {
		if (py_script->path)
			free(py_script->path);

#ifdef PYTHON                 /* --- BEGIN PYTHON BLOCK --- */
		if (py_script->py_code_obj)
			Py_CLEAR(py_script->py_code_obj);
		if (py_script->global_dict) {
			PyDict_Clear((PyObject *)py_script->global_dict); /* clear k,v */
			Py_CLEAR(py_script->global_dict);
		}
#endif                        /* --- END   PYTHON BLOCK --- */

	}
	return;
}
#define COPY_STRING(dst,src) \
	do { \
	   if (!((dst) = strdup(src))) { \
	     log_err(errno, __func__, "could not copy string"); \
	     goto ERROR_EXIT; \
	   } \
	} while (0)

int
pbs_python_ext_alloc_python_script(
	const char *script_path,
	struct python_script **py_script /* returned value */
	) {

#ifdef PYTHON                 /* --- BEGIN PYTHON BLOCK --- */

	struct python_script *tmp_py_script = NULL;
	size_t nbytes = sizeof(struct python_script);
	struct stat sbuf;

	*py_script = NULL; /* init, to be safe */

	if (!(tmp_py_script = (struct python_script *) malloc(nbytes))) {
		log_err(errno, __func__, "failed to malloc struct python_script");
		goto ERROR_EXIT;
	}
	(void) memset(tmp_py_script, 0, nbytes);
	/* check for recompile true by default */
	tmp_py_script->check_for_recompile = 1;

	COPY_STRING(tmp_py_script->path, script_path);
	/* store the stat */
	if ((stat(script_path, &sbuf) == -1)) {
		snprintf(log_buffer, LOG_BUF_SIZE-1,
			"failed to stat <%s>", script_path);
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		log_err(errno, __func__, log_buffer);
		goto ERROR_EXIT;
	}
	(void) memcpy(&(tmp_py_script->cur_sbuf), &sbuf,
		sizeof(tmp_py_script->cur_sbuf));
	/* ok, we are set with py_script */
	*py_script = tmp_py_script;
	return 0;

ERROR_EXIT:
	if (tmp_py_script) {
		pbs_python_ext_free_python_script(tmp_py_script);
		free(tmp_py_script);
	}
	return -1;
#else

	log_err(-1, __func__, "--> Python is disabled <--");
	return -1;
#endif                        /* --- END   PYTHON BLOCK --- */
}

/**
 * @brief
 * 	Function to create a separate namespace. Essentially a sandbox to run
 * 	python scrips independently.
 *
 * @param[in] interp_data - pointer to interpreter data
 *
 */

void *
pbs_python_ext_namespace_init(
	struct python_interpreter_data *interp_data)
{

#ifdef PYTHON                        /* --- BEGIN PYTHON BLOCK --- */

	PyObject *namespace_dict = NULL;

	namespace_dict = PyDict_New(); /* New Refrence MUST Decref */
	if (!namespace_dict) {
		pbs_python_write_error_to_log(__func__);
		goto ERROR_EXIT;
	}
	/*
	 * setup our namespace, by including all the modules that are needed to
	 * run the python scripts
	 */
	if ((PyDict_SetItemString(namespace_dict, "__builtins__",
		PyEval_GetBuiltins()) == -1)
		) {
		pbs_python_write_error_to_log(__func__);
		goto ERROR_EXIT;
	}

	/*
	 * Now, add our extension object/module to the namespace.
	 */
	if ((PyDict_SetItemString(namespace_dict,
		PBS_PYTHON_V1_MODULE_EXTENSION_NAME,
		pbs_v1_module_init()) == -1)
		) {
		snprintf(log_buffer, LOG_BUF_SIZE-1, "%s|adding extension object",
			__func__);
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		pbs_python_write_error_to_log(__func__);
		goto ERROR_EXIT;
	}

	return namespace_dict;

ERROR_EXIT:
	if (namespace_dict) {
		PyDict_Clear(namespace_dict);
		Py_CLEAR(namespace_dict);
	}
	return namespace_dict;

#else	/* !PYTHON */
	return NULL;
#endif  /* --- END   PYTHON BLOCK --- */

}

/**
 *
 * @brief
 *	Checks if hook script needs recompilation.
 *
 * @param[in] interp_data - data to the python interpreter that will interpret
 *				the script.
 * @return	int
 * @retval	-2 	script  compilation failed
 * @retval	-1 	other failures
 * @retval	0 	success
 *
 * @note
 *	If  called by pbs_python command, then any log messages are logged as
 *	DEBUG3; otherwise, DEBUG2.
 */
int
pbs_python_check_and_compile_script(struct python_interpreter_data *interp_data,
	struct python_script *py_script)
{

#ifdef	PYTHON           /* -- BEGIN ONLY IF PYTHON IS CONFIGURED -- */
	struct stat nbuf; /* new stat buf */
	struct stat obuf; /* old buf */
	int recompile = 1;

	if (!interp_data || !py_script) {
		log_err(-1, __func__, "Either interp_data or py_script is NULL");
		return -1;
	}

	/* ok, first time go straight to compile */
	do {
		if (!py_script->py_code_obj)
			break;
		(void) memcpy(&obuf, &(py_script->cur_sbuf), sizeof(obuf));
		if (py_script->check_for_recompile) {
			if ((stat(py_script->path, &nbuf) != 1) &&
				(nbuf.st_ino   == obuf.st_ino) &&
				(nbuf.st_size  == obuf.st_size) &&
				(nbuf.st_mtime == obuf.st_mtime)) {
				recompile = 0;
			} else {
				recompile = 1;
				(void) memcpy(&(py_script->cur_sbuf), &nbuf,
					sizeof(py_script->cur_sbuf));
				Py_CLEAR(py_script->py_code_obj); /* we are rebuilding */
			}
		}
	} while (0);

	if (recompile) {
		snprintf(log_buffer, LOG_BUF_SIZE,
			"Compiling script file: <%s>", py_script->path);

		if (IS_PBS_PYTHON_CMD(pbs_python_daemon_name))
			log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_SERVER,
				LOG_INFO, interp_data->daemon_name, log_buffer);
		else
			log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
				PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
				LOG_INFO, interp_data->daemon_name, log_buffer);

		if (!(py_script->py_code_obj =
			_pbs_python_compile_file(py_script->path,
			"<embedded code object>"))) {
			pbs_python_write_error_to_log("Failed to compile script");
			return -2;
		}
	}

	/* set dict to null during compilation, clearing previous global/local */
	/* dictionary to prevent leaks.                                        */
	if (py_script->global_dict) {
		PyDict_Clear((PyObject *)py_script->global_dict);
		Py_CLEAR(py_script->global_dict);
	}

	return 0;
#else	/* !PYTHON */
	return -1;
#endif 	/* PYTHON */
}

/**
 * @brief
 *	runs python script in namespace.
 *
 * @param[in] interp_data - pointer to interpreter data
 * @param[in] py_script - pointer to python script info
 * @param[out] exit_code - exit code
 *
 * @return	int
 * @retval	-3 	script executed but got KeyboardInterrupt
 * @retval	-2 	script  compiled or executed with error
 * @retval	-1 	other failures
 * @retval	0 	success
 */
int
pbs_python_run_code_in_namespace(struct python_interpreter_data *interp_data,
	struct python_script *py_script,
	int *exit_code)
{

#ifdef	PYTHON           /* -- BEGIN ONLY IF PYTHON IS CONFIGURED -- */

	PyObject *pdict;
	struct stat nbuf; /* new stat buf */
	struct stat obuf; /* old buf */
	int recompile = 1;
	PyObject *ptype;
	PyObject *pvalue;
	PyObject *ptraceback;
	PyObject *pobjStr;
	char      *pStr;
	int rc=0;

	if (!interp_data || !py_script) {
		log_err(-1, __func__, "Either interp_data or py_script is NULL");
		return -1;
	}

	/* ok, first time go straight to compile */
	do {
		if (!py_script->py_code_obj)
			break;
		(void) memcpy(&obuf, &(py_script->cur_sbuf), sizeof(obuf));
		if (py_script->check_for_recompile) {
			if ((stat(py_script->path, &nbuf) != -1) &&
				(nbuf.st_ino   == obuf.st_ino)    &&
				(nbuf.st_size  == obuf.st_size)   &&
				(nbuf.st_mtime == obuf.st_mtime)
				) {
				recompile = 0;
			} else {
				recompile = 1;
				(void) memcpy(&(py_script->cur_sbuf), &nbuf,
					sizeof(py_script->cur_sbuf));
				Py_CLEAR(py_script->py_code_obj); /* we are rebuilding */
			}
		}
	} while (0);

	if (recompile) {
		snprintf(log_buffer, LOG_BUF_SIZE-1,
			"Compiling script file: <%s>", py_script->path);
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		if (IS_PBS_PYTHON_CMD(pbs_python_daemon_name))
			log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_SERVER,
				LOG_INFO, interp_data->daemon_name, log_buffer);
		else
			log_event(PBSEVENT_SYSTEM|PBSEVENT_ADMIN |
				PBSEVENT_DEBUG, PBS_EVENTCLASS_SERVER,
				LOG_INFO, interp_data->daemon_name, log_buffer);

		if (!(py_script->py_code_obj =
			_pbs_python_compile_file(py_script->path,
			"<embedded code object>"))) {
			pbs_python_write_error_to_log("Failed to compile script");
			return -2;
		}
	}

	/* make new namespace dictionary, NOTE new reference */

	if (!(pdict = (PyObject *)pbs_python_ext_namespace_init(interp_data))) {
		log_err(-1, __func__, "while calling pbs_python_ext_namespace_init");
		return -1;
	}
	if ((pbs_python_setup_namespace_dict(pdict) == -1)) {
		Py_CLEAR(pdict);
		return -1;
	}

	/* clear previous global/local dictionary */
	if (py_script->global_dict) {
		PyDict_Clear((PyObject *)py_script->global_dict); /* clear k,v */
		Py_CLEAR(py_script->global_dict);
	}

	py_script->global_dict = pdict;

	PyErr_Clear(); /* clear any exceptions before starting code */
	/* precompile strings of code to bytecode objects */
	(void) PyEval_EvalCode((PyCodeObject *)py_script->py_code_obj,
		pdict, pdict);
	/* check for exception */
	if (PyErr_Occurred()) {
		if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
			pbs_python_write_error_to_log("Python script received a KeyboardInterrupt");
			return -3;
		}

		if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
			PyErr_Fetch(&ptype, &pvalue, &ptraceback);
			PyErr_Clear(); /* just in case, not clear from API doc */

			if (pvalue) {
				pobjStr = PyObject_Str(pvalue); /* new ref */
				pStr = PyString_AsString(pobjStr);
				rc = (int) atol(pStr);
				Py_XDECREF(pobjStr);
			}

			Py_XDECREF(ptype);
			Py_XDECREF(pvalue);

#if !defined(WIN32)
			Py_XDECREF(ptraceback);
#elif !defined(_DEBUG)
			/* for some reason this crashes on Windows Debug version */
			Py_XDECREF(ptraceback);
#endif

		} else {
			pbs_python_write_error_to_log("Error evaluating Python script");
			return -2;
		}
	}
	PyErr_Clear();

	if (exit_code)
		*exit_code=rc; /* set exit code if var is not null */

	return 0;
#else	/* !PYTHON */
	return -1;
#endif 	/* PYTHON */

}


#ifdef PYTHON               /*  === BEGIN ALL FUNCTIONS REQUIRING PYTHON HEADERS === */

/**
 * @brief
 *	only compile the python script.
 *
 * @param[in]	file_name - abs file name
 * @param[out]  compiled_code_file_name - compiled file
 *
 * @return	object
 * @retval
 * @retval	error code	error
 *
 */
static PyObject *
_pbs_python_compile_file(const char *file_name,
	const char *compiled_code_file_name)
{
	FILE *fp = NULL;

	long len = 0;
	size_t file_sz = 0;   /* script file no. of bytes */
	char *file_buffer = NULL; /* buffer to hold the python script file */
	char *cp = NULL; /* useful character pointer */
	PyObject *rv = NULL;

	fp = fopen(file_name, "rb");
	if (!fp) {
		snprintf(log_buffer, LOG_BUF_SIZE-1,
			"could not open file <%s>: %s\n", file_name, strerror(errno));
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		log_err(errno, __func__, log_buffer);
		goto ERROR_EXIT;
	}

	if ((fseek(fp, 0L, SEEK_END) == 0)) { /* ok we reached the end */
		len = ftell(fp);
		if (len == -1) {
			snprintf(log_buffer, LOG_BUF_SIZE-1,
				"could not determine the file length: %s\n", strerror(errno));
			log_buffer[LOG_BUF_SIZE-1] = '\0';
			log_err(errno, __func__, log_buffer);
			goto ERROR_EXIT;
		}
		if ((fseek(fp, 0L, SEEK_SET) == -1)) {
			snprintf(log_buffer, LOG_BUF_SIZE-1,
				"could not fseek to beginning: %s\n", strerror(errno));
			log_buffer[LOG_BUF_SIZE-1] = '\0';
			log_err(errno, __func__, log_buffer);
			goto ERROR_EXIT;
		}
		file_sz = len; /* ok good we have a file size */
	} else { /* Uh-oh bad news */
		snprintf(log_buffer, LOG_BUF_SIZE-1,
			"could not fseek to end: %s\n", strerror(errno));
		log_buffer[LOG_BUF_SIZE-1] = '\0';
		log_err(errno, __func__, log_buffer);
		goto ERROR_EXIT;
	}
	/* allocate memory for file + \n\0 */
	file_sz += 2;

	if (!(file_buffer = (char *)PyMem_Malloc(sizeof(char)*file_sz))) {
		/* could not allocate memory */
		pbs_python_write_error_to_log(__func__);
		goto ERROR_EXIT;
	}

	/* read the file, clean up the file for DOS \r stuff */
	file_sz = fread(file_buffer, sizeof(char), (file_sz - 2), fp);

	file_buffer[file_sz] = '\n';
	file_buffer[file_sz+1] = '\0';

	if (*file_buffer == '\r')
		*file_buffer = ' ';
	/* TODO handle \r in string constants? */
	for (cp = file_buffer + 1; *cp != '\0'; cp++) {
		if (*cp == '\r') {
			if (*(cp - 1) == '\\') {
				*(cp - 1) = ' ';
				*cp = '\\';
			} else {
				*cp = ' ';
			}
		}
	}

	fclose(fp);
	/* compile the string to a code object,NEW reference caller must DECREF */
	rv = Py_CompileString(file_buffer, compiled_code_file_name, Py_file_input);
	PyMem_Free(file_buffer);
	return rv;

ERROR_EXIT:
	if (fp)
		fclose(fp);
	if (file_buffer)
		PyMem_Free(file_buffer); /* to be safe */
	return rv;
}


#endif /* PYTHON */

