/*****************************************************************************\
 *  slurm_lua.c - Lua integration common functions
 *****************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include <dlfcn.h>
#include <limits.h>
#include <stdio.h>

#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "src/common/assoc_mgr.h"
#include "src/common/data.h"
#include "src/common/log.h"
#include "src/common/parse_time.h"
#include "src/common/sluid.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/xstring.h"
#include "src/lua/slurm_lua.h"

#include "src/interfaces/serializer.h"

/* Max table depth for data_t conversions */
#define MAX_DEPTH 16
#define DUMP_DATA_FOREACH_ARGS_MAGIC 0x02141444

typedef struct {
	int magic; /* DUMP_DATA_FOREACH_ARGS_MAGIC */
	lua_State *L;
	int rc;
	int table_index;
	int field_index;
} dump_data_foreach_args_t;

static void *lua_handle = NULL;

#if LUA_VERSION_NUM >= 502
#define LUA_ERROR_BACKTRACE
#endif

/*
 * These are defined here so when we link with something other than the
 * slurmctld we will have these symbols defined. They will get overwritten when
 * linking with the slurmctld.
 */
#if defined (__APPLE__)
extern uint16_t accounting_enforce __attribute__((weak_import));
extern void *acct_db_conn  __attribute__((weak_import));
#else
uint16_t accounting_enforce = 0;
void *acct_db_conn = NULL;
#endif

#define LUA_ERROR_HANDLER_FUNC "slurm_backtrace_on_error"

#define T(status_code, string, err) { status_code, #status_code, string, err }

static const struct {
	lua_status_code_t status_code;
	const char *status_code_string;
	const char *string;
	slurm_err_t err;
} lua_status_codes[] = {
	/*
	 * Status codes macros from lua.h and messages derived from:
	 *	https://www.lua.org/manual/5.3/manual.html
	 */
	T(LUA_OK, "SUCCESS", SLURM_SUCCESS),
	T(LUA_YIELD, "Thread yielded", ESLURM_LUA_FUNC_FAILED),
	T(LUA_ERRRUN, "Runtime error", ESLURM_LUA_FUNC_FAILED_RUNTIME_ERROR),
	T(LUA_ERRSYNTAX, "Syntax error during precompilation",
	  ESLURM_LUA_INVALID_SYNTAX),
	T(LUA_ERRMEM, "Memory allocation error", ESLURM_LUA_FUNC_FAILED_ENOMEM),
#ifdef LUA_ERRGCMM
	T(LUA_ERRGCMM, "Error while running a __gc metamethod",
	  ESLURM_LUA_FUNC_FAILED_GARBAGE_COLLECTOR),
#endif
	T(LUA_ERRERR, "Error while running the message handler",
	  ESLURM_LUA_FUNC_FAILED_RUNTIME_ERROR),
};
#undef T

static int _lua_to_data(lua_State *L, data_t *dst, const int index,
			const int depth, const char *parent,
			const bool parent_is_table);
static int _from_data(lua_State *L, const data_t *src);

extern const char *slurm_lua_status_code_string(lua_status_code_t sc)
{
	for (int i = 0; i < ARRAY_SIZE(lua_status_codes); i++)
		if (lua_status_codes[i].status_code == sc)
			return lua_status_codes[i].string;

	/*
	 * Should never happen but only Lua controls returns these values so it
	 * is out of Slurm's control.
	 */
	return "Unknown Lua status code";
}

extern const char *slurm_lua_status_code_stringify(lua_status_code_t sc)
{
	for (int i = 0; i < ARRAY_SIZE(lua_status_codes); i++)
		if (lua_status_codes[i].status_code == sc)
			return lua_status_codes[i].status_code_string;

	return "INVALID";
}

extern slurm_err_t slurm_lua_status_error(lua_status_code_t sc)
{
	for (int i = 0; i < ARRAY_SIZE(lua_status_codes); i++)
		if (lua_status_codes[i].status_code == sc)
			return lua_status_codes[i].err;

	return ESLURM_LUA_FUNC_FAILED;
}

#ifdef LUA_ERROR_BACKTRACE

/*
 * Error callback handler function.
 * Partially based on msghandler() from lua.c (from the lua binary).
 */
static int _on_error_callback(lua_State *L)
{
	const char *msg = NULL;

	/*
	 * Only log the backtrace when running at least under DEBUG_FLAG_SCRIPT
	 * as this is not a free operation and may end up logging excessively.
	 */
	if (!(slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT))
		return 1;

	/* Lua should have already have already placed error string on stack */
	if (!(msg = lua_tostring(L, -1))) {
		/*
		 * Request Lua convert error to string if lua_tostring() failed
		 * for any reason or gracefully fail while getting the type of
		 * what was unexpectedly pushed on the stack
		 */
		if (luaL_callmeta(L, -1, "__tostring") &&
		    lua_type(L, -1) == LUA_TSTRING)
			msg = lua_tostring(L, -1);
		else
			msg = lua_pushfstring(L, "Unknown error of type %s",
					      luaL_typename(L, -1));
	}

	/* msg should always have a string at this point */
	xassert(msg && msg[0]);

	log_flag(SCRIPT, "%s: Lua@0x%"PRIxPTR" failed: %s",
		 __func__, (uintptr_t) L, msg);

	/* Request Lua generate backtrace */
	luaL_traceback(L, L, NULL, 1);

	if ((msg = lua_tostring(L, -1))) {
		char *save_ptr = NULL, *token = NULL, *str = NULL;
		int count = 0, line = 0;

		xassert(msg && msg[0]);

		/* count number of lines */
		for (const char *ptr = msg; ptr && *ptr; count++)
			if ((ptr = strstr(ptr, "\n")))
				ptr++;

		/*
		 * Split up the backtrace by each newline to keep the logs
		 * readable
		 */
		str = xstrdup(msg);
		token = strtok_r(str, "\n", &save_ptr);
		while (token) {
			token = strtok_r(NULL, "\n", &save_ptr);
			line++;

			log_flag(SCRIPT, "%s: Lua@0x%"PRIxPTR" backtrace[%04d/%04d]: %s",
				 __func__, (uintptr_t) L, line, count, token);
		}
		xfree(str);
	}

	/*
	 * Pop backtrace off stack to preserve returning original error message
	 * for existing logging
	 */
	lua_pop(L, -1);

	/* returning original error message */
	return 1;
}

/*
 * Push error callback before pcall args
 * IN L - Lua state
 * RET index for function (aka msgh)
 */
static int _push_error_callback(lua_State *L)
{
	const int index = 1;

	/*
	 * Always place error handler function at the very bottom of the stack
	 * to avoid it getting moved around by any of the arg or return stack
	 * shifting
	 *
	 * The error handler doesn't work when registered via slurm_functions[]
	 * due to the requirement to be placed directly on the stack.
	 */

	lua_getglobal(L, LUA_ERROR_HANDLER_FUNC);

	/* There must be something on the stack here */
	xassert(lua_gettop(L) > 0);

	lua_insert(L, index);

	return index;
}

/*
 * Register error handler callback into Lua globals
 * IN L - Lua stack
 */
static void _register_error_callback(lua_State *L)
{
	lua_register(L, LUA_ERROR_HANDLER_FUNC, _on_error_callback);
}

