/*
** 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 "eval.h"
#include "zbxexpr.h"
#include "zbxstr.h"
#include "zbxalgo.h"
#include "zbxeval.h"
/* The tag expression token is virtual token used during item query filter processing. */
#define ZBX_EVAL_TOKEN_TAG_EXPRESSION (1000)
/******************************************************************************
* *
* Purpose: parses item query /host/key?[filter] into host, key and filter *
* components *
* *
* Parameters: str - [IN] item query *
* len - [IN] query length *
* query - [IN] parsed item query *
* *
* Return value: number of parsed characters *
* *
******************************************************************************/
size_t zbx_eval_parse_query(const char *str, size_t len, zbx_item_query_t *query)
{
size_t n;
const char *host, *key, *filter;
if (0 == (n = eval_parse_query(str, &host, &key, &filter)) || n != len)
return 0;
query->host = (host != key - 1 ? zbx_substr(host, 0, key - host - 2) : NULL);
if (NULL != filter)
{
query->key = zbx_substr(key, 0, (filter - key) - 3);
query->filter = zbx_substr(filter, 0, (str + len - filter) - 2);
}
else
{
query->key = zbx_substr(key, 0, (str + len - key) - 1);
query->filter = NULL;
}
return n;
}
/******************************************************************************
* *
* Purpose: frees resources allocated by item reference *
* *
******************************************************************************/
void zbx_eval_clear_query(zbx_item_query_t *query)
{
zbx_free(query->host);
zbx_free(query->key);
zbx_free(query->filter);
}
/******************************************************************************
* *
* Purpose: Prepares filter expression by converting property comparisons *
* prop =/<> "value" to prop("value")/not prop("value") function *
* calls. *
* *
* Parameters: ctx - [IN] evaluation context *
* *
******************************************************************************/
void zbx_eval_prepare_filter(zbx_eval_context_t *ctx)
{
int i, j;
for (i = 0; i < ctx->stack.values_num; i++)
{
zbx_eval_token_t *prop = &ctx->stack.values[i];
if (0 == (prop->type & ZBX_EVAL_CLASS_PROPERTY))
continue;
for (j = i + 1; j < ctx->stack.values_num; j++)
{
zbx_eval_token_t *op = &ctx->stack.values[j];
if (ZBX_EVAL_TOKEN_OP_EQ == op->type || ZBX_EVAL_TOKEN_OP_NE == op->type)
{
zbx_eval_token_t *func;
func = &ctx->stack.values[j - 1];
if (i != j - 1)
{
zbx_strloc_t loc;
loc = prop->loc;
*prop = *func;
func->loc = loc;
}
func->opt = 1;
func->type = ZBX_EVAL_TOKEN_FUNCTION;
if (ZBX_EVAL_TOKEN_OP_NE == op->type)
op->type = ZBX_EVAL_TOKEN_OP_NOT;
else
op->type = ZBX_EVAL_TOKEN_NOP;
break;
}
}
}
}
/******************************************************************************
* *
* Purpose: applies binary operation to stack *
* *
* Parameters: token - [IN] operation token *
* stack - [IN/OUT] target stack *
* index - [IN/OUT] stack index *
* *
******************************************************************************/
static void eval_filter_apply_op2(zbx_eval_token_t *token, zbx_vector_eval_token_t *stack,
zbx_vector_uint64_t *index)
{
zbx_eval_token_t *left, *right;
int li, ri;
li = (int)index->values[index->values_num - 2];
left = &stack->values[li];
ri = (int)index->values[index->values_num - 1];
right = &stack->values[ri];
if (ZBX_EVAL_TOKEN_TAG_EXPRESSION == left->type || ZBX_EVAL_TOKEN_TAG_EXPRESSION == right->type)
{
switch (token->type)
{
case ZBX_EVAL_TOKEN_OP_AND:
if (ZBX_EVAL_TOKEN_TAG_EXPRESSION == left->type)
{
memmove(left, right, (stack->values_num - ri) * sizeof(zbx_eval_token_t));
stack->values_num -= (ri - li);
}
else
stack->values_num--;
break;
default:
left->type = ZBX_EVAL_TOKEN_TAG_EXPRESSION;
stack->values_num = li + 1;
break;
}
index->values_num--;
return;
}
zbx_vector_eval_token_append_ptr(stack, token);
index->values_num--;
}
/******************************************************************************
* *
* Purpose: applies unary operation to stack *
* *
* Parameters: token - [IN] operation token *
* stack - [IN/OUT] target stack *
* index - [IN/OUT] stack index *
* *
******************************************************************************/
static void eval_filter_apply_op1(zbx_eval_token_t *token, zbx_vector_eval_token_t *stack,
const zbx_vector_uint64_t *index)
{
zbx_eval_token_t *right;
int ri;
ri = (int)index->values[index->values_num - 1];
right = &stack->values[ri];
if (ZBX_EVAL_TOKEN_TAG_EXPRESSION != right->type)
zbx_vector_eval_token_append_ptr(stack, token);
}
/******************************************************************************
* *
* Purpose: applies function to stack *
* *
* Parameters: ctx - [IN] evaluation context *
* token - [IN] function token *
* stack - [IN/OUT] target stack *
* index - [IN/OUT] stack index *
* *
******************************************************************************/
static void eval_filter_apply_func(zbx_eval_context_t *ctx, zbx_eval_token_t *token,
zbx_vector_eval_token_t *stack, const zbx_vector_uint64_t *index)
{
zbx_eval_token_t *left;
int li;
if (ZBX_CONST_STRLEN("tag") == token->loc.r - token->loc.l + 1 &&
0 == memcmp(ctx->expression + token->loc.l, "tag", ZBX_CONST_STRLEN("tag")))
{
li = (int)index->values[index->values_num - 1];
left = &stack->values[li];
left->type = ZBX_EVAL_TOKEN_TAG_EXPRESSION;
stack->values_num = li + 1;
}
else
zbx_vector_eval_token_append_ptr(stack, token);
}
/******************************************************************************
* *
* Purpose: gets operator in text format *
* *
* Parameters: op - [IN] operator type *
* *
* Return value: operator in text format *
* *
* Comments: This function will return 'unsupported operator' for unsupported *
* operators, causing the expression evaluation to fail. However *
* this should not happen as the supported operators are verified *
* during expression parsing. *
* *
******************************************************************************/
static const char *eval_op_str(zbx_token_type_t op)
{
switch (op)
{
case ZBX_EVAL_TOKEN_OP_EQ:
return "=";
case ZBX_EVAL_TOKEN_OP_NE:
return "<>";
case ZBX_EVAL_TOKEN_OP_AND:
return " and ";
case ZBX_EVAL_TOKEN_OP_OR:
return " or ";
case ZBX_EVAL_TOKEN_OP_NOT:
return "not ";
default:
return "unsupported operator";
}
}
/******************************************************************************
* *
* Purpose: unquotes string *
* *
* Parameters: str - [IN] string to unquote *
* *
* Return value: unquoted string *
* *
* Comments: string is unquoted in same buffer *
* *
******************************************************************************/
static char *eval_unquote_str(char *str)
{
char *dst, *src;
if ('\"' != *str)
return str;
src = str;
dst = src++;
while ('\0' != *src && '"' != *src)
{
if ('\\' == *src)
{
if ('\0' == *(++src))
break;
}
*dst++ = *src++;
}
*dst = '\0';
return str;
}
/******************************************************************************
* *
* Purpose: generates filter expression from the specified stack *
* *
* Parameters: ctx - [IN] evaluation context *
* stack - [IN] expression stack *
* groups - [OUT] group values to match *
* filter - [OUT] generated filter *
* error - [OUT] error message *
* *
* Return value: SUCCEED - filter expression was successfully generated *
* FAIL - otherwise *
* *
******************************************************************************/
static int eval_generate_filter(const zbx_eval_context_t *ctx, const zbx_vector_eval_token_t *stack,
zbx_vector_str_t *groups, char **filter, char **error)
{
zbx_vector_str_t out;
int i, ret = FAIL;
char *tmp;
if (0 == stack->values_num)
{
*error = zbx_strdup(NULL, "invalid filter expression");
return FAIL;
}
if (ZBX_EVAL_TOKEN_TAG_EXPRESSION == stack->values[0].type)
{
*filter = NULL;
return SUCCEED;
}
zbx_vector_str_create(&out);
for (i = 0; i < stack->values_num; i++)
{
zbx_eval_token_t *token = &stack->values[i];
if (0 != (token->type & ZBX_EVAL_CLASS_OPERATOR2))
{
if (2 > out.values_num)
{
*error = zbx_strdup(NULL, "not enough values on stack for binary operation");
goto out;
}
tmp = zbx_dsprintf(NULL, "(%s%s%s)", out.values[out.values_num - 2], eval_op_str(token->type),
out.values[out.values_num - 1]);
zbx_free(out.values[out.values_num - 2]);
zbx_free(out.values[out.values_num - 1]);
out.values_num -= 1;
out.values[out.values_num - 1] = tmp;
}
else if (0 != (token->type & ZBX_EVAL_CLASS_OPERATOR1))
{
if (1 > out.values_num)
{
*error = zbx_strdup(NULL, "not enough values on stack for unary operation");
goto out;
}
tmp = zbx_dsprintf(NULL, "%s%s", eval_op_str(token->type), out.values[out.values_num - 1]);
zbx_free(out.values[out.values_num - 1]);
out.values[out.values_num - 1] = tmp;
}
else if (ZBX_EVAL_TOKEN_FUNCTION == token->type)
{
if (1 > out.values_num)
{
*error = zbx_strdup(NULL, "not enough values on stack for property comparison");
goto out;
}
tmp = zbx_dsprintf(NULL, "{%d}", groups->values_num);
zbx_vector_str_append(groups, eval_unquote_str(out.values[out.values_num - 1]));
out.values[out.values_num - 1] = tmp;
}
else if (ZBX_EVAL_TOKEN_NOP != token->type)
zbx_vector_str_append(&out, zbx_substr(ctx->expression, token->loc.l, token->loc.r));
}
if (1 != out.values_num)
{
*error = zbx_strdup(NULL, "too many values left on stack after generating filter expression");
goto out;
}
*filter = out.values[0];
out.values_num = 0;
ret = SUCCEED;
out:
zbx_vector_str_clear_ext(&out, zbx_str_free);
zbx_vector_str_destroy(&out);
return ret;
}
/******************************************************************************
* *
* Purpose: generates group SQL filter expression from item filter *
* *
* Parameters: ctx - [IN] filter expression evaluation context *
* groups - [OUT] group values to match *
* filter - [OUT] generated filter *
* error - [OUT] error message *
* *
* Return value: SUCCEED - filter expression was successfully generated *
* FAIL - otherwise *
* *
* Comments: The filter SQL is generated in two steps. *
* 1) The filter expression token stack is simplified by removing *
* tag related expression parts, so tags do not affect selection *
* item candidates. *
* This is done by 'evaluating' the original filter expression *
* token stack according to the following rules: *
* * group comparisons are copied without changes *
* * tag comparisons are replaced with TAG_EXPRESSION token *
* * TAG_EXPRESSION and is replaced with *
* *
* * TAG_EXPRESSION or is replaced with *
* TAG_EXPRESSION *
* * not TAG_EXPRESSION is replaced with TAG_EXPRESSION *
* *
* 2) At this point the simplified stack will contain either only *
* group comparisons/logical operators or TAG_EXPRESSION token. *
* In the first case the filter is generated by replacing group *
* comparisons with {N}, where N is the index of value to *
* compare in groups vector. *
* In the second case it means that selection of item candidates *
* cannot be restricted by groups and the filter must be NULL. *
* *
******************************************************************************/
int zbx_eval_get_group_filter(zbx_eval_context_t *ctx, zbx_vector_str_t *groups, char **filter, char **error)
{
zbx_vector_eval_token_t stack; /* simplified filter expression token stack */
zbx_vector_uint64_t output; /* pseudo output stack, containing indexes of expression */
/* fragments in the simplified filter expression token stack */
int i, ret = FAIL;
zbx_vector_eval_token_create(&stack);
zbx_vector_uint64_create(&output);
for (i = 0; i < ctx->stack.values_num; i++)
{
zbx_eval_token_t *token = &ctx->stack.values[i];
if (0 != (token->type & ZBX_EVAL_CLASS_OPERATOR2))
{
if (2 > output.values_num)
{
*error = zbx_strdup(NULL, "not enough values on stack for binary operation");
goto out;
}
eval_filter_apply_op2(token, &stack, &output);
}
else if (0 != (token->type & ZBX_EVAL_CLASS_OPERATOR1))
{
if (1 > output.values_num)
{
*error = zbx_strdup(NULL, "not enough values on stack for unary operation");
goto out;
}
eval_filter_apply_op1(token, &stack, &output);
}
else if (ZBX_EVAL_TOKEN_FUNCTION == token->type)
{
eval_filter_apply_func(ctx, token, &stack, &output);
}
else if (ZBX_EVAL_TOKEN_NOP != token->type)
{
zbx_vector_uint64_append(&output, stack.values_num);
zbx_vector_eval_token_append_ptr(&stack, token);
}
}
ret = eval_generate_filter(ctx, &stack, groups, filter, error);
out:
zbx_vector_uint64_destroy(&output);
zbx_vector_eval_token_destroy(&stack);
return ret;
}