//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//
/* File:    servload.cpp
 *
 * Purpose: WinCE services loading management.
 *
 */
#include <windows.h>
#include <types.h>
#include <tchar.h>
#include "serv.h"
#include <cardserv.h>
#include <service.h>
#include <devload.h>
#include <svsutil.hxx>
#include <creg.hxx>
#include <eventLogMsgs.h>

#include "servrc.h"

#ifdef DEBUG

DBGPARAM dpCurSettings = {
	TEXT("services"), {
	TEXT("Errors"),TEXT("Warnings"),TEXT("Undefined"),TEXT("Initialization"),
	TEXT("Undefined"),TEXT("Accept"),TEXT("Active Services"),TEXT("Timers"),
	TEXT("Undefined"),TEXT("Dying Devs"),TEXT("Undefined"),TEXT("Undefined"),
	TEXT("Undefined"),TEXT("Undefined"),TEXT("Undefined"),TEXT("Undefined") },
	0x0001
};
#endif  // DEBUG

extern BOOL g_fUseCELog;

//
// Services Callbacks
//
// Shutsdown a service when it's running from SERVICE_INIT_STANDALONE.
void ServiceCallbackShutdown(void) {
	SetEvent(g_hCleanEvt);
}


// Called on ActivateService.  Loads up a service based on information in RegPath
// and finds and index 	
HANDLE
StartOneService(
	LPCWSTR   RegPathInit,
	DWORD     LoadOrder,
	BOOL      fStandaloneProcess
	)
{
	HKEY DevKey;
	DWORD Context;
	HANDLE Handle;
	DWORD Index;
	DWORD Flags;
	DWORD status;
	DWORD ValLen;
	DWORD ValType;
	WCHAR DevDll[DEVDLL_LEN];
	WCHAR Prefix[DEVPREFIX_LEN];
	const WCHAR* RegPath;
	WCHAR  RegPathBuf[MAX_PATH+1];
	BOOL  fHasFullServicePath;
	WCHAR FriendlyName[FRIENDLY_NAME_LEN];

	DEBUGMSG(ZONE_ACTIVE,
		(TEXT("SERVICES!StartOneService starting %s.\r\n"), RegPathInit));

	if (NULL == wcsstr(RegPathInit,L"\\")) {
		// If the service doesn't have a registry path in it (which will be the case
		// when ActivateService() is called programatically) then build it up.

		if (wcslen(RegPathInit) + SERVICE_BUILTIN_KEY_STR_LEN + 2 > SVSUTIL_CONSTSTRLEN(RegPathBuf)) {
			DEBUGMSG(ZONE_ERROR,(L"SERVICES!ActivateService registry string is too long.\r\n"));
			SetLastError(ERROR_INVALID_PARAMETER);
			return 0;
		}

		wcscpy(RegPathBuf,SERVICE_BUILTIN_KEY);
		RegPathBuf[SERVICE_BUILTIN_KEY_STR_LEN] = TEXT('\\');
		wcscpy(RegPathBuf+SERVICE_BUILTIN_KEY_STR_LEN+1,RegPathInit);
		RegPath = RegPathBuf;
		fHasFullServicePath = FALSE;
	}
	else {
		// It's being called on services.exe startup with full registry key passed 
		// into us from regenum.dll.
		RegPath = RegPathInit;
		fHasFullServicePath = TRUE;
	}

	//
	// Get the required (dll, prefix, index) and optional (flags and context) values.
	//
	status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,RegPath,0,0,&DevKey);
	if (status) {
		DEBUGMSG(ZONE_ERROR,(TEXT("SERVICES!StartOneService RegOpenKeyEx(%s) returned %d.\r\n"),RegPath, status));
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}

	ValLen = sizeof(DevDll);
	status = RegQueryValueEx(DevKey,DEVLOAD_DLLNAME_VALNAME,NULL,
	                         &ValType,(PUCHAR)DevDll,&ValLen);

	if (status != ERROR_SUCCESS) {
		DEBUGMSG(ZONE_ERROR,(TEXT("SERVICES!StartOneService RegQueryValueEx(%s\\DllName) returned %d\r\n"),RegPath, status));
		RegCloseKey(DevKey);
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}

	ValLen = sizeof(Prefix);
	status = RegQueryValueEx(DevKey,DEVLOAD_PREFIX_VALNAME,NULL,
	                         &ValType,(PUCHAR)Prefix,&ValLen);

	if (status != ERROR_SUCCESS) {
		DEBUGMSG(ZONE_ERROR,(TEXT("SERVICES!StartOneService RegQueryValueEx(%s\\Prefix) returned %d\r\n"),RegPath, status));
		RegCloseKey(DevKey);
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}

	ValLen = sizeof(Index);
	status = RegQueryValueEx(DevKey,DEVLOAD_INDEX_VALNAME,NULL,
	                         &ValType,(PUCHAR)&Index,&ValLen);

	if (status != ERROR_SUCCESS) {
		DEBUGMSG(ZONE_ERROR,(TEXT("SERVICES!StartOneService RegQueryValueEx(%s\\Index) returned %d\r\n"),RegPath, status));
		RegCloseKey(DevKey);
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}

	ValLen = sizeof(Context);
	status = RegQueryValueEx(DevKey,DEVLOAD_CONTEXT_VALNAME,NULL,
	                         &ValType,(PUCHAR)&Context,&ValLen);

	if (status != ERROR_SUCCESS) {
		DEBUGMSG(ZONE_ACTIVE,(TEXT("SERVICES!StartOneService RegQueryValueEx(%s\\Context) returned %d\r\n"),RegPath, status));
		Context = 0;
	}

	ValLen = FRIENDLY_NAME_LEN*sizeof(WCHAR);
	status = RegQueryValueEx(DevKey,SERVICE_FRIENDLY_NAME,NULL,
	                         &ValType,(PUCHAR)FriendlyName,&ValLen);

	if (status != ERROR_SUCCESS)
		FriendlyName[0] = 0;
    

	ValLen = sizeof(Flags);
	status = RegQueryValueEx(DevKey,DEVLOAD_FLAGS_VALNAME,NULL,
	                         &ValType,(PUCHAR)&Flags,&ValLen);

	if (status != ERROR_SUCCESS) {
		DEBUGMSG(ZONE_ACTIVE,(TEXT("SERVICES!StartOneService RegQueryValueEx(%s\\Flags) returned %d\r\n"),RegPath, status));
		Flags = DEVFLAGS_NONE;  // default is no flags set
	}
	if (Flags & DEVFLAGS_NOLOAD) {
		DEBUGMSG(ZONE_ACTIVE,(TEXT("SERVICES!StartOneService not loading %s with DEVFLAGS_NOLOAD\n"),RegPath));
		// Really success, but we cannot distinguish success at
		// deliberately not loading from a failure to load.
		RegCloseKey(DevKey);
		SetLastError(ERROR_SERVICE_NOT_ACTIVE);
		return 0;
	}
	RegCloseKey(DevKey);

	// Spin a new services.exe (assuming we haven't been spun as such already).
	if ((Context & SERVICE_INIT_STANDALONE) && !fStandaloneProcess) {
		WCHAR szArgs[MAX_PATH+20];

		if (fHasFullServicePath)
			wsprintf(szArgs,L"load standalone %s",RegPathInit + SVSUTIL_CONSTSTRLEN(L"Services\\"));
		else
			wsprintf(szArgs,L"load standalone %s",RegPathInit);
    	
		if (! CreateProcess(SERVICE_SERVICES_EXE_PROCNAME,szArgs,NULL,NULL,FALSE,0,
			                NULL,NULL,NULL,NULL)) {
			DEBUGMSG(ZONE_ERROR,(L"SERVICES.EXE: CreateProcess(\"services.exe\",\"load standone\" failed,GLE=0x%08x\r\n",GetLastError()));
			return 0;
		}
		return (HANDLE)1;
	}

	Handle = RegisterServiceEx(Prefix,Index,DevDll,Context,Flags,RegPath,FriendlyName);

	if (Handle && (Context & SERVICE_INIT_STOPPED)) {
		if (! StartSuperService(RegPath,(HANDLE)Handle,TRUE)) {
			SERV_DeregisterService(Handle);
			Handle = 0;
		}
	}

	if (Handle)
		WriteEventLogSuccess(SERVICES_LOAD_SUCCEED_MSG,FriendlyName);
	else
		WriteEventLogFailure(SERVICES_LOAD_FAILED_MSG,FriendlyName);

	if (Handle && (Context & SERVICE_INIT_STANDALONE)) {
		EnterCriticalSection(&g_ServCS);
		if (!IsValidService((fsdev_t *)Handle)) {
			LeaveCriticalSection(&g_ServCS);
			return 0;
		}

		ServicesExeCallbackFunctions serviceCallback;
		memset(&serviceCallback,0,sizeof(serviceCallback));

		serviceCallback.pfnServiceShutdown  = ServiceCallbackShutdown;

		InternalServiceIOControl((fsdev_t *)Handle,IOCTL_SERVICE_CALLBACK_FUNCTIONS,(BYTE*)&serviceCallback,sizeof(serviceCallback),0,0);
		LeaveCriticalSection(&g_ServCS);
	}

	return Handle;
}

