/*
** 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 "zbxmodules.h"
#include "module.h"

#include "zbxstr.h"
#include "zbxsysinfo.h"
#include "zbxalgo.h"

#define ZBX_MODULE_FUNC_INIT			"zbx_module_init"
#define ZBX_MODULE_FUNC_API_VERSION		"zbx_module_api_version"
#define ZBX_MODULE_FUNC_ITEM_LIST		"zbx_module_item_list"
#define ZBX_MODULE_FUNC_ITEM_PROCESS		"zbx_module_item_process"
#define ZBX_MODULE_FUNC_ITEM_TIMEOUT		"zbx_module_item_timeout"
#define ZBX_MODULE_FUNC_UNINIT			"zbx_module_uninit"
#define ZBX_MODULE_FUNC_HISTORY_WRITE_CBS	"zbx_module_history_write_cbs"

static zbx_vector_ptr_t	modules;

zbx_history_float_cb_t		*history_float_cbs = NULL;
zbx_history_integer_cb_t	*history_integer_cbs = NULL;
zbx_history_string_cb_t		*history_string_cbs = NULL;
zbx_history_text_cb_t		*history_text_cbs = NULL;
zbx_history_log_cb_t		*history_log_cbs = NULL;

/******************************************************************************
 *                                                                            *
 * Purpose: add items supported by module                                     *
 *                                                                            *
 * Parameters: metrics       - list of items supported by module              *
 *             error         - error buffer                                   *
 *             max_error_len - error buffer size                              *
 *                                                                            *
 * Return value: SUCCEED - all module items were added or there were none     *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	zbx_register_module_items(zbx_metric_t *metrics, char *error, size_t max_error_len)
{
	int	i;

	for (i = 0; NULL != metrics[i].key; i++)
	{
		/* accept only CF_HAVEPARAMS flag from module items */
		metrics[i].flags &= CF_HAVEPARAMS;
		/* the flag means that the items comes from a loadable module */
		metrics[i].flags |= CF_MODULE;

		if (SUCCEED != zbx_add_metric(&metrics[i], error, max_error_len))
			return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: add module to the list of successfully loaded modules             *
 *                                                                            *
 ******************************************************************************/
static zbx_module_t	*zbx_register_module(void *lib, char *name)
{
	zbx_module_t	*module;

	module = (zbx_module_t *)zbx_malloc(NULL, sizeof(zbx_module_t));
	module->lib = lib;
	module->name = zbx_strdup(NULL, name);
	zbx_vector_ptr_append(&modules, module);

	return module;
}

/******************************************************************************
 *                                                                            *
 * Purpose: registers callback functions for history export                   *
 *                                                                            *
 * Parameters: module            - module pointer for later reference         *
 *             history_write_cbs - callbacks                                  *
 *                                                                            *
 ******************************************************************************/
