/* 
** 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 "service.h"

#include "cfg.h"
#include "log.h"
#include "alias.h"
#include "zbxconf.h"
#include "perfmon.h"

static int ZabbixRemoveEventSource(void);
static int ZabbixInstallEventSource(char *path);

#define uninit() { zbx_on_exit(); }

/*
 * Static data
 */

static	SERVICE_STATUS		serviceStatus;
static	SERVICE_STATUS_HANDLE	serviceHandle;

int application_status = ZBX_APP_RUNNED;

static void	parent_signal_handler(int sig)
{
	switch(sig)
	{
	case SIGINT:
	case SIGTERM:
		zabbix_log( LOG_LEVEL_INFORMATION, "Got signal. Exiting ...");
		uninit();
		ExitProcess( FAIL );
		break;
	}
}

/*
 * ZABBIX service control handler
 */

static VOID WINAPI ServiceCtrlHandler(DWORD ctrlCode)
{
	int do_exit = 0;

	serviceStatus.dwServiceType		= SERVICE_WIN32_OWN_PROCESS;
	serviceStatus.dwCurrentState		= SERVICE_RUNNING;
	serviceStatus.dwControlsAccepted	= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
	serviceStatus.dwWin32ExitCode		= 0;
	serviceStatus.dwServiceSpecificExitCode	= 0;
	serviceStatus.dwCheckPoint		= 0;
	serviceStatus.dwWaitHint		= 0;

	switch(ctrlCode)
	{
		case SERVICE_CONTROL_STOP:
		case SERVICE_CONTROL_SHUTDOWN:
			serviceStatus.dwCurrentState	= SERVICE_STOP_PENDING;
			serviceStatus.dwWaitHint	= 4000;
			SetServiceStatus(serviceHandle,&serviceStatus);

			ZBX_DO_EXIT();

			/* Allow other threads to terminate */
			zbx_sleep(1);

			serviceStatus.dwCurrentState	= SERVICE_STOPPED;
			serviceStatus.dwWaitHint	= 0;
			serviceStatus.dwCheckPoint	= 0; 
			serviceStatus.dwWin32ExitCode	= 0; 
			break;
		default:
			break;
	}

	SetServiceStatus(serviceHandle, &serviceStatus);
}

/*
 * The entry point for a ZABBIX service.
 */

static VOID WINAPI ServiceEntry(DWORD argc,LPTSTR *argv)
{
	serviceHandle = RegisterServiceCtrlHandler(ZABBIX_SERVICE_NAME, ServiceCtrlHandler);

	/* Now we start service initialization */
	serviceStatus.dwServiceType		= SERVICE_WIN32_OWN_PROCESS;
	serviceStatus.dwCurrentState		= SERVICE_START_PENDING;
	serviceStatus.dwControlsAccepted	= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
	serviceStatus.dwWin32ExitCode		= 0;
	serviceStatus.dwServiceSpecificExitCode	= 0;
	serviceStatus.dwCheckPoint		= 0;
	serviceStatus.dwWaitHint		= 2000;

	SetServiceStatus(serviceHandle, &serviceStatus);

	/* Now service is running */
	serviceStatus.dwCurrentState	= SERVICE_RUNNING;
	serviceStatus.dwWaitHint	= 0;
	SetServiceStatus(serviceHandle, &serviceStatus);

	MAIN_ZABBIX_ENTRY();
}


/*
 * Initialize service
 */

void service_start(void)
{
	int c = 0;
	static SERVICE_TABLE_ENTRY serviceTable[] = {
		{ ZABBIX_SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceEntry },
		{ NULL,NULL } 
		};

	/* Create synchronization stuff */
/*	eventShutdown = CreateEvent(NULL,TRUE,FALSE,NULL); */

	if (!StartServiceCtrlDispatcher(serviceTable))
	{
		if(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == GetLastError())
		{
			zbx_error("\n\n\t!!!ATTENTION!!! ZABBIX Agent runned as a console application. !!!ATTENTION!!!\n");
			MAIN_ZABBIX_ENTRY();
		}
		else
		{
			zbx_error("StartServiceCtrlDispatcher() failed: %s", strerror_from_system(GetLastError()));
		}

	}

/*	CloseHandle(eventShutdown); */
}


