/*
** 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 "zbxeval.h"
#include "eval.h"
#include "zbxalgo.h"
#include "zbxexpr.h"
#include "zbxvariant.h"
ZBX_VECTOR_IMPL(eval_token, zbx_eval_token_t)
static int is_whitespace(char c)
{
return 0 != isspace((unsigned char)c) ? SUCCEED : FAIL;
}
/******************************************************************************
* *
* Purpose: finds number of following whitespace characters *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* *
* Return value: number of whitespace characters found *
* *
******************************************************************************/
static size_t eval_get_whitespace_len(zbx_eval_context_t *ctx, size_t pos)
{
const char *ptr = ctx->expression + pos;
while (SUCCEED == is_whitespace(*ptr))
ptr++;
return ptr - ctx->expression - pos;
}
/******************************************************************************
* *
* Purpose: updates constant variable index in trigger expression *
* *
* Parameters: ctx - [IN] evaluation context *
* token - [IN] variable token *
* *
* Comments: The index is used to refer constant values by using $ in *
* trigger names. Function arguments are excluded. *
* *
******************************************************************************/
static void eval_update_const_variable(zbx_eval_context_t *ctx, zbx_eval_token_t *token)
{
zbx_variant_set_none(&token->value);
if (0 != (ctx->rules & ZBX_EVAL_PARSE_CONST_INDEX))
{
int i;
for (i = 0; i < ctx->ops.values_num; i++)
{
if (0 != (ctx->ops.values[i].type & ZBX_EVAL_CLASS_FUNCTION))
return;
}
token->opt = ctx->const_index++;
}
}
/******************************************************************************
* *
* Purpose: Checks if the character can be a part of a compound number *
* following a macro. *
* *
******************************************************************************/
static int eval_is_compound_number_char(char c, int pos)
{
if (0 != isdigit((unsigned char)c))
return SUCCEED;
switch (c)
{
case '.':
case '{':
return SUCCEED;
case 'e':
case 'E':
return (0 != pos ? SUCCEED : FAIL);
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: parses functionid token ({}) *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_parse_functionid(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
zbx_token_t tok;
if (SUCCEED == zbx_token_parse_objectid(ctx->expression, ctx->expression + pos, &tok))
{
token->type = ZBX_EVAL_TOKEN_FUNCTIONID;
token->opt = ctx->functionid_index++;
token->loc = tok.loc;
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: parses macro *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* tok - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_parse_macro(zbx_eval_context_t *ctx, int pos, zbx_token_t *tok)
{
if (0 != (ctx->rules & ZBX_EVAL_PARSE_MACRO) &&
SUCCEED == zbx_token_parse_macro(ctx->expression, ctx->expression + pos, tok))
{
return SUCCEED;
}
else if (0 != (ctx->rules & ZBX_EVAL_PARSE_USERMACRO) && '$' == ctx->expression[pos + 1] &&
SUCCEED == zbx_token_parse_user_macro(ctx->expression, ctx->expression + pos, tok))
{
return SUCCEED;
}
else if (0 != (ctx->rules & ZBX_EVAL_PARSE_LLDMACRO) && '#' == ctx->expression[pos + 1] &&
SUCCEED == zbx_token_parse_lld_macro(ctx->expression, ctx->expression + pos, tok))
{
return SUCCEED;
}
else if ('{' == ctx->expression[pos + 1] && SUCCEED == zbx_token_parse_nested_macro(ctx->expression,
ctx->expression + pos, 0, tok))
{
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: parses numeric value *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* pos_r - [OUT] *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_parse_number(zbx_eval_context_t *ctx, size_t pos, size_t *pos_r)
{
int len, offset = 0;
char *end;
double tmp;
if ('-' == ctx->expression[pos])
offset++;
if (FAIL == zbx_suffixed_number_parse(ctx->expression + pos + offset, &len))
return FAIL;
len += offset;
tmp = strtod(ctx->expression + pos, &end) * (double)suffix2factor(ctx->expression[(int)pos + len - 1]);
if (HUGE_VAL == tmp || -HUGE_VAL == tmp || EDOM == errno)
return FAIL;
*pos_r = pos + (size_t)len - 1;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses constant value *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* error - [OUT] error message *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: A constant is a number or macro depending on parsing rules. *
* Number can be a compound value, consisting of several macros *
* digits etc. *
* *
******************************************************************************/
static int eval_parse_constant(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
zbx_token_t tok;
size_t offset = pos;
zbx_token_type_t type = 0, last_type = 0;
do
{
if ('{' == (ctx->expression[offset]))
{
last_type = ZBX_TOKEN_MACRO;
if (SUCCEED != eval_parse_macro(ctx, (int)offset, &tok))
break;
if (pos == offset)
{
switch (tok.type)
{
case ZBX_TOKEN_MACRO:
case ZBX_TOKEN_FUNC_MACRO:
case ZBX_TOKEN_SIMPLE_MACRO:
type = ZBX_EVAL_TOKEN_VAR_MACRO;
break;
case ZBX_TOKEN_USER_FUNC_MACRO:
case ZBX_TOKEN_USER_MACRO:
type = ZBX_EVAL_TOKEN_VAR_USERMACRO;
break;
case ZBX_TOKEN_LLD_MACRO:
case ZBX_TOKEN_LLD_FUNC_MACRO:
type = ZBX_EVAL_TOKEN_VAR_LLDMACRO;
break;
}
}
else
type = ZBX_EVAL_TOKEN_VAR_NUM;
offset = tok.loc.r + 1;
switch (ctx->expression[offset])
{
case 's':
case 'm':
case 'h':
case 'd':
case 'w':
case 'K':
case 'M':
case 'G':
case 'T':
type = ZBX_EVAL_TOKEN_VAR_NUM;
offset++;
goto out;
}
}
else if (ZBX_EVAL_TOKEN_VAR_NUM != last_type && SUCCEED == eval_parse_number(ctx, offset, &offset))
{
last_type = type = ZBX_EVAL_TOKEN_VAR_NUM;
offset++;
}
else if (SUCCEED == eval_is_compound_number_char(ctx->expression[offset], offset - pos))
offset++;
else
break;
}
while (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPOUND_CONST));
out:
if (0 == type)
{
*error = zbx_dsprintf(*error, "invalid token starting with \"%s\"", ctx->expression + pos);
return FAIL;
}
if (ZBX_EVAL_TOKEN_VAR_NUM == type && 0 == (ctx->rules & ZBX_EVAL_PARSE_VAR_NUM))
{
*error = zbx_dsprintf(*error, "invalid token starting with \"%s\"", ctx->expression + pos);
return FAIL;
}
if (ZBX_EVAL_TOKEN_VAR_NUM == type || ZBX_EVAL_TOKEN_VAR_USERMACRO == type)
eval_update_const_variable(ctx, token);
token->type = type;
token->loc.l = pos;
token->loc.r = offset - 1;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses single character token *
* *
* Parameters: pos - [IN] starting position *
* type - [IN] token type *
* token - [OUT] parsed token *
* *
******************************************************************************/
static void eval_parse_character_token(size_t pos, zbx_token_type_t type, zbx_eval_token_t *token)
{
token->type = type;
token->loc.l = pos;
token->loc.r = pos;
}
/******************************************************************************
* *
* Purpose: parses token starting with '<' *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: Tokens starting with '<' are '<', '<=' and '<>'. *
* *
******************************************************************************/
static int eval_parse_less_character_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE_EQ) && '>' == ctx->expression[pos + 1])
{
token->type = ZBX_EVAL_TOKEN_OP_NE;
}
else
{
if (0 == (ctx->rules & ZBX_EVAL_PARSE_COMPARE_SORT))
return FAIL;
if ('=' != ctx->expression[pos + 1])
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_LT, token);
return SUCCEED;
}
token->type = ZBX_EVAL_TOKEN_OP_LE;
}
token->loc.l = pos;
token->loc.r = pos + 1;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses token starting with '>' *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Comments: Tokens starting with '>' are '>' and '>='. *
* *
******************************************************************************/
static void eval_parse_greater_character_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
if ('=' == ctx->expression[pos + 1])
{
token->type = ZBX_EVAL_TOKEN_OP_GE;
token->loc.l = pos;
token->loc.r = pos + 1;
}
else
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_GT, token);
}
/******************************************************************************
* *
* Purpose: parses string variable token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* error - [OUT] error message in case of failure *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: String variable token is token starting with '"'. *
* *
******************************************************************************/
static int eval_parse_string_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
const char *ptr = ctx->expression + pos + 1;
for (; '\0' != *ptr; ptr++)
{
if (*ptr == '"')
{
token->type = ZBX_EVAL_TOKEN_VAR_STR;
token->loc.l = pos;
token->loc.r = ptr - ctx->expression;
eval_update_const_variable(ctx, token);
return SUCCEED;
}
if ('\\' == *ptr)
{
if (0 == (ZBX_EVAL_PARSE_STR_V64_COMPAT & ctx->rules) && '"' != ptr[1] && '\\' != ptr[1])
{
*error = zbx_dsprintf(*error, "invalid escape sequence in string starting with \"%s\"",
ptr);
return FAIL;
}
if (0 != (ZBX_EVAL_PARSE_STR_V64_COMPAT & ctx->rules) && '"' != ptr[1])
continue;
else
ptr++;
}
}
*error = zbx_dsprintf(*error, "unterminated string at \"%s\"", ctx->expression + pos);
return FAIL;
}
/******************************************************************************
* *
* Purpose: parses numeric variable token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: Time suffixes s,m,h,d,w are supported. *
* *
******************************************************************************/
static int eval_parse_number_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
int len, offset = 0;
char *end;
double tmp;
if ('-' == ctx->expression[pos])
offset++;
if (FAIL == zbx_suffixed_number_parse(ctx->expression + pos + offset, &len))
return FAIL;
len += offset;
token->type = ZBX_EVAL_TOKEN_VAR_NUM;
tmp = strtod(ctx->expression + pos, &end) * suffix2factor(ctx->expression[pos + len - 1]);
if (HUGE_VAL == tmp || -HUGE_VAL == tmp || EDOM == errno)
return FAIL;
token->loc.l = pos;
token->loc.r = pos + len - 1;
eval_update_const_variable(ctx, token);
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses logical operation token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: Keywords are 'and', 'or' and 'not', followed by separator *
* character (whitespace or '('). *
* *
******************************************************************************/
static int eval_parse_logic_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
if (0 == strncmp(ctx->expression + pos, "and", 3))
{
token->loc.r = pos + 2;
token->type = ZBX_EVAL_TOKEN_OP_AND;
}
else if (0 == strncmp(ctx->expression + pos, "or", 2))
{
token->loc.r = pos + 1;
token->type = ZBX_EVAL_TOKEN_OP_OR;
}
else if (0 == strncmp(ctx->expression + pos, "not", 3))
{
token->loc.r = pos + 2;
token->type = ZBX_EVAL_TOKEN_OP_NOT;
}
else
return FAIL;
/* keyword must be followed by whitespace or opening parenthesis */
if ('(' != ctx->expression[token->loc.r + 1] && SUCCEED != is_whitespace(ctx->expression[token->loc.r + 1]))
return FAIL;
token->loc.l = pos;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses function token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: Function token is non-keyword alpha characters followed by '('. *
* *
******************************************************************************/
static int eval_parse_function_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
const char *ptr = ctx->expression + pos;
while (0 != isalpha((unsigned char)*ptr) || '_' == *ptr || 0 != isdigit((unsigned char)*ptr))
ptr++;
if ('(' == *ptr)
{
token->type = ZBX_EVAL_TOKEN_FUNCTION;
token->loc.l = pos;
token->loc.r = ptr - ctx->expression - 1;
token->opt = ctx->stack.values_num;
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: parses item query filter (?[group="xyz"]) *
* *
* Parameters: ptr - [IN] filter to parse *
* [OUT] reference to next character after filter *
* *
* Return value: SUCCEED - filter was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_parse_query_filter(const char **ptr)
{
const char *filter = *ptr;
if ('[' != *(++filter))
return FAIL;
filter++;
while (']' != *filter)
{
if ('\0' == *filter)
return FAIL;
if ('"' == *filter)
{
while ('"' != *(++filter))
{
if ('\0' == *filter)
return FAIL;
if ('\\' == *filter && '\0' == *(++filter))
return FAIL;
}
}
filter++;
}
*ptr = ++filter;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses item query /host/key?[filter] into host, key and filter *
* components *
* *
* Parameters: str - [IN] item query *
* phost - [OUT] reference to host or NULL (optional) *
* pkey - [OUT] reference to key *
* pfilter - [OUT] reference to filter or NULL *
* *
* Return value: The number of parsed characters, 0 if there was an error. *
* *
******************************************************************************/
size_t eval_parse_query(const char *str, const char **phost, const char **pkey, const char **pfilter)
{
#define MVAR_HOST_HOST "{HOST.HOST"
#define MVAR_ITEM_KEY "{ITEM.KEY"
const char *host = str + 1, *key, *filter, *end;
key = host;
if ('*' == *key)
{
key++;
}
else if ('{' == *key)
{
if (0 == strncmp(key, MVAR_HOST_HOST, ZBX_CONST_STRLEN(MVAR_HOST_HOST)))
{
size_t offset = 0;
if ('}' == key[ZBX_CONST_STRLEN(MVAR_HOST_HOST)])
{
offset = 1;
}
else if (0 != isdigit((unsigned char)key[ZBX_CONST_STRLEN(MVAR_HOST_HOST)]) &&
'}' == key[ZBX_CONST_STRLEN(MVAR_HOST_HOST) + 1])
{
offset = 2;
}
if (0 != offset)
key += ZBX_CONST_STRLEN(MVAR_HOST_HOST) + offset;
}
}
else if ('/' != *key)
{
while (SUCCEED == zbx_is_hostname_char(*key))
key++;
}
if ('/' != *key)
return 0;
end = ++key;
if ('*' == *key)
{
end++;
}
else if ('{' == *key)
{
if (0 == strncmp(key, MVAR_ITEM_KEY, ZBX_CONST_STRLEN(MVAR_ITEM_KEY)))
{
size_t offset = 0;
if ('}' == key[ZBX_CONST_STRLEN(MVAR_ITEM_KEY)])
{
offset = 1;
}
else if (0 != isdigit((unsigned char)key[ZBX_CONST_STRLEN(MVAR_ITEM_KEY)]) &&
'}' == key[ZBX_CONST_STRLEN(MVAR_ITEM_KEY) + 1])
{
offset = 2;
}
if (0 != offset)
end += ZBX_CONST_STRLEN(MVAR_ITEM_KEY) + offset;
}
}
else if (SUCCEED != zbx_parse_key(&end))
return 0;
if (*end == '?')
{
filter = end;
if (SUCCEED != eval_parse_query_filter(&end))
return 0;
filter += 2;
}
else
filter = NULL;
if (NULL != phost)
{
*phost = host;
*pkey = key;
*pfilter = filter;
}
return end - str;
#undef MVAR_HOST_HOST
#undef MVAR_ITEM_KEY
}
/******************************************************************************
* *
* Purpose: parses history query token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* error - [OUT] error message in case of failure *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: History query token is the first argument of history functions *
* to specify item(s) in format /host/key *
* *
******************************************************************************/
static int eval_parse_query_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
size_t len;
if (0 == (len = eval_parse_query(ctx->expression + pos, NULL, NULL, NULL)))
{
*error = zbx_dsprintf(*error, "invalid item query starting at \"%s\"", ctx->expression + pos);
return FAIL;
}
token->type = ZBX_EVAL_TOKEN_ARG_QUERY;
token->loc.l = pos;
token->loc.r = pos + len - 1;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses time period token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* error - [OUT] error message in case of failure *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: Time period token is the second argument of history functions *
* to specify the history range in format [:]. *
* *
******************************************************************************/
static int eval_parse_period_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
size_t offset = pos;
for (;'\0' != ctx->expression[offset]; offset++)
{
if ('{' == ctx->expression[offset] && 0 != (ctx->rules & ZBX_EVAL_PARSE_COMPOUND_CONST))
{
zbx_token_t tok;
if (SUCCEED == eval_parse_macro(ctx, offset, &tok))
offset = tok.loc.r;
continue;
}
if (',' == ctx->expression[offset] || ')' == ctx->expression[offset] ||
SUCCEED == is_whitespace(ctx->expression[offset]))
{
token->type = ZBX_EVAL_TOKEN_ARG_PERIOD;
token->loc.l = pos;
token->loc.r = offset - 1;
zbx_variant_set_none(&token->value);
return SUCCEED;
}
}
*error = zbx_dsprintf(*error, "unterminated function at \"%s\"", ctx->expression + pos);
return FAIL;
}
/******************************************************************************
* *
* Purpose: parses property token *
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
* Comments: Keywords are 'and', 'or' and 'not', followed by separator *
* character (whitespace or '('). *
* *
******************************************************************************/
static int eval_parse_property_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROP_TAG) && 0 == strncmp(ctx->expression + pos, "tag", 3))
{
token->loc.r = pos + 2;
token->type = ZBX_EVAL_TOKEN_PROP_TAG;
}
else if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROP_GROUP) && 0 == strncmp(ctx->expression + pos, "group", 5))
{
token->loc.r = pos + 4;
token->type = ZBX_EVAL_TOKEN_PROP_GROUP;
}
else
return FAIL;
token->loc.l = pos;
return SUCCEED;
}
/******************************************************************************
* *
* Parameters: ctx - [IN] evaluation context *
* pos - [IN] starting position *
* token - [OUT] parsed token *
* error - [OUT] error message in case of failure *
* *
* Return value: SUCCEED - token was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_parse_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
size_t skip;
skip = eval_get_whitespace_len(ctx, pos);
pos += skip;
switch (ctx->expression[pos])
{
case '{':
if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type &&
ZBX_EVAL_TOKEN_ARG_QUERY == ctx->stack.values[ctx->stack.values_num - 1].type)
{
return eval_parse_period_token(ctx, pos, token, error);
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_FUNCTIONID) &&
SUCCEED == eval_parse_functionid(ctx, pos, token))
{
return SUCCEED;
}
return eval_parse_constant(ctx, pos, token, error);
case '+':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_ADD, token);
return SUCCEED;
}
break;
case '-':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_VAR))
{
if (0 == (ctx->last_token_type & ZBX_EVAL_CLASS_OPERAND) &&
SUCCEED == eval_parse_number_token(ctx, pos, token))
{
return SUCCEED;
}
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
{
if (0 == (ctx->last_token_type & ZBX_EVAL_CLASS_OPERAND))
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_MINUS, token);
else
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_SUB, token);
return SUCCEED;
}
break;
case '/':
if (ZBX_EVAL_TOKEN_GROUP_OPEN == ctx->last_token_type &&
0 != (ctx->rules & ZBX_EVAL_PARSE_ITEM_QUERY))
{
zbx_eval_token_t *func_token = NULL;
if (2 <= ctx->ops.values_num)
func_token = &ctx->ops.values[ctx->ops.values_num - 2];
if (NULL == func_token || 0 == (func_token->type & ZBX_EVAL_CLASS_FUNCTION))
{
*error = zbx_dsprintf(*error, "item query must be first argument of a"
" historical function at \"%s\"", ctx->expression + pos);
return FAIL;
}
func_token->type = ZBX_EVAL_TOKEN_HIST_FUNCTION;
return eval_parse_query_token(ctx, pos, token, error);
}
else if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_DIV, token);
return SUCCEED;
}
break;
case '*':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_MUL, token);
return SUCCEED;
}
break;
case '<':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE))
{
if (SUCCEED == eval_parse_less_character_token(ctx, pos, token))
return SUCCEED;
}
break;
case '>':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE_SORT))
{
eval_parse_greater_character_token(ctx, pos, token);
return SUCCEED;
}
break;
case '=':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE_EQ))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_EQ, token);
return SUCCEED;
}
break;
case '(':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_GROUP))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_GROUP_OPEN, token);
return SUCCEED;
}
break;
case ')':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_GROUP))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_GROUP_CLOSE, token);
return SUCCEED;
}
break;
case '"':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_VAR_STR))
return eval_parse_string_token(ctx, pos, token, error);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/* after ',' there will be at least one value on the stack */
if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type && 0 < ctx->stack.values_num &&
ZBX_EVAL_TOKEN_ARG_QUERY == ctx->stack.values[ctx->stack.values_num - 1].type)
{
return eval_parse_period_token(ctx, pos, token, error);
}
ZBX_FALLTHROUGH;
case '.':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_VAR_NUM))
return eval_parse_constant(ctx, pos, token, error);
break;
case '#':
if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type &&
ZBX_EVAL_TOKEN_ARG_QUERY == ctx->stack.values[ctx->stack.values_num - 1].type)
{
return eval_parse_period_token(ctx, pos, token, error);
}
break;
case ',':
if (0 != (ctx->rules & ZBX_EVAL_PARSE_FUNCTION_ARGS))
{
eval_parse_character_token(pos, ZBX_EVAL_TOKEN_COMMA, token);
return SUCCEED;
}
break;
case '\0':
return SUCCEED;
default:
if (0 != isalpha((unsigned char)ctx->expression[pos]))
{
/* logical operation must be separated by whitespace or '(', ')', ',' characters */
if (0 != (ctx->rules & ZBX_EVAL_PARSE_LOGIC) &&
(0 != skip || 0 != (ctx->last_token_type & ZBX_EVAL_CLASS_SEPARATOR) ||
ZBX_EVAL_TOKEN_GROUP_CLOSE == ctx->last_token_type))
{
if (SUCCEED == eval_parse_logic_token(ctx, pos, token))
return SUCCEED;
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_FUNCTION_NAME) &&
SUCCEED == eval_parse_function_token(ctx, pos, token))
{
return SUCCEED;
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY) &&
SUCCEED == eval_parse_property_token(ctx, pos, token))
{
return SUCCEED;
}
}
break;
}
*error = zbx_dsprintf(*error, "invalid token starting with \"%s\"", ctx->expression + pos);
return FAIL;
}
/******************************************************************************
* *
* Purpose: adds operator/function token to evaluation stack *
* *
* Parameters: ctx - [IN] evaluation context *
* token - [IN] token to add *
* error - [OUT] *
* *
******************************************************************************/
static int eval_append_operator(zbx_eval_context_t *ctx, zbx_eval_token_t *token, char **error)
{
if (0 != (token->type & ZBX_EVAL_CLASS_FUNCTION))
{
int i, params = 0;
for (i = (int)token->opt; i < ctx->stack.values_num; i++)
{
if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_FUNCTION))
params -= (int)ctx->stack.values[i].opt - 1;
else if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_OPERAND))
params++;
else if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_OPERATOR2))
params--;
}
token->opt = params;
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY))
{
zbx_eval_token_t *prop = NULL, *value = NULL;
if (0 != (ctx->stack.values[ctx->stack.values_num - 1].type & ZBX_EVAL_CLASS_PROPERTY))
{
prop = &ctx->stack.values[ctx->stack.values_num - 1];
if (2 > ctx->stack.values_num)
{
*error = zbx_dsprintf(*error, "missing comparison string for property at \"%s\"",
ctx->expression + prop->loc.l);
return FAIL;
}
value = &ctx->stack.values[ctx->stack.values_num - 2];
if (0 == (value->type & ZBX_EVAL_CLASS_OPERAND))
{
*error = zbx_dsprintf(*error, "property must be compared with a constant value at"
" \"%s\"", ctx->expression + prop->loc.l);
return FAIL;
}
}
if (0 != (ctx->stack.values[ctx->stack.values_num - 1].type & ZBX_EVAL_CLASS_OPERAND) &&
0 != (token->type & ZBX_EVAL_CLASS_OPERATOR2))
{
if (0 != (ctx->stack.values[ctx->stack.values_num - 2].type & ZBX_EVAL_CLASS_PROPERTY))
{
prop = &ctx->stack.values[ctx->stack.values_num - 2];
value = &ctx->stack.values[ctx->stack.values_num - 1];
}
}
if (NULL != prop)
{
if ((ZBX_EVAL_TOKEN_VAR_STR != value->type && ZBX_EVAL_TOKEN_VAR_USERMACRO != value->type &&
ZBX_EVAL_TOKEN_VAR_LLDMACRO != value->type))
{
*error = zbx_dsprintf(*error, "invalid value type compared with property at \"%s\"",
ctx->expression + prop->loc.l);
return FAIL;
}
if (ZBX_EVAL_TOKEN_OP_EQ != token->type && ZBX_EVAL_TOKEN_OP_NE != token->type)
{
*error = zbx_dsprintf(*error, "invalid operator used with property at \"%s\"",
ctx->expression + prop->loc.l);
return FAIL;
}
}
}
zbx_vector_eval_token_append_ptr(&ctx->stack, token);
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: adds operand token to evaluation stack *
* *
* Parameters: ctx - [IN] evaluation context *
* token - [IN] token to add *
* error - [OUT] *
* *
******************************************************************************/
static int eval_append_operand(zbx_eval_context_t *ctx, zbx_eval_token_t *token, char **error)
{
if (0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERAND))
{
*error = zbx_dsprintf(*error, "operand following another operand at \"%s\"",
ctx->expression + token->loc.l);
return FAIL;
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY))
{
int i;
zbx_eval_token_t *prop = NULL;
for (i = ctx->stack.values_num - 1; i >= 0; i--)
{
if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_PROPERTY))
{
prop = &ctx->stack.values[i];
continue;
}
if (0 == (ctx->stack.values[i].type & ZBX_EVAL_CLASS_OPERAND))
break;
}
if (0 != (token->type & ZBX_EVAL_CLASS_PROPERTY))
{
if (NULL != prop)
{
*error = zbx_dsprintf(*error, "property must be compared with a constant value at"
" \"%s\"", ctx->expression + prop->loc.l);
return FAIL;
}
prop = token;
}
if (NULL != prop && 2 < ctx->stack.values_num - i)
{
*error = zbx_dsprintf(*error, "property must be compared with a constant value at"
" \"%s\"", ctx->expression + prop->loc.l);
return FAIL;
}
}
zbx_vector_eval_token_append_ptr(&ctx->stack, token);
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: adds null argument token to evaluation stack *
* *
* Parameters: ctx - [IN] evaluation context *
* *
******************************************************************************/
static void eval_append_arg_null(zbx_eval_context_t *ctx)
{
zbx_eval_token_t null_token = {.type = ZBX_EVAL_TOKEN_ARG_NULL};
zbx_vector_eval_token_append_ptr(&ctx->stack, &null_token);
}
/******************************************************************************
* *
* Purpose: frees resources allocated by evaluation context *
* *
* Parameters: ctx - [IN] evaluation context *
* *
******************************************************************************/
static void eval_clear(zbx_eval_context_t *ctx)
{
if (NULL != ctx->stack.values)
{
int i;
for (i = 0; i < ctx->stack.values_num; i++)
zbx_variant_clear(&ctx->stack.values[i].value);
zbx_vector_eval_token_destroy(&ctx->stack);
}
}
/******************************************************************************
* *
* Purpose: parses expression into tokens in postfix notation order *
* *
* Parameters: ctx - [OUT] evaluation context *
* expression - [IN] expression to parse *
* rules - [IN] parsing rules *
* error - [OUT] error message in case of failure *
* *
* Return value: SUCCEED - expression was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_parse_expression(zbx_eval_context_t *ctx, const char *expression, zbx_uint64_t rules, char **error)
{
size_t pos = 0;
int ret = FAIL;
zbx_eval_token_t *optoken;
ctx->expression = expression;
ctx->rules = rules;
ctx->last_token_type = ZBX_EVAL_CLASS_SEPARATOR;
ctx->const_index = 0;
ctx->functionid_index = 0;
zbx_vector_eval_token_create(&ctx->stack);
zbx_vector_eval_token_reserve(&ctx->stack, 16);
zbx_vector_eval_token_create(&ctx->ops);
zbx_vector_eval_token_reserve(&ctx->ops, 16);
while ('\0' != expression[pos])
{
zbx_eval_token_t token = {0};
if (SUCCEED != eval_parse_token(ctx, pos, &token, error))
goto out;
if (0 == token.type)
break;
/* serialization used for parsed expression caching has limits expression to 0x7fffffff */
if ((zbx_uint32_t)0x7fffffff < token.loc.r)
{
*error = zbx_strdup(*error, "too long expression");
goto out;
}
if (ZBX_EVAL_TOKEN_ARG_QUERY == ctx->last_token_type && ZBX_EVAL_TOKEN_COMMA != token.type &&
ZBX_EVAL_TOKEN_GROUP_CLOSE != token.type)
{
*error = zbx_dsprintf(*error, "invalid expression following query token at \"%s\"",
ctx->expression + pos);
goto out;
}
if (ZBX_EVAL_TOKEN_GROUP_CLOSE == token.type && ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type)
eval_append_arg_null(ctx);
if (ZBX_EVAL_TOKEN_GROUP_OPEN == token.type)
{
if (0 == (ctx->last_token_type & (ZBX_EVAL_BEFORE_OPERAND | ZBX_EVAL_CLASS_FUNCTION)))
{
*error = zbx_dsprintf(*error, "opening parenthesis must follow operator or function"
" at \"%s\"", ctx->expression + pos);
goto out;
}
zbx_vector_eval_token_append_ptr(&ctx->ops, &token);
}
else if (0 != (token.type & ZBX_EVAL_CLASS_FUNCTION))
{
if (0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERAND))
{
*error = zbx_dsprintf(*error, "function must follow operand or unary operator"
" at \"%s\"", ctx->expression + pos);
goto out;
}
zbx_vector_eval_token_append_ptr(&ctx->ops, &token);
}
else if (ZBX_EVAL_TOKEN_COMMA == token.type)
{
/* comma must follow and operand, comma or function */
if (0 == (ctx->last_token_type & ZBX_EVAL_CLASS_OPERAND) &&
(0 == (ctx->last_token_type & ZBX_EVAL_CLASS_SEPARATOR)))
{
*error = zbx_dsprintf(*error, "comma must follow an operand or separator at \"%s\"",
ctx->expression + pos);
goto out;
}
if (0 != (ctx->last_token_type & ZBX_EVAL_CLASS_SEPARATOR))
eval_append_arg_null(ctx);
for (optoken = NULL; 0 < ctx->ops.values_num; ctx->ops.values_num--)
{
optoken = &ctx->ops.values[ctx->ops.values_num - 1];
if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
break;
if (FAIL == eval_append_operator(ctx, optoken, error))
goto out;
}
if (NULL == optoken)
{
*error = zbx_dsprintf(*error, "missing function argument separator for comma at\"%s\"",
ctx->expression + pos);
goto out;
}
}
else if (ZBX_EVAL_TOKEN_GROUP_CLOSE == token.type)
{
/* right parenthesis must follow and operand, right parenthesis or function */
if (0 == (ctx->last_token_type & (ZBX_EVAL_CLASS_OPERAND | ZBX_EVAL_CLASS_PROPERTY |
ZBX_EVAL_CLASS_SEPARATOR)) &&
(ctx->ops.values_num < 2 ||
ZBX_EVAL_TOKEN_FUNCTION != ctx->ops.values[ctx->ops.values_num - 2].type))
{
*error = zbx_dsprintf(*error, "right parenthesis must follow an operand or left"
" parenthesis at \"%s\"", ctx->expression + pos);
goto out;
}
for (optoken = NULL; 0 < ctx->ops.values_num; ctx->ops.values_num--)
{
optoken = &ctx->ops.values[ctx->ops.values_num - 1];
if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
{
ctx->ops.values_num--;
break;
}
if (FAIL == eval_append_operator(ctx, optoken, error))
goto out;
}
if (NULL == optoken)
{
*error = zbx_dsprintf(*error, "missing left parenthesis for right parenthesis"
" at \"%s\"", ctx->expression + pos);
goto out;
}
if (0 != ctx->ops.values_num)
{
optoken = &ctx->ops.values[ctx->ops.values_num - 1];
if (0 != (optoken->type & ZBX_EVAL_CLASS_FUNCTION))
{
if (FAIL == eval_append_operator(ctx, optoken, error))
goto out;
ctx->ops.values_num--;
}
else if (ZBX_EVAL_TOKEN_GROUP_OPEN == ctx->last_token_type)
{
*error = zbx_dsprintf(*error, "parenthesis cannot close empty group at \"%s\"",
ctx->expression + pos);
goto out;
}
}
}
else if (0 != (token.type & (ZBX_EVAL_CLASS_OPERAND | ZBX_EVAL_CLASS_PROPERTY)))
{
if (FAIL == eval_append_operand(ctx, &token, error))
goto out;
}
else if (0 != (token.type & ZBX_EVAL_CLASS_OPERATOR))
{
/* binary operator cannot be used after operator */
if (0 != (token.type & ZBX_EVAL_CLASS_OPERATOR2) &&
0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERATOR))
{
*error = zbx_dsprintf(*error, "binary operator must be used after operand at \"%s\"",
ctx->expression + pos);
goto out;
}
/* unary !,- operators cannot follow an operand */
if (0 != (token.type & ZBX_EVAL_CLASS_OPERATOR1) &&
0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERAND))
{
*error = zbx_dsprintf(*error, "unary operator cannot follow an operand at \"%s\"",
ctx->expression + pos);
goto out;
}
for (; 0 < ctx->ops.values_num; ctx->ops.values_num--)
{
optoken = &ctx->ops.values[ctx->ops.values_num - 1];
if ((optoken->type & ZBX_EVAL_OP_PRIORITY) > (token.type & ZBX_EVAL_OP_PRIORITY) ||
0 != (token.type & ZBX_EVAL_CLASS_OPERATOR1))
break;
if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
break;
if (FAIL == eval_append_operator(ctx, optoken, error))
goto out;
}
zbx_vector_eval_token_append_ptr(&ctx->ops, &token);
}
ctx->last_token_type = token.type;
pos = token.loc.r + 1;
}
if (0 != (ctx->last_token_type & ZBX_EVAL_CLASS_OPERATOR))
{
*error = zbx_strdup(*error, "expression ends with operator");
goto out;
}
if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type)
{
*error = zbx_strdup(*error, "expression ends with comma");
goto out;
}
for (; 0 < ctx->ops.values_num; ctx->ops.values_num--)
{
optoken = &ctx->ops.values[ctx->ops.values_num - 1];
if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
{
*error = zbx_dsprintf(*error, "mismatched () brackets in expression: %s", ctx->expression);
goto out;
}
if (FAIL == eval_append_operator(ctx, optoken, error))
goto out;
}
if (0 == ctx->stack.values_num)
{
*error = zbx_strdup(*error, "empty expression");
goto out;
}
if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY) && 1 == ctx->stack.values_num)
{
if (0 != (ctx->stack.values[ctx->stack.values_num - 1].type & ZBX_EVAL_CLASS_PROPERTY))
{
zbx_eval_token_t *prop = &ctx->stack.values[ctx->stack.values_num - 1];
*error = zbx_dsprintf(*error, "missing comparison string for property at \"%s\"",
ctx->expression + prop->loc.l);
goto out;
}
}
ret = SUCCEED;
out:
zbx_vector_eval_token_destroy(&ctx->ops);
if (SUCCEED != ret)
eval_clear(ctx);
return ret;
}
/******************************************************************************
* *
* Purpose: parses expression into tokens in postfix notation order *
* *
* Parameters: ctx - [OUT] evaluation context *
* expression - [IN] expression to parse *
* rules - [IN] parsing rules *
* error - [OUT] error message in case of failure *
* *
* Return value: SUCCEED - expression was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_eval_parse_expression(zbx_eval_context_t *ctx, const char *expression, zbx_uint64_t rules, char **error)
{
return eval_parse_expression(ctx, expression, rules, error);
}
/******************************************************************************
* *
* Purpose: initializes context so it can be cleared without parsing *
* *
* Parameters: ctx - [IN] evaluation context *
* *
******************************************************************************/
void zbx_eval_init(zbx_eval_context_t *ctx)
{
memset(ctx, 0, sizeof(zbx_eval_context_t));
}
/******************************************************************************
* *
* Purpose: frees resources allocated by evaluation context *
* *
* Parameters: ctx - [IN] evaluation context *
* *
******************************************************************************/
void zbx_eval_clear(zbx_eval_context_t *ctx)
{
eval_clear(ctx);
}
/******************************************************************************
* *
* Purpose: returns evaluation context status *
* *
* Parameters: ctx - [IN] evaluation context *
* *
* Return value: SUCCEED - contains parsed expression *
* FAIL - Empty, either parsing failed or was initialized *
* without parsing. *
* *
******************************************************************************/
int zbx_eval_status(const zbx_eval_context_t *ctx)
{
return (NULL == ctx->expression ? FAIL : SUCCEED);
}