//
// Function to load services on services.exe start up.
//
BOOL
InitServices(VOID)
{
	HMODULE hDevDll = 0;
	PFN_SERVICE_ENTRY DevEntryFn;
	BOOL fRet = FALSE;
	HANDLE h = 0;

#if ! defined (SHIP_BUILD)
	HKEY hKey;

	// Use CE Log?
	if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, SERVICE_BUILTIN_KEY, 0, KEY_ALL_ACCESS, &hKey)) {
		DWORD ValType;
		DWORD ValLen = sizeof(DWORD);
		
		if (ERROR_SUCCESS != RegQueryValueEx(hKey,L"UseCELog",NULL,
		                     &ValType,(PUCHAR)&g_fUseCELog,&ValLen) ||
		                     ValType != REG_DWORD ||
		                     ValLen  != sizeof(DWORD)) {
			g_fUseCELog = FALSE;
		}
		
		RegCloseKey(hKey);
	}
#endif

	hDevDll = LoadDriver(L"regenum.dll");
	
	if (hDevDll == NULL) {
		RETAILMSG(1,(L"SERVICES!InitServices LoadDriver(\"regenum.dll\") failed %d\r\n",GetLastError()));
		goto done;
	}
	DevEntryFn = (PFN_SERVICE_ENTRY)GetProcAddress(hDevDll, L"Enum");
	
	if (DevEntryFn == NULL) {
		RETAILMSG(1,(L"SERVICES!InitServices GetProcAddr(regenum.dll, enumerate) failed %d\r\n",GetLastError()));
		goto done;
	}

	h = (DevEntryFn)(SERVICE_BUILTIN_KEY,ActivateService,NULL);
	if (h) {
		// regenum allocates a table to hold all these tables which we don't use.
		LocalFree((PBYTE)h);
	}

	fRet = TRUE;