#else

#define _push_error_callback(L) 0
#define _register_error_callback(L) ((void) 0)

#endif

static int _setup_stringarray(lua_State *L, int limit, char **data)
{
	/*
	 * if limit/data empty this will create an empty array intentionally to
	 * allow the client code to iterate over it
	 */
	lua_newtable(L);
	for (int i = 0; i < limit && data && data[i]; i++) {
		/* by convention lua indexes array tables from 1 */
		lua_pushnumber(L, i + 1);
		lua_pushstring(L, data[i]);
		lua_settable(L, -3);
	}
	return 1;
}

extern int slurm_lua_pcall(lua_State *L, int nargs, int nresults,
			   char **err_ptr, const char *caller)
{
	lua_status_code_t sc;
	int rc;
	int msgh = 0;

	if (slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT) {
		/* Resolve out the error handler if it was already pushed */
		msgh = _push_error_callback(L);
	}

	sc = lua_pcall(L, nargs, nresults, msgh);
	rc = slurm_lua_status_error(sc);

	if (rc) {
		/*
		 * When a lua_pcall() fails, Lua "pushes a single value on the
		 * stack (the error object)" per lua_pcall() description in the
		 * reference manual:
		 *	https://www.lua.org/manual/5.3/manual.html
		 * When msgh == 0, this is the same as the return value of
		 * lua_pcall().
		 *
		 * This function will lua_pop() that value to remove it from
		 * the stack.
		 */
		if (!(*err_ptr = xstrdup(lua_tostring(L, -1))))
			*err_ptr = xstrdup(slurm_strerror(rc));

		lua_pop(L, 1);

		error("%s: lua_pcall(0x%"PRIxPTR", %d, %d, %d)=%s(%s)=%s",
		      caller, (uintptr_t) L, nargs, nresults, msgh,
		      slurm_lua_status_code_stringify(sc),
		      slurm_lua_status_code_string(sc), *err_ptr);
	} else {
		log_flag(SCRIPT, "%s: lua_pcall(0x%"PRIxPTR", %d, %d, %d)=%s(%s)=%s",
		      caller, (uintptr_t) L, nargs, nresults, msgh,
		      slurm_lua_status_code_stringify(sc),
		      slurm_lua_status_code_string(sc), slurm_strerror(rc));
	}

	/* Pop error handler off stack if pushed */
	if (msgh)
		lua_remove(L, msgh);

	return rc;
}

extern bool slurm_lua_is_function_defined(lua_State *L, const char *name)
{
	bool rc = false;

	lua_getglobal(L, name);
	rc = lua_isfunction(L, -1);
	lua_pop(L, -1);

	return rc;
}

/*
 *   Verify all required functions are defined in the script
 */
static int _check_lua_script_functions(lua_State *L, const char *plugin,
				       const char *script_path,
				       const char **req_fxns)
{
	int rc = 0;
	const char **ptr = NULL;
	for (ptr = req_fxns; ptr && *ptr; ptr++) {
		if (!slurm_lua_is_function_defined(L, *ptr)) {
			error("%s: %s: missing required function %s",
			      plugin, script_path, *ptr);
			rc = -1;
		}
	}

	return (rc);
}

/*
 *  Lua interface to Slurm log facility:
 */
static int _log_lua_msg (lua_State *L)
{
	const char *prefix  = "lua";
	int        level    = 0;
	const char *msg;

	/*
	 *  Optional numeric prefix indicating the log level
	 *  of the message.
	 */

	/* Pop message off the lua stack */
	msg = lua_tostring(L, -1);
	lua_pop(L, 1);

	/* Pop level off stack: */
	level = (int)lua_tonumber(L, -1);
	lua_pop(L, 1);

	/* Call appropriate slurm log function based on log-level argument */
	if (level > 4)
		debug4 ("%s: %s", prefix, msg);
	else if (level == 4)
		debug3 ("%s: %s", prefix, msg);
	else if (level == 3)
		debug2 ("%s: %s", prefix, msg);
	else if (level == 2)
		debug ("%s: %s", prefix, msg);
	else if (level == 1)
		verbose ("%s: %s", prefix, msg);
	else if (level == 0)
		info ("%s: %s", prefix, msg);
	return (0);
}

static int _log_lua_error(lua_State *L)
{
	const char *prefix  = "lua";
	const char *msg     = lua_tostring(L, -1);
	error ("%s: %s", prefix, msg);
	return (0);
}

static int _time_str2mins(lua_State *L) {
	const char *time = lua_tostring(L, -1);
	int minutes = time_str2mins(time);
	lua_pushnumber(L, minutes);
	return 1;
}

static int _get_qos_priority(lua_State *L)
{
	const char *qos_name = lua_tostring(L, -1);
	slurmdb_qos_rec_t qos = { 0 };

	qos.name = xstrdup(qos_name);
	if (assoc_mgr_fill_in_qos(acct_db_conn, &qos, accounting_enforce, NULL,
				  false)) {
		error("Invalid QOS name: %s", qos.name);
		xfree(qos.name);
		return 0;
	}
	xfree(qos.name);

	lua_pushnumber(L, qos.priority);
	return 1;
}

static int _parse(lua_State *L, const char *mime_type)
{
	int rc = EINVAL;
	const char *str = NULL;
	size_t str_len = 0;
	data_t *data = NULL;

	if (!(str = lua_tolstring(L, -1, &str_len))) {
		rc = ESLURM_LUA_INVALID_CONVERSION_TYPE;
		goto failed;
	}

	if ((rc = serialize_g_string_to_data(&data, str, str_len, mime_type)))
		goto failed;

	log_flag_hex(SCRIPT, str, str_len,
		     "%s: Lua@0x%" PRIxPTR "[+%d]: parsed %s",
		     __func__, (uintptr_t) L, lua_gettop(L), mime_type);

	/* Pop string arg off stack after converting it */
	lua_pop(L, -1);

	if ((rc = slurm_lua_from_data(L, data)))
		goto failed;

	FREE_NULL_DATA(data);
	return 1;
failed:
	error("%s: Lua@0x%" PRIxPTR "[+%d]: parsing string as %s failed: %s",
	      __func__, (uintptr_t) L, lua_gettop(L), mime_type,
	      slurm_strerror(rc));

	if (str_len > 0)
		log_flag_hex(SCRIPT, str, str_len, "%s: parsing %s failed",
			     __func__, mime_type);

	FREE_NULL_DATA(data);

	(void) lua_pushfstring(L, "Conversion from %s failed: %s", mime_type,
			       slurm_strerror(rc));
	lua_error(L);
	fatal_abort("lua_error() should never return");
}

