/* 
** ZABBIX
** Copyright (C) 2000-2005 SIA Zabbix
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**/

#include "common.h"

#include "cfg.h"
#include "db.h"
#include "log.h"
#include "zlog.h"
#include "sysinfo.h"
#include "zbxserver.h"
#include "zbxicmpping.h"

#include "pinger.h"
#include "dbcache.h"

static zbx_process_t	zbx_process;
static int		pinger_num;

static ZBX_FPING_HOST	*hosts = NULL;
static int		hosts_allocated = 4;

/******************************************************************************
 *                                                                            *
 * Function: process_value                                                    *
 *                                                                            *
 * Purpose: process new item value                                            *
 *                                                                            *
 * Parameters: key - item key                                                 *
 *             host - host name                                               *
 *             value - new value of the item                                  *
 *                                                                            *
 * Return value: SUCCEED - new value sucesfully processed                     *
 *               FAIL - otherwise                                             *
 *                                                                            *
 * Author: Alexei Vladishev                                                   *
 *                                                                            *
 * Comments: can be done in process_data()                                    *
 *                                                                            *
 ******************************************************************************/
static void process_value(char *key, ZBX_FPING_HOST *host, zbx_uint64_t *value_ui64, double *value_dbl, int now, int *items, int ping_result, const char *error)
{
	DB_RESULT	result;
	DB_ROW		row;
	DB_ITEM		item;
	AGENT_RESULT	value;
	char		istatus[16];

	assert(value_ui64 || value_dbl);

	zabbix_log(LOG_LEVEL_DEBUG, "In process_value(%s@%s)",
			key,
			host->addr);

	if (0 != CONFIG_REFRESH_UNSUPPORTED)
		zbx_snprintf(istatus, sizeof(istatus), "%d,%d",
				ITEM_STATUS_ACTIVE,
				ITEM_STATUS_NOTSUPPORTED);
	else
		zbx_snprintf(istatus, sizeof(istatus), "%d",
				ITEM_STATUS_ACTIVE);

	result = DBselect("select %s where " ZBX_SQL_MOD(h.hostid,%d) "=%d and h.status=%d and h.hostid=i.hostid"
			" and h.proxy_hostid=0 and h.useip=%d and h.%s='%s' and i.key_='%s' and i.status in (%s)"
			" and i.type=%d and i.nextcheck<=%d" DB_NODE,
			ZBX_SQL_ITEM_SELECT,
			CONFIG_PINGER_FORKS,
			pinger_num - 1,
			HOST_STATUS_MONITORED,
			host->useip,
			host->useip ? "ip" : "dns",
			host->addr,
			key,
			istatus,
			ITEM_TYPE_SIMPLE,
			now,
			DBnode_local("h.hostid"));

	while (NULL != (row = DBfetch(result))) {
		DBget_item_from_db(&item, row);

		if (ping_result == NOTSUPPORTED)
		{
			if (0 == CONFIG_DBSYNCER_FORKS)
			{
				DBbegin();
		
				DBupdate_item_status_to_notsupported(&item, now, error);

				DBcommit();
			}
			else
				DCadd_nextcheck(&item, now, 0, error);
		}
		else
		{
			init_result(&value);

			if (NULL != value_ui64)
			{
				SET_UI64_RESULT(&value, *value_ui64);
			}
			else
			{
				SET_DBL_RESULT(&value, *value_dbl);
			}

			if (0 == CONFIG_DBSYNCER_FORKS)
			{
				DBbegin();
				switch (zbx_process) {
				case ZBX_PROCESS_SERVER:
					process_new_value(&item, &value, now);
					update_triggers(item.itemid);
					break;
				case ZBX_PROCESS_PROXY:
					proxy_process_new_value(&item, &value, now);
					break;
				}
				DBcommit();
			}
			else
			{
				switch (zbx_process) {
				case ZBX_PROCESS_SERVER:
					process_new_value(&item, &value, now);
					break;
				case ZBX_PROCESS_PROXY:
					proxy_process_new_value(&item, &value, now);
					break;
				}
				DCadd_nextcheck(&item, now, 0, NULL);
			}

			free_result(&value);
		}

		(*items)++;
	}
	DBfree_result(result);
}

/******************************************************************************
 *                                                                            *
 * Function: process_values                                                   *
 *                                                                            *
 * Purpose: process new item value                                            *
 *                                                                            *
 * Parameters:                                                                *
 *                                                                            *
 * Return value: successfully processed items                                 *
 *                                                                            *
 * Author: Alexei Vladishev                                                   *
 *                                                                            *
 * Comments: can be done in process_data()                                    *
 *                                                                            *
 ******************************************************************************/
