/* ** 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 "postinit.h" #include "../db_lengths_constants.h" #include "zbxexpression.h" #include "zbxtasks.h" #include "zbxcachevalue.h" #include "zbxcacheconfig.h" #include "zbxdbwrap.h" #include "zbxdb.h" #include "zbxdbhigh.h" #include "zbxexpr.h" #include "zbxstr.h" #include "zbxnum.h" #define ZBX_HIST_MACRO_NONE (-1) #define ZBX_HIST_MACRO_ITEM_VALUE 0 #define ZBX_HIST_MACRO_ITEM_LASTVALUE 1 /****************************************************************************** * * * Purpose: gets total number of triggers on system * * * ******************************************************************************/ static int get_trigger_count(void) { zbx_db_result_t result; zbx_db_row_t row; int triggers_num; result = zbx_db_select("select count(*) from triggers"); if (NULL != (row = zbx_db_fetch(result))) triggers_num = atoi(row[0]); else triggers_num = 0; zbx_db_free_result(result); return triggers_num; } /****************************************************************************** * * * Purpose: checks if this is historical macro that cannot be expanded for * * bulk event name update * * * * Parameters: macro - [IN] * * * * Return value: ZBX_HIST_MACRO_* defines * * * ******************************************************************************/ static int is_historical_macro(const char *macro) { if (0 == strncmp(macro, "ITEM.VALUE", ZBX_CONST_STRLEN("ITEM.VALUE"))) return ZBX_HIST_MACRO_ITEM_VALUE; if (0 == strncmp(macro, "ITEM.LASTVALUE", ZBX_CONST_STRLEN("ITEM.LASTVALUE"))) return ZBX_HIST_MACRO_ITEM_LASTVALUE; return ZBX_HIST_MACRO_NONE; } /****************************************************************************** * * * Purpose: translates historical macro to lld macro format * * * * Parameters: macro - [IN] macro type (see ZBX_HIST_MACRO_* defines) * * * * Return value: macro * * * * Comments: Some of the macros can be converted to different name. * * * ******************************************************************************/ static const char *convert_historical_macro(int macro) { /* When expanding macros for old events ITEM.LASTVALUE macro would */ /* always expand to one (latest) value. Expanding it as ITEM.VALUE */ /* makes more sense in this case. */ const char *macros[] = {"#ITEM.VALUE", "#ITEM.VALUE"}; return macros[macro]; } /****************************************************************************** * * * Purpose: pre-process trigger name(description) by expanding non historical * * macros * * * * Parameters: trigger - [IN] * * historical - [OUT] 1 - trigger name contains historical macros * * 0 - otherwise * * * * Comments: Some historical macros might be replaced with other macros to * * better match the trigger name at event creation time. * * * ******************************************************************************/ static void preprocess_trigger_name(zbx_db_trigger *trigger, int *historical) { int pos = 0, macro_len; zbx_token_t token; size_t name_alloc, name_len, replace_alloc = 64, replace_offset, r, l; char *replace; const char *macro; zbx_db_event event; *historical = FAIL; replace = (char *)zbx_malloc(NULL, replace_alloc); name_alloc = name_len = strlen(trigger->description) + 1; while (SUCCEED == zbx_token_find(trigger->description, pos, &token, ZBX_TOKEN_SEARCH_BASIC)) { if (ZBX_TOKEN_MACRO == token.type || ZBX_TOKEN_FUNC_MACRO == token.type) { /* the macro excluding the opening and closing brackets {}, for example: ITEM.VALUE */ if (ZBX_TOKEN_MACRO == token.type) { l = token.data.macro.name.l; r = token.data.macro.name.r; } else { l = token.data.func_macro.macro.l + 1; r = token.data.func_macro.macro.r - 1; } macro = trigger->description + l; int macro_type; if (ZBX_HIST_MACRO_NONE != (macro_type = is_historical_macro(macro))) { if (0 != isdigit(*(trigger->description + r))) macro_len = r - l; else macro_len = r - l + 1; macro = convert_historical_macro(macro_type); token.loc.r += zbx_replace_mem_dyn(&trigger->description, &name_alloc, &name_len, l, macro_len, macro, strlen(macro)); *historical = SUCCEED; } } pos = token.loc.r; } memset(&event, 0, sizeof(zbx_db_event)); event.object = EVENT_OBJECT_TRIGGER; event.objectid = trigger->triggerid; event.trigger = *trigger; zbx_substitute_simple_macros(NULL, &event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &trigger->description, ZBX_MACRO_TYPE_TRIGGER_DESCRIPTION, NULL, 0); if (SUCCEED == *historical) { pos = 0; name_alloc = name_len = strlen(trigger->description) + 1; while (SUCCEED == zbx_token_find(trigger->description, pos, &token, ZBX_TOKEN_SEARCH_BASIC)) { if (ZBX_TOKEN_LLD_MACRO == token.type || ZBX_TOKEN_LLD_FUNC_MACRO == token.type) { if (ZBX_TOKEN_LLD_MACRO == token.type) { l = token.data.lld_macro.name.l; r = token.data.lld_macro.name.r; } else { l = token.data.lld_func_macro.macro.l + 2; r = token.data.lld_func_macro.macro.r - 1; } macro = trigger->description + l; if (ZBX_HIST_MACRO_NONE != is_historical_macro(macro)) { macro_len = r - l + 1; replace_offset = 0; zbx_strncpy_alloc(&replace, &replace_alloc, &replace_offset, macro, macro_len); token.loc.r += zbx_replace_mem_dyn(&trigger->description, &name_alloc, &name_len, l - 1, macro_len + 1, replace, replace_offset); } } pos = token.loc.r; } } zbx_free(replace); } /****************************************************************************** * * * Purpose: updates event/problem names for trigger with bulk request * * * * Parameters: trigger - [IN] * * sql - [IN/OUT] sql query * * sql_alloc - [IN/OUT] sql query size * * sql_offset - [IN/OUT] sql query length * * * * Return value: SUCCEED - update was successful * * FAIL - otherwise * * * * Comments: Event names for triggers without historical macros will be the * * same and can be updated with a single sql query. * * * ******************************************************************************/ static int process_event_bulk_update(const zbx_db_trigger *trigger, char **sql, size_t *sql_alloc, size_t *sql_offset) { char *name_esc; int ret; name_esc = zbx_db_dyn_escape_string_len(trigger->description, EVENT_NAME_LEN); zbx_snprintf_alloc(sql, sql_alloc, sql_offset, "update events" " set name='%s'" " where source=%d" " and object=%d" " and objectid=" ZBX_FS_UI64 ";\n", name_esc, EVENT_SOURCE_TRIGGERS, EVENT_OBJECT_TRIGGER, trigger->triggerid); if (SUCCEED == (ret = zbx_db_execute_overflowed_sql(sql, sql_alloc, sql_offset))) { zbx_snprintf_alloc(sql, sql_alloc, sql_offset, "update problem" " set name='%s'" " where source=%d" " and object=%d" " and objectid=" ZBX_FS_UI64 ";\n", name_esc, EVENT_SOURCE_TRIGGERS, EVENT_OBJECT_TRIGGER, trigger->triggerid); ret = zbx_db_execute_overflowed_sql(sql, sql_alloc, sql_offset); } zbx_free(name_esc); return ret; } /****************************************************************************** * * * Purpose: Updates event/problem names for a trigger with separate requests * * for each event. * * * * Parameters: trigger - [IN] * * sql - [IN/OUT] sql query * * sql_alloc - [IN/OUT] sql query size * * sql_offset - [IN/OUT] sql query length * * * * Return value: SUCCEED - update was successful * * FAIL - otherwise * * * * Comments: Event names for triggers with historical macros might differ and * * historical macros in trigger name must be expanded for each * * event. * * * ******************************************************************************/ static int process_event_update(const zbx_db_trigger *trigger, char **sql, size_t *sql_alloc, size_t *sql_offset) { zbx_db_result_t result; zbx_db_row_t row; zbx_db_event event; char *name, *name_esc; int ret = SUCCEED; memset(&event, 0, sizeof(zbx_db_event)); result = zbx_db_select("select eventid,source,object,objectid,clock,value,acknowledged,ns,name" " from events" " where source=%d" " and object=%d" " and objectid=" ZBX_FS_UI64 " order by eventid", EVENT_SOURCE_TRIGGERS, EVENT_OBJECT_TRIGGER, trigger->triggerid); while (SUCCEED == ret && NULL != (row = zbx_db_fetch(result))) { ZBX_STR2UINT64(event.eventid, row[0]); event.source = atoi(row[1]); event.object = atoi(row[2]); ZBX_STR2UINT64(event.objectid, row[3]); event.clock = atoi(row[4]); event.value = atoi(row[5]); event.acknowledged = atoi(row[6]); event.ns = atoi(row[7]); event.name = row[8]; event.trigger = *trigger; name = zbx_strdup(NULL, trigger->description); zbx_substitute_simple_macros(NULL, &event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &name, ZBX_MACRO_TYPE_TRIGGER_DESCRIPTION, NULL, 0); name_esc = zbx_db_dyn_escape_string_len(name, EVENT_NAME_LEN); zbx_snprintf_alloc(sql, sql_alloc, sql_offset, "update events" " set name='%s'" " where eventid=" ZBX_FS_UI64 ";\n", name_esc, event.eventid); if (SUCCEED == (ret = zbx_db_execute_overflowed_sql(sql, sql_alloc, sql_offset))) { zbx_snprintf_alloc(sql, sql_alloc, sql_offset, "update problem" " set name='%s'" " where eventid=" ZBX_FS_UI64 ";\n", name_esc, event.eventid); ret = zbx_db_execute_overflowed_sql(sql, sql_alloc, sql_offset); } zbx_free(name_esc); zbx_free(name); } zbx_db_free_result(result); return ret; } /****************************************************************************** * * * Purpose: updates event names in events and problem tables * * * * Return value: SUCCEED - update was successful * * FAIL - otherwise * * * ******************************************************************************/ static int update_event_names(void) { zbx_db_result_t result; zbx_db_row_t row; zbx_db_trigger trigger; int ret = SUCCEED, triggers_num, processed_num = 0, last_completed = 0; char *sql; size_t sql_alloc = 4096, sql_offset = 0; zbx_dc_um_handle_t *um_handle; zabbix_log(LOG_LEVEL_WARNING, "starting event name update forced by database upgrade"); if (0 == (triggers_num = get_trigger_count())) goto out; memset(&trigger, 0, sizeof(zbx_db_trigger)); sql = (char *)zbx_malloc(NULL, sql_alloc); result = zbx_db_select( "select triggerid,description,expression,priority,comments,url,url_name," "recovery_expression,recovery_mode,value" " from triggers" " order by triggerid"); um_handle = zbx_dc_open_user_macros(); while (SUCCEED == ret && NULL != (row = zbx_db_fetch(result))) { ZBX_STR2UINT64(trigger.triggerid, row[0]); trigger.description = zbx_strdup(NULL, row[1]); trigger.expression = zbx_strdup(NULL, row[2]); ZBX_STR2UCHAR(trigger.priority, row[3]); trigger.comments = zbx_strdup(NULL, row[4]); trigger.url = zbx_strdup(NULL, row[5]); trigger.url_name = zbx_strdup(NULL, row[6]); trigger.recovery_expression = zbx_strdup(NULL, row[7]); ZBX_STR2UCHAR(trigger.recovery_mode, row[8]); ZBX_STR2UCHAR(trigger.value, row[9]); int historical; preprocess_trigger_name(&trigger, &historical); if (FAIL == historical) ret = process_event_bulk_update(&trigger, &sql, &sql_alloc, &sql_offset); else ret = process_event_update(&trigger, &sql, &sql_alloc, &sql_offset); zbx_db_trigger_clean(&trigger); processed_num++; int completed; if (last_completed != (completed = (int)(100.0 * processed_num / triggers_num))) { zabbix_log(LOG_LEVEL_WARNING, "completed %d%% of event name update", completed); last_completed = completed; } } zbx_dc_close_user_macros(um_handle); if (SUCCEED == ret) { if (ZBX_DB_OK > zbx_db_flush_overflowed_sql(sql, sql_offset)) ret = FAIL; } zbx_db_free_result(result); zbx_free(sql); out: if (SUCCEED == ret) zabbix_log(LOG_LEVEL_WARNING, "event name update completed"); else zabbix_log(LOG_LEVEL_WARNING, "event name update failed"); return ret; } /****************************************************************************** * * * Purpose: processes post initialization tasks * * * * Return value: SUCCEED - update was successful * * FAIL - otherwise * * * ******************************************************************************/ int zbx_check_postinit_tasks(char **error) { zbx_db_result_t result; zbx_db_row_t row; int ret = SUCCEED; /* avoid filling value cache with unnecessary data during event name update */ zbx_vc_disable(); result = zbx_db_select("select taskid from task where type=%d and status=%d", ZBX_TM_TASK_UPDATE_EVENTNAMES, ZBX_TM_STATUS_NEW); if (NULL != (row = zbx_db_fetch(result))) { zbx_db_begin(); if (SUCCEED == (ret = update_event_names())) { zbx_db_execute("delete from task where taskid=%s", row[0]); zbx_db_commit(); } else zbx_db_rollback(); } zbx_db_free_result(result); if (SUCCEED != ret) *error = zbx_strdup(*error, "cannot update event names"); zbx_vc_enable(); return ret; }