static int _dump(lua_State *L, const char *mime_type)
{
	int rc = EINVAL;
	char *str = NULL;
	size_t str_len = 0;
	data_t *data = data_new();

	if ((rc = slurm_lua_to_data(L, data)))
		goto failed;

	/* Pop table arg off stack after converting it */
	lua_pop(L, -1);

	if ((rc = serialize_g_data_to_string(&str, &str_len, data, mime_type,
					     SER_FLAGS_NONE)))
		goto failed;

	log_flag_hex(SCRIPT, str, str_len,
		     "%s: Lua@0x%" PRIxPTR "[+%d]: dumped %pD->%s",
		     __func__, (uintptr_t) L, lua_gettop(L), data, mime_type);

	FREE_NULL_DATA(data);

	(void) lua_pushstring(L, str);

	xfree(str);
	return 1;
failed:
	error("%s: Lua@0x%" PRIxPTR "[+%d]: dumping %pD as %s failed: %s",
	      __func__, (uintptr_t) L, lua_gettop(L), data, mime_type,
	      slurm_strerror(rc));

	FREE_NULL_DATA(data);
	xfree(str);

	(void) lua_pushfstring(L, "Conversion to %s failed: %s", mime_type,
			       slurm_strerror(rc));
	lua_error(L);
	fatal_abort("lua_error() should never return");
}

static int _from_json(lua_State *L)
{
	static bool load_once = false;

	if (!load_once) {
		serializer_required(MIME_TYPE_JSON);
		load_once = true;
	}

	return _parse(L, MIME_TYPE_JSON);
}

static int _to_json(lua_State *L)
{
	static bool load_once = false;

	if (!load_once) {
		serializer_required(MIME_TYPE_JSON);
		load_once = true;
	}

	return _dump(L, MIME_TYPE_JSON);
}

static int _from_yaml(lua_State *L)
{
	static bool load_once = false;

	if (!load_once) {
		serializer_required(MIME_TYPE_YAML);
		load_once = true;
	}

	return _parse(L, MIME_TYPE_YAML);
}

static int _to_yaml(lua_State *L)
{
	static bool load_once = false;

	if (!load_once) {
		serializer_required(MIME_TYPE_YAML);
		load_once = true;
	}

	return _dump(L, MIME_TYPE_YAML);
}

static const struct luaL_Reg slurm_functions[] = {
	{ "log", _log_lua_msg },
	{ "error", _log_lua_error },
	{ "time_str2mins", _time_str2mins },
	{ "get_qos_priority", _get_qos_priority },
	{ "to_json", _to_json },
	{ "from_json", _from_json },
	{ "to_yaml", _to_yaml },
	{ "from_yaml", _from_yaml },
	{ NULL, NULL }
};

static void _register_slurm_output_errtab(lua_State *L)
{
	int i;

	for (i = 0; i < slurm_errtab_size; i++) {
		lua_pushnumber(L, slurm_errtab[i].xe_number);
		lua_setfield(L, -2, slurm_errtab[i].xe_name);
	}
}

static void _register_slurm_output_functions(lua_State *L)
{
	char *unpack_str;
	char tmp_string[100];

#if LUA_VERSION_NUM == 501
	unpack_str = "unpack";
#else
	unpack_str = "table.unpack";
#endif
	/*
	 *  Register slurm output functions in a global "slurm" table
	 */
	lua_newtable(L);
	slurm_lua_table_register(L, NULL, slurm_functions);

	/*
	 *  Create more user-friendly lua versions of Slurm log functions.
	 */
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.error (string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_error");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.log (0, string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_info");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.log (1, string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_verbose");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.log (2, string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_debug");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.log (3, string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_debug2");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.log (4, string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_debug3");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.log (5, string.format(%s({...})))",
		 unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_debug4");
	snprintf(tmp_string, sizeof(tmp_string),
		 "slurm.user_msg (string.format(%s({...})))", unpack_str);
	luaL_loadstring(L, tmp_string);
	lua_setfield(L, -2, "log_user");

	/*
	 * Error codes: slurm.SUCCESS, slurm.FAILURE, slurm.ERROR, etc.
	 */
	lua_pushnumber(L, SLURM_ERROR);
	lua_setfield(L, -2, "ERROR");
	lua_pushnumber(L, SLURM_ERROR);
	lua_setfield(L, -2, "FAILURE");
	lua_pushnumber(L, SLURM_SUCCESS);
	lua_setfield(L, -2, "SUCCESS");
	_register_slurm_output_errtab(L);

	/*
	 * Other definitions needed to interpret data
	 * slurm.MEM_PER_CPU, slurm.NO_VAL, etc.
	 */
	lua_pushnumber(L, ALLOC_SID_ADMIN_HOLD);
	lua_setfield(L, -2, "ALLOC_SID_ADMIN_HOLD");
	lua_pushnumber(L, ALLOC_SID_USER_HOLD);
	lua_setfield(L, -2, "ALLOC_SID_USER_HOLD");
	lua_pushnumber(L, INFINITE);
	lua_setfield(L, -2, "INFINITE");
	lua_pushnumber(L, (double) INFINITE64);
	lua_setfield(L, -2, "INFINITE64");
	lua_pushnumber(L, MAIL_INVALID_DEPEND);
	lua_setfield(L, -2, "MAIL_INVALID_DEPEND");
	lua_pushnumber(L, MAIL_JOB_BEGIN);
	lua_setfield(L, -2, "MAIL_JOB_BEGIN");
	lua_pushnumber(L, MAIL_JOB_END);
	lua_setfield(L, -2, "MAIL_JOB_END");
	lua_pushnumber(L, MAIL_JOB_FAIL);
	lua_setfield(L, -2, "MAIL_JOB_FAIL");
	lua_pushnumber(L, MAIL_JOB_REQUEUE);
	lua_setfield(L, -2, "MAIL_JOB_REQUEUE");
	lua_pushnumber(L, MAIL_JOB_TIME100);
	lua_setfield(L, -2, "MAIL_JOB_TIME100");
	lua_pushnumber(L, MAIL_JOB_TIME90);
	lua_setfield(L, -2, "MAIL_JOB_TIME890");
	lua_pushnumber(L, MAIL_JOB_TIME80);
	lua_setfield(L, -2, "MAIL_JOB_TIME80");
	lua_pushnumber(L, MAIL_JOB_TIME50);
	lua_setfield(L, -2, "MAIL_JOB_TIME50");
	lua_pushnumber(L, MAIL_JOB_STAGE_OUT);
	lua_setfield(L, -2, "MAIL_JOB_STAGE_OUT");
	lua_pushnumber(L, MEM_PER_CPU);
	lua_setfield(L, -2, "MEM_PER_CPU");
	lua_pushnumber(L, NICE_OFFSET);
	lua_setfield(L, -2, "NICE_OFFSET");
	lua_pushnumber(L, JOB_SHARED_NONE);
	lua_setfield(L, -2, "JOB_SHARED_NONE");
	lua_pushnumber(L, JOB_SHARED_OK);
	lua_setfield(L, -2, "JOB_SHARED_OK");
	lua_pushnumber(L, JOB_SHARED_USER);
	lua_setfield(L, -2, "JOB_SHARED_USER");
	lua_pushnumber(L, JOB_SHARED_MCS);
	lua_setfield(L, -2, "JOB_SHARED_MCS");
	lua_pushnumber(L, (double) NO_VAL64);
	lua_setfield(L, -2, "NO_VAL64");
	lua_pushnumber(L, NO_VAL);
	lua_setfield(L, -2, "NO_VAL");
	lua_pushnumber(L, NO_VAL16);
	lua_setfield(L, -2, "NO_VAL16");
	lua_pushnumber(L, NO_VAL8);
	lua_setfield(L, -2, "NO_VAL8");
	lua_pushnumber(L, SHARED_FORCE);
	lua_setfield(L, -2, "SHARED_FORCE");

	/*
	 * job_desc bitflags
	 */
	lua_pushnumber(L, GRES_ALLOW_TASK_SHARING);
	lua_setfield(L, -2, "GRES_ALLOW_TASK_SHARING");
	lua_pushnumber(L, GRES_DISABLE_BIND);
	lua_setfield(L, -2, "GRES_DISABLE_BIND");
	lua_pushnumber(L, GRES_ENFORCE_BIND);
	lua_setfield(L, -2, "GRES_ENFORCE_BIND");
	lua_pushnumber(L, GRES_MULT_TASKS_PER_SHARING);
	lua_setfield(L, -2, "GRES_MULT_TASKS_PER_SHARING");
	lua_pushnumber(L, GRES_ONE_TASK_PER_SHARING);
	lua_setfield(L, -2, "GRES_ONE_TASK_PER_SHARING");
	lua_pushnumber(L, KILL_INV_DEP);
	lua_setfield(L, -2, "KILL_INV_DEP");
	lua_pushnumber(L, NO_KILL_INV_DEP);
	lua_setfield(L, -2, "NO_KILL_INV_DEP");
	lua_pushnumber(L, SPREAD_JOB);
	lua_setfield(L, -2, "SPREAD_JOB");
	lua_pushnumber(L, USE_MIN_NODES);
	lua_setfield(L, -2, "USE_MIN_NODES");
	lua_pushnumber(L, STEPMGR_ENABLED);
	lua_setfield(L, -2, "STEPMGR_ENABLED");
	lua_pushnumber(L, SPREAD_SEGMENTS);
	lua_setfield(L, -2, "SPREAD_SEGMENTS");
	lua_pushnumber(L, CONSOLIDATE_SEGMENTS);
	lua_setfield(L, -2, "CONSOLIDATE_SEGMENTS");
	lua_pushnumber(L, EXPEDITED_REQUEUE);
	lua_setfield(L, -2, "EXPEDITED_REQUEUE");

	lua_pushstring(L, slurm_conf.cluster_name);
	lua_setfield(L, -2, "CLUSTER_NAME");
}