static void	zbx_register_history_write_cbs(zbx_module_t *module, ZBX_HISTORY_WRITE_CBS history_write_cbs)
{
	if (NULL != history_write_cbs.history_float_cb)
	{
		size_t	j = 0;

		if (NULL == history_float_cbs)
		{
			history_float_cbs = (zbx_history_float_cb_t *)zbx_malloc(history_float_cbs,
					sizeof(zbx_history_float_cb_t));
			history_float_cbs[0].module = NULL;
		}

		while (NULL != history_float_cbs[j].module)
			j++;

		history_float_cbs = (zbx_history_float_cb_t *)zbx_realloc(history_float_cbs, (j + 2) *
				sizeof(zbx_history_float_cb_t));
		history_float_cbs[j].module = module;
		history_float_cbs[j].history_float_cb = history_write_cbs.history_float_cb;
		history_float_cbs[j + 1].module = NULL;
	}

	if (NULL != history_write_cbs.history_integer_cb)
	{
		size_t	j = 0;

		if (NULL == history_integer_cbs)
		{
			history_integer_cbs = (zbx_history_integer_cb_t *)zbx_malloc(history_integer_cbs,
					sizeof(zbx_history_integer_cb_t));
			history_integer_cbs[0].module = NULL;
		}

		while (NULL != history_integer_cbs[j].module)
			j++;

		history_integer_cbs = (zbx_history_integer_cb_t *)zbx_realloc(history_integer_cbs, (j + 2) *
				sizeof(zbx_history_integer_cb_t));
		history_integer_cbs[j].module = module;
		history_integer_cbs[j].history_integer_cb = history_write_cbs.history_integer_cb;
		history_integer_cbs[j + 1].module = NULL;
	}

	if (NULL != history_write_cbs.history_string_cb)
	{
		size_t	j = 0;

		if (NULL == history_string_cbs)
		{
			history_string_cbs = (zbx_history_string_cb_t *)zbx_malloc(history_string_cbs,
					sizeof(zbx_history_string_cb_t));
			history_string_cbs[0].module = NULL;
		}

		while (NULL != history_string_cbs[j].module)
			j++;

		history_string_cbs = (zbx_history_string_cb_t *)zbx_realloc(history_string_cbs, (j + 2) *
				sizeof(zbx_history_string_cb_t));
		history_string_cbs[j].module = module;
		history_string_cbs[j].history_string_cb = history_write_cbs.history_string_cb;
		history_string_cbs[j + 1].module = NULL;
	}

	if (NULL != history_write_cbs.history_text_cb)
	{
		size_t	j = 0;

		if (NULL == history_text_cbs)
		{
			history_text_cbs = (zbx_history_text_cb_t *)zbx_malloc(history_text_cbs,
					sizeof(zbx_history_text_cb_t));
			history_text_cbs[0].module = NULL;
		}

		while (NULL != history_text_cbs[j].module)
			j++;

		history_text_cbs = (zbx_history_text_cb_t *)zbx_realloc(history_text_cbs, (j + 2) *
				sizeof(zbx_history_text_cb_t));
		history_text_cbs[j].module = module;
		history_text_cbs[j].history_text_cb = history_write_cbs.history_text_cb;
		history_text_cbs[j + 1].module = NULL;
	}

	if (NULL != history_write_cbs.history_log_cb)
	{
		size_t	j = 0;

		if (NULL == history_log_cbs)
		{
			history_log_cbs = (zbx_history_log_cb_t *)zbx_malloc(history_log_cbs,
					sizeof(zbx_history_log_cb_t));
			history_log_cbs[0].module = NULL;
		}

		while (NULL != history_log_cbs[j].module)
			j++;

		history_log_cbs = (zbx_history_log_cb_t *)zbx_realloc(history_log_cbs, (j + 2) *
				sizeof(zbx_history_log_cb_t));
		history_log_cbs[j].module = module;
		history_log_cbs[j].history_log_cb = history_write_cbs.history_log_cb;
		history_log_cbs[j + 1].module = NULL;
	}
}

static int	zbx_module_compare_func(const void *d1, const void *d2)
{
	const zbx_module_t	*m1 = *(const zbx_module_t * const *)d1;
	const zbx_module_t	*m2 = *(const zbx_module_t * const *)d2;

	ZBX_RETURN_IF_NOT_EQUAL(m1->lib, m2->lib);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: load loadable module                                              *
 *                                                                            *
 * Parameters: path    - directory where modules are located                  *
 *             name    - module name                                          *
 *             timeout - timeout in seconds for processing of items by module *
 *                                                                            *
 * Return value: SUCCEED - module was successfully loaded or found amongst    *
 *                         previously loaded                                  *
 *               FAIL    - loading of module failed                           *
 *                                                                            *
 ******************************************************************************/
static int	zbx_load_module(const char *path, char *name, int timeout)
{
	void			*lib;
	char			full_name[MAX_STRING_LEN], error[MAX_STRING_LEN];
	int			(*func_init)(void), (*func_version)(void), version;
	zbx_metric_t		*(*func_list)(void);
	void			(*func_timeout)(int);
	ZBX_HISTORY_WRITE_CBS	(*func_history_write_cbs)(void);
	zbx_module_t		*module, module_tmp;

	if ('/' != *name)
		zbx_snprintf(full_name, sizeof(full_name), "%s/%s", path, name);
	else
		zbx_snprintf(full_name, sizeof(full_name), "%s", name);

	zabbix_log(LOG_LEVEL_DEBUG, "loading module \"%s\"", full_name);

	if (NULL == (lib = dlopen(full_name, RTLD_NOW)))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot load module \"%s\": %s", name, dlerror());
		return FAIL;
	}

	module_tmp.lib = lib;
	if (FAIL != zbx_vector_ptr_search(&modules, &module_tmp, zbx_module_compare_func))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "module \"%s\" has already been loaded", name);
		return SUCCEED;
	}

	if (NULL == (*(void **)(&func_version) = dlsym(lib, ZBX_MODULE_FUNC_API_VERSION)))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot find \"" ZBX_MODULE_FUNC_API_VERSION "()\""
				" function in module \"%s\": %s", name, dlerror());
		goto fail;
	}

	if (ZBX_MODULE_API_VERSION != (version = func_version()))
	{
		zabbix_log(LOG_LEVEL_CRIT, "unsupported module \"%s\" version: %d", name, version);
		goto fail;
	}

	if (NULL == (*(void **)(&func_init) = dlsym(lib, ZBX_MODULE_FUNC_INIT)))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "cannot find \"" ZBX_MODULE_FUNC_INIT "()\""
				" function in module \"%s\": %s", name, dlerror());
	}
	else if (ZBX_MODULE_OK != func_init())
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot initialize module \"%s\"", name);
		goto fail;
	}

	if (NULL == (*(void **)(&func_list) = dlsym(lib, ZBX_MODULE_FUNC_ITEM_LIST)))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "cannot find \"" ZBX_MODULE_FUNC_ITEM_LIST "()\""
				" function in module \"%s\": %s", name, dlerror());
	}
	else
	{
		if (SUCCEED != zbx_register_module_items(func_list(), error, sizeof(error)))
		{
			zabbix_log(LOG_LEVEL_CRIT, "cannot load module \"%s\": %s", name, error);
			goto fail;
		}

		if (NULL == (*(void **)(&func_timeout) = dlsym(lib, ZBX_MODULE_FUNC_ITEM_TIMEOUT)))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "cannot find \"" ZBX_MODULE_FUNC_ITEM_TIMEOUT "()\""
					" function in module \"%s\": %s", name, dlerror());
		}
		else
			func_timeout(timeout);
	}

	/* module passed validation and can now be registered */
	module = zbx_register_module(lib, name);

	if (NULL == (*(void **)(&func_history_write_cbs) = dlsym(lib, ZBX_MODULE_FUNC_HISTORY_WRITE_CBS)))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "cannot find \"" ZBX_MODULE_FUNC_HISTORY_WRITE_CBS "()\""
				" function in module \"%s\": %s", name, dlerror());
	}
	else
		zbx_register_history_write_cbs(module, func_history_write_cbs());

	return SUCCEED;
