/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "zbxembed.h"
#include "embed_xml.h"
#include "embed.h"
#include "httprequest.h"
#include "zabbix.h"
#include "global.h"
#include "console.h"
#include "zbxjson.h"
#include "zbxstr.h"
#include "webdriver.h"
#include "browser_element.h"
#include "browser_alert.h"
#define ZBX_ES_MEMORY_LIMIT (1024 * 1024 * 512)
#define ZBX_ES_STACK_LIMIT 1000
/* maximum number of consequent runtime errors after which it's treated as fatal error */
#define ZBX_ES_MAX_CONSEQUENT_RT_ERROR 3
#define ZBX_ES_SCRIPT_HEADER "function(value){"
#define ZBX_ES_SCRIPT_FOOTER "\n}"
typedef struct
{
const void *heapptr; /* js object heap ptr */
void *data;
zbx_es_obj_type_t type;
}
zbx_es_obj_data_t;
/******************************************************************************
* *
* Purpose: fatal error handler *
* *
******************************************************************************/
static void es_handle_error(void *udata, const char *msg)
{
zbx_es_env_t *env = (zbx_es_env_t *)udata;
zabbix_log(LOG_LEVEL_WARNING, "Cannot process javascript, fatal error: %s", msg);
env->fatal_error = 1;
env->error = zbx_strdup(env->error, msg);
longjmp(env->loc, 1);
}
/*
* Memory allocation routines to track and limit script memory usage.
*/
static void *es_malloc(void *udata, duk_size_t size)
{
zbx_es_env_t *env = (zbx_es_env_t *)udata;
uint64_t *uptr;
if (env->total_alloc + size + 8 > env->max_total_alloc)
env->max_total_alloc = env->total_alloc + size + 8;
if (env->total_alloc + size + 8 > ZBX_ES_MEMORY_LIMIT)
{
if (NULL == env->ctx)
env->error = zbx_strdup(env->error, "cannot allocate memory");
return NULL;
}
env->total_alloc += (size + 8);
uptr = zbx_malloc(NULL, size + 8);
*uptr++ = size;
return uptr;
}
static void *es_realloc(void *udata, void *ptr, duk_size_t size)
{
zbx_es_env_t *env = (zbx_es_env_t *)udata;
uint64_t *uptr = ptr;
size_t old_size;
if (NULL != uptr)
{
--uptr;
old_size = *uptr + 8;
}
else
old_size = 0;
if (env->total_alloc + size + 8 - old_size > env->max_total_alloc)
env->max_total_alloc = env->total_alloc + size + 8 - old_size;
if (env->total_alloc + size + 8 - old_size > ZBX_ES_MEMORY_LIMIT)
{
if (NULL == env->ctx)
env->error = zbx_strdup(env->error, "cannot allocate memory");
return NULL;
}
env->total_alloc += size + 8 - old_size;
uptr = zbx_realloc(uptr, size + 8);
*uptr++ = size;
return uptr;
}
static void es_free(void *udata, void *ptr)
{
zbx_es_env_t *env = (zbx_es_env_t *)udata;
uint64_t *uptr = ptr;
if (NULL != ptr)
{
env->total_alloc -= (*(--uptr) + 8);
zbx_free(uptr);
}
}
/******************************************************************************
* *
* Purpose: decodes 3-byte utf-8 sequence *
* *
* Parameters: ptr - [IN] pointer to the 3 byte sequence *
* out - [OUT] decoded value *
* *
* Return value: SUCCEED *
* FAIL *
* *
******************************************************************************/
static int utf8_decode_3byte_sequence(const char *ptr, zbx_uint32_t *out)
{
*out = ((unsigned char)*ptr++ & 0xFu) << 12;
if (0x80 != (*ptr & 0xC0))
return FAIL;
*out |= ((unsigned char)*ptr++ & 0x3Fu) << 6;
if (0x80 != (*ptr & 0xC0))
return FAIL;
*out |= ((unsigned char)*ptr & 0x3Fu);
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: decodes duktape string into utf-8 *
* *
* Parameters: duk_str - [IN] pointer to the first char of NULL terminated *
* Duktape string *
* out_str - [OUT] on success, pointer to pointer to the first *
* char of allocated NULL terminated UTF8 string *
* *
* Return value: SUCCEED *
* FAIL *
* *
******************************************************************************/
int es_duktape_string_decode(const char *duk_str, char **out_str)
{
const char *in, *end;
char *out;
size_t len;
len = strlen(duk_str);
out = *out_str = zbx_malloc(*out_str, len + 1);
end = duk_str + len;
for (in = duk_str; in < end;)
{
if (0x7f >= (unsigned char)*in)
{
*out++ = *in++;
continue;
}
if (0xdf >= (unsigned char)*in)
{
if (2 > end - in)
goto fail;
*out++ = *in++;
*out++ = *in++;
continue;
}
if (0xef >= (unsigned char)*in)
{
zbx_uint32_t c1, c2, u;
if (3 > end - in || FAIL == utf8_decode_3byte_sequence(in, &c1))
goto fail;
if (0xd800 > c1 || 0xdbff < c1)
{
/* normal 3-byte sequence */
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
continue;
}
/* decode unicode supplementary character represented as surrogate pair */
in += 3;
if (3 > end - in || FAIL == utf8_decode_3byte_sequence(in, &c2) || 0xdc00 > c2 || 0xdfff < c2)
goto fail;
u = 0x10000 + ((((zbx_uint32_t)c1 & 0x3ff) << 10) | (c2 & 0x3ff));
*out++ = (char)(0xf0 | u >> 18);
*out++ = (char)(0x80 | (u >> 12 & 0x3f));
*out++ = (char)(0x80 | (u >> 6 & 0x3f));
*out++ = (char)(0x80 | (u & 0x3f));
in += 3;
continue;
}
/* duktape can use the four-byte UTF-8 style supplementary character sequence */
if (0xf0 >= (unsigned char)*in)
{
if (4 > end - in)
goto fail;
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
continue;
}
goto fail;
}
*out = '\0';
return SUCCEED;
fail:
zbx_free(*out_str);
return FAIL;
}
/******************************************************************************
* *
* Purpose: push result string on duktape value stack *
* *
* Comments: The string might be modified by this function. *
* *
******************************************************************************/
void es_push_result_string(duk_context *ctx, char *str, size_t size)
{
zbx_replace_invalid_utf8(str);
duk_push_lstring(ctx, str, size);
}
/******************************************************************************
* *
* Purpose: timeout checking callback *
* *
******************************************************************************/
int zbx_es_check_timeout(void *udata)
{
zbx_es_env_t *env = (zbx_es_env_t *)udata;
if (time(NULL) - env->start_time.sec > env->timeout)
return 1;
return 0;
}
/******************************************************************************
* *
* Purpose: initializes embedded scripting engine *
* *
******************************************************************************/
void zbx_es_init(zbx_es_t *es)
{
es->env = NULL;
}
/******************************************************************************
* *
* Purpose: destroys embedded scripting engine *
* *
******************************************************************************/
void zbx_es_destroy(zbx_es_t *es)
{
char *error = NULL;
if (SUCCEED == zbx_es_is_env_initialized(es) && SUCCEED != zbx_es_destroy_env(es, &error))
{
zabbix_log(LOG_LEVEL_WARNING, "Cannot destroy embedded scripting engine environment: %s", error);
}
}
/******************************************************************************
* *
* Purpose: initializes embedded scripting engine environment *
* *
* Parameters: es - [IN] embedded scripting engine *
* config_source_ip - [IN] *
* error - [OUT] error message *
* *
* Return value: SUCCEED *
* FAIL *
* *
******************************************************************************/
int zbx_es_init_env(zbx_es_t *es, const char *config_source_ip, char **error)
{
volatile int ret = FAIL;
zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
es->env = zbx_malloc(NULL, sizeof(zbx_es_env_t));
memset(es->env, 0, sizeof(zbx_es_env_t));
es->env->max_total_alloc = 0;
es->env->config_source_ip = config_source_ip;
if (0 != setjmp(es->env->loc))
{
*error = zbx_strdup(*error, es->env->error);
goto out;
}
if (NULL == (es->env->ctx = duk_create_heap(es_malloc, es_realloc, es_free, es->env, es_handle_error)))
{
*error = zbx_strdup(*error, "cannot create context");
goto out;
}
/* initialize Zabbix object */
zbx_es_init_zabbix(es, error);
/* initialize console object */
zbx_es_init_console(es, error);
/* remove Duktape object */
duk_push_global_object(es->env->ctx);
duk_del_prop_string(es->env->ctx, -1, "Duktape");
duk_pop(es->env->ctx);
es_init_global_functions(es);
/* put environment object to be accessible from duktape C calls */
duk_push_global_stash(es->env->ctx);
duk_push_string(es->env->ctx, "\xff""\xff""zbx_env");
duk_push_pointer(es->env->ctx, (void *)es->env);
duk_def_prop(es->env->ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE | DUK_DEFPROP_HAVE_ENUMERABLE |
DUK_DEFPROP_HAVE_CONFIGURABLE);
/* JSON parse/stringify is used internally, store them into stash to prevent them */
/* from being freed when assigning null to them in scripts */
duk_get_global_string(es->env->ctx, "JSON"); /* [stash,JSON] */
duk_push_string(es->env->ctx, "\xff""\xff""duk_json_parse"); /* [stash,JSON,"_parse"] */
duk_get_prop_string(es->env->ctx, -2, "parse"); /* [stash,JSON,"_parse",JSON.parse] */
es->env->json_parse = duk_get_heapptr(es->env->ctx, -1);
duk_def_prop(es->env->ctx, -4, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE | DUK_DEFPROP_HAVE_ENUMERABLE |
DUK_DEFPROP_HAVE_CONFIGURABLE); /* [stash,JSON] */
duk_push_string(es->env->ctx, "\xff""\xff""duk_json_stringify"); /* [stash,JSON,"_stringify"] */
duk_get_prop_string(es->env->ctx, -2, "stringify"); /* [stash,JSON,"_stringify",JSON.stringify] */
es->env->json_stringify = duk_get_heapptr(es->env->ctx, -1);
duk_def_prop(es->env->ctx, -4, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE | DUK_DEFPROP_HAVE_ENUMERABLE |
DUK_DEFPROP_HAVE_CONFIGURABLE); /* [stash,JSON] */
duk_pop(es->env->ctx);
/* initialize HttpRequest prototype */
if (FAIL == zbx_es_init_httprequest(es, error))
goto out;
if (FAIL == zbx_es_init_xml(es, error))
goto out;
es->env->timeout = ZBX_ES_TIMEOUT;
zbx_hashset_create(&es->env->objmap, 0, ZBX_DEFAULT_PTR_HASH_FUNC, ZBX_DEFAULT_PTR_COMPARE_FUNC);
ret = SUCCEED;
out:
if (SUCCEED != ret)
{
zbx_es_debug_disable(es);
zbx_free(es->env->error);
zbx_free(es->env);
}
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret),
ZBX_NULL2EMPTY_STR(*error));
return ret;
}
static void es_objmap_destroy(zbx_hashset_t *objmap)
{
#ifdef HAVE_LIBCURL
zbx_hashset_iter_t iter;
zbx_es_obj_data_t *obj;
zbx_hashset_iter_reset(objmap, &iter);
while (NULL != (obj = (zbx_es_obj_data_t *)zbx_hashset_iter_next(&iter)))
{
switch (obj->type)
{
case ES_OBJ_HTTPREQUEST:
es_httprequest_free(obj->data);
break;
case ES_OBJ_BROWSER:
webdriver_release((zbx_webdriver_t *)obj->data);
break;
case ES_OBJ_ELEMENT:
wd_element_free((zbx_wd_element_t *)obj->data);
break;
case ES_OBJ_ALERT:
wd_alert_free((zbx_wd_alert_t *)obj->data);
break;
}
}
#endif
zbx_hashset_destroy(objmap);
}
/******************************************************************************
* *
* Purpose: destroys initialized embedded scripting engine environment *
* *
* Parameters: es - [IN] the embedded scripting engine *
* error - [OUT] the error message *
* *
* Return value: SUCCEED *
* FAIL *
* *
******************************************************************************/
int zbx_es_destroy_env(zbx_es_t *es, char **error)
{
int ret;
zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
if (0 != setjmp(es->env->loc))
{
ret = FAIL;
*error = zbx_strdup(*error, es->env->error);
goto out;
}
duk_destroy_heap(es->env->ctx);
es_objmap_destroy(&es->env->objmap);
zbx_es_debug_disable(es);
zbx_free(es->env->browser_endpoint);
zbx_free(es->env->error);
zbx_free(es->env);
ret = SUCCEED;
out:
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret),
ZBX_NULL2EMPTY_STR(*error));
return ret;
}
/******************************************************************************
* *
* Purpose: checks if the scripting engine environment is initialized *
* *
* Parameters: es - [IN] the embedded scripting engine *
* *
* Return value: SUCCEED - the scripting engine is initialized *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_es_is_env_initialized(zbx_es_t *es)
{
return (NULL == es->env ? FAIL : SUCCEED);
}
/******************************************************************************
* *
* Purpose: checks if fatal error has occurred *
* *
* Comments: Fatal error may put the scripting engine in unknown state, it's *
* safer to destroy it instead of continuing to work with it. *
* *
******************************************************************************/
int zbx_es_fatal_error(zbx_es_t *es)
{
if (0 != es->env->fatal_error || ZBX_ES_MAX_CONSEQUENT_RT_ERROR < es->env->rt_error_num)
return SUCCEED;
if (ZBX_ES_STACK_LIMIT < duk_get_top(es->env->ctx))
{
zabbix_log(LOG_LEVEL_WARNING, "embedded scripting engine stack exceeded limits,"
" resetting scripting environment");
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: compiles script into bytecode *
* *
* Parameters: es - [IN] the embedded scripting engine *
* script - [IN] the script to compile *
* code - [OUT] the bytecode *
* size - [OUT] the size of compiled bytecode *
* error - [OUT] the error message *
* *
* Return value: SUCCEED *
* FAIL *
* *
* Comments: this function allocates the bytecode array, which must be *
* freed by the caller after being used. *
* *
******************************************************************************/
int zbx_es_compile(zbx_es_t *es, const char *script, char **code, int *size, char **error)
{
unsigned char *buffer;
duk_size_t sz;
size_t len;
char * volatile func = NULL, *ptr;
volatile int ret = FAIL;
zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
if (SUCCEED == zbx_es_fatal_error(es))
{
*error = zbx_strdup(*error, "cannot continue javascript processing after fatal scripting engine error");
goto out;
}
if (0 != setjmp(es->env->loc))
{
*error = zbx_strdup(*error, es->env->error);
goto out;
}
/* wrap the code block into a function: function(value){\n} */
len = strlen(script);
ptr = func = zbx_malloc(NULL, len + ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_HEADER) +
ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_FOOTER) + 1);
memcpy(ptr, ZBX_ES_SCRIPT_HEADER, ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_HEADER));
ptr += ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_HEADER);
memcpy(ptr, script, len);
ptr += len;
memcpy(ptr, ZBX_ES_SCRIPT_FOOTER, ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_FOOTER));
ptr += ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_FOOTER);
*ptr = '\0';
duk_push_lstring(es->env->ctx, func, ptr - func);
duk_push_lstring(es->env->ctx, "function", ZBX_CONST_STRLEN("function"));
if (0 != duk_pcompile(es->env->ctx, DUK_COMPILE_FUNCTION))
{
*error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1));
duk_pop(es->env->ctx);
goto out;
}
duk_dump_function(es->env->ctx);
if (NULL != (buffer = (unsigned char *)duk_get_buffer(es->env->ctx, -1, &sz)))
{
*size = sz;
*code = zbx_malloc(NULL, sz);
memcpy(*code, buffer, sz);
ret = SUCCEED;
}
else
*error = zbx_strdup(*error, "empty function compilation result");
duk_pop(es->env->ctx);
out:
zbx_free(func);
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret), ZBX_NULL2EMPTY_STR(*error));
return ret;
}
/******************************************************************************
* *
* Purpose: executes script *
* *
* Parameters: es - [IN] the embedded scripting engine *
* script - [IN] the script to execute *
* code - [IN] the precompiled bytecode *
* size - [IN] the size of precompiled bytecode *
* param - [IN] the parameter to pass to the script *
* script_ret - [OUT] the result value *
* error - [OUT] the error message *
* *
* Return value: SUCCEED *
* FAIL *
* *
* Comments: Some scripting engines cannot compile into bytecode, but can *
* cache some compilation data that can be reused for the next *
* compilation. Because of that execute function accepts script and *
* bytecode parameters. *
* *
******************************************************************************/
int zbx_es_execute(zbx_es_t *es, const char *script, const char *code, int size, const char *param,
char **script_ret, char **error)
{
void *buffer;
volatile int ret = FAIL;
zabbix_log(LOG_LEVEL_DEBUG, "In %s() param:%s", __func__, param);
zbx_timespec(&es->env->start_time);
es->env->http_req_objects = 0;
es->env->logged_msgs = 0;
if (NULL != es->env->json)
{
zbx_json_clean(es->env->json);
zbx_json_addarray(es->env->json, "logs");
}
if (SUCCEED == zbx_es_fatal_error(es))
{
*error = zbx_strdup(*error, "cannot continue javascript processing after fatal scripting engine error");
goto out;
}
ZBX_UNUSED(script);
if (0 != setjmp(es->env->loc))
{
*error = zbx_strdup(*error, es->env->error);
goto out;
}
buffer = duk_push_fixed_buffer(es->env->ctx, size);
memcpy(buffer, code, size);
duk_load_function(es->env->ctx);
duk_push_string(es->env->ctx, param);
if (DUK_EXEC_SUCCESS != duk_pcall(es->env->ctx, 1))
{
duk_small_int_t rc = 0;
es->env->rt_error_num++;
if (0 != duk_is_object(es->env->ctx, -1))
{
/* try to get 'stack' property of the object on stack, assuming it's an Error object */
if (0 != (rc = duk_get_prop_string(es->env->ctx, -1, "stack")))
*error = zbx_strdup(*error, duk_get_string(es->env->ctx, -1));
duk_pop(es->env->ctx);
}
/* If the object does not have stack property, return the object itself as error. */
/* This allows to simply throw "error message" from scripts */
if (0 == rc)
*error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1));
duk_pop(es->env->ctx);
goto out;
}
if (NULL != script_ret || SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
{
if (0 == duk_check_type(es->env->ctx, -1, DUK_TYPE_UNDEFINED))
{
if (0 != duk_check_type(es->env->ctx, -1, DUK_TYPE_NULL))
{
ret = SUCCEED;
if (NULL != script_ret)
*script_ret = NULL;
zabbix_log(LOG_LEVEL_DEBUG, "%s() output: null", __func__);
}
else
{
char *output = NULL;
if (SUCCEED != (ret = es_duktape_string_decode(
duk_safe_to_string(es->env->ctx, -1), &output)))
{
*error = zbx_strdup(*error, "could not convert return value to utf8");
}
else
{
if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_TRACE))
{
zabbix_log(LOG_LEVEL_DEBUG, "%s() output:'%s'", __func__, output);
}
else
{
zabbix_log(LOG_LEVEL_DEBUG, "%s() output:'%.*s'", __func__, 512,
output);
}
}
if (SUCCEED == ret && NULL != script_ret)
*script_ret = output;
else
zbx_free(output);
}
}
else
{
if (NULL == script_ret)
{
zabbix_log(LOG_LEVEL_DEBUG, "%s(): undefined return value", __func__);
ret = SUCCEED;
}
else
*error = zbx_strdup(*error, "undefined return value");
}
}
else
ret = SUCCEED;
duk_pop(es->env->ctx);
es->env->rt_error_num = 0;
out:
if (NULL != es->env->json)
{
zbx_json_close(es->env->json);
zbx_json_adduint64(es->env->json, "ms", zbx_get_duration_ms(&es->env->start_time));
}
/* Duktape documentation recommends calling duk_gc() twice, see https://duktape.org/api#duk_gc */
duk_gc(es->env->ctx, 0);
duk_gc(es->env->ctx, 0);
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s allocated memory: " ZBX_FS_SIZE_T
" max allocated or requested memory: " ZBX_FS_SIZE_T " max allowed memory: %d",
__func__, zbx_result_string(ret),
ZBX_NULL2EMPTY_STR(*error), (zbx_fs_size_t)es->env->total_alloc,
(zbx_fs_size_t)es->env->max_total_alloc, ZBX_ES_MEMORY_LIMIT);
es->env->max_total_alloc = 0;
return ret;
}
/******************************************************************************
* *
* Purpose: sets script execution timeout *
* *
* Parameters: es - [IN] the embedded scripting engine *
* timeout - [IN] the script execution timeout in seconds *
* *
******************************************************************************/
void zbx_es_set_timeout(zbx_es_t *es, int timeout)
{
es->env->timeout = timeout;
}
void zbx_es_debug_enable(zbx_es_t *es)
{
if (NULL == es->env->json)
{
es->env->json = zbx_malloc(NULL, sizeof(struct zbx_json));
zbx_json_init(es->env->json, ZBX_JSON_STAT_BUF_LEN);
}
}
const char *zbx_es_debug_info(const zbx_es_t *es)
{
if (NULL == es->env->json)
return NULL;
return es->env->json->buffer;
}
void zbx_es_debug_disable(zbx_es_t *es)
{
if (NULL == es->env->json)
return;
zbx_json_free(es->env->json);
zbx_free(es->env->json);
}
/******************************************************************************
* *
* Purpose: executes command (script in form of a text) *
* *
* Parameters: command - [IN] command in form of a text *
* param - [IN] script parameters *
* timeout - [IN] timeout for the execution (seconds) *
* config_source_ip - [IN] *
* result - [OUT] result of an execution *
* error - [OUT] error message *
* max_error_len - [IN] maximum length of an error *
* debug - [OUT] debug data (optional) *
* *
* Return value: SUCCEED *
* FAIL *
* *
******************************************************************************/
int zbx_es_execute_command(const char *command, const char *param, int timeout, const char *config_source_ip,
char **result, char *error, size_t max_error_len, char **debug)
{
int size, ret = SUCCEED;
char *code = NULL, *errmsg = NULL;
zbx_es_t es;
zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
zbx_es_init(&es);
if (FAIL == zbx_es_init_env(&es, config_source_ip, &errmsg))
{
zbx_snprintf(error, max_error_len, "cannot initialize scripting environment: %s", errmsg);
zbx_free(errmsg);
ret = FAIL;
goto failure;
}
if (NULL != debug)
zbx_es_debug_enable(&es);
if (FAIL == zbx_es_compile(&es, command, &code, &size, &errmsg))
{
zbx_snprintf(error, max_error_len, "cannot compile script: %s", errmsg);
zbx_free(errmsg);
ret = FAIL;
goto out;
}
if (0 != timeout)
zbx_es_set_timeout(&es, timeout);
if (FAIL == zbx_es_execute(&es, NULL, code, size, param, result, &errmsg))
{
zbx_snprintf(error, max_error_len, "cannot execute script: %s", errmsg);
zbx_free(errmsg);
ret = FAIL;
goto out;
}
out:
if (NULL != debug)
*debug = zbx_strdup(NULL, zbx_es_debug_info(&es));
if (FAIL == zbx_es_destroy_env(&es, &errmsg))
{
zabbix_log(LOG_LEVEL_WARNING, "cannot destroy embedded scripting engine environment: %s", errmsg);
zbx_free(errmsg);
}
zbx_free(code);
zbx_free(errmsg);
failure:
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
return ret;
}
zbx_es_env_t *zbx_es_get_env(duk_context *ctx)
{
zbx_es_env_t *env;
duk_push_global_stash(ctx);
if (1 != duk_get_prop_string(ctx, -1, "\xff""\xff""zbx_env"))
return NULL;
env = (zbx_es_env_t *)duk_to_pointer(ctx, -1);
duk_pop_2(ctx);
return env;
}
/******************************************************************************
* *
* Purpose: call base class prototype with arguments *
* *
* Parameters: ctx - [IN] duktape context *
* base - [IN] base class name *
* args - [IN] number of prototype arguments on stack *
* *
* Return value: 0 *
* *
******************************************************************************/
duk_ret_t es_super(duk_context *ctx, const char *base, int args)
{
zbx_es_env_t *env;
if (NULL == (env = zbx_es_get_env(ctx)))
return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot access internal environment");
duk_get_global_string(ctx, base);
duk_push_this(ctx);
for (int i = 0; i < args; i++)
duk_dup(ctx, i);
env->constructor_chain = 1;
duk_call_method(ctx, args);
return 0;
}
/******************************************************************************
* *
* Purpose: check if the method is called from constructor chain *
* *
* Parameters: ctx - [IN] duktape context *
* *
* Return value: !0 - method is called from constructor chain *
* 0 - otherwise *
* *
******************************************************************************/
int es_is_chained_constructor_call(duk_context *ctx)
{
zbx_es_env_t *env;
if (NULL == (env = zbx_es_get_env(ctx)))
return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot access internal environment");
int constructor_chain = env->constructor_chain;
env->constructor_chain = 0;
return constructor_chain || duk_is_constructor_call(env->ctx);
}
/******************************************************************************
* *
* Purpose: attach data pointer to current object *
* *
* Comments: This function must be used only from object constructor *
* *
******************************************************************************/
void es_obj_attach_data(zbx_es_env_t *env, void *objptr, void *data, zbx_es_obj_type_t type)
{
zbx_es_obj_data_t obj_local;
duk_push_this(env->ctx);
obj_local.heapptr = objptr;
duk_pop(env->ctx);
obj_local.data = data;
obj_local.type = type;
zbx_hashset_insert(&env->objmap, &obj_local, sizeof(obj_local));
}
/******************************************************************************
* *
* Purpose: get data pointer attached to current object *
* *
* Parameters: env - [IN] *
* objptr - [IN] js object heap pointer *
* type - [IN] object type *
* *
* Comments: This function must be used only from object methods. *
* *
******************************************************************************/
void *es_obj_get_data(zbx_es_env_t *env, const void *objptr, zbx_es_obj_type_t type)
{
zbx_es_obj_data_t obj_local, *obj;
obj_local.heapptr = objptr;
if (NULL != (obj = zbx_hashset_search(&env->objmap, &obj_local)) && obj->type == type)
return obj->data;
return NULL;
}
/******************************************************************************
* *
* Purpose: detach data pointer from current object *
* *
* Parameters: env - [IN] *
* objptr - [IN] object js heap pointer *
* *
* Return value: detached data pointer *
* *
* Comments: The finalizing object must be on the top of the stack (-1). *
* If the pointer contains allocated data it must be freed by the *
* caller. *
* This function must be used only from object destructor. *
* *
******************************************************************************/
void *es_obj_detach_data(zbx_es_env_t *env, void *objptr, zbx_es_obj_type_t type)
{
if (NULL != objptr)
{
zbx_es_obj_data_t obj_local, *obj;
void *data;
obj_local.heapptr = objptr;
if (NULL == (obj = zbx_hashset_search(&env->objmap, &obj_local)) || obj->type != type)
return NULL;
data = obj->data;
zbx_hashset_remove_direct(&env->objmap, obj);
return data;
}
else
return NULL;
}