extern void slurm_lua_table_register(lua_State *L, const char *libname,
				     const luaL_Reg *l)
{
#if LUA_VERSION_NUM == 501
	luaL_register(L, libname, l);
#else
	luaL_setfuncs(L, l, 0);
	if (libname)
		lua_setglobal(L, libname);
#endif
}

/*
 * Get fields in an existing slurmctld job record.
 *
 * This is an incomplete list of job record fields. Add more as needed and
 * send patches to slurm-dev@schedmd.com.
 */
extern int slurm_lua_job_record_field(lua_State *L, const job_record_t *job_ptr,
				      const char *name)
{
	int i;
	char sluid_str[SLUID_STR_BYTES];

	if (!job_ptr) {
		error("_job_rec_field: job_ptr is NULL");
		lua_pushnil(L);
	} else if (!xstrcmp(name, "account")) {
		lua_pushstring(L, job_ptr->account);
	} else if (!xstrcmp(name, "admin_comment")) {
		lua_pushstring(L, job_ptr->admin_comment);
	} else if (!xstrcmp(name, "alloc_node")) {
               lua_pushstring(L, job_ptr->alloc_node);
	} else if (!xstrcmp(name, "argv")) {
		if (job_ptr->details)
			_setup_stringarray(L, job_ptr->details->argc,
					   job_ptr->details->argv);
		else
			lua_newtable(L);
	} else if (!xstrcmp(name, "array_job_id")) {
		lua_pushnumber(L, job_ptr->array_job_id);
	} else if (!xstrcmp(name, "array_task_cnt")) {
		if (job_ptr->array_recs)
			lua_pushnumber(L, job_ptr->array_recs->task_cnt);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "array_task_id")) {
		lua_pushnumber(L, job_ptr->array_task_id);
	} else if (!xstrcmp(name, "batch_features")) {
		lua_pushstring(L, job_ptr->batch_features);
	} else if (!xstrcmp(name, "batch_host")) {
		lua_pushstring(L, job_ptr->batch_host);
	} else if (!xstrcmp(name, "best_switch")) {
		lua_pushnumber(L, job_ptr->best_switch);
	} else if (!xstrcmp(name, "burst_buffer")) {
		lua_pushstring(L, job_ptr->burst_buffer);
	} else if (!xstrcmp(name, "comment")) {
		lua_pushstring(L, job_ptr->comment);
	} else if (!xstrcmp(name, "container")) {
		lua_pushstring(L, job_ptr->container);
	} else if (!xstrcmp(name, "core_spec")) {
		if (job_ptr->details &&
		    (job_ptr->details->core_spec != NO_VAL16) &&
		    !(job_ptr->details->core_spec & CORE_SPEC_THREAD))
			lua_pushnumber(L, job_ptr->details->core_spec);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "cpus_per_tres")) {
		lua_pushstring(L, job_ptr->cpus_per_tres);
	} else if (!xstrcmp(name, "delay_boot")) {
		lua_pushnumber(L, job_ptr->delay_boot);
	} else if (!xstrcmp(name, "curr_dependency")) {
		/*
		 * Name it "curr_dependency" rather than "dependency" because
		 * the job's dependency value can change as individual
		 * dependencies change. This prevents the use of "dependency"
		 * when someone is expecting the original dependency value.
		 */
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->dependency);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "orig_dependency")) {
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->orig_dependency);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "derived_ec")) {
		lua_pushnumber(L, job_ptr->derived_ec);
	} else if (!xstrcmp(name, "direct_set_prio")) {
		lua_pushnumber(L, job_ptr->direct_set_prio);
	} else if (!xstrcmp(name, "end_time")) {
		lua_pushnumber(L, job_ptr->end_time);
	} else if (!xstrcmp(name, "exit_code")) {
		lua_pushnumber(L, job_ptr->exit_code);
	} else if (!xstrcmp(name, "extra")) {
		lua_pushstring(L, job_ptr->extra);
	} else if (!xstrcmp(name, "features")) {
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->features);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "gres")) {
		/* "gres" replaced by "tres_per_node" in v18.08 */
		lua_pushstring(L, job_ptr->tres_per_node);
	} else if (!xstrcmp(name, "gres_req")) {
		lua_pushstring(L, job_ptr->tres_fmt_req_str);
	} else if (!xstrcmp(name, "gres_used")) {
		lua_pushstring(L, job_ptr->gres_used);
	} else if (!xstrcmp(name, "group_id")) {
		lua_pushnumber(L, job_ptr->group_id);
	} else if (!xstrcmp(name, "job_id")) {
		lua_pushnumber(L, job_ptr->job_id);
	} else if (!xstrcmp(name, "sluid")) {
		print_sluid(job_ptr->db_index, sluid_str, sizeof(sluid_str));
		lua_pushstring(L, sluid_str);
	} else if (!xstrcmp(name, "original_sluid")) {
		if (job_ptr->step_id.sluid) {
			print_sluid(job_ptr->step_id.sluid, sluid_str,
				    sizeof(sluid_str));
			lua_pushstring(L, sluid_str);
		} else {
			lua_pushnil(L);
		}
	} else if (!xstrcmp(name, "job_state")) {
		lua_pushnumber(L, job_ptr->job_state);
	} else if (!xstrcmp(name, "licenses")) {
		lua_pushstring(L, job_ptr->licenses);
	} else if (!xstrcmp(name, "max_cpus")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->max_cpus);
		else
			lua_pushnumber(L, 0);
	} else if (!xstrcmp(name, "max_nodes")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->max_nodes);
		else
			lua_pushnumber(L, 0);
	} else if (!xstrcmp(name, "mcs_label")) {
		lua_pushstring(L, job_ptr->mcs_label);
	} else if (!xstrcmp(name, "mem_per_tres")) {
		lua_pushstring(L, job_ptr->mem_per_tres);
	} else if (!xstrcmp(name, "min_cpus")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->min_cpus);
		else
			lua_pushnumber(L, 0);
	} else if (!xstrcmp(name, "min_mem_per_node")) {
		if (job_ptr->details &&
		    (job_ptr->details->pn_min_memory != NO_VAL64) &&
		    !(job_ptr->details->pn_min_memory & MEM_PER_CPU))
			lua_pushnumber(L, job_ptr->details->pn_min_memory);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "min_mem_per_cpu")) {
		if (job_ptr->details &&
		    (job_ptr->details->pn_min_memory != NO_VAL64) &&
		    (job_ptr->details->pn_min_memory & MEM_PER_CPU))
			lua_pushnumber(L, job_ptr->details->pn_min_memory &
				       ~MEM_PER_CPU);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "min_nodes")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->min_nodes);
		else
			lua_pushnumber(L, 0);
	} else if (!xstrcmp(name, "name")) {
		lua_pushstring(L, job_ptr->name);
	} else if (!xstrcmp(name, "nice")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->nice);
		else
			lua_pushnumber(L, NO_VAL16);
	} else if (!xstrcmp(name, "nodes")) {
		lua_pushstring(L, job_ptr->nodes);
	} else if (!xstrcmp(name, "origin_cluster")) {
		lua_pushstring(L, job_ptr->origin_cluster);
		/* Continue support for old hetjob terminology. */
	} else if (!xstrcmp(name, "pack_job_id") ||
		   !xstrcmp(name, "het_job_id")) {
		lua_pushnumber(L, job_ptr->het_job_id);
	} else if (!xstrcmp(name, "pack_job_id_set") ||
		   !xstrcmp(name, "het_job_id_set")) {
		lua_pushstring(L, job_ptr->het_job_id_set);
	} else if (!xstrcmp(name, "pack_job_offset") ||
		   !xstrcmp(name, "het_job_offset")) {
		lua_pushnumber(L, job_ptr->het_job_offset);
	} else if (!xstrcmp(name, "partition")) {
		lua_pushstring(L, job_ptr->partition);
	} else if (!xstrcmp(name, "pn_min_cpus")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->pn_min_cpus);
		else
			lua_pushnumber(L, NO_VAL);
	} else if (!xstrcmp(name, "pn_min_memory")) {
		/*
		 * FIXME: Remove this in the future, lua can't handle 64bit
		 * numbers!!!.  Use min_mem_per_node|cpu instead.
		 */
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->pn_min_memory);
		else
			lua_pushnumber(L, (double) NO_VAL64);
	} else if (!xstrcmp(name, "priority")) {
		lua_pushnumber(L, job_ptr->priority);
	} else if (!xstrcmp(name, "qos")) {
		if (job_ptr->qos_ptr) {
			lua_pushstring(L, job_ptr->qos_ptr->name);
		} else {
			lua_pushnil(L);
		}
	} else if (!xstrcmp(name, "reboot")) {
		lua_pushnumber(L, job_ptr->reboot);
	} else if (!xstrcmp(name, "req_switch")) {
		lua_pushnumber(L, job_ptr->req_switch);
	} else if (!xstrcmp(name, "resizing")) {
		int resizing = IS_JOB_RESIZING(job_ptr) ? 1 : 0;
		lua_pushnumber(L, resizing);
	} else if (!xstrcmp(name, "restart_cnt")) {
		lua_pushnumber(L, job_ptr->restart_cnt);
	} else if (!xstrcmp(name, "resv_name")) {
		lua_pushstring(L, job_ptr->resv_name);
	} else if (!xstrcmp(name, "script")) {
		buf_t *bscript = get_job_script(job_ptr);
		if (bscript) {
			char *script = bscript->head;
			if (script && script[0] != '\0')
				lua_pushstring(L, script);
			else
				lua_pushnil(L);
		} else
			lua_pushnil(L);
		FREE_NULL_BUFFER(bscript);
	} else if (!xstrcmp(name, "segment_size")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->segment_size);
		else
			lua_pushnumber(L, 0);
	} else if (!xstrcmp(name, "selinux_context")) {
		lua_pushstring(L, job_ptr->selinux_context);
 	} else if (!xstrcmp(name, "site_factor")) {
		if (job_ptr->site_factor == NO_VAL)
			lua_pushnumber(L, job_ptr->site_factor);
		else
			lua_pushnumber(L,
				       (((int64_t)job_ptr->site_factor)
					- NICE_OFFSET));
	} else if (!xstrcmp(name, "spank_job_env")) {
		if ((job_ptr->spank_job_env_size == 0) ||
		    (job_ptr->spank_job_env == NULL)) {
			lua_pushnil(L);
		} else {
			lua_newtable(L);
			for (i = 0; i < job_ptr->spank_job_env_size; i++) {
				if (job_ptr->spank_job_env[i] != NULL) {
					lua_pushnumber(L, i);
					lua_pushstring(
						L, job_ptr->spank_job_env[i]);
					lua_settable(L, -3);
				}
			}
		}
	} else if (!xstrcmp(name, "spank_job_env_size")) {
		lua_pushnumber(L, job_ptr->spank_job_env_size);
	} else if (!xstrcmp(name, "start_time")) {
		lua_pushnumber(L, job_ptr->start_time);
	} else if (!xstrcmp(name, "std_err")) {
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->std_err);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "std_in")) {
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->std_in);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "std_out")) {
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->std_out);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "submit_time")) {
		if (job_ptr->details)
			lua_pushnumber(L, job_ptr->details->submit_time);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "thread_spec")) {
		if (job_ptr->details &&
		    (job_ptr->details->core_spec != NO_VAL16) &&
		    (job_ptr->details->core_spec & CORE_SPEC_THREAD))
			lua_pushnumber(L, job_ptr->details->core_spec &
						  ~CORE_SPEC_THREAD);
		else
			lua_pushnil(L);
	} else if (!xstrcmp(name, "time_limit")) {
		lua_pushnumber(L, job_ptr->time_limit);
	} else if (!xstrcmp(name, "time_min")) {
		lua_pushnumber(L, job_ptr->time_min);
	} else if (!xstrcmp(name, "total_cpus")) {
		lua_pushnumber(L, job_ptr->total_cpus);
	} else if (!xstrcmp(name, "total_nodes")) {
		lua_pushnumber(L, job_ptr->total_nodes);
	} else if (!xstrcmp(name, "tres_alloc_str")) {
		lua_pushstring(L, job_ptr->tres_alloc_str);
	} else if (!xstrcmp(name, "tres_bind")) {
		lua_pushstring(L, job_ptr->tres_bind);
	} else if (!xstrcmp(name, "tres_fmt_alloc_str")) {
		lua_pushstring(L, job_ptr->tres_fmt_alloc_str);
	} else if (!xstrcmp(name, "tres_fmt_req_str")) {
		lua_pushstring(L, job_ptr->tres_fmt_req_str);
	} else if (!xstrcmp(name, "tres_freq")) {
		lua_pushstring(L, job_ptr->tres_freq);
	} else if (!xstrcmp(name, "tres_per_job")) {
		lua_pushstring(L, job_ptr->tres_per_job);
	} else if (!xstrcmp(name, "tres_per_node")) {
		lua_pushstring(L, job_ptr->tres_per_node);
	} else if (!xstrcmp(name, "tres_per_socket")) {
		lua_pushstring(L, job_ptr->tres_per_socket);
	} else if (!xstrcmp(name, "tres_per_task")) {
		lua_pushstring(L, job_ptr->tres_per_task);
	} else if (!xstrcmp(name, "tres_req_str")) {
		lua_pushstring(L, job_ptr->tres_req_str);
	} else if (!xstrcmp(name, "user_id")) {
		lua_pushnumber(L, job_ptr->user_id);
	} else if (!xstrcmp(name, "user_name")) {
		lua_pushstring(L, job_ptr->user_name);
	} else if (!xstrcmp(name, "wait4switch")) {
		lua_pushnumber(L, job_ptr->wait4switch);
	} else if (!xstrcmp(name, "wait4switch_start")) {
		lua_pushnumber(L, job_ptr->wait4switch_start);
	} else if (!xstrcmp(name, "wckey")) {
		lua_pushstring(L, job_ptr->wckey);
	} else if (!xstrcmp(name, "work_dir")) {
		if (job_ptr->details)
			lua_pushstring(L, job_ptr->details->work_dir);
		else
			lua_pushnil(L);
	} else {
		lua_pushnil(L);
	}

	return 1;
}

