/*-------------------------------------------------------------------------
 *
 * service.c
 *	  Microsoft Windows Win32 Service Integration
 *
 * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  $PostgreSQL$
 *
 *-------------------------------------------------------------------------
 */

#include <signal.h>
#include "postgres.h"

#define assert(a)

static SERVICE_STATUS status;
static SERVICE_STATUS_HANDLE hStatus = (SERVICE_STATUS_HANDLE)0;
static HANDLE shutdownHandles[2];
static pid_t postmasterPID = -1;
#define shutdownEvent     shutdownHandles[0]
#define postmasterProcess shutdownHandles[1]
#define UNREGISTER_AS_SERVICE_STRING	"/unregister"
#define REGISTER_AS_SERVICE_STRING		"/register"
#define RUNNING_AS_SERVICE_STRING		"/runningAsService"

static char* pgwin32_formatCommandLine(char *serviceName,char *otherArgv[])
{
	static char cmdLine[MAX_PATH];
	char path[MAX_PATH];
	int i;

	/* Format up service command line */
	GetModuleFileName(NULL, path, MAX_PATH);
	snprintf(cmdLine,sizeof(cmdLine),"\"%s\"",path);
	if (serviceName)
	{
		strcat(cmdLine," ");
		strcat(cmdLine,RUNNING_AS_SERVICE_STRING);
		strcat(cmdLine," \"");
		strcat(cmdLine,serviceName);
		strcat(cmdLine,"\"");
	}

	i = 0;
	while (otherArgv && otherArgv[i] != NULL)
	{
		strcat(cmdLine," \""); /* quote all args; overkill, but simpler than parsing */
		strcat(cmdLine,otherArgv[i]);
		strcat(cmdLine,"\"");
		i++;
	}

	return cmdLine;
}

static void pgwin32_SetServiceStatus(DWORD currentState)
{
	assert(hStatus);
	status.dwCurrentState = currentState;
	SetServiceStatus(hStatus, (LPSERVICE_STATUS)&status);
}

static bool pgwin32_IsInstalled(SC_HANDLE hSCM, char* serviceName)
{
	bool bResult = false;
	SC_HANDLE hService = OpenService(hSCM, serviceName, SERVICE_QUERY_CONFIG);
	if (hService != NULL)
	{
		bResult = true;
		CloseServiceHandle(hService);
	}
    return bResult;
}

static void WINAPI pgwin32_ServiceHandler(DWORD request)
{
    switch (request)
    {
		case SERVICE_CONTROL_STOP:
		case SERVICE_CONTROL_SHUTDOWN:
			pgwin32_SetServiceStatus(SERVICE_STOP_PENDING);
			SetEvent(shutdownEvent);
			return;

		case SERVICE_CONTROL_PAUSE:
			kill(postmasterPID,SIGHUP);
			return;

		/* FIXME: These could be used to replace other signals etc */
		case SERVICE_CONTROL_CONTINUE:
		case SERVICE_CONTROL_INTERROGATE:
		default:
			break;
    }
}

static bool pgwin32_UnregisterServer(char *serviceName)
{
	bool bResult = false;
	SC_HANDLE hSCM;

    if ((hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL)
        MessageBox(NULL, "Couldn't open service manager", serviceName, MB_OK);
    else
	{
		if (!pgwin32_IsInstalled(hSCM,serviceName))
			MessageBox(NULL, "Service not found", serviceName, MB_OK);
		else
		{
			SC_HANDLE hService;
			if ((hService = OpenService(hSCM, serviceName, DELETE)) == NULL)
				MessageBox(NULL, "Couldn't open service", serviceName, MB_OK);
			else
			{
				if (!(bResult = DeleteService(hService)))
					MessageBox(NULL, "Service could not be deleted", serviceName, MB_OK);
				else
					MessageBox(NULL, "Unregistered", serviceName, MB_OK);
				CloseServiceHandle(hService);
			}
		}
		CloseServiceHandle(hSCM);
	}
    return bResult;
}

static bool pgwin32_RegisterServer(char *serviceName, char *otherArgv[])
{
	bool bResult = false;
	SC_HANDLE hSCM;

    if ((hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL)
        MessageBox(NULL, "Couldn't open service manager", serviceName, MB_OK);
    else
	{
		if (pgwin32_IsInstalled(hSCM,serviceName))
			MessageBox(NULL, "Service already registered", serviceName, MB_OK);
		else
		{
			SC_HANDLE hService;
			if ((hService = CreateService(hSCM, serviceName, serviceName,
										  SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
										  SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
										  pgwin32_formatCommandLine(serviceName,otherArgv),
										  NULL, NULL, "RPCSS\0", NULL, NULL)) == NULL)
				MessageBox(NULL, "Couldn't create service", serviceName, MB_OK);
			else {
				bResult = true;
				MessageBox(NULL, "Registered", serviceName, MB_OK);
				CloseServiceHandle(hService);
			}
		}
		CloseServiceHandle(hSCM);
	}
	return bResult;
}

static char *gServiceName = NULL;
static char **gOtherArgv  = NULL;

static VOID WINAPI pgwin32_ServiceMain(DWORD argc, LPTSTR *argv)
{
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	DWORD ret;
	char *serviceName;
	char **otherArgv;

	// Initialize variables
	serviceName = gServiceName;
	otherArgv   = gOtherArgv;

    status.dwWin32ExitCode	= S_OK;
    status.dwCheckPoint		= 0;
    status.dwWaitHint		= 0;
    status.dwServiceType	= SERVICE_WIN32_OWN_PROCESS;
    status.dwControlsAccepted			= SERVICE_ACCEPT_STOP;
    status.dwServiceSpecificExitCode	= 0;
	status.dwCurrentState = SERVICE_START_PENDING;

	memset(&pi,0,sizeof(pi));
	memset(&si,0,sizeof(si));
	si.cb = sizeof(si);

    // Register the control request handler
    if ((hStatus = RegisterServiceCtrlHandler(serviceName, pgwin32_ServiceHandler)) == (SERVICE_STATUS_HANDLE)0)
        return;

	if ((shutdownEvent = CreateEvent(NULL,true,false,NULL)) == NULL)
		return;

	// Start the postmaster
    pgwin32_SetServiceStatus(SERVICE_START_PENDING);
	if (!CreateProcess(NULL,pgwin32_formatCommandLine(NULL,otherArgv),NULL,NULL,TRUE,0,NULL,NULL,&si,&pi))
	{
		pgwin32_SetServiceStatus(SERVICE_STOPPED);
		return;
	}
	postmasterPID		= pi.dwProcessId;
	postmasterProcess	= pi.hProcess;
	CloseHandle(pi.hThread);
	pgwin32_SetServiceStatus(SERVICE_RUNNING);

	// Wait for quit...
	ret = WaitForMultipleObjects(2,shutdownHandles,FALSE,INFINITE);
	pgwin32_SetServiceStatus(SERVICE_STOP_PENDING);
	switch (ret)
	{
		case WAIT_OBJECT_0: /* shutdown event */
			kill(postmasterPID,SIGTERM);
			WaitForSingleObject(postmasterProcess,INFINITE);
			break;

		case (WAIT_OBJECT_0+1): /* postmaster went down */
			break;

		default:
			assert(false);
	}

	CloseHandle(shutdownEvent);
	CloseHandle(postmasterProcess);

	pgwin32_SetServiceStatus(SERVICE_STOPPED);
}

static bool pgwin32_SetServiceDispatcher(char *serviceName, char *otherArgv[])
{
	SERVICE_TABLE_ENTRY st[] = {{ serviceName, pgwin32_ServiceMain },
								{ NULL, NULL }};
	gServiceName	= serviceName;
	gOtherArgv		= otherArgv;
	return StartServiceCtrlDispatcher(st);
}

void pgwin32_CheckServiceOptions(int argc, char *argv[])
{
	if (argc >= 3)
	{
		if (strcmp(argv[1],UNREGISTER_AS_SERVICE_STRING) == 0)
			exit(pgwin32_UnregisterServer(argv[2]) ? 0 : 1);
		if (strcmp(argv[1],REGISTER_AS_SERVICE_STRING) == 0)
			exit(pgwin32_RegisterServer(argv[2],&argv[3]) ? 0 : 1);
		if (strcmp(argv[1],RUNNING_AS_SERVICE_STRING) == 0)
			exit(pgwin32_SetServiceDispatcher(argv[2],&argv[3]) ? 0 : 1);
	}
}
