/*
** 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 "zbxcommon.h"
#include "zbxrtc.h"
#include "zbx_rtc_constants.h"
#include "zbxserialize.h"
#include "zbxjson.h"
#include "zbxnix.h"
#include "zbxdiag.h"
#include "zbxstr.h"
#include "zbxnum.h"
#include "zbxalgo.h"
#include "zbxipcservice.h"
#include "zbxprof.h"
#include "zbxtime.h"
ZBX_PTR_VECTOR_IMPL(rtc_sub, zbx_rtc_sub_t *)
ZBX_PTR_VECTOR_IMPL(rtc_hook, zbx_rtc_hook_t *)
/******************************************************************************
* *
* Purpose: get runtime control option targets *
* *
******************************************************************************/
int zbx_rtc_get_command_target(const char *data, pid_t *pid, int *proc_type, int *proc_num, int *scope,
char **result)
{
struct zbx_json_parse jp;
char buf[MAX_STRING_LEN];
if (FAIL == zbx_json_open(data, &jp))
{
*result = zbx_dsprintf(NULL, "Invalid parameters \"%s\"\n", data);
return FAIL;
}
if (NULL != scope)
{
if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_SCOPE, buf, sizeof(buf), NULL))
*scope = atoi(buf);
else
*scope = ZBX_PROF_UNKNOWN;
}
if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_PID, buf, sizeof(buf), NULL))
{
zbx_uint64_t pid_ui64;
if (SUCCEED != zbx_is_uint64(buf, &pid_ui64) || 0 == pid_ui64)
{
*result = zbx_dsprintf(NULL, "Invalid pid value \"%s\"\n", buf);
return FAIL;
}
*pid = (pid_t)pid_ui64;
}
else
*pid = 0;
if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_PROCESS_NUM, buf, sizeof(buf), NULL))
*proc_num = atoi(buf);
else
*proc_num = 0;
if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_PROCESS_NAME, buf, sizeof(buf), NULL))
{
if (ZBX_PROCESS_TYPE_UNKNOWN == (*proc_type = get_process_type_by_name(buf)))
{
*result = zbx_dsprintf(NULL, "Invalid parameters \"%s\"\n", data);
return FAIL;
}
}
else
*proc_type = ZBX_PROCESS_TYPE_UNKNOWN;
return SUCCEED;
}
#if defined(HAVE_SIGQUEUE)
/******************************************************************************
* *
* Purpose: change log level of service process *
* *
******************************************************************************/
static void rtc_change_service_loglevel(zbx_uint32_t code)
{
if (ZBX_RTC_LOG_LEVEL_INCREASE == code)
zabbix_increase_log_level();
else if (ZBX_RTC_LOG_LEVEL_DECREASE == code)
zabbix_decrease_log_level();
zabbix_report_log_level_change();
}
/******************************************************************************
* *
* Purpose: dispatch log level change runtime control option *
* *
******************************************************************************/
static void rtc_process_loglevel_option(zbx_rtc_t *rtc, zbx_uint32_t code, const char *data, char **result)
{
pid_t pid;
int proc_type, proc_num;
if (SUCCEED != zbx_rtc_get_command_target(data, &pid, &proc_type, &proc_num, NULL, result))
return;
/* all processes */
if (0 == pid && ZBX_PROCESS_TYPE_UNKNOWN == proc_type)
{
rtc_change_service_loglevel(code);
zbx_rtc_notify(rtc, (unsigned char)proc_type, proc_num, code, data, (zbx_uint32_t)strlen(data) + 1);
zbx_signal_process_by_pid(0, (int)ZBX_RTC_MAKE_MESSAGE(code, 0, 0), result);
return;
}
if (0 != pid)
{
if (pid == getpid())
{
rtc_change_service_loglevel(code);
/* temporary message, the signal forwarding command output will be changed later */
*result = zbx_strdup(NULL, "Changed log level for the main process\n");
}
else
zbx_signal_process_by_pid((int)pid, (int)ZBX_RTC_MAKE_MESSAGE(code, 0, 0), result);
return;
}
if (0 == zbx_rtc_notify(rtc, (unsigned char)proc_type, proc_num, code, data, (zbx_uint32_t)strlen(data) + 1))
zbx_signal_process_by_type(proc_type, proc_num, (int)ZBX_RTC_MAKE_MESSAGE(code, 0, 0), result);
}
/******************************************************************************
* *
* Purpose: dispatch profiler runtime control option *
* *
******************************************************************************/
static void rtc_process_profiler_option(zbx_uint32_t code, const char *data, char **result)
{
pid_t pid;
int proc_type, proc_num, scope;
if (SUCCEED != zbx_rtc_get_command_target(data, &pid, &proc_type, &proc_num, &scope, result))
return;
/* all processes */
if (0 == pid && ZBX_PROCESS_TYPE_UNKNOWN == proc_type)
{
zbx_signal_process_by_pid(0, (int)ZBX_RTC_MAKE_MESSAGE(code, 0, 0), result);
return;
}
if (0 != pid)
{
if (pid == getpid())
*result = zbx_strdup(NULL, "Cannot use profiler with main process\n");
else
zbx_signal_process_by_pid((int)pid, (int)ZBX_RTC_MAKE_MESSAGE(code, scope, 0), result);
return;
}
zbx_signal_process_by_type(proc_type, proc_num, (int)ZBX_RTC_MAKE_MESSAGE(code, scope, 0), result);
}
#endif
/******************************************************************************
* *
* Purpose: process diaginfo runtime control option *
* *
* Parameters: data - [IN] the runtime control parameter (optional) *
* result - [OUT] the runtime control result *
* *
******************************************************************************/
static void rtc_process_diaginfo(const char *data, char **result)
{
struct zbx_json_parse jp;
char buf[MAX_STRING_LEN];
unsigned int scope;
if (FAIL == zbx_json_open(data, &jp) ||
SUCCEED != zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_SECTION, buf, sizeof(buf), NULL))
{
*result = zbx_dsprintf(NULL, "Invalid parameters \"%s\"\n", data);
return;
}
if (0 == strcmp(buf, "all"))
{
scope = (1 << ZBX_DIAGINFO_HISTORYCACHE) | (1 << ZBX_DIAGINFO_PREPROCESSING) |
(1 << ZBX_DIAGINFO_LOCKS);
}
else if (0 == strcmp(buf, ZBX_DIAG_HISTORYCACHE))
{
scope = 1 << ZBX_DIAGINFO_HISTORYCACHE;
}
else if (0 == strcmp(buf, ZBX_DIAG_PREPROCESSING))
{
scope = 1 << ZBX_DIAGINFO_PREPROCESSING;
}
else if (0 == strcmp(buf, ZBX_DIAG_LOCKS))
{
scope = 1 << ZBX_DIAGINFO_LOCKS;
}
else
{
if (NULL == *result)
*result = zbx_dsprintf(NULL, "Unknown diaginfo section \"%s\"\n", buf);
return;
}
zbx_diag_log_info(scope, result);
}
/******************************************************************************
* *
* Purpose: notify client based subscribers *
* *
******************************************************************************/
static void rtc_notify_client(zbx_rtc_sub_t *sub, zbx_uint32_t code, const unsigned char *data, zbx_uint32_t size)
{
if (FAIL == zbx_ipc_client_send(sub->source.client, code, data, size))
{
zabbix_log(LOG_LEVEL_WARNING, "cannot send RTC notification to client \"%s\" #%d",
get_process_type_string(sub->process_type), sub->process_num);
}
}
/******************************************************************************
* *
* Purpose: notify service based subscribers *
* *
******************************************************************************/
static void rtc_notify_service(zbx_rtc_sub_t *sub, zbx_uint32_t code, const unsigned char *data, zbx_uint32_t size)
{
zbx_ipc_socket_t sock;
char *error = NULL;
if (FAIL == zbx_ipc_socket_open(&sock, sub->source.service, SEC_PER_MIN, &error))
{
zabbix_log(LOG_LEVEL_WARNING, "cannot send RTC notification to service \"%s\" #%d: %s",
get_process_type_string(sub->process_type), sub->process_num, error);
zbx_free(error);
return;
}
if (FAIL == zbx_ipc_socket_write(&sock, code, data, size))
{
zabbix_log(LOG_LEVEL_WARNING, "cannot send RTC notification to service \"%s\" #%d",
get_process_type_string(sub->process_type), sub->process_num);
}
zbx_ipc_socket_close(&sock);
}
/******************************************************************************
* *
* Purpose: match RTC message *
* *
******************************************************************************/
static int rtc_match_message(const zbx_vector_uint32_t *msgs, zbx_uint32_t code)
{
for (int i = 0; i < msgs->values_num; i++)
{
if (msgs->values[i] == code)
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: notify subscribers *
* *
******************************************************************************/
int zbx_rtc_notify(zbx_rtc_t *rtc, unsigned char process_type, int process_num, zbx_uint32_t code,
const char *data, zbx_uint32_t size)
{
int i, notified_num = 0;
for (i = 0; i < rtc->subs.values_num; i++)
{
if (ZBX_PROCESS_TYPE_UNKNOWN != process_type && process_type != rtc->subs.values[i]->process_type)
continue;
if (0 != process_num && 0 != rtc->subs.values[i]->process_num &&
process_num != rtc->subs.values[i]->process_num)
{
continue;
}
if (SUCCEED != rtc_match_message(&rtc->subs.values[i]->msgs, code))
continue;
switch (rtc->subs.values[i]->type)
{
case ZBX_RTC_SUB_CLIENT:
rtc_notify_client(rtc->subs.values[i], code, (const unsigned char *)data, size);
break;
case ZBX_RTC_SUB_SERVICE:
rtc_notify_service(rtc->subs.values[i], code, (const unsigned char *)data, size);
}
notified_num++;
}
return notified_num;
}
/******************************************************************************
* *
* Purpose: notifies remote subscribers *
* *
******************************************************************************/
int zbx_rtc_notify_generic(zbx_ipc_async_socket_t *rtc, unsigned char process_type, int process_num,
zbx_uint32_t code, const char *data, zbx_uint32_t size)
{
unsigned char *notify_data, *ptr;
zbx_uint32_t notify_data_size;
int ret = FAIL;
/* */
notify_data_size = (zbx_uint32_t)(sizeof(process_type) + sizeof(process_num) + sizeof(code) + 2 * sizeof(size))
+ size;
notify_data = (unsigned char *)zbx_malloc(NULL, notify_data_size);
ptr = notify_data;
ptr += zbx_serialize_value(ptr, process_type);
ptr += zbx_serialize_int(ptr, process_num);
ptr += zbx_serialize_value(ptr, code);
ptr += zbx_serialize_value(ptr, size);
(void)zbx_serialize_str(ptr, data, size);
if (FAIL == zbx_ipc_async_socket_send(rtc, ZBX_RTC_NOTIFY, notify_data, notify_data_size))
{
zabbix_log(LOG_LEVEL_CRIT, "cannot send %s notification", get_process_type_string(process_type));
goto out;
}
ret = (int)notify_data_size;
out:
zbx_free(notify_data);
return ret;
}
/******************************************************************************
* *
* Purpose: deserialize subscription target notification codes *
* *
******************************************************************************/
static size_t rtc_deserialize_msgs(const unsigned char *data, zbx_vector_uint32_t *msgs)
{
int msgs_num;
const unsigned char *ptr = data;
zbx_uint32_t msg;
ptr += zbx_deserialize_value(ptr, &msgs_num);
/* reserve additional slot for shutdown notification */
zbx_vector_uint32_reserve(msgs, (size_t)msgs_num + 1);
for (int i = 0; i < msgs_num; i++)
{
ptr += zbx_deserialize_value(ptr, &msg);
zbx_vector_uint32_append(msgs, msg);
}
/* automatically subscribe also for shutdown notifications */
zbx_vector_uint32_append(msgs, ZBX_RTC_SHUTDOWN);
return (size_t)(ptr - data);
}
/******************************************************************************
* *
* Purpose: subscribe for client based RTC notifications *
* *
******************************************************************************/
static void rtc_subscribe(zbx_rtc_t *rtc, zbx_ipc_client_t *client, const unsigned char *data)
{
zbx_rtc_sub_t *sub;
sub = (zbx_rtc_sub_t *)zbx_malloc(NULL, sizeof(zbx_rtc_sub_t));
zbx_vector_uint32_create(&sub->msgs);
sub->type = ZBX_RTC_SUB_CLIENT;
sub->source.client = client;
data += zbx_deserialize_value(data, &sub->process_type);
data += zbx_deserialize_value(data, &sub->process_num);
(void)rtc_deserialize_msgs(data, &sub->msgs);
zbx_vector_rtc_sub_append(&rtc->subs, sub);
}
/******************************************************************************
* *
* Purpose: subscribe for service based RTC notifications *
* *
******************************************************************************/
static void rtc_subscribe_service(zbx_rtc_t *rtc, const unsigned char *data)
{
zbx_rtc_sub_t *sub;
zbx_uint32_t service_len;
sub = (zbx_rtc_sub_t *)zbx_malloc(NULL, sizeof(zbx_rtc_sub_t));
zbx_vector_uint32_create(&sub->msgs);
sub->type = ZBX_RTC_SUB_SERVICE;
data += zbx_deserialize_value(data, &sub->process_type);
data += zbx_deserialize_value(data, &sub->process_num);
data += rtc_deserialize_msgs(data, &sub->msgs);
(void)zbx_deserialize_str(data, &sub->source.service, service_len);
zbx_vector_rtc_sub_append(&rtc->subs, sub);
}
/******************************************************************************
* *
* Purpose: process runtime control option *
* *
* Parameters: rtc - [IN] the RTC service *
* code - [IN] the request code *
* data - [IN] the runtime control parameter (optional) *
* result - [OUT] the runtime control result *
* *
******************************************************************************/
static void rtc_process_request(zbx_rtc_t *rtc, zbx_uint32_t code, const unsigned char *data,
char **result)
{
zbx_uint32_t notify_code, notify_size;
int process_num;
char *notify_data = NULL;
unsigned char process_type;
switch (code)
{
#if defined(HAVE_SIGQUEUE)
case ZBX_RTC_LOG_LEVEL_INCREASE:
case ZBX_RTC_LOG_LEVEL_DECREASE:
rtc_process_loglevel_option(rtc, code, (const char *)data, result);
return;
case ZBX_RTC_PROF_ENABLE:
case ZBX_RTC_PROF_DISABLE:
rtc_process_profiler_option(code, (const char *)data, result);
return;
#endif
case ZBX_RTC_HOUSEKEEPER_EXECUTE:
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_HOUSEKEEPER, 0, ZBX_RTC_HOUSEKEEPER_EXECUTE, NULL, 0);
return;
case ZBX_RTC_CONFIG_CACHE_RELOAD:
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_CONFSYNCER, 0, ZBX_RTC_CONFIG_CACHE_RELOAD, NULL, 0);
return;
case ZBX_RTC_SNMP_CACHE_RELOAD:
#ifdef HAVE_NETSNMP
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_SNMP_POLLER, 0, ZBX_RTC_SNMP_CACHE_RELOAD, NULL, 0);
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_POLLER, 0, ZBX_RTC_SNMP_CACHE_RELOAD, NULL, 0);
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_UNREACHABLE, 0, ZBX_RTC_SNMP_CACHE_RELOAD, NULL, 0);
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_TRAPPER, 0, ZBX_RTC_SNMP_CACHE_RELOAD, NULL, 0);
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_DISCOVERYMANAGER, 0, ZBX_RTC_SNMP_CACHE_RELOAD, NULL, 0);
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_TASKMANAGER, 0, ZBX_RTC_SNMP_CACHE_RELOAD, NULL, 0);
#else
*result = zbx_strdup(NULL, "Invalid runtime control option: no SNMP support enabled\n");
#endif
return;
case ZBX_RTC_DIAGINFO:
rtc_process_diaginfo((const char *)data, result);
return;
case ZBX_RTC_NOTIFY:
data += zbx_deserialize_value(data, &process_type);
data += zbx_deserialize_int(data, &process_num);
data += zbx_deserialize_value(data, ¬ify_code);
data += zbx_deserialize_value(data, ¬ify_size);
(void)zbx_deserialize_str(data, ¬ify_data, notify_size);
zbx_rtc_notify(rtc, process_type, process_num, notify_code, notify_data, notify_size);
zbx_free(notify_data);
return;
default:
*result = zbx_strdup(*result, "Unknown runtime control option\n");
}
}
/******************************************************************************
* *
* Purpose: add hook for the specified control code *
* *
******************************************************************************/
static void rtc_add_control_hook(zbx_rtc_t *rtc, zbx_ipc_client_t *client, zbx_uint32_t code)
{
zbx_rtc_hook_t *hook;
hook = (zbx_rtc_hook_t *)zbx_malloc(NULL, sizeof(zbx_rtc_hook_t));
hook->client = client;
hook->code = code;
zbx_vector_rtc_hook_append(&rtc->hooks, hook);
}
/******************************************************************************
* *
* Purpose: notify matching hooks and remove them *
* *
******************************************************************************/
static void rtc_notify_hooks(zbx_rtc_t *rtc, zbx_uint32_t code, unsigned char *data, zbx_uint32_t size)
{
int i;
for (i = 0; i < rtc->hooks.values_num;)
{
if (rtc->hooks.values[i]->code == code)
{
(void)zbx_ipc_client_send(rtc->hooks.values[i]->client, code, data, size);
zbx_free(rtc->hooks.values[i]);
zbx_vector_rtc_hook_remove_noorder(&rtc->hooks, i);
continue;
}
i++;
}
}
/******************************************************************************
* *
* Purpose: process runtime control option *
* *
******************************************************************************/
static void rtc_process(zbx_rtc_t *rtc, zbx_ipc_client_t *client, zbx_uint32_t code, const unsigned char *data,
zbx_rtc_process_request_ex_func_t cb_proc_req)
{
char *result = NULL, *result_ex = NULL;
zbx_uint32_t size = 0;
if (NULL == cb_proc_req || FAIL == cb_proc_req(rtc, code, data, &result_ex))
rtc_process_request(rtc, code, data, &result);
if (ZBX_RTC_NOTIFY != code)
{
if (NULL != result_ex)
result = zbx_strdcat(result, result_ex);
if (NULL == result)
{
/* generate default success message if no specific success or error messages were returned */
result = zbx_strdup(NULL, "Runtime control command was forwarded successfully\n");
}
size = (zbx_uint32_t)strlen(result) + 1;
zbx_ipc_client_send(client, code, (unsigned char *)result, size);
}
zbx_free(result_ex);
zbx_free(result);
}
/******************************************************************************
* *
* Purpose: initialize runtime control service *
* *
******************************************************************************/
int zbx_rtc_init(zbx_rtc_t *rtc ,char **error)
{
zbx_vector_rtc_sub_create(&rtc->subs);
zbx_vector_rtc_hook_create(&rtc->hooks);
return zbx_ipc_service_start(&rtc->service, ZBX_IPC_SERVICE_RTC, error);
}
/******************************************************************************
* *
* Purpose: accept and process runtime control request *
* *
******************************************************************************/
void zbx_rtc_dispatch(zbx_rtc_t *rtc, zbx_ipc_client_t *client, zbx_ipc_message_t *message,
zbx_rtc_process_request_ex_func_t cb_proc_req)
{
zabbix_log(LOG_LEVEL_DEBUG, "In %s() code:%u size:%u", __func__, message->code, message->size);
switch (message->code)
{
case ZBX_RTC_SUBSCRIBE:
rtc_subscribe(rtc, client, message->data);
break;
case ZBX_RTC_SUBSCRIBE_SERVICE:
rtc_subscribe_service(rtc, message->data);
break;
case ZBX_RTC_CONFIG_CACHE_RELOAD_WAIT:
rtc_add_control_hook(rtc, client, ZBX_RTC_CONFIG_SYNC_NOTIFY);
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_CONFSYNCER, 0, ZBX_RTC_CONFIG_CACHE_RELOAD, NULL, 0);
break;
case ZBX_RTC_CONFIG_SYNC_NOTIFY:
case ZBX_RTC_SERVICE_SYNC_NOTIFY:
rtc_notify_hooks(rtc, message->code, message->data, message->size);
break;
default:
rtc_process(rtc, client, message->code, message->data, cb_proc_req);
break;
}
zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}
/******************************************************************************
* *
* Purpose: wait for sync notification while optionally dispatching runtime *
* control commands *
* *
******************************************************************************/
int zbx_rtc_wait_for_sync_finish(zbx_rtc_t *rtc, zbx_rtc_process_request_ex_func_t cb_proc_req)
{
zbx_timespec_t rtc_timeout = {1, 0};
int sync = 0;
while (ZBX_IS_RUNNING() && 0 == sync)
{
zbx_ipc_client_t *client;
zbx_ipc_message_t *message;
(void)zbx_ipc_service_recv(&rtc->service, &rtc_timeout, &client, &message);
if (NULL != message)
{
switch (message->code)
{
case ZBX_RTC_CONFIG_SYNC_NOTIFY:
case ZBX_RTC_SERVICE_SYNC_NOTIFY:
sync = 1;
break;
case ZBX_RTC_LOG_LEVEL_DECREASE:
case ZBX_RTC_LOG_LEVEL_INCREASE:
case ZBX_RTC_SUBSCRIBE:
zbx_rtc_dispatch(rtc, client, message, cb_proc_req);
break;
default:
if (ZBX_IPC_RTC_MAX >= message->code)
{
const char *rtc_error = "Cannot perform specified runtime control"
" command during startup\n";
zbx_ipc_client_send(client, message->code,
(const unsigned char *)rtc_error,
(zbx_uint32_t)strlen(rtc_error) + 1);
}
}
zbx_ipc_message_free(message);
}
if (NULL != client)
zbx_ipc_client_release(client);
}
return !ZBX_IS_RUNNING() && FAIL == ZBX_EXIT_STATUS() ? FAIL : SUCCEED;
}
/******************************************************************************
* *
* Purpose: send shutdown signal to all subscribers *
* *
******************************************************************************/
void zbx_rtc_shutdown_subs(zbx_rtc_t *rtc)
{
zbx_rtc_notify(rtc, ZBX_PROCESS_TYPE_UNKNOWN, 0, ZBX_RTC_SHUTDOWN, NULL, 0);
}
/******************************************************************************
* *
* Purpose: free rtc subscription *
* *
******************************************************************************/
void zbx_rtc_sub_free(zbx_rtc_sub_t *sub)
{
switch (sub->type)
{
case ZBX_RTC_SUB_CLIENT:
zbx_ipc_client_close(sub->source.client);
break;
case ZBX_RTC_SUB_SERVICE:
zbx_free(sub->source.service);
break;
}
zbx_vector_uint32_destroy(&sub->msgs);
zbx_free(sub);
}