/* Generic stack dump function for debugging purposes */
extern void slurm_lua_stack_dump(const char *plugin, char *header, lua_State *L)
{
#if _DEBUG
	int i;
	int top = lua_gettop(L);

	info("%s: dumping %s stack, %d elements", plugin, header, top);
	for (i = 1; i <= top; i++) {  /* repeat for each level */
		int type = lua_type(L, i);
		switch (type) {
		case LUA_TSTRING:
			info("string[%d]:%s", i, lua_tostring(L, i));
			break;
		case LUA_TBOOLEAN:
			info("boolean[%d]:%s", i,
			     lua_toboolean(L, i) ? "true" : "false");
			break;
		case LUA_TNUMBER:
			info("number[%d]:%d", i,
			     (int) lua_tonumber(L, i));
			break;
		default:
			info("other[%d]:%s", i, lua_typename(L, type));
			break;
		}
	}
#endif
}

extern int slurm_lua_loadscript(lua_State **L, const char *plugin,
				const char *script_path,
				const char **req_fxns,
				time_t *load_time,
				void (*local_options)(lua_State *L),
				char **err_msg)
{
	lua_State *new = NULL;
	lua_State *curr = *L;
	struct stat st;
	int rc = 0;
	char *err_str = NULL, *ret_err_str = NULL;

	if (stat(script_path, &st) != 0) {
		err_str = xstrdup_printf("Unable to stat %s: %s",
					 script_path, strerror(errno));
		goto fini_error;
	}

	if (st.st_mtime <= *load_time) {
		debug3("%s: %s: skipping loading Lua script: %s", plugin,
		       __func__, script_path);
		return SLURM_SUCCESS;
	}
	debug3("%s: %s: loading Lua script: %s", __func__, plugin, script_path);

	/* Initialize lua */
	if (!(new = luaL_newstate())) {
		err_str = xstrdup_printf("luaL_newstate() failed to allocate");
		goto fini_error;
	}

	luaL_openlibs(new);
	if (luaL_loadfile(new, script_path)) {
		err_str = xstrdup_printf("%s: %s",
					 script_path, lua_tostring(new, -1));
		lua_close(new);
		goto fini_error;
	}

	/*
	 *  Register Slurm functions in lua state:
	 *  logging and slurm structure read/write functions
	 */
	_register_slurm_output_functions(new);
	if (*(local_options))
		(*(local_options))(new);
	else
		lua_setglobal(new, "slurm"); /* done in local_options */

	/* Register error handler globally */
	_register_error_callback(new);

	/*
	 *  Run the user script:
	 */
	if ((rc = slurm_lua_pcall(new, 0, 1, &ret_err_str, __func__))) {
		err_str = xstrdup_printf("%s: %s", script_path, ret_err_str);
		xfree(ret_err_str);
		lua_close(new);
		goto fini_error;
	}

	/*
	 *  Get any return code from the lua script
	 */
	rc = (int) lua_tonumber(new, -1);
	if (rc != SLURM_SUCCESS) {
		err_str = xstrdup_printf("%s: returned %d on load",
					 script_path, rc);
		lua_close(new);
		goto fini_error;
	}

	/*
	 *  Check for required lua script functions:
	 */
	rc = _check_lua_script_functions(new, plugin, script_path, req_fxns);
	if (rc != SLURM_SUCCESS) {
		err_str = xstrdup_printf("%s: required function(s) not present",
					 script_path);
		goto fini_error;
	}

	*load_time = st.st_mtime;
	if (curr)
		lua_close(curr);
	*L = new;
	return SLURM_SUCCESS;

fini_error:
	if (curr) {
		xstrfmtcat(err_str, ", using previous script");
		rc = SLURM_SUCCESS;
	} else {
		rc = SLURM_ERROR;
	}
	error("%s: %s", plugin, err_str);
	if (err_msg) {
		xfree(*err_msg);
		*err_msg = err_str;
		err_str = NULL;
	} else {
		xfree(err_str);
	}
	return rc;
}