static int process_values(ZBX_FPING_HOST *hosts, int hosts_count, int now)
{
	int		i, items = 0, ping_result;
	char		error[ITEM_ERROR_LEN_MAX];

	zabbix_log(LOG_LEVEL_DEBUG, "In process_values()");

	zbx_setproctitle("pinger [pinging hosts]");

	ping_result = do_ping(hosts, hosts_count, error, sizeof(error));

	if (0 != CONFIG_DBSYNCER_FORKS)
		DCinit_nextchecks();

	for (i = 0; i < hosts_count; i++) {
		if (ping_result == NOTSUPPORTED)
			zabbix_log(LOG_LEVEL_DEBUG, "Host [%s] %s",
					hosts[i].addr,
					error);
		else
			zabbix_log(LOG_LEVEL_DEBUG, "Host [%s] alive [" ZBX_FS_UI64 "] " ZBX_FS_DBL " sec.",
					hosts[i].addr,
					hosts[i].alive,
					hosts[i].sec);

		process_value(SERVER_ICMPPING_KEY, &hosts[i], &hosts[i].alive, NULL, now, &items, ping_result, error);
		process_value(SERVER_ICMPPINGSEC_KEY, &hosts[i], NULL, &hosts[i].sec, now, &items, ping_result, error);
	}

	if (0 != CONFIG_DBSYNCER_FORKS)
		DCflush_nextchecks();

	return items;
}

/******************************************************************************
 *                                                                            *
 * Function: get_pinger_hosts                                                 *
 *                                                                            *
 * Purpose: creates buffer which contains list of hosts to ping               *
 *                                                                            *
 * Parameters:                                                                *
 *                                                                            *
 * Return value: SUCCEED - the file was created succesfully                   *
 *               FAIL - otherwise                                             *
 *                                                                            *
 * Author: Alexei Vladishev, Aleksander Vladishev                             *
 *                                                                            *
 * Comments:                                                                  *
 *                                                                            *
 ******************************************************************************/
static int	get_pinger_hosts(int now)
{
	DB_RESULT	result;
	DB_ROW		row;
	int		hosts_count = 0;
	char		istatus[16];

	zabbix_log(LOG_LEVEL_DEBUG, "In get_pinger_hosts()");

	if (0 != CONFIG_REFRESH_UNSUPPORTED)
		zbx_snprintf(istatus, sizeof(istatus), "%d,%d",
				ITEM_STATUS_ACTIVE,
				ITEM_STATUS_NOTSUPPORTED);
	else
		zbx_snprintf(istatus, sizeof(istatus), "%d",
				ITEM_STATUS_ACTIVE);

	/* Select hosts monitored by IP */
	result = DBselect("select distinct h.ip from hosts h,items i where " ZBX_SQL_MOD(h.hostid,%d) "=%d"
			" and i.hostid=h.hostid and h.proxy_hostid=0 and h.status=%d and i.key_ in ('%s','%s')"
			" and i.type=%d and i.status in (%s) and h.useip=1 and i.nextcheck<=%d" DB_NODE, 
			CONFIG_PINGER_FORKS,
			pinger_num - 1,
			HOST_STATUS_MONITORED,
			SERVER_ICMPPING_KEY, SERVER_ICMPPINGSEC_KEY,
			ITEM_TYPE_SIMPLE,
			istatus,
			now,
			DBnode_local("h.hostid"));

	while (NULL != (row = DBfetch(result))) {
		if (hosts_count == hosts_allocated) {
			hosts_allocated *= 2;
			hosts = zbx_realloc(hosts, hosts_allocated * sizeof(ZBX_FPING_HOST));
		}

		memset(&hosts[hosts_count], '\0', sizeof(ZBX_FPING_HOST));
		strscpy(hosts[hosts_count].addr, row[0]);
		hosts[hosts_count].useip = 1;

		zabbix_log(LOG_LEVEL_DEBUG, "IP [%s]", hosts[hosts_count].addr);

		hosts_count++;
	}
	DBfree_result(result);

	/* Select hosts monitored by hostname */
	result = DBselect("select distinct h.dns from hosts h,items i where " ZBX_SQL_MOD(h.hostid,%d) "=%d"
			" and i.hostid=h.hostid and h.proxy_hostid=0 and h.status=%d and i.key_ in ('%s','%s')"
			" and i.type=%d and i.status in (%s) and h.useip=0 and i.nextcheck<=%d" DB_NODE,
			CONFIG_PINGER_FORKS,
			pinger_num - 1,
			HOST_STATUS_MONITORED,
			SERVER_ICMPPING_KEY, SERVER_ICMPPINGSEC_KEY,
			ITEM_TYPE_SIMPLE,
			istatus,
			now,
			DBnode_local("h.hostid"));

	while (NULL != (row = DBfetch(result))) {
		if (hosts_count == hosts_allocated) {
			hosts_allocated *= 2;
			hosts = zbx_realloc(hosts, hosts_allocated * sizeof(ZBX_FPING_HOST));
		}

		memset(&hosts[hosts_count], '\0', sizeof(ZBX_FPING_HOST));
		strscpy(hosts[hosts_count].addr, row[0]);
		hosts[hosts_count].useip = 0;

		zabbix_log(LOG_LEVEL_DEBUG, "DNS name [%s]", hosts[hosts_count].addr);

		hosts_count++;
	}
	DBfree_result(result);

	zabbix_log(LOG_LEVEL_DEBUG, "End of get_pinger_hosts():%d", hosts_count);

	return hosts_count;
}

