blob: 0112399cb80c9cd7f7a08496186fd78703f9db77 [file]
/*****************************************************************************\
* 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));
}