/*
 *  Init function to dlopen() the appropriate Lua libraries, and
 *  ensure the lua version matches what we compiled against along with other
 *  init things.
 */
extern int slurm_lua_init(void)
{
	slurm_lua_fini();

	char *const lua_libs[] = {
		"liblua.so",
#if LUA_VERSION_NUM == 504
		"liblua-5.4.so",
		"liblua5.4.so",
		"liblua5.4.so.0",
		"liblua.so.5.4",
#elif LUA_VERSION_NUM == 503
		"liblua-5.3.so",
		"liblua5.3.so",
		"liblua5.3.so.0",
		"liblua.so.5.3",
#elif LUA_VERSION_NUM == 502
		"liblua-5.2.so",
		"liblua5.2.so",
		"liblua5.2.so.0",
		"liblua.so.5.2",
#else
		"liblua-5.1.so",
		"liblua5.1.so",
		"liblua5.1.so.0",
		"liblua.so.5.1",
#endif
		NULL
	};
	int i = 0;

	/*
	 *  Need to dlopen() liblua.so with RTLD_GLOBAL in order to
	 *   ensure symbols from liblua are available to libs opened
	 *   by any lua scripts.
	 */
	while (lua_libs[i] &&
	       !(lua_handle = dlopen(lua_libs[i], RTLD_NOW | RTLD_GLOBAL)))
		i++;

	if (!lua_handle) {
		error("Failed to open liblua.so: %s", dlerror());
		return SLURM_ERROR;
	}

	/* Load any serializer plugins for JSON/YAML conversions */
	serializer_g_init();

	return SLURM_SUCCESS;
}