/******************************************************************************
 *                                                                            *
 * Function: get_minnextcheck                                                 *
 *                                                                            *
 * Purpose: calculate when we have to process earliest simple check           *
 *                                                                            *
 * Parameters: now - current timestamp                                        *
 *                                                                            *
 * Return value: timestamp of earliest check or -1 if not found               *
 *                                                                            *
 * Author: Aleksander Vladishev                                               *
 *                                                                            *
 * Comments:                                                                  *
 *                                                                            *
 ******************************************************************************/
static int get_minnextcheck()
{
	DB_RESULT	result;
	DB_ROW		row;
	int		res;
	char		istatus[16];

	zabbix_log(LOG_LEVEL_DEBUG, "In get_minnextcheck()");

	if (0 != CONFIG_REFRESH_UNSUPPORTED)
		zbx_snprintf(istatus, sizeof(istatus), "%d,%d",
				ITEM_STATUS_ACTIVE,
				ITEM_STATUS_NOTSUPPORTED);
	else
		zbx_snprintf(istatus, sizeof(istatus), "%d",
				ITEM_STATUS_ACTIVE);

	result = DBselect("select count(*),min(i.nextcheck) from items i,hosts h where " ZBX_SQL_MOD(h.hostid,%d) "=%d"
			" and h.status=%d and h.hostid=i.hostid and h.proxy_hostid=0 and i.key_ in ('%s','%s')"
			" and i.type=%d and i.status in (%s)" DB_NODE, 
			CONFIG_PINGER_FORKS,
			pinger_num - 1,
			HOST_STATUS_MONITORED,
			SERVER_ICMPPING_KEY, SERVER_ICMPPINGSEC_KEY,
			ITEM_TYPE_SIMPLE,
			istatus,
			DBnode_local("h.hostid"));

	if (NULL == (row = DBfetch(result)) || DBis_null(row[0]) == SUCCEED || DBis_null(row[1]) == SUCCEED)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "No items to update for minnextcheck.");
		res = FAIL; 
	}
	else
	{
		if (atoi(row[0]) == 0)
		{
			res = FAIL;
		}
		else
		{
			res = atoi(row[1]);
		}
	}
	DBfree_result(result);

	return res;
}

/******************************************************************************
 *                                                                            *
 * Function: main_pinger_loop                                                 *
 *                                                                            *
 * Purpose: periodically perform ICMP pings                                   *
 *                                                                            *
 * Parameters:                                                                *
 *                                                                            *
 * Return value:                                                              *
 *                                                                            *
 * Author: Alexei Vladishev                                                   *
 *                                                                            *
 * Comments: never returns                                                    *
 *                                                                            *
 ******************************************************************************/
void main_pinger_loop(zbx_process_t p, int num)
{
	int		now, nextcheck, sleeptime;
	int		hosts_count, items;
	double		sec;

	zabbix_log(LOG_LEVEL_DEBUG, "In main_pinger_loop(num:%d)",
			num);

	zbx_process = p;
	pinger_num = num;

	hosts = zbx_malloc(hosts, hosts_allocated * sizeof(ZBX_FPING_HOST));

	zbx_setproctitle("pinger [connecting to the database]");

	DBconnect(ZBX_DB_CONNECT_NORMAL);

	for (;;)
	{
		now = time(NULL);
		sec = zbx_time();

		items = 0;
		if (0 < (hosts_count = get_pinger_hosts(now)))
			items = process_values(hosts, hosts_count, now); 
	
		sec = zbx_time() - sec;

		nextcheck = get_minnextcheck();

		if (FAIL == nextcheck)
			sleeptime = CONFIG_PINGER_FREQUENCY;
		else
		{
			sleeptime = nextcheck - time(NULL);
			if (sleeptime < 0)
				sleeptime = 0;
			else if (sleeptime > CONFIG_PINGER_FREQUENCY)
				sleeptime = CONFIG_PINGER_FREQUENCY;
		}

		zabbix_log(LOG_LEVEL_DEBUG, "Pinger spent " ZBX_FS_DBL " seconds while processing %d items."
				" Nextcheck after %d sec.",
				sec,
				items,
				sleeptime);

		if (sleeptime > 0) {
			zbx_setproctitle("pinger [sleeping for %d seconds]", 
					sleeptime);

			sleep(sleeptime);
		}
	}

	/* Never reached */
	DBclose();
}