done:
	if (hDevDll)
		FreeLibrary(hDevDll);

	return fRet;
}

typedef DWORD (WINAPI *PFN_COFREEUNUSEDLIBRARIES)(void);
typedef DWORD (WINAPI *PFN_COINITIALIZEEX)(LPVOID lpvReserved, DWORD dwCoInit);

PFN_COFREEUNUSEDLIBRARIES g_pfnCoFreeUnusedLibraries;
PFN_COINITIALIZEEX        g_pfnCoInitializeEx;

// Sleep 2 minutes between calls.
#define CO_FREE_UNUSED_SLEEP_MS   (1000*60*2)  

DWORD WINAPI ServicesCoFreeUnusedLibrariesThread(LPVOID lpv) {

	if (FAILED(g_pfnCoInitializeEx(NULL,COINIT_MULTITHREADED))) {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES: CoInitializeEx failed, cannot start ServicesCoFreeUnusedLibrariesThread, GLE=0x%08x\r\n",GetLastError()));
		return 0;
	}

	while (1) {
		DEBUGMSG(ZONE_TIMERS,(L"SERVICES: Sleeping %d seconds before next call to CoFreeUnusedLibraries()\r\n",CO_FREE_UNUSED_SLEEP_MS / 1000));
		Sleep(CO_FREE_UNUSED_SLEEP_MS);

		DEBUGMSG(ZONE_TIMERS,(L"SERVICES: Making periodic call to CoFreeUnusedLibraries()\r\n"));
		g_pfnCoFreeUnusedLibraries();
	}
	DEBUGCHK(0);
	return 0;
}

