/* ** 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 "zbxexpr.h" #include "zbxstr.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 <expression> is replaced with * * <expression> * * * TAG_EXPRESSION or <expression> 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; }