/*
 * Close down the lib, free memory and such.
 */
extern void slurm_lua_fini(void)
{
	if (lua_handle)
		dlclose(lua_handle);
}

static data_for_each_cmd_t _dump_data_foreach_list(const data_t *data,
						   void *arg)
{
	dump_data_foreach_args_t *args = arg;
	lua_State *L = args->L;

	xassert(args->magic == DUMP_DATA_FOREACH_ARGS_MAGIC);

	if ((args->rc = slurm_lua_from_data(L, data)))
		return DATA_FOR_EACH_FAIL;

	lua_rawseti(L, args->table_index, args->field_index);
	args->field_index++;

	return DATA_FOR_EACH_CONT;
}

static data_for_each_cmd_t _dump_data_foreach_dict(const char *key,
						   const data_t *data,
						   void *arg)
{
	dump_data_foreach_args_t *args = arg;
	lua_State *L = args->L;

	xassert(args->magic == DUMP_DATA_FOREACH_ARGS_MAGIC);

	if ((args->rc = slurm_lua_from_data(L, data)))
		return DATA_FOR_EACH_FAIL;

	lua_setfield(L, args->table_index, key);

	return DATA_FOR_EACH_CONT;
}

static int _from_data_list(lua_State *L, const data_t *src)
{
	dump_data_foreach_args_t args = {
		.magic = DUMP_DATA_FOREACH_ARGS_MAGIC,
		.L = L,
		.rc = SLURM_SUCCESS,
		.field_index = 1,
	};

	lua_createtable(L, data_get_list_length(src), 0);

	args.table_index = lua_gettop(L);

	(void) data_list_for_each_const(src, _dump_data_foreach_list, &args);

	return args.rc;
}

static int _from_data_dict(lua_State *L, const data_t *src)
{
	dump_data_foreach_args_t args = {
		.magic = DUMP_DATA_FOREACH_ARGS_MAGIC,
		.L = L,
		.rc = SLURM_SUCCESS,
	};

	lua_createtable(L, data_get_dict_length(src), 0);

	args.table_index = lua_gettop(L);

	(void) data_dict_for_each_const(src, _dump_data_foreach_dict, &args);

	return args.rc;
}

static int _from_data(lua_State *L, const data_t *src)
{
	switch (data_get_type(src)) {
	case DATA_TYPE_LIST:
		return _from_data_list(L, src);
	case DATA_TYPE_DICT:
		return _from_data_dict(L, src);
	case DATA_TYPE_NULL:
		lua_pushnil(L);
		return SLURM_SUCCESS;
	case DATA_TYPE_INT_64:
		lua_pushinteger(L, data_get_int(src));
		return SLURM_SUCCESS;
	case DATA_TYPE_FLOAT:
		lua_pushnumber(L, data_get_float(src));
		return SLURM_SUCCESS;
	case DATA_TYPE_STRING:
		lua_pushstring(L, data_get_string(src));
		return SLURM_SUCCESS;
	case DATA_TYPE_BOOL:
		lua_pushboolean(L, data_get_bool(src));
		return SLURM_SUCCESS;
	case DATA_TYPE_NONE:
		;/* fall through */
	case DATA_TYPE_MAX:
		;/* fall through */
	};

	fatal_abort("should never happen");
}

extern int slurm_lua_from_data(lua_State *L, const data_t *src)
{
	if (!L)
		return ESLURM_LUA_INVALID_STATE;

	if (!src)
		return ESLURM_DATA_PTR_NULL;

	return _from_data(L, src);
}

/* Log details on function at index */
static void _log_function(lua_State *L, const int index, const char *label)
{
	lua_Debug ar = { 0 };

	lua_pushvalue(L, index);

	if (lua_getinfo(L, ">nSl", &ar))
		log_flag(SCRIPT, "%s: type=%s name=%s%s%s%s source=%s:%d-%d executing=%d",
			 label, ar.what, ((ar.name && ar.name[0]) ?
					  ar.name : "<ANONYMOUS>"),
			 ((ar.namewhat && ar.namewhat[0]) ? "(" : ""),
			 ((ar.namewhat && ar.namewhat[0]) ?
			  ar.namewhat : ""),
			 ((ar.namewhat && ar.namewhat[0]) ? ")" : ""),
			 ar.short_src, ar.linedefined,
			 ar.lastlinedefined, ar.currentline);

	lua_pop(L, 1);
}

/* Ask Lua for string and retry by converting to a string */
static int _dump_string(lua_State *L, char **ptr, const int index,
			const char *label)
{
	const char *str = NULL;
	size_t len = 0;

	if (!(str = lua_tolstring(L, index, &len)) &&
	    luaL_callmeta(L, index, "__tostring"))
		str = lua_tolstring(L, index, &len);

	/*
	 * Only log string if it was set and was a sane length.
	 * Allow empty string (len == 0).
	 */
	if (!str || (len >= MAX_VAL)) {
		log_flag(SCRIPT, "%s: invalid string", label);
		return ESLURM_LUA_INVALID_CONVERSION_TYPE;
	}

	*ptr = xstrndup(str, len);
	return SLURM_SUCCESS;
}

static int _dump_data_string(lua_State *L, data_t *dst, const int index,
			     const char *label)
{
	char *str = NULL;
	int rc = EINVAL;

	if ((rc = _dump_string(L, &str, index, label)))
		return rc;

	log_flag_hex(SCRIPT, str, (xsize(str) - 1), "%s: string", label);
	data_set_string_own(dst, str);
	return SLURM_SUCCESS;
}

static int _foreach_table_row(lua_State *L, data_t *dst, int *last_key_ptr,
			      const int index, const char *parent,
			      const int depth)
{
	/*
	 * Lua stack:
	 * -1 -> value
	 * -2 -> key
	 */
	const int key_type = lua_type(L, -2);
	int nkey = -1;
	char *label = NULL;
	data_t *child = NULL;
	int rc = EINVAL;

	if (key_type == LUA_TNUMBER) {
		if (slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT)
			xstrfmtcat(label, "%s[" LUA_NUMBER_FMT "]", parent,
				   lua_tonumber(L, -2));

#if LUA_VERSION_NUM >= 503
		/* Skip conversion to string */
		if (lua_isinteger(L, -2)) {
			nkey = lua_tointeger(L, -2);
			child = data_key_set_int(dst, nkey);
		}
#endif
	}

	if (!child) {
		char *key = NULL;

		if ((rc = _dump_string(L, &key, -2, parent)))
			return rc;
		else if (!key)
			return ESLURM_LUA_INVALID_CONVERSION_TYPE;

		if ((slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT) && !label)
			xstrfmtcat(label, "%s[%s]", parent, key);

		child = data_key_set(dst, key);

		/*
		 * Only attempt conversion to integer if we know this could be a
		 * list
		 */
		if (*last_key_ptr >= 0)
			nkey = slurm_atoul(key);

		xfree(key);
	}

	if (*last_key_ptr >= 0) {
		/* Track if the keys are a natural index */
		if ((nkey > 0) && (nkey < ULONG_MAX) &&
		    (*last_key_ptr + 1) == nkey)
			*last_key_ptr = nkey;
		else
			*last_key_ptr = -1;
	}

	if ((rc = _lua_to_data(L, child, lua_gettop(L), (depth + 1), label,
			       true)))
		return rc;

	/* Pop value off stack to use key for next loop */
	lua_pop(L, 1);
	xfree(label);
	return SLURM_SUCCESS;
}

static int _dump_table(lua_State *L, data_t *dst, const int index,
		       const char *parent, const int depth)
{
	int rc = SLURM_SUCCESS;
	int list_length = -1, last_key = 0;

	if (luaL_getmetafield(L, index, "__len") != LUA_TNIL) {
#if LUA_VERSION_NUM >= 503
		if (lua_isinteger(L, -1))
			list_length = lua_tointeger(L, -1);
		else
#endif
			list_length = lua_tonumber(L, -1);

		log_flag(SCRIPT, "%s: metadata: __len=%d", parent, list_length);

		lua_pop(L, 1);
	}

	xassert(lua_istable(L, index));

	/*
	 * Assume table is a dictionary until entire table is dumped and every
	 * key can be verified
	 */
	(void) data_set_dict(dst);

	/* Walk each table row */
	lua_pushnil(L);

	while (lua_next(L, index) && !rc)
		rc = _foreach_table_row(L, dst, &last_key, index, parent,
					depth);

	/*
	 * Per 3.4.7 – The Length Operator of the Lua 5.3 manual:
	 * A sequence is the natural index with out any borders (aka
	 * nil)s for the dictionary keys starting at 1. We are going to
	 * treat a Lua sequence the same as a list.
	 *
	 * __len is not a reliably set field but verify when it is present.
	 */
	if (((list_length > 0) && (list_length == last_key)) ||
	    (last_key >= 0)) {
		(void) data_convert_type(dst, DATA_TYPE_LIST);
		log_flag(SCRIPT, "%s: dumped list -> __len=%d last_key=%d length=%zu",
			 parent, list_length, last_key,
			 data_get_list_length(dst));
	} else {
		log_flag(SCRIPT, "%s: dumped dictionary -> __len=%d last_key=%d length=%zu",
			 parent, list_length, last_key,
			 data_get_dict_length(dst));
	}

	return rc;
}

/* Log details on unsupported type at index */
static void _log_invalid_type(lua_State *L, const int index, const char *label,
			      const int type, const char *typename)
{
	char *str = NULL;

	if (!(slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT))
		return;

	if (type == LUA_TFUNCTION)
		_log_function(L, index, label);
	else
		(void) _dump_string(L, &str, index, label);

	if (str)
		log_flag_hex(SCRIPT, str, xsize(str),
			     "%s: unsupported Lua type[0x%x]: %s",
			     label, type, typename);
	else
		log_flag(SCRIPT, "%s: unsupported Lua type[0x%x]: %s",
			     label, type, typename);

	xfree(str);
}

static int _lua_to_data(lua_State *L, data_t *dst, const int index,
			const int depth, const char *parent,
			const bool parent_is_table)
{
	int rc = EINVAL;
	char *label = NULL;
	const int type = lua_type(L, index);
	const char *typename = lua_typename(L, type);

	xassert(index > 0);

	/* Add type to Label (or hex if not an unknown Lua type) */
	if (!(slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT))
		; /* do nothing without logging active */
	else if (parent_is_table)
		label = xstrdup(parent);
	else if (typename)
		xstrfmtcat(label, "%s->%s", parent, typename);
	else
		xstrfmtcat(label, "%s->0x%0x", parent, type);

	if (depth > MAX_DEPTH) {
		log_flag(SCRIPT, "%s: table depth %d/%d too deep",
			 label, depth, MAX_DEPTH);
		rc = ESLURM_LUA_INVALID_CONVERSION_TYPE;
		goto done;
	}

	if (luaL_getmetafield(L, index, "__metatable") != LUA_TNIL) {
		const char *metatable = NULL;

		if (!(metatable = lua_tostring(L, -1))) {
			/* Metatable name isn't string? */
			metatable = "INVALID";
		}

		log_flag(SCRIPT, "%s: rejecting __metatable==%s",
			 label, metatable);

		lua_pop(L, 1);
		rc = ESLURM_LUA_INVALID_CONVERSION_TYPE;
		goto done;
	}

	switch (type) {
	case LUA_TNONE:
		log_flag(SCRIPT, "%s: none", label);
		data_set_null(dst);
		rc = SLURM_SUCCESS;
		goto done;
	case LUA_TNIL:
		log_flag(SCRIPT, "%s: nil", label);
		data_set_null(dst);
		rc = SLURM_SUCCESS;
		goto done;
	case LUA_TNUMBER:
		log_flag(SCRIPT, "%s: number=" LUA_NUMBER_FMT,
			 label, lua_tonumber(L, index));
#if LUA_VERSION_NUM >= 503
		if (lua_isinteger(L, index))
			data_set_int(dst, lua_tointeger(L, index));
		else
#endif
			data_set_float(dst, lua_tonumber(L, index));
		rc = SLURM_SUCCESS;
		goto done;
	case LUA_TBOOLEAN:
		log_flag(SCRIPT, "%s: boolean=%s",
			 label, BOOL_STRINGIFY(lua_toboolean(L, index)));
		data_set_bool(dst, lua_toboolean(L, index));
		rc = SLURM_SUCCESS;
		goto done;
	case LUA_TSTRING:
		rc = _dump_data_string(L, dst, index, label);
		goto done;
	case LUA_TTABLE:
		rc = _dump_table(L, dst, index, label, depth);
		goto done;
	}

	_log_invalid_type(L, index, label, type, typename);
	rc = ESLURM_LUA_INVALID_CONVERSION_TYPE;

done:
	xfree(label);
	return rc;
}

static int _to_data(lua_State *L, data_t *dst, const int index)
{
	char *label = NULL;
	int rc = EINVAL, copy = -1;
	const int top = lua_gettop(L);

	/* Copy to avoid changing value due to conversion to string */
	lua_pushvalue(L, index);
	copy = lua_gettop(L);

	if (slurm_conf.debug_flags & DEBUG_FLAG_SCRIPT)
		xstrfmtcat(label, "%s: Lua@0x%" PRIxPTR "[+%d]", __func__,
			   (uintptr_t) L, copy);

	if ((rc = _lua_to_data(L, dst, copy, 0, label, false)))
		data_set_null(dst);

	/* drop any additions to the stack */
	lua_settop(L, top);

	xfree(label);
	return rc;
}

extern int slurm_lua_to_data(lua_State *L, data_t *dst)
{
	if (!L)
		return ESLURM_LUA_INVALID_STATE;

	if (!dst)
		return ESLURM_DATA_PTR_NULL;

	return _to_data(L, dst, lua_gettop(L));
}