/*
 * Create service
 */

int ZabbixCreateService(char *path)
{
#define MAX_CMD_LEN MAX_PATH*2

	SC_HANDLE		mgr,service;
	SERVICE_DESCRIPTION	sd;
	LPTSTR			szDesc = TEXT("Provides system monitoring");
	char			execName[MAX_PATH];
	char			configFile[MAX_PATH];
	char			cmdLine[MAX_CMD_LEN];
	int			ret = SUCCEED;

	_fullpath(execName, path, MAX_PATH);

	if( NULL == strstr(execName, ".exe") )
		zbx_strlcat(execName, ".exe", sizeof(execName));

	mgr = OpenSCManager(NULL,NULL,GENERIC_WRITE);
	if ( NULL == mgr )
	{
		zbx_error("ERROR: Cannot connect to Service Manager [%s]",strerror_from_system(GetLastError()));
		return FAIL;
	}

	if(NULL == CONFIG_FILE)
	{
		zbx_snprintf(cmdLine, sizeof(cmdLine), "\"%s\"", execName);
	}
	else
	{
		_fullpath(configFile, CONFIG_FILE, MAX_PATH);
		zbx_snprintf(cmdLine, sizeof(cmdLine), "\"%s\" --config \"%s\"", execName, configFile);
	}

	service = CreateService(mgr,
		ZABBIX_SERVICE_NAME,
		ZABBIX_EVENT_SOURCE,
		GENERIC_READ,
		SERVICE_WIN32_OWN_PROCESS,
		SERVICE_AUTO_START,
		SERVICE_ERROR_NORMAL,
		cmdLine,NULL,NULL,NULL,NULL,NULL);

	if (service == NULL)
	{
		DWORD code = GetLastError();

		if (ERROR_SERVICE_EXISTS == code)
		{
			zbx_error("ERROR: Service named '%s' already exist", ZABBIX_SERVICE_NAME);
		}
		else
		{
			zbx_error("ERROR: Cannot create service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(code));
		}
		ret = FAIL;
	}
	else
	{
		zbx_error("Service \"%s\" installed successfully.", ZABBIX_SERVICE_NAME);
		CloseServiceHandle(service);
    
		/* Updates the service description */
		service = OpenService(mgr, ZABBIX_SERVICE_NAME, SERVICE_CHANGE_CONFIG);
		sd.lpDescription = szDesc;
		if (0 == ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &sd))
			zbx_error("Service description update failed. %x", GetLastError());
		CloseServiceHandle(service);
	}
	CloseServiceHandle(mgr);

	if(ret != SUCCEED)
	{
		return FAIL;
	}

	return ZabbixInstallEventSource(execName);
}


/*
 * Remove service
 */

int ZabbixRemoveService(void)
{
	SC_HANDLE mgr,service;
	int ret = SUCCEED;

	mgr=OpenSCManager(NULL,NULL,GENERIC_WRITE);
	if (mgr==NULL)
	{
		zbx_error("ERROR: Cannot connect to Service Manager [%s]",strerror_from_system(GetLastError()));
		return FAIL;
	}

	service=OpenService(mgr,ZABBIX_SERVICE_NAME,DELETE);
	if (service==NULL)
	{
		zbx_error("ERROR: Cannot open service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(GetLastError()));
		ret = FAIL;
	}
	else
	{
		if (DeleteService(service))
		{
			zbx_error("Service \"%s\" uninstalled successfully", ZABBIX_SERVICE_NAME);
		}
		else
		{
			zbx_error("ERROR: Cannot remove service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(GetLastError()));
			ret = FAIL;
		}

		CloseServiceHandle(service);
	}

	CloseServiceHandle(mgr);

	if(ret == SUCCEED)
	{
		ret = ZabbixRemoveEventSource();
	}

	return ret;
}


/*
 * Start service
 */

int ZabbixStartService(void)
{
	SC_HANDLE mgr,service;
	int ret = SUCCEED;

	mgr=OpenSCManager(NULL,NULL,GENERIC_WRITE);

	if (mgr==NULL)
	{
		zbx_error("ERROR: Cannot connect to Service Manager [%s]",strerror_from_system(GetLastError()));
		return FAIL;
	}

	service=OpenService(mgr,ZABBIX_SERVICE_NAME,SERVICE_START);

	if (service==NULL)
	{
		zbx_error("ERROR: Cannot open service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(GetLastError()));
		ret = FAIL;
	}
	else
	{
		if (StartService(service,0,NULL))
		{
			zbx_error("Service \"%s\" started successfully.", ZABBIX_SERVICE_NAME);
		}
		else
		{
			zbx_error("ERROR: Cannot start service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(GetLastError()));
			ret = FAIL;
		}

		CloseServiceHandle(service);
	}

	CloseServiceHandle(mgr);
	return ret;
}


/*
 * Stop service
 */

int ZabbixStopService(void)
{
	SC_HANDLE mgr,service;
	int ret = SUCCEED;

	mgr=OpenSCManager(NULL,NULL,GENERIC_WRITE);
	if (mgr==NULL)
	{
		zbx_error("ERROR: Cannot connect to Service Manager [%s]",strerror_from_system(GetLastError()));
		return FAIL;
	}

	service=OpenService(mgr,ZABBIX_SERVICE_NAME,SERVICE_STOP);
	if (service==NULL)
	{
		zbx_error("ERROR: Cannot open service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(GetLastError()));
		ret = FAIL;
	}
	else
	{
		SERVICE_STATUS status;

		if (ControlService(service,SERVICE_CONTROL_STOP,&status))
		{
			zbx_error("Service \"%s\" stopped successfully.", ZABBIX_SERVICE_NAME);
		}
		else
		{
			zbx_error("ERROR: Cannot stop service named '%s' [%s]", ZABBIX_SERVICE_NAME, strerror_from_system(GetLastError()));
			ret = FAIL;
		}

		CloseServiceHandle(service);
	}

	CloseServiceHandle(mgr);
	return ret;
}


/*
 * Install event source
 */
static int ZabbixInstallEventSource(char *path)
{
	HKEY	hKey;
	DWORD	dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
	char	execName[MAX_PATH];
	char	regkey[MAX_STRING_LEN];

	_fullpath(execName, path, MAX_PATH);

	zbx_snprintf(regkey, sizeof(regkey), "System\\CurrentControlSet\\Services\\EventLog\\System\\%s", ZABBIX_EVENT_SOURCE);

	if (ERROR_SUCCESS != RegCreateKeyEx(HKEY_LOCAL_MACHINE,
		regkey,
		0,
		NULL,
		REG_OPTION_NON_VOLATILE,
		KEY_SET_VALUE,
		NULL,
		&hKey,
		NULL))
	{
		zbx_error("Unable to create registry key: %s",strerror_from_system(GetLastError()));
		return FAIL;
	}

	RegSetValueEx(hKey,"TypesSupported",0,REG_DWORD,(BYTE *)&dwTypes,sizeof(DWORD));
	RegSetValueEx(hKey,"EventMessageFile",0,REG_EXPAND_SZ,(BYTE *)execName,(DWORD)strlen(execName)+1);

	RegCloseKey(hKey);
	zbx_error("Event source \"%s\" installed successfully.", ZABBIX_EVENT_SOURCE);

	return SUCCEED;
}


/*
 * Remove event source
 */

static int ZabbixRemoveEventSource(void)
{
	char	regkey[MAX_STRING_LEN];

	zbx_snprintf(regkey, sizeof(regkey), "System\\CurrentControlSet\\Services\\EventLog\\System\\%s", ZABBIX_EVENT_SOURCE);

	if (ERROR_SUCCESS == RegDeleteKey(HKEY_LOCAL_MACHINE, regkey))
	{
		zbx_error("Event source \"%s\" uninstalled successfully.", ZABBIX_EVENT_SOURCE);
	}
	else
	{
		zbx_error("Unable to uninstall event source \"%s\": [%s]", ZABBIX_EVENT_SOURCE, strerror_from_system(GetLastError()));
		return FAIL;
	}

	return SUCCEED;
}

void	init_main_process(void)
{
	signal( SIGINT,  parent_signal_handler);
	signal( SIGTERM, parent_signal_handler );
}