fail:
	dlclose(lib);

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: load loadable modules (dynamic libraries)                         *
 *                                                                            *
 * Parameters: path - directory where modules are located                     *
 *             file_names - list of module names                              *
 *             timeout - timeout in seconds for processing of items by module *
 *             verbose - output list of loaded modules                        *
 *                                                                            *
 * Return value: SUCCEED - all modules are successfully loaded                *
 *               FAIL - loading of modules failed                             *
 *                                                                            *
 ******************************************************************************/
int	zbx_load_modules(const char *path, char **file_names, int timeout, int verbose)
{
	char	**file_name;
	int	ret = SUCCEED;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_vector_ptr_create(&modules);

	if (NULL == *file_names)
		goto out;

	for (file_name = file_names; NULL != *file_name; file_name++)
	{
		if (SUCCEED != (ret = zbx_load_module(path, *file_name, timeout)))
			goto out;
	}

	if (0 != verbose)
	{
		char	*buffer;
		int	i = 0;

		/* if execution reached this point at least one module was loaded successfully */
		buffer = zbx_strdcat(NULL, ((zbx_module_t *)modules.values[i++])->name);

		while (i < modules.values_num)
		{
			buffer = zbx_strdcat(buffer, ", ");
			buffer = zbx_strdcat(buffer, ((zbx_module_t *)modules.values[i++])->name);
		}

		zabbix_log(LOG_LEVEL_WARNING, "loaded modules: %s", buffer);
		zbx_free(buffer);
	}
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: unload module and free allocated resources                        *
 *                                                                            *
 ******************************************************************************/
static void	zbx_unload_module(void *data)
{
	zbx_module_t	*module = (zbx_module_t *)data;
	int		(*func_uninit)(void);

	if (NULL == (*(void **)(&func_uninit) = dlsym(module->lib, ZBX_MODULE_FUNC_UNINIT)))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "cannot find \"" ZBX_MODULE_FUNC_UNINIT "()\""
				" function in module \"%s\": %s", module->name, dlerror());
	}
	else if (ZBX_MODULE_OK != func_uninit())
		zabbix_log(LOG_LEVEL_WARNING, "uninitialization of module \"%s\" failed", module->name);

	dlclose(module->lib);
	zbx_free(module->name);
	zbx_free(module);
}

/******************************************************************************
 *                                                                            *
 * Purpose: Unload already loaded loadable modules (dynamic libraries).       *
 *          It is called on process shutdown.                                 *
 *                                                                            *
 ******************************************************************************/
void	zbx_unload_modules(void)
{
	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_free(history_float_cbs);
	zbx_free(history_integer_cbs);
	zbx_free(history_string_cbs);
	zbx_free(history_text_cbs);
	zbx_free(history_log_cbs);

	zbx_vector_ptr_clear_ext(&modules, zbx_unload_module);
	zbx_vector_ptr_destroy(&modules);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}