/* ** 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 <https://www.gnu.org/licenses/>. **/ #include "eval.h" #include "zbxstr.h" #include "zbxalgo.h" #include "zbxvariant.h" #include "zbxserialize.h" #include "zbxnum.h" #define ZBX_EVAL_STATIC_BUFFER_SIZE 4096 /****************************************************************************** * * * Purpose: Reserves number of bytes in the specified buffer, reallocating if * * necessary. * * * * Parameters: buffer - [IN/OUT] * * buffer_size - [IN/OUT] * * reserve - [IN] number of bytes to reserve * * ptr - [IN/OUT] pointer to offset in buffer * * * * Comments: Initially static buffer is used, allocating dynamic buffer when * * static buffer is too small. * * * ******************************************************************************/ static void reserve_buffer(unsigned char **buffer, size_t *buffer_size, size_t reserve, unsigned char **ptr) { size_t offset = *ptr - *buffer, new_size; if (offset + reserve <= *buffer_size) return; new_size = *buffer_size; while (offset + reserve >= new_size) new_size = new_size + (new_size / 2); if (ZBX_EVAL_STATIC_BUFFER_SIZE == *buffer_size) { unsigned char *old = *buffer; *buffer = zbx_malloc(NULL, new_size); memcpy(*buffer, old, offset); } else *buffer = zbx_realloc(*buffer, new_size); *buffer_size = new_size; *ptr = *buffer + offset; } static void serialize_variant(unsigned char **buffer, size_t *size, const zbx_variant_t *value, unsigned char **ptr) { size_t len; reserve_buffer(buffer, size, 1, ptr); **ptr = value->type; (*ptr)++; switch (value->type) { case ZBX_VARIANT_UI64: reserve_buffer(buffer, size, sizeof(value->data.ui64), ptr); *ptr += zbx_serialize_uint64(*ptr, value->data.ui64); break; case ZBX_VARIANT_DBL: reserve_buffer(buffer, size, sizeof(value->data.dbl), ptr); *ptr += zbx_serialize_double(*ptr, value->data.dbl); break; case ZBX_VARIANT_STR: len = strlen(value->data.str) + 1; reserve_buffer(buffer, size, len, ptr); memcpy(*ptr, value->data.str, len); *ptr += len; break; case ZBX_VARIANT_NONE: break; default: zabbix_log(LOG_LEVEL_DEBUG, "TYPE: %d", value->type); THIS_SHOULD_NEVER_HAPPEN; (*ptr)[-1] = ZBX_VARIANT_NONE; break; } } static zbx_uint32_t deserialize_variant(const unsigned char *ptr, zbx_variant_t *value) { const unsigned char *start = ptr; unsigned char type; zbx_uint64_t ui64; double dbl; char *str; size_t len; ptr += zbx_deserialize_char(ptr, &type); switch (type) { case ZBX_VARIANT_UI64: ptr += zbx_deserialize_uint64(ptr, &ui64); zbx_variant_set_ui64(value, ui64); break; case ZBX_VARIANT_DBL: ptr += zbx_deserialize_double(ptr, &dbl); zbx_variant_set_dbl(value, dbl); break; case ZBX_VARIANT_STR: len = strlen((const char *)ptr) + 1; str = zbx_malloc(NULL, len); memcpy(str, ptr, len); zbx_variant_set_str(value, str); ptr += len; break; case ZBX_VARIANT_NONE: zbx_variant_set_none(value); break; default: THIS_SHOULD_NEVER_HAPPEN; zbx_variant_set_none(value); break; } return ptr - start; } /****************************************************************************** * * * Purpose: serializes evaluation context into buffer * * * * Parameters: ctx - [IN] evaluation context * * malloc_func - [IN] buffer memory allocation function, * * optional (by default the buffer is * * allocated in heap) * * data - [OUT] buffer with serialized evaluation context * * * * Comments: Location of the replaced tokens (with token.value set) are not * * serialized, making it impossible to reconstruct the expression * * text with replaced tokens. Context serialization/deserialization * * must be used for context caching. * * * * Return value: size of serialized data * * * ******************************************************************************/ size_t zbx_eval_serialize(const zbx_eval_context_t *ctx, zbx_mem_malloc_func_t malloc_func, unsigned char **data) { int i; unsigned char buffer_static[ZBX_EVAL_STATIC_BUFFER_SIZE], *buffer = buffer_static, *ptr = buffer, len_buff[6]; size_t buffer_size = ZBX_EVAL_STATIC_BUFFER_SIZE; zbx_uint32_t len, len_offset; if (NULL == malloc_func) malloc_func = ZBX_DEFAULT_MEM_MALLOC_FUNC; ptr += zbx_serialize_uint31_compact(ptr, ctx->stack.values_num); for (i = 0; i < ctx->stack.values_num; i++) { const zbx_eval_token_t *token = &ctx->stack.values[i]; /* reserve space for maximum possible worst case scenario with empty variant: */ /* 4 bytes token type, 6 bytes per compact uint31 and 1 byte empty variant (4+3*6+1) */ reserve_buffer(&buffer, &buffer_size, 23, &ptr); ptr += zbx_serialize_value(ptr, token->type); ptr += zbx_serialize_uint31_compact(ptr, token->opt); ptr += zbx_serialize_uint31_compact(ptr, token->loc.l); ptr += zbx_serialize_uint31_compact(ptr, token->loc.r); serialize_variant(&buffer, &buffer_size, &token->value, &ptr); } len = ptr - buffer; len_offset = zbx_serialize_uint31_compact(len_buff, len); *data = malloc_func(NULL, len + len_offset); memcpy(*data, len_buff, len_offset); memcpy(*data + len_offset, buffer, len); if (buffer != buffer_static) zbx_free(buffer); return len + len_offset; } /****************************************************************************** * * * Purpose: deserializes evaluation context from buffer * * * * Parameters: ctx - [OUT] evaluation context * * expression - [IN] expression, evaluation context was created * * from * * rules - [IN] composition and evaluation rules * * data - [IN] buffer with serialized context * * * ******************************************************************************/ void zbx_eval_deserialize(zbx_eval_context_t *ctx, const char *expression, zbx_uint64_t rules, const unsigned char *data) { zbx_uint32_t i, tokens_num, len, pos; memset(ctx, 0, sizeof(zbx_eval_context_t)); ctx->expression = expression; ctx->rules = rules; data += zbx_deserialize_uint31_compact(data, &len); data += zbx_deserialize_uint31_compact(data, &tokens_num); zbx_vector_eval_token_create(&ctx->stack); zbx_vector_eval_token_reserve(&ctx->stack, tokens_num); ctx->stack.values_num = tokens_num; for (i = 0; i < tokens_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; data += zbx_deserialize_value(data, &token->type); data += zbx_deserialize_uint31_compact(data, &token->opt); data += zbx_deserialize_uint31_compact(data, &pos); token->loc.l = pos; data += zbx_deserialize_uint31_compact(data, &pos); token->loc.r = pos; data += deserialize_variant(data, &token->value); } } int zbx_eval_compare_tokens_by_loc(const void *d1, const void *d2) { const zbx_eval_token_t *t1 = *(const zbx_eval_token_t * const *)d1; const zbx_eval_token_t *t2 = *(const zbx_eval_token_t * const *)d2; ZBX_RETURN_IF_NOT_EQUAL(t1->loc.l, t2->loc.l); return 0; } static const zbx_eval_token_t *eval_get_next_function_token(const zbx_eval_context_t *ctx, int token_index) { if (0 != (ctx->stack.values[token_index].type & ZBX_EVAL_CLASS_FUNCTION)) return NULL; for(int i = token_index + 1; i < ctx->stack.values_num; i++) { const zbx_eval_token_t *token = &ctx->stack.values[i]; if (0 != (token->type & ZBX_EVAL_CLASS_FUNCTION)) { if (token->opt < (zbx_uint32_t)(i - token_index)) return NULL; return &ctx->stack.values[i]; } } return NULL; } /****************************************************************************** * * * Purpose: prints token into string quoting/escaping if necessary * * * * Parameters: ctx - [IN] evaluation context * * str - [IN/OUT] output buffer * * str_alloc - [IN/OUT] output buffer size * * str_offset - [IN/OUT] output buffer offset * * token - [IN] token to print * * * ******************************************************************************/ static void eval_token_print_alloc(const zbx_eval_context_t *ctx, char **str, size_t *str_alloc, size_t *str_offset, const zbx_eval_token_t *token) { int quoted = 0, check_value = 0; const char *value_str; if (ZBX_VARIANT_NONE == token->value.type) return; if (ZBX_VARIANT_ERR == token->value.type) { if (0 == (ctx->rules & ZBX_EVAL_COMPOSE_MASK_ERROR)) zbx_snprintf_alloc(str, str_alloc, str_offset, "ERROR(%s)", token->value.data.err); else zbx_strcpy_alloc(str, str_alloc, str_offset, "*ERROR*"); return; } switch (token->type) { case ZBX_EVAL_TOKEN_FUNCTIONID: if (0 != (ctx->rules & ZBX_EVAL_COMPOSE_FUNCTIONID)) { zbx_variant_t functionid; zbx_variant_copy(&functionid, &token->value); if (SUCCEED == zbx_variant_convert(&functionid, ZBX_VARIANT_UI64)) { zbx_snprintf_alloc(str, str_alloc, str_offset, "{" ZBX_FS_UI64 "}", functionid.data.ui64); return; } zbx_variant_clear(&functionid); } break; case ZBX_EVAL_TOKEN_VAR_STR: quoted = 1; break; case ZBX_EVAL_TOKEN_VAR_MACRO: if (0 == (ctx->rules & ZBX_EVAL_COMPOSE_LLD)) check_value = 1; break; case ZBX_EVAL_TOKEN_VAR_USERMACRO: if (0 != (ctx->rules & ZBX_EVAL_COMPOSE_QUOTE)) quoted = 1; else if (0 == (ctx->rules & ZBX_EVAL_COMPOSE_LLD)) check_value = 1; break; case ZBX_EVAL_TOKEN_VAR_LLDMACRO: if (0 != (ctx->rules & ZBX_EVAL_COMPOSE_QUOTE)) quoted = 1; else if (0 != (ctx->rules & ZBX_EVAL_COMPOSE_LLD)) check_value = 1; break; } if (0 != check_value) { if (ZBX_VARIANT_STR == token->value.type && SUCCEED != eval_suffixed_number_parse(token->value.data.str, NULL)) { quoted = 1; } } value_str = zbx_variant_value_desc(&token->value); if (0 == quoted) { zbx_strcpy_alloc(str, str_alloc, str_offset, value_str); } else { const zbx_eval_token_t *func_token = NULL; if (0 != (ctx->rules & ZBX_EVAL_PARSE_STR_V64_COMPAT)) func_token = eval_get_next_function_token(ctx, (int)(token - ctx->stack.values)); zbx_strquote_alloc_opt(str, str_alloc, str_offset, value_str, NULL != func_token && ZBX_EVAL_TOKEN_HIST_FUNCTION == func_token->type ? ZBX_STRQUOTE_SKIP_BACKSLASH : ZBX_STRQUOTE_DEFAULT); } } /****************************************************************************** * * * Purpose: composes expression by replacing processed macro tokens * * (with values) starting from definite position of * * original expression * * * * Parameters: ctx - [IN] evaluation context * * expression - [OUT] composed expression * * shift_pos - [IN] position which to start from * * * ******************************************************************************/ void zbx_eval_compose_expression_from_pos(const zbx_eval_context_t *ctx, char **expression, size_t pos) { zbx_vector_ptr_t tokens; const zbx_eval_token_t *token; int i; size_t expression_alloc = 0, expression_offset = 0; zbx_vector_ptr_create(&tokens); for (i = 0; i < ctx->stack.values_num; i++) { if (ctx->stack.values[i].loc.l < pos) continue; if (ZBX_EVAL_TOKEN_VAR_MACRO == ctx->stack.values[i].type || ZBX_EVAL_TOKEN_VAR_USERMACRO == ctx->stack.values[i].type || ZBX_EVAL_TOKEN_VAR_STR == ctx->stack.values[i].type) { if (ZBX_VARIANT_NONE != ctx->stack.values[i].value.type) zbx_vector_ptr_append(&tokens, &ctx->stack.values[i]); } } zbx_vector_ptr_sort(&tokens, zbx_eval_compare_tokens_by_loc); for (i = 0; i < tokens.values_num; i++) { token = (const zbx_eval_token_t *)tokens.values[i]; zbx_strncpy_alloc(expression, &expression_alloc, &expression_offset, ctx->expression + pos, token->loc.l - pos); pos = token->loc.r + 1; eval_token_print_alloc(ctx, expression, &expression_alloc, &expression_offset, token); } if ('\0' != ctx->expression[pos]) zbx_strcpy_alloc(expression, &expression_alloc, &expression_offset, ctx->expression + pos); zbx_vector_ptr_destroy(&tokens); } /****************************************************************************** * * * Purpose: composes expression by replacing processed tokens (with values) * * in original expression * * * * Parameters: ctx - [IN] evaluation context * * expression - [OUT] composed expression * * * ******************************************************************************/ void zbx_eval_compose_expression(const zbx_eval_context_t *ctx, char **expression) { zbx_vector_ptr_t tokens; const zbx_eval_token_t *token; int i; size_t pos = 0, expression_alloc = 0, expression_offset = 0; /* Handle exceptions that are set when expression evaluation failed. */ /* Exception stack consists of two tokens - error message and exception. */ if (2 == ctx->stack.values_num && ZBX_EVAL_TOKEN_EXCEPTION == ctx->stack.values[1].type) { zbx_strcpy_alloc(expression, &expression_alloc, &expression_offset, "throw("); eval_token_print_alloc(ctx, expression, &expression_alloc, &expression_offset, &ctx->stack.values[0]); zbx_chrcpy_alloc(expression, &expression_alloc, &expression_offset, ')'); return; } zbx_vector_ptr_create(&tokens); for (i = 0; i < ctx->stack.values_num; i++) { if (ZBX_VARIANT_NONE != ctx->stack.values[i].value.type) zbx_vector_ptr_append(&tokens, &ctx->stack.values[i]); } zbx_vector_ptr_sort(&tokens, zbx_eval_compare_tokens_by_loc); for (i = 0; i < tokens.values_num; i++) { token = (const zbx_eval_token_t *)tokens.values[i]; if (0 != token->loc.l) { zbx_strncpy_alloc(expression, &expression_alloc, &expression_offset, ctx->expression + pos, token->loc.l - pos); } pos = token->loc.r + 1; eval_token_print_alloc(ctx, expression, &expression_alloc, &expression_offset, token); } if ('\0' != ctx->expression[pos]) zbx_strcpy_alloc(expression, &expression_alloc, &expression_offset, ctx->expression + pos); zbx_vector_ptr_destroy(&tokens); } /****************************************************************************** * * * Purpose: checks if string has possible user macro * * * * Parameters: str - [IN] string to check * * len - [IN] string length * * * * Return value: SUCCEED - string might contain user macro * * FAIL - otherwise * * * ******************************************************************************/ static int eval_has_usermacro(const char *str, size_t len) { const char *ptr; if (4 > len) return FAIL; /* stop earlier to account for at least one character macro name and terminating '}' */ for (ptr = str; ptr < str + len - 3; ) { if ('{' == *ptr++) { if ('$' == *ptr) return SUCCEED; if ('{' == *ptr++) if ('$' == *ptr) return SUCCEED; ptr++; } } return FAIL; } /****************************************************************************** * * * Purpose: expands user macros in item query * * * * Parameters: itemquery - [IN] * * len - [IN] item query length * * hostids - [IN] linked hostids * * hostids_num - [IN] number of linked hostids * * um_expand_cb - [IN] resolver callback * * um_data - [IN] resolver callback data * * out - [OUT] item query with expanded macros * * error - [OUT] Error message, optional. If specified, * * the function will return failure at the * * first failed macro expansion. * * * ******************************************************************************/ static int eval_query_expand_user_macros(const char *itemquery, size_t len, const zbx_uint64_t *hostids, int hostids_num, zbx_macro_expand_func_t um_expand_cb, void *um_data, char **out, char **error) { zbx_eval_context_t ctx; zbx_item_query_t query; int i, ret = FAIL; char *errmsg = NULL, *filter = NULL; if (len != zbx_eval_parse_query(itemquery, len, &query)) { if (NULL != error) { *error = zbx_strdup(NULL, "cannot parse item query"); return FAIL; } return SUCCEED; } if (NULL == query.filter) { ret = SUCCEED; goto out; } if (SUCCEED != zbx_eval_parse_expression(&ctx, query.filter, ZBX_EVAL_PARSE_QUERY_EXPRESSION | ZBX_EVAL_COMPOSE_QUOTE, &errmsg)) { if (NULL != error) *error = zbx_dsprintf(NULL, "cannot parse item query filter: %s", errmsg); else ret = SUCCEED; zbx_free(errmsg); goto out; } for (i = 0; i < ctx.stack.values_num; i++) { zbx_eval_token_t *token = &ctx.stack.values[i]; char *value = NULL; switch (token->type) { case ZBX_EVAL_TOKEN_VAR_USERMACRO: value = zbx_substr_unquote(ctx.expression, token->loc.l, token->loc.r); ret = um_expand_cb(um_data, &value, hostids, hostids_num, error); break; case ZBX_EVAL_TOKEN_VAR_STR: if (SUCCEED != eval_has_usermacro(ctx.expression + token->loc.l, token->loc.r - token->loc.l + 1)) { continue; } value = zbx_substr_unquote(ctx.expression, token->loc.l, token->loc.r); ret = um_expand_cb(um_data, &value, hostids, hostids_num, error); break; default: continue; } if (FAIL == ret) { zbx_free(value); zbx_eval_clear(&ctx); goto out; } zbx_variant_clear(&token->value); zbx_variant_set_str(&token->value, value); } zbx_eval_compose_expression(&ctx, &filter); zbx_eval_clear(&ctx); *out = zbx_dsprintf(NULL, "/%s/%s?[%s]", ZBX_NULL2EMPTY_STR(query.host), query.key, filter); ret = SUCCEED; out: zbx_free(filter); zbx_eval_clear_query(&query); return ret; } /****************************************************************************** * * * Purpose: expands user macros in parsed expression * * * * Parameters: ctx - [IN] evaluation context * * hostids - [IN] linked hostids * * hostids_num - [IN] number of linked hostids * * um_expand_cb - [IN] resolver callback * * data - [IN] resolver callback data * * error - [OUT] Error message, optional. If specified * * the function will return failure at the * * first failed macro expansion. * * * * Return value: SUCCEED - macros were expanded successfully * * FAIL - error parameter was given and at least one of * * macros was not expanded * * * ******************************************************************************/ int zbx_eval_expand_user_macros(const zbx_eval_context_t *ctx, const zbx_uint64_t *hostids, int hostids_num, zbx_macro_expand_func_t um_expand_cb, void *data, char **error) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; char *value = NULL; int ret; switch (token->type) { case ZBX_EVAL_TOKEN_VAR_USERMACRO: value = zbx_substr_unquote(ctx->expression, token->loc.l, token->loc.r); ret = um_expand_cb(data, &value, hostids, hostids_num, error); break; case ZBX_EVAL_TOKEN_VAR_STR: if (SUCCEED != eval_has_usermacro(ctx->expression + token->loc.l, token->loc.r - token->loc.l + 1)) { continue; } if (ZBX_VARIANT_NONE != token->value.type) { if (FAIL == zbx_variant_convert(&token->value, ZBX_VARIANT_STR)) { zabbix_log(LOG_LEVEL_CRIT, "cannot convert value from %s to %s", zbx_variant_type_desc(&token->value), zbx_get_variant_type_desc(ZBX_VARIANT_STR)); THIS_SHOULD_NEVER_HAPPEN; if (NULL != error) { *error = zbx_dsprintf(*error, "cannot convert value from %s to %s", zbx_variant_type_desc(&token->value), zbx_get_variant_type_desc(ZBX_VARIANT_STR)); } ret = FAIL; break; } value = token->value.data.str; zbx_variant_set_none(&token->value); } else value = zbx_substr_unquote(ctx->expression, token->loc.l, token->loc.r); ret = um_expand_cb(data, &value, hostids, hostids_num, error); break; case ZBX_EVAL_TOKEN_VAR_NUM: case ZBX_EVAL_TOKEN_ARG_PERIOD: if (SUCCEED != eval_has_usermacro(ctx->expression + token->loc.l, token->loc.r - token->loc.l + 1)) { continue; } value = zbx_substr_unquote(ctx->expression, token->loc.l, token->loc.r); ret = um_expand_cb(data, &value, hostids, hostids_num, error); break; case ZBX_EVAL_TOKEN_ARG_QUERY: if (SUCCEED != eval_has_usermacro(ctx->expression + token->loc.l, token->loc.r - token->loc.l + 1)) { continue; } ret = eval_query_expand_user_macros(ctx->expression + token->loc.l, token->loc.r - token->loc.l + 1, hostids, hostids_num, um_expand_cb, data, &value, error); break; default: continue; } if (FAIL == ret) { zbx_free(value); return FAIL; } if (NULL != value) { zbx_variant_clear(&token->value); zbx_variant_set_str(&token->value, value); } } return SUCCEED; } /******************************************************************************* * * * Purpose: sets eval context to exception that will be returned when executed * * * * Parameters: ctx - [IN] evaluation context * * message - [IN] exception message (memory is owned by context) * * * *******************************************************************************/ void zbx_eval_set_exception(zbx_eval_context_t *ctx, char *message) { zbx_eval_token_t *token; memset(ctx, 0, sizeof(zbx_eval_context_t)); zbx_vector_eval_token_create(&ctx->stack); zbx_vector_eval_token_reserve(&ctx->stack, 2); ctx->stack.values_num = 2; token = ctx->stack.values; memset(token, 0, 2 * sizeof(zbx_eval_token_t)); token->type = ZBX_EVAL_TOKEN_VAR_STR; zbx_variant_set_str(&token->value, message); (++token)->type = ZBX_EVAL_TOKEN_EXCEPTION; } /****************************************************************************** * * * Purpose: extracts functionid from token * * * * Parameters: expression - [IN] original expression * * token - [IN] * * functionid - [OUT] extracted functionid * * * * Return value: SUCCEED - functionid was extracted successfully * * FAIL - otherwise (incorrect token or invalid data) * * * * Comment: The extracted functionid will be cached as token value, so the * * next time it can be used without extracting the value from * * expression. * * * ******************************************************************************/ static int expression_extract_functionid(const char *expression, zbx_eval_token_t *token, zbx_uint64_t *functionid) { if (ZBX_EVAL_TOKEN_FUNCTIONID != token->type) return FAIL; switch (token->value.type) { case ZBX_VARIANT_UI64: *functionid = token->value.data.ui64; return SUCCEED; case ZBX_VARIANT_NONE: if (SUCCEED != zbx_is_uint64_n(expression + token->loc.l + 1, token->loc.r - token->loc.l - 1, functionid)) { THIS_SHOULD_NEVER_HAPPEN; break; } zbx_variant_set_ui64(&token->value, *functionid); return SUCCEED; } return FAIL; } /****************************************************************************** * * * Purpose: deserializes expression and extracts specified tokens into values * * * * Parameters: data - [IN] serialized expression * * expression - [IN] original expression * * mask - [IN] tokens to extract * * * * Return value: Expression evaluation context. * * * ******************************************************************************/ zbx_eval_context_t *zbx_eval_deserialize_dyn(const unsigned char *data, const char *expression, zbx_uint64_t mask) { zbx_eval_context_t *ctx; int i; zbx_uint64_t functionid; char *value; ctx = (zbx_eval_context_t *)zbx_malloc(NULL, sizeof(zbx_eval_context_t)); zbx_eval_deserialize(ctx, expression, ZBX_EVAL_TRIGGER_EXPRESSION, data); for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; switch (token->type) { case ZBX_EVAL_TOKEN_FUNCTIONID: if (0 == (mask & ZBX_EVAL_EXTRACT_FUNCTIONID)) continue; expression_extract_functionid(expression, token, &functionid); break; case ZBX_EVAL_TOKEN_VAR_STR: if (0 != (mask & ZBX_EVAL_EXTRACT_VAR_STR) && ZBX_VARIANT_NONE == token->value.type) { /* extract string variable value for macro resolving */ value = zbx_substr_unquote(expression, token->loc.l, token->loc.r); zbx_variant_set_str(&token->value, value); } break; case ZBX_EVAL_TOKEN_VAR_MACRO: if (0 != (mask & ZBX_EVAL_EXTRACT_VAR_MACRO) && ZBX_VARIANT_NONE == token->value.type) { /* extract macro for resolving */ value = zbx_substr_unquote(expression, token->loc.l, token->loc.r); zbx_variant_set_str(&token->value, value); } break; case ZBX_EVAL_TOKEN_VAR_USERMACRO: if (0 != (mask & ZBX_EVAL_EXTRACT_VAR_USERMACRO) && ZBX_VARIANT_NONE == token->value.type) { /* extract macro for resolving */ value = zbx_substr_unquote(expression, token->loc.l, token->loc.r); zbx_variant_set_str(&token->value, value); } break; } } return ctx; } /****************************************************************************** * * * Purpose: gets functionids from parsed expression * * * * Parameters: ctx - [IN] evaluation context * * functionids - [OUT] extracted functionids * * * ******************************************************************************/ void zbx_eval_get_functionids(zbx_eval_context_t *ctx, zbx_vector_uint64_t *functionids) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; zbx_uint64_t functionid; if (SUCCEED == expression_extract_functionid(ctx->expression, token, &functionid)) zbx_vector_uint64_append(functionids, functionid); } } /****************************************************************************** * * * Purpose: gets function identifiers from parsed expression in order they * * are written * * * * Parameters: ctx - [IN] evaluation context * * functionids - [OUT] extracted functionids * * * ******************************************************************************/ void zbx_eval_get_functionids_ordered(zbx_eval_context_t *ctx, zbx_vector_uint64_t *functionids) { int i; zbx_vector_ptr_t tokens; zbx_vector_ptr_create(&tokens); for (i = 0; i < ctx->stack.values_num; i++) { if (ZBX_EVAL_TOKEN_FUNCTIONID == ctx->stack.values[i].type) zbx_vector_ptr_append(&tokens, &ctx->stack.values[i]); } zbx_vector_ptr_sort(&tokens, zbx_eval_compare_tokens_by_loc); for (i = 0; i < tokens.values_num; i++) { zbx_eval_token_t *token = (zbx_eval_token_t *)tokens.values[i]; zbx_uint64_t functionid; if (SUCCEED == expression_extract_functionid(ctx->expression, token, &functionid)) zbx_vector_uint64_append(functionids, functionid); } zbx_vector_ptr_destroy(&tokens); } /****************************************************************************** * * * Purpose: checks if expression contains timer function calls (date, time, * * now, dayofweek, dayofmonth) * * * * Parameters: ctx - [IN] evaluation context * * * * Return value: SUCCEED - expression contains timer function call(s) * * FAIL - otherwise * * * ******************************************************************************/ int zbx_eval_check_timer_functions(const zbx_eval_context_t *ctx) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; if (ZBX_EVAL_TOKEN_FUNCTION != token->type) continue; if (SUCCEED == eval_compare_token(ctx, &token->loc, "date", ZBX_CONST_STRLEN("date"))) return SUCCEED; if (SUCCEED == eval_compare_token(ctx, &token->loc, "time", ZBX_CONST_STRLEN("time"))) return SUCCEED; if (SUCCEED == eval_compare_token(ctx, &token->loc, "now", ZBX_CONST_STRLEN("now"))) return SUCCEED; if (SUCCEED == eval_compare_token(ctx, &token->loc, "dayofmonth", ZBX_CONST_STRLEN("dayofmonth"))) return SUCCEED; if (SUCCEED == eval_compare_token(ctx, &token->loc, "dayofweek", ZBX_CONST_STRLEN("dayofweek"))) return SUCCEED; } return FAIL; } /****************************************************************************** * * * Purpose: extracts functionids from serialized expression * * * * Parameters: expression - [IN] original expression * * data - [IN] serialized expression * * functionids - [OUT] extracted functionids * * * ******************************************************************************/ void zbx_get_serialized_expression_functionids(const char *expression, const unsigned char *data, zbx_vector_uint64_t *functionids) { zbx_uint32_t i, tokens_num, len, loc_l, loc_r, opt; zbx_token_type_t type; zbx_uint64_t functionid; unsigned char var_type; data += zbx_deserialize_uint31_compact(data, &len); data += zbx_deserialize_uint31_compact(data, &tokens_num); for (i = 0; i < tokens_num; i++) { data += zbx_deserialize_value(data, &type); data += zbx_deserialize_uint31_compact(data, &opt); data += zbx_deserialize_uint31_compact(data, &loc_l); data += zbx_deserialize_uint31_compact(data, &loc_r); data += zbx_deserialize_char(data, &var_type); switch (var_type) { case ZBX_VARIANT_UI64: data += sizeof(zbx_uint64_t); break; case ZBX_VARIANT_DBL: data += sizeof(double); break; case ZBX_VARIANT_STR: data += strlen((const char *)data) + 1; break; case ZBX_VARIANT_NONE: break; default: THIS_SHOULD_NEVER_HAPPEN; return; } if (ZBX_EVAL_TOKEN_FUNCTIONID == type) { if (SUCCEED == zbx_is_uint64_n(expression + loc_l + 1, loc_r - loc_l - 1, &functionid)) zbx_vector_uint64_append(functionids, functionid); else THIS_SHOULD_NEVER_HAPPEN; } } zbx_vector_uint64_sort(functionids, ZBX_DEFAULT_UINT64_COMPARE_FUNC); zbx_vector_uint64_uniq(functionids, ZBX_DEFAULT_UINT64_COMPARE_FUNC); } /****************************************************************************** * * * Purpose: gets Nth constant in expression * * * * Parameters: ctx - [IN] evaluation context * * index - [IN] constant index * * value - [OUT] constant value * * * ******************************************************************************/ void zbx_eval_get_constant(const zbx_eval_context_t *ctx, int index, char **value) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; switch (token->type) { case ZBX_EVAL_TOKEN_VAR_STR: case ZBX_EVAL_TOKEN_VAR_NUM: case ZBX_EVAL_TOKEN_VAR_USERMACRO: if (index == (int)token->opt + 1) { zbx_free(*value); if (ZBX_VARIANT_NONE != token->value.type) { *value = zbx_strdup(NULL, zbx_variant_value_desc(&token->value)); } else { *value = zbx_substr_unquote(ctx->expression, token->loc.l, token->loc.r); } return; } break; } } } /******************************************************************************* * * * Purpose: replaces functionid in parsed expression with new functionid macro * * * * Parameters: ctx - [IN] evaluation context * * old_functionid - [IN] * * new_functionid - [OUT] * * * *******************************************************************************/ void zbx_eval_replace_functionid(zbx_eval_context_t *ctx, zbx_uint64_t old_functionid, zbx_uint64_t new_functionid) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; zbx_uint64_t token_functionid; if (ZBX_EVAL_TOKEN_FUNCTIONID != token->type) continue; if (ZBX_VARIANT_UI64 != token->value.type) { if (ZBX_VARIANT_NONE != token->value.type) continue; if (SUCCEED != zbx_is_uint64_n(ctx->expression + token->loc.l + 1, token->loc.r - token->loc.l - 1, &token_functionid)) { THIS_SHOULD_NEVER_HAPPEN; continue; } zbx_variant_set_ui64(&token->value, token_functionid); } if (token->value.data.ui64 == old_functionid) { zbx_variant_set_ui64(&token->value, new_functionid); /* mark functionid as replaced to check if any non-replaced functionids are left */ token->opt = ZBX_MAX_UINT31_1; } } } /****************************************************************************** * * * Purpose: validates parsed expression to check if all functionids were * * replaced * * * * Parameters: ctx [IN] evaluation context * * error - [IN] error message * * * ******************************************************************************/ int zbx_eval_validate_replaced_functionids(zbx_eval_context_t *ctx, char **error) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; if (ZBX_EVAL_TOKEN_FUNCTIONID != token->type) continue; if (ZBX_MAX_UINT31_1 != token->opt) { *error = zbx_dsprintf(*error, "non-updated functionid found at \"%s\"", ctx->expression + token->loc.l); return FAIL; } } return SUCCEED; } /****************************************************************************** * * * Purpose: copies parsed expression * * * * Parameters: dst - [OUT] destination evaluation context * * src - [IN] source evaluation context * * expression - [IN] copied destination expression * * * ******************************************************************************/ void zbx_eval_copy(zbx_eval_context_t *dst, const zbx_eval_context_t *src, const char *expression) { int i; dst->expression = expression; dst->rules = src->rules; zbx_vector_eval_token_create(&dst->stack); zbx_vector_eval_token_reserve(&dst->stack, (size_t)src->stack.values_num); zbx_vector_eval_token_append_array(&dst->stack, src->stack.values, src->stack.values_num); for (i = 0; i < dst->stack.values_num; i++) { if (ZBX_VARIANT_NONE != src->stack.values[i].value.type) zbx_variant_copy(&dst->stack.values[i].value, &src->stack.values[i].value); } } /****************************************************************************** * * * Purpose: formats function evaluation error message * * * * Parameters: function - [IN] function name * * host - [IN] host name, can be NULL * * key - [IN] item key, can be NULL * * parameter - [IN] function parameters list * * error - [IN] error message * * * * Return value: formatted error message * * * ******************************************************************************/ char *zbx_eval_format_function_error(const char *function, const char *host, const char *key, const char *parameter, const char *error) { char *msg = NULL; size_t msg_alloc = 0, msg_offset = 0; zbx_snprintf_alloc(&msg, &msg_alloc, &msg_offset, "Cannot evaluate function %s(/%s/%s", function, (NULL != host ? host : "?"), (NULL != key ? key : "?")); if (NULL != parameter && '\0' != *parameter) zbx_snprintf_alloc(&msg, &msg_alloc, &msg_offset, ",%s", parameter); zbx_chrcpy_alloc(&msg, &msg_alloc, &msg_offset, ')'); if (NULL != error && '\0' != *error) zbx_snprintf_alloc(&msg, &msg_alloc, &msg_offset, ": %s", error); zbx_chrcpy_alloc(&msg, &msg_alloc, &msg_offset, '.'); return msg; } /******************************************************************************* * * * Purpose: copies history query into vector and replaces it with vector index * * * * Parameters: ctx - [IN] evaluation context * * refs - [OUT] item references * * * *******************************************************************************/ void zbx_eval_extract_item_refs(zbx_eval_context_t *ctx, zbx_vector_str_t *refs) { int i; for (i = 0; i < ctx->stack.values_num; i++) { zbx_eval_token_t *token = &ctx->stack.values[i]; if (ZBX_EVAL_TOKEN_ARG_QUERY != token->type) continue; if (ZBX_VARIANT_STR == token->value.type) { zbx_vector_str_append(refs, token->value.data.str); zbx_variant_set_none(&token->value); } else zbx_vector_str_append(refs, zbx_substr(ctx->expression, token->loc.l, token->loc.r)); zbx_variant_clear(&token->value); zbx_variant_set_ui64(&token->value, refs->values_num - 1); } }