// Determines if we're to run CoFreeUnusedLibraries thread in the background and starts it if we are.
void StartCoFreeUnusedLibrariesThread() {
	CReg reg(HKEY_LOCAL_MACHINE,SERVICE_BUILTIN_KEY);

	if (0 == reg.ValueDW(SERVICE_COFREEUNUSED_VALUE,TRUE)) {
		DEBUGMSG(ZONE_INIT,(L"SERVICES: Will not start CoFreeUnusedLibrariesThread because it was over-ridden in the registry\r\n"));
		return;
	}

	HINSTANCE hOle32 = LoadLibrary(L"\\windows\\ole32.dll");
	if (hOle32 == NULL) {
		DEBUGMSG(ZONE_INIT,(L"SERVICES: Will not start CoFreeUnusedLibrariesThread, cannot load ole32.dll, GLE=0x%08x\r\n",GetLastError()));
		return;
	}

	if ((NULL == (g_pfnCoFreeUnusedLibraries = (PFN_COFREEUNUSEDLIBRARIES) GetProcAddress(hOle32,L"CoFreeUnusedLibraries"))) ||
		(NULL == (g_pfnCoInitializeEx = (PFN_COINITIALIZEEX) GetProcAddress(hOle32,L"CoInitializeEx")))) {
		DEBUGCHK(0); // This fcn should always be part of ole32.dll, even in mini-configurations.
		DEBUGMSG(ZONE_ERROR,(L"SERVICES: Will not start CoFreeUnusedLibrariesThread, ole32.dll doesn't export required fcns, GLE=0x%08x\r\n",GetLastError()));
		return;
	}

	DEBUGMSG(ZONE_INIT,(L"SERVICES: Starting ServicesCoFreeUnusedLibrariesThread\r\n"));

	if (NULL == CreateThread(NULL, 0, ServicesCoFreeUnusedLibrariesThread, NULL, 0, NULL))
		DEBUGMSG(ZONE_ERROR,(L"SERVICES: Will not start CoFreeUnusedLibrariesThread, cannot create thread, GLE=0x%08x.\r\n",GetLastError()));

	// alright never to free ole32.dll and not close thread handle, because services.exe never terminates.
	return;
}


BOOL ServerLoadInit(void)
{
	DEBUGREGISTER(NULL);
	DEBUGMSG(ZONE_INIT, (TEXT("SERVICES!ServerLoadInit\r\n")));

	if (! SuperServicesInit())
		return FALSE;

	OpenServicesEventLog();

	if (! InitServices())
		return FALSE;

	StartCoFreeUnusedLibrariesThread();
	return TRUE;
}



//
//  Event log helpers
//

HANDLE g_hEventLog;
const WCHAR cszServicesEventLog[] = L"Service Control Manager";

WCHAR g_szErrorUnknown[MAX_PATH];

BOOL OpenServicesEventLog(void) {
	DEBUGCHK(g_hEventLog == NULL);
	if (NULL == (g_hEventLog = RegisterEventSource(NULL,cszServicesEventLog)))
		return FALSE;

	if (! LoadString (GetModuleHandle(NULL), IDS_OPERATION_FAILED, g_szErrorUnknown, SVSUTIL_ARRLEN(g_szErrorUnknown))) {
		DeregisterEventSource(g_hEventLog);
		return FALSE;
	}

	return TRUE;
}

BOOL WriteEventLogSuccess(DWORD dwMsgID, const WCHAR *szServiceName) {
	if (! g_hEventLog || (szServiceName[0] == 0))
		return FALSE;

	const WCHAR *rgArray[] = { szServiceName };

	return ReportEvent(g_hEventLog,EVENTLOG_INFORMATION_TYPE,0,dwMsgID,
	                   NULL,1,0,rgArray,NULL);
}

BOOL WriteEventLogFailure(DWORD dwMsgID, const WCHAR *szServiceName) {
	if (! g_hEventLog || (szServiceName[0] == 0))
		return FALSE;

	WCHAR szError[MAX_PATH];
	DWORD dwGLE = GetLastError();

	if (dwGLE == 0) {
		DEBUGCHK(0); // Service should SetLastError() if it's going to fail.  Encourage service writers to do this with a DEBUGCHK.
		wsprintf(szError,g_szErrorUnknown,0);
	}
	else {
		if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,NULL,dwGLE,0,szError,SVSUTIL_CONSTSTRLEN(szError),NULL))
			wsprintf(szError,g_szErrorUnknown,dwGLE);
	}

	const WCHAR *rgArray[] = { szServiceName, szError };

	BOOL fRet = ReportEvent(g_hEventLog,EVENTLOG_ERROR_TYPE,0,dwMsgID,
	                   NULL,2,0,rgArray,NULL);

	// FormatMessage() and/or ReportEvent() may end up changing the last error code, so reset it here.
	if (dwGLE)
		SetLastError(dwGLE);

	return fRet;
}


