//
// 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:    service.cpp
 *
 * Purpose: WinCE service manager
 *
 */


#include <windows.h>
#include <service.h>
#include <cardserv.h>
#include <devload.h>
#include <console.h>
#include "serv.h"
#include <eventLogMsgs.h>


// disable 0 length array warning
#pragma warning (disable : 4200)
#include <celog.h>

CRITICAL_SECTION g_ServCS;

// List of services themselves, with service instance handle.
LIST_ENTRY g_ActiveServiceList;

// List of services whose resources have been allocated, but have not been
// added to the g_ActiveServiceList list yet during RegisterDeviceEx.
LIST_ENTRY g_NewServiceList;  

// List of dying services.
LIST_ENTRY g_DyingServiceList;

fsopendev_t *g_lpOpenDevs;
HANDLE g_hDevApiHandle, g_hDevFileApiHandle, g_hCleanEvt;

BOOL ServerLoadInit(void);


#if ! defined (SHIP_BUILD)
// Write load/unload service info to CELog?
BOOL g_fUseCELog;
#endif

// services.exe specific APIs.
const PFNVOID FDevApiMethods[] = {
//xref ApiSetStart
	(PFNVOID)SERV_ProcNotify,
	(PFNVOID)0,
	(PFNVOID)SERV_ActivateService,
	(PFNVOID)SERV_RegisterService,
	(PFNVOID)SERV_DeregisterService,
	(PFNVOID)SERV_CloseAllServiceHandles,
	(PFNVOID)SERV_CreateServiceHandle,
	(PFNVOID)SERV_GetServiceByIndex,
	(PFNVOID)SERV_ServiceIoControl,
	(PFNVOID)SERV_ServiceAddPort,
	(PFNVOID)SERV_ServiceUnbindPorts,
	(PFNVOID)SERV_EnumServices,
	(PFNVOID)SERV_GetServiceHandle,
	(PFNVOID)SERV_ServiceClosePort,
	
//xref ApiSetEnd
};

#define NUM_FDEV_APIS (sizeof(FDevApiMethods)/sizeof(FDevApiMethods[0]))

const DWORD FDevApiSigs[NUM_FDEV_APIS] = {
	FNSIG3(DW, DW, DW),	   // ProcNotify
	FNSIG0(),
	FNSIG2(PTR, DW),          // ActivateService
	FNSIG4(PTR, DW, PTR, DW), // RegisterService
	FNSIG1(DW),               // DeregisterService
	FNSIG1(DW),               // CloseAllServiceHandles
	FNSIG4(PTR, DW, DW, DW),  // CreateServiceHandle
	FNSIG2(DW, PTR),          // GetServiceByIndex
	FNSIG8(DW, DW, PTR, DW, PTR, DW, PTR, PTR), // ServiceIoControl,
	FNSIG5(DW, PTR, DW, DW, PTR),  // ServiceAddPort
	FNSIG1(DW),               // ServiceUnbindPorts
	FNSIG3(PTR,PTR,PTR),      // EnumServices
	FNSIG3(PTR,PTR,PTR)       // GetServiceHandle
};

// Communication from filesys methods to services.exe
const PFNVOID DevFileApiMethods[] = {
	(PFNVOID)SERV_CloseFileHandle,
	(PFNVOID)0,
	(PFNVOID)SERV_ReadFile,
	(PFNVOID)SERV_WriteFile,
	(PFNVOID)SERV_GetFileSize,
	(PFNVOID)SERV_SetFilePointer,
	(PFNVOID)SERV_GetFileInformationByHandle,
	(PFNVOID)SERV_FlushFileBuffers,
	(PFNVOID)SERV_GetFileTime,
	(PFNVOID)SERV_SetFileTime,
	(PFNVOID)SERV_SetEndOfFile,
	(PFNVOID)SERV_DeviceIoControl,
};

#define NUM_FAPIS (sizeof(DevFileApiMethods)/sizeof(DevFileApiMethods[0]))

const DWORD DevFileApiSigs[NUM_FAPIS] = {
	FNSIG1(DW),                 // CloseFileHandle
	FNSIG0(),
	FNSIG5(DW,PTR,DW,PTR,PTR),  // ReadFile
	FNSIG5(DW,PTR,DW,PTR,PTR),  // WriteFile
	FNSIG2(DW,PTR),             // GetFileSize
	FNSIG4(DW,DW,PTR,DW),       // SetFilePointer
	FNSIG2(DW,PTR),             // GetFileInformationByHandle
	FNSIG1(DW),                 // FlushFileBuffers
	FNSIG4(DW,PTR,PTR,PTR),     // GetFileTime
	FNSIG4(DW,PTR,PTR,PTR),     // SetFileTime
	FNSIG1(DW),                 // SetEndOfFile,
	FNSIG8(DW, DW, PTR, DW, PTR, DW, PTR, PTR), // DeviceIoControl
};

PFNVOID ServicesGetProcAddr(LPCWSTR type, LPCWSTR lpszName, HINSTANCE hInst) {
	WCHAR szFcnName[MAX_PATH];
	memcpy(szFcnName,type,3*sizeof(WCHAR));
	szFcnName[3] = L'_';
	wcscpy(&szFcnName[4],lpszName);

	return (PFNVOID)GetProcAddress(hInst, szFcnName);
}

static BOOL NotSupportedBool (void)
{
	SetLastError(ERROR_NOT_SUPPORTED);
	return FALSE;
}

static DWORD NotSupportedDword (void)
{
	SetLastError(ERROR_NOT_SUPPORTED);
	return -1;
}

// We need to spin a thread to load and free libraries because it is unsafe
// to do it under the context of a PSL caller thread.
DWORD WINAPI ServicesFreeLibraryThread(LPVOID lpv) {
	FreeLibrary((HINSTANCE) lpv);
	return 0;
}

typedef DWORD (WINAPI *PFN_SERVICE_THREAD)(LPVOID lpv);


// A number of operatations, in particular anything using TLS, must not
// be performed in the context of a process calling into services.exe via a PSL
// but must instead be called with services.exe as the owner.  To make this
// happen we create a thread in services.exe and block until its completed.

// By convention, pFunc should return ERROR_SUCCESS on successful operation and an error otherwise.
DWORD ServiceSpinThreadAndWait(PFN_SERVICE_THREAD pFunc, LPVOID lpv) {
	HANDLE hThread = CreateThread(NULL,0,pFunc,lpv,0,NULL);

	if (hThread) {
		DWORD dwRetVal;
		WaitForSingleObject(hThread,INFINITE);

		if (GetExitCodeThread(hThread,&dwRetVal)) {
			CloseHandle(hThread);
			return dwRetVal;
		}
		else {
			DEBUGMSG(ZONE_ERROR,(L"SERVICES: GetExitCodeThread failed, GLE=0x%08x\r\n",GetLastError()));
			DEBUGCHK(0);
			CloseHandle(hThread);
			goto errret;
		}
	}
#if defined (DEBUG)
	else {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES: Unable to spin a LoadLibrary thread, forced to load library on current thread.  GLE=0x%08x\r\n",GetLastError()));
	}
#endif

errret:
	DWORD dwErr = GetLastError();
	
	if (dwErr == 0) {
		// Getting to this block indicates a system error, sys should've set an err code.
		// If it did not we still return err, because calling function relies on return
		// to determine flow of control.
		DEBUGCHK(0);
		dwErr = ERROR_SERVICE_CANNOT_ACCEPT_CTRL;
	}

	return dwErr;
}

#if ! defined (SHIP_BUILD)
#define ServicesCeLog(args)  if (g_fUseCELog) RETAILCELOGMSG(1,args)
#else
#define ServicesCeLog(args)  ((void)0)
#endif // SHIP_BUILD

void FreeService(fsdev_t *lpdev, BOOL fRemoveFromDyingList, BOOL fSpinThread, BOOL fLogEvent)  {
	EnterCriticalSection(&g_ServCS);
	DEBUGCHK(! IsValidService(lpdev));

	if (fRemoveFromDyingList) {
		DEBUGCHK( IsValidService(lpdev,&g_DyingServiceList));
		RemoveEntryList((PLIST_ENTRY)lpdev);
	}
	LeaveCriticalSection(&g_ServCS);

	if (lpdev->hLib && fSpinThread) {
		// when being called from a PSL, spin a thread so that services.exe owns
		// library unload.  This is to keep TLS from getting confused.
		ServiceSpinThreadAndWait(ServicesFreeLibraryThread, lpdev->hLib);
	}
	else if (lpdev->hLib) {
		// we're being called from DeregisterServiceThread or another thread
		// that services.exe owns, so it's safe to unload in this context without spinning
		// a new thread.
		VERIFY_SERVICES_EXE_IS_OWNER();
		FreeLibrary(lpdev->hLib);
	}

	if (fLogEvent) {
		WriteEventLogSuccess(SERVICES_SHUTDOWN_SUCCEED_MSG,lpdev->szFriendlyName);
	}

	if (lpdev->szDllName)
		LocalFree(lpdev->szDllName);
	if (lpdev->szRegPath)
		LocalFree(lpdev->szRegPath);

	LocalFree(lpdev);
}

fsdev_t *FindPrefixInList(LPCWSTR szPrefix, DWORD dwIndex, PLIST_ENTRY pList) {
	fsdev_t *lpTrav;

	for (lpTrav = (fsdev_t *)pList->Flink;
		 lpTrav != (fsdev_t *)pList;
		 lpTrav = (fsdev_t *)(lpTrav->list.Flink)) {
		 if (!_wcsnicmp(lpTrav->type,szPrefix,sizeof(lpTrav->type)/sizeof(WCHAR)) && (lpTrav->index == dwIndex)) {
			  return lpTrav;
		 }
	}
	return NULL;
}


//  @func       HANDLE | RegisterService | Register a new service
//  @parm       LPCWSTR | lpszType | service id (SER, PAR, AUD, etc) - must be 3 characters long
//  @parm       DWORD | dwIndex | index between 0 and 9, ie: COM2 would be 2
//  @parm       LPCWSTR | lpszLib | dll containing driver code
//  @parm       DWORD | dwInfo | instance information
//      @rdesc  Returns a handle to a service, or 0 for failure.  This handle should
//                      only be passed to DeRegisterService.
//      @comm   For stream based services, the services will be .dll files.
//                      Each service is initialized by
//                      a call to the RegisterService API (performed by the server process).
//                      The lpszLib parameter is what will be to open the service.  The
//                      lpszType is a three character string which is used to identify the 
//                      function entry points in the DLL, so that multiple services can exist
//                      in one DLL.  The lpszLib is the name of the DLL which contains the
//                      entry points.  Finally, dwInfo is passed in to the init routine. 
//                      Most likely, the server process will read this information out of a
//                      table or registry in order to initialize all devices at startup time.
//      @xref <f DeregisterService>
//

HANDLE
SERV_RegisterService(
	LPCWSTR lpszType,
	DWORD   dwIndex,
	LPCWSTR lpszLib,
	DWORD   dwInfo
	)
{
	return RegisterServiceEx(
		        lpszType,
		        dwIndex,
		        lpszLib,
		        dwInfo,
		        DEVFLAGS_NONE,
		        NULL,
		        NULL
		        );
}

// Does actual work of loading service DLL into memory and calling xxx_Init.
// MUST be done in context of services.exe as owner process, not PSL.
DWORD WINAPI RegisterServiceThread(LPVOID lpv) {
	DWORD dwErr = ERROR_SUCCESS;
	fsdev_t *lpdev   = (fsdev_t*) lpv;

	VERIFY_SERVICES_EXE_IS_OWNER();

	// load library and get function pointers setup.
	lpdev->hLib = (lpdev->dwFlags & DEVFLAGS_LOADLIBRARY) ? LoadLibrary(lpdev->szDllName) : LoadDriver(lpdev->szDllName);
	if (!lpdev->hLib) {
		dwErr = ERROR_FILE_NOT_FOUND;
		goto done;
	}

	lpdev->fnInit = (pInitFn)ServicesGetProcAddr(lpdev->type,L"Init",lpdev->hLib);
	lpdev->fnDeinit = (pDeinitFn)ServicesGetProcAddr(lpdev->type,L"Deinit",lpdev->hLib);
	lpdev->fnOpen = (pOpenFn)ServicesGetProcAddr(lpdev->type,L"Open",lpdev->hLib);
	lpdev->fnClose = (pCloseFn)ServicesGetProcAddr(lpdev->type,L"Close",lpdev->hLib);
	lpdev->fnRead = (pReadFn)ServicesGetProcAddr(lpdev->type,L"Read",lpdev->hLib);
	lpdev->fnWrite = (pWriteFn)ServicesGetProcAddr(lpdev->type,L"Write",lpdev->hLib);
	lpdev->fnSeek = (pSeekFn)ServicesGetProcAddr(lpdev->type,L"Seek",lpdev->hLib);
	lpdev->fnControl = (pControlFn)ServicesGetProcAddr(lpdev->type,L"IOControl",lpdev->hLib);

	if (!(lpdev->fnInit && lpdev->fnDeinit && lpdev->fnControl)) {
		dwErr = ERROR_INVALID_FUNCTION;
		goto done;
	}

	if (!lpdev->fnOpen) lpdev->fnOpen = (pOpenFn) NotSupportedBool;
	if (!lpdev->fnClose) lpdev->fnClose = (pCloseFn) NotSupportedBool;
	if (!lpdev->fnControl) lpdev->fnControl = (pControlFn) NotSupportedBool;
	if (!lpdev->fnRead) lpdev->fnRead = (pReadFn) NotSupportedDword;
	if (!lpdev->fnWrite) lpdev->fnWrite = (pWriteFn) NotSupportedDword;
	if (!lpdev->fnSeek) lpdev->fnSeek = (pSeekFn) NotSupportedDword;

	lpdev->dwRefCnt = 1;

	// check for uniqueness
	EnterCriticalSection (&g_ServCS);
	if (FindPrefixInList(lpdev->type,lpdev->index,&g_ActiveServiceList) || FindPrefixInList(lpdev->type,lpdev->index,&g_NewServiceList) ||
		FindPrefixInList(lpdev->type,lpdev->index,&g_DyingServiceList)) {
		LeaveCriticalSection(&g_ServCS);
		dwErr = ERROR_DEVICE_IN_USE;
		goto done;
	}

	InsertTailList(&g_NewServiceList,(PLIST_ENTRY)lpdev);
	LeaveCriticalSection(&g_ServCS);

	// call xxx_Init
	__try {
		lpdev->dwData = lpdev->fnInit(lpdev->dwContext);

		if (!(lpdev->dwData)) {
			dwErr = ERROR_OPEN_FAILED;
		}
	} __except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
		dwErr = ERROR_INVALID_PARAMETER;
	}

	if (lpdev->dwFlags & DEVFLAGS_UNLOAD && (dwErr == ERROR_SUCCESS)) {
		// No app can set DEVFLAGS_UNLOAD (only settable through reg config),
		// err code isn't important as long as it causes library to get freed.
		dwErr = ERROR_INTERNAL_ERROR;
	}

	// regardless of dwErr code, fall through to this code to take service of g_NewServiceList.
	EnterCriticalSection(&g_ServCS);
	DEBUGCHK(IsValidService(lpdev,&g_NewServiceList));
	RemoveEntryList((PLIST_ENTRY)lpdev);

	if (ERROR_SUCCESS == dwErr) {
		InsertTailList(&g_ActiveServiceList, (PLIST_ENTRY)lpdev);
	}
	LeaveCriticalSection(&g_ServCS);

done:
	if (ERROR_SUCCESS != dwErr && lpdev->hLib) {
		// Free the library here (because we'd have to spin up another thread
		// in RegisterService call to FreeService otherwise) but don't free lpdev 
		// memory structures, let RegisterServiceEx handle this since it created it.
		FreeLibrary(lpdev->hLib);
		lpdev->hLib = 0;
	}
	return dwErr;
}

HANDLE
RegisterServiceEx(
	LPCWSTR lpszType,
	DWORD dwIndex,
	LPCWSTR lpszLib,
	DWORD dwInfo,
	DWORD dwFlags,
	LPCWSTR lpszFullRegPath,
	LPCWSTR lpszFriendlyName
	)
{
	HANDLE hDev = 0;
	fsdev_t *lpdev = 0;
	DWORD dwErr = ERROR_SUCCESS;
	BOOL  fInCreateDevList = FALSE;

	ServicesCeLog((L"SERVICES: +RegisterService(%.5s,%d,%.16s,%d)",lpszType,dwIndex,lpszLib,dwInfo));

	lpszType = (LPWSTR) ValidatePWSTR((LPWSTR)lpszType);
	lpszLib  = (LPWSTR) ValidatePWSTR((LPWSTR)lpszLib);
	// lpszFullRegPath can only be set internally be services.exe so always trust it.

	if (!lpszType || !lpszLib || dwIndex > 9) {
		dwErr = ERROR_INVALID_PARAMETER;
		goto errret;
	}

	if (!(lpdev = (fsdev_t*) LocalAlloc(LPTR,sizeof(fsdev_t)))) {
		dwErr = ERROR_OUTOFMEMORY;
		goto errret;
	}
	
	__try {
		// populate lpdev structure.
		memset(lpdev, 0, sizeof(fsdev_t));
		memcpy(lpdev->type,lpszType,sizeof(lpdev->type));
		lpdev->index     = dwIndex;
		lpdev->dwContext = dwInfo;
		lpdev->dwFlags   = dwFlags;

		lpdev->dwDllNameLen = wcslen(lpszLib);
		if (NULL == (lpdev->szDllName = (WCHAR *) LocalAlloc(LMEM_FIXED,sizeof(WCHAR)*(lpdev->dwDllNameLen+1)))) {
			dwErr = ERROR_OUTOFMEMORY;
			goto errret;
		}
		wcscpy(lpdev->szDllName,lpszLib);

		if (lpszFriendlyName)
			wcscpy(lpdev->szFriendlyName,lpszFriendlyName);
		else
			lpdev->szFriendlyName[0] = 0;

		if (lpszFullRegPath) {
			lpdev->dwRegPathLen = wcslen(lpszFullRegPath);
			if (NULL == (lpdev->szRegPath = (WCHAR*) LocalAlloc(LMEM_FIXED,(lpdev->dwRegPathLen+1)*sizeof(WCHAR)))) {
				dwErr = ERROR_OUTOFMEMORY;
				goto errret;
			}
			wcscpy(lpdev->szRegPath,lpszFullRegPath);
		}

		// if services.exe owns the thread already, don't bother creating a worker.
		if ((DWORD)GetOwnerProcess() == GetCurrentProcessId())
			dwErr = RegisterServiceThread(lpdev);
		else
			dwErr = ServiceSpinThreadAndWait(RegisterServiceThread,lpdev);

	} __except (EXCEPTION_EXECUTE_HANDLER) {
		dwErr = ERROR_INVALID_PARAMETER;
	}

errret:
	if (dwErr) {
		if (lpdev)
			FreeService(lpdev,FALSE,TRUE,FALSE);

		SetLastError(dwErr);
  		DEBUGMSG(ZONE_ERROR,(L"SERVICES!RegisterService fails, GLE=0x%08x\r\n",dwErr));
	}
	else {
		DEBUGCHK(lpdev && lpdev->szDllName && lpdev->hLib);
		hDev = (HANDLE)lpdev;
	}

	ServicesCeLog((L"SERVICES: -RegisterService returns hDev=0x%08x, LastError=0x%08x",hDev,dwErr));
	return hDev;
}


//
// IsValidService - function to check if passed in Service handle is actually a
// valid registered Service.
//
// Return TRUE if it is a valid registered Service, FALSE otherwise.
//
BOOL IsValidService(fsdev_t * lpdev,PLIST_ENTRY pList) {
	fsdev_t * lpTrav;
	PLIST_ENTRY pSearchList = pList ? pList : &g_ActiveServiceList;

	for (lpTrav = (fsdev_t *)pSearchList->Flink;
		 lpTrav != (fsdev_t *)pSearchList;
		 lpTrav = (fsdev_t *)(lpTrav->list.Flink)) {
		if (lpTrav == lpdev) {
			return TRUE;
		}
	}
	return FALSE;
}

//
// IsValidFileHandle - called to make sure passed file handle is valid.
//
BOOL IsValidFileHandle(fsopendev_t *fsodev) {
	fsopendev_t *lpTrav = g_lpOpenDevs;
	while (lpTrav) {
		 if (lpTrav == fsodev) {
			 DEBUGCHK(lpTrav->lpDev);
			 return TRUE;
		 }
		 lpTrav = lpTrav->nextptr;
	}
	return FALSE;
}

void OpenAddRef(fsopendev_t *fsodev) {
	fsodev->dwOpenRefCnt++;
	fsodev->lpDev->dwRefCnt++;
}

void OpenDelRef(fsopendev_t *fsodev) {
	fsodev->lpDev->dwRefCnt--;
	if (fsodev->lpDev->dwRefCnt == 0) {
		LeaveCriticalSection(&g_ServCS);
		FreeService(fsodev->lpDev);
		EnterCriticalSection(&g_ServCS);
	}

	fsodev->dwOpenRefCnt--;
	if (fsodev->dwOpenRefCnt == 0) {
		DEBUGCHK(!IsValidFileHandle(fsodev));
		LocalFree(fsodev);  
	}
}

BOOL WillServiceDeinit(fsdev_t *lpdev) {
	DWORD dwBufOut = 1;

	// Before calling xxx_Deinit on a service, we call it with IOCTL_SERVICE_CAN_DEINIT.  If 
	// the service returns TRUE (indicating it processed the response) and if it set dwBufOut=0,
	// then we won't dereference service or call xxx_Deinit on it.
	if (InternalServiceIOControl(lpdev,IOCTL_SERVICE_QUERY_CAN_DEINIT,0,0,(PBYTE)&dwBufOut,sizeof(dwBufOut))) {
		return (dwBufOut == 0) ? FALSE : TRUE;
	}
	return TRUE;
}

// Calls xxx_DeInit on a services and unloads it (iff the ref count is 0).
// Must be done in context of services.exe as owner, not from a PSL call.

DWORD WINAPI DeregisterServiceThread(LPVOID lpv) {
	fsdev_t *lpdev = (fsdev_t*) lpv;

	VERIFY_SERVICES_EXE_IS_OWNER();

	__try {
		lpdev->fnDeinit(lpdev->dwData);
	}
	__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!DeregisterService:xxx_Deinit on handle 0x%08x throws exception 0x%08x\r\n",lpdev,GetExceptionCode()));
	}

	EnterCriticalSection(&g_ServCS);
	DEBUGCHK(IsValidService(lpdev,&g_DyingServiceList));

	lpdev->dwRefCnt--;
	if (lpdev->dwRefCnt == 0) {
		LeaveCriticalSection(&g_ServCS);
		FreeService(lpdev,TRUE,FALSE);
	} else {
		DEBUGMSG(ZONE_DYING, (L"SERVICES!DeregisterService:Not all references to service(0x%08x) have been removed\r\n",lpdev));
		LeaveCriticalSection(&g_ServCS);
	}

	return ERROR_SUCCESS;
}

//      @func   BOOL | DeregisterService | Deregister a registered Service
//      @parm   HANDLE | hDevice | handle to registered service, from RegisterService
//      @rdesc  Returns TRUE for success, FALSE for failure
//      @comm   DeregisterService can be used if a service is being shut down.
//                      An example would be:<nl>
//                      <tab>DeregisterService(h1);<nl>
//                      where h1 was returned from a call to RegisterService.
//      @xref <f RegisterService>


BOOL SERV_DeregisterService(HANDLE hDevice) {
	return DeregisterServiceEx(hDevice,TRUE);
}

BOOL DeregisterServiceEx(HANDLE hDevice, BOOL fRecordInLog) {
	fsdev_t *lpdev;
	BOOL retval = FALSE;

	ServicesCeLog((L"SERVICES: +DeregisterService(0x%08x)",hDevice));

	EnterCriticalSection(&g_ServCS);
	__try {
		lpdev = (fsdev_t *)hDevice;
		if (!IsValidService(lpdev)) {
			SetLastError(ERROR_INVALID_PARAMETER);
			LeaveCriticalSection(&g_ServCS);
			goto errret;
		}

		if (! WillServiceDeinit(lpdev)) {
			SetLastError(ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
			LeaveCriticalSection(&g_ServCS);
			goto errret;
		}
		
		RemoveEntryList((PLIST_ENTRY)lpdev);
		InsertTailList(&g_DyingServiceList,(PLIST_ENTRY)lpdev);

		UnloadSuperServices(lpdev,TRUE,TRUE);  // releases critical section and doesn't reclaim it.

		// if services.exe owns the thread already, don't bother creating a worker.
		if ((DWORD)GetOwnerProcess() == GetCurrentProcessId())
			retval = (ERROR_SUCCESS == DeregisterServiceThread(lpdev));
		else
			retval = (ERROR_SUCCESS == ServiceSpinThreadAndWait(DeregisterServiceThread,lpdev));

errret:
		;
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		// no need to leave critical section here because it's 
		// been freed after UnloadSuperServices(), only a .exe
		// (not garbage application data or buggy service) could cause an AV 
		// to get us to this block before then.
		SetLastError(ERROR_INVALID_PARAMETER);
	}

	ServicesCeLog((L"SERVICES: -DeregisterService() returns %d",retval));
	return retval;
}

#define DEVICE_NAME_SIZE 6  // i.e. "COM1:" (includes space for 0 terminator)

void FormatDeviceName(LPWSTR lpszName, fsdev_t * lpdev)
{
	memcpy(lpszName, lpdev->type, sizeof(lpdev->type));
	lpszName[sizeof(lpdev->type)/sizeof(WCHAR)+0] = (WCHAR)(L'0' + lpdev->index);
	lpszName[sizeof(lpdev->type)/sizeof(WCHAR)+1] = L':';
	lpszName[sizeof(lpdev->type)/sizeof(WCHAR)+2] = 0;
}

BOOL SERV_GetServiceByIndex(DWORD dwIndex, LPWIN32_FIND_DATA lpFindFileData) {
	fsdev_t *lpdev;
	BOOL bRet = FALSE;

	if (PSLGetCallerTrust() != OEM_CERTIFY_TRUST) {
		lpFindFileData = (LPWIN32_FIND_DATA) MapCallerPtr(lpFindFileData,sizeof(WIN32_FIND_DATA));
		if (!lpFindFileData) {
			SetLastError(ERROR_INVALID_PARAMETER);
			return FALSE;
		}
	}

	ENTER_DEVICE_FUNCTION {
		lpdev = (fsdev_t *)g_ActiveServiceList.Flink;
		while (dwIndex && lpdev != (fsdev_t *)&g_ActiveServiceList) {
			dwIndex--;
			lpdev = (fsdev_t *)lpdev->list.Flink;
		}
		if (lpdev != (fsdev_t *)&g_ActiveServiceList) {
			lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
			memset (&lpFindFileData->ftCreationTime, 0, sizeof(lpFindFileData->ftCreationTime));
			memset (&lpFindFileData->ftLastAccessTime, 0, sizeof(lpFindFileData->ftLastAccessTime));
			memset (&lpFindFileData->ftLastWriteTime, 0, sizeof(lpFindFileData->ftLastWriteTime));
			lpFindFileData->nFileSizeHigh = 0;
			lpFindFileData->nFileSizeLow = 0;
			lpFindFileData->dwOID = 0xffffffff;
			FormatDeviceName(lpFindFileData->cFileName, lpdev);
			bRet = TRUE;
		}
	} EXIT_DEVICE_FUNCTION;
	return bRet;
}

// assumes len == 5 and lpnew[4] == ':'
HANDLE SERV_CreateServiceHandle(LPCWSTR lpNew, DWORD dwAccess, DWORD dwShareMode, HPROCESS hProc) {
	HANDLE hDev = INVALID_HANDLE_VALUE;
	DWORD dwErrCode = ERROR_DEV_NOT_EXIST;
	fsopendev_t *lpopendev = NULL;
	fsdev_t *lpdev = NULL;
	BOOL    fDoDec = FALSE;
	BOOL    fFreeDev = FALSE;

	if (NULL == (lpNew = (LPWSTR) ValidatePWSTR((LPWSTR)lpNew))) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}
	
	EnterCriticalSection(&g_ServCS);
	__try {
		for (lpdev = (fsdev_t *)g_ActiveServiceList.Flink;
		     lpdev != (fsdev_t *)&g_ActiveServiceList;
		     lpdev = (fsdev_t *)lpdev->list.Flink) 
		{
			if (!_wcsnicmp(lpNew,lpdev->type,sizeof(lpdev->type)/sizeof(WCHAR))) {
				if ((DWORD)(lpNew[3]-L'0') == lpdev->index) {
					if (!(lpopendev = (fsopendev_t *) LocalAlloc(LPTR,sizeof(fsopendev_t)))) {
						dwErrCode = ERROR_OUTOFMEMORY;
						goto errret;
					}
					lpopendev->lpDev = lpdev;
					lpdev->dwRefCnt++;
					fDoDec = TRUE;
					LeaveCriticalSection(&g_ServCS);

					__try {
						 lpopendev->dwOpenData = lpdev->fnOpen(lpdev->dwData,dwAccess,dwShareMode);
					}
					__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
						DEBUGMSG(ZONE_ERROR,(L"SERVICES!CreateHandle:xxx_Open(%s) throws exception 0x%08x\r\n",lpNew,GetExceptionCode()));
						lpopendev->dwOpenData = 0;
					}

					EnterCriticalSection(&g_ServCS);
					fDoDec = FALSE;
					lpdev->dwRefCnt--;
					if ((!lpopendev->dwOpenData) || (!IsValidService(lpdev))) {
						fFreeDev = (lpdev->dwRefCnt == 0);
						dwErrCode = ERROR_OPEN_FAILED;
						goto errret;
					}
					if (!(hDev = CreateAPIHandle(g_hDevFileApiHandle, lpopendev))) {
						hDev = INVALID_HANDLE_VALUE;
						dwErrCode = ERROR_OUTOFMEMORY;
						goto errret;
					}
					// OK, we managed to create the handle
					dwErrCode = 0;
					lpopendev->hProc = hProc;
					lpopendev->KHandle = hDev;
					lpopendev->dwOpenRefCnt = 1;
					lpopendev->nextptr = g_lpOpenDevs;
					g_lpOpenDevs = lpopendev;
					break;
				}
			}
		}
errret:
		if( dwErrCode ) {
			if (lpopendev)
				LocalFree(lpopendev);

			SetLastError( dwErrCode );
		}
	}
	__except(EXCEPTION_EXECUTE_HANDLER) {
		SetLastError(ERROR_INVALID_PARAMETER);
		if (lpopendev)
			LocalFree(lpopendev);
		if (fDoDec) {
			EnterCriticalSection(&g_ServCS);
			lpdev->dwRefCnt--;
			fFreeDev = (lpdev->dwRefCnt == 0);
			LeaveCriticalSection(&g_ServCS);
		}
	}
	LeaveCriticalSection(&g_ServCS);

	if (fFreeDev) {
		FreeService(lpdev);
	}
	return hDev;
}

BOOL SERV_CloseFileHandle(fsopendev_t *fsodev)
{
	BOOL retval = FALSE;
	fsopendev_t *fsTrav;

	EnterCriticalSection(&g_ServCS);
	if (g_lpOpenDevs == fsodev)
		g_lpOpenDevs = fsodev->nextptr;
	else {
		fsTrav = g_lpOpenDevs;
		while (fsTrav != NULL && fsTrav->nextptr != fsodev) {
			fsTrav = fsTrav->nextptr;
		}
		if (fsTrav != NULL) {
			fsTrav->nextptr = fsodev->nextptr;
		} else {
			LeaveCriticalSection(&g_ServCS);
			DEBUGMSG(ZONE_ERROR, (L"SERVICES!CloseHandle fails, fsodev (0x%08x) not in list\r\n", fsodev));
			SetLastError(ERROR_INVALID_PARAMETER);
			return FALSE;
		}
	}
	DEBUGCHK(fsodev->dwOpenRefCnt);

	if (IsValidService(fsodev->lpDev)) {
		OpenAddRef(fsodev);
		LeaveCriticalSection(&g_ServCS);
		__try {
			retval = fsodev->lpDev->fnClose(fsodev->dwOpenData);
		}
		__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
			DEBUGMSG(ZONE_ERROR,(L"SERVICES!CloseHandle:xxx_Close on handle 0x%08x throws exception 0x%08x\r\n",fsodev,GetExceptionCode()));
		}

		EnterCriticalSection(&g_ServCS);
		DEBUGCHK(fsodev->dwOpenRefCnt > 1);
		OpenDelRef(fsodev);
	}
	fsodev->dwOpenRefCnt--;

	if (!fsodev->dwOpenRefCnt) {
		LocalFree(fsodev);
	}
	LeaveCriticalSection(&g_ServCS);
	return retval;
}

// If we don't trust a pointer we can't do a strlen on it.  If first few bytes are
// legit assume rest of it's OK, too.  Since services.exe never writes out to
// strings (all write buffers require size) this is safe.
PSTR ValidatePSTR(const PSTR psz, const DWORD cbLen) {
	if (PSLGetCallerTrust() != OEM_CERTIFY_TRUST) {
		return (PSTR) MapCallerPtr(psz,cbLen);
	}
	return psz;
}

PWSTR ValidatePWSTR(const PWSTR psz, const DWORD cbLen) {
	if (PSLGetCallerTrust() != OEM_CERTIFY_TRUST) {
		return (PWSTR) MapCallerPtr(psz,cbLen);
	}
	return psz;
}

HANDLE SERV_ActivateService(LPCWSTR lpszDevKey, DWORD dwClientInfo) {
	if (NULL == (lpszDevKey = (LPWSTR)ValidatePWSTR((LPWSTR)lpszDevKey))) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}
	return StartOneService(lpszDevKey,dwClientInfo,FALSE);
}


// The return buffer is filled with a double-NULL terminated list of
// NULL terminated strings of the device names, for instance:
// HTP0:\\0TEL0:\\0TEL1:\\0\\0
BOOL SERV_EnumServices(PBYTE pBuffer, DWORD *pdwServiceEntries, DWORD *pdwBufferLen) {
	DWORD      dwRet = ERROR_INVALID_PARAMETER;
	fsdev_t    *pTrav;
	DWORD      cbBytesRequired = 0;
	DWORD      dwCurrent       = 0;
	DWORD      dwServiceEntries = 0;
	ServiceEnumInfo *pEnumTrav;
	WCHAR      *szWriteTrav;
	DWORD      i =0;

	if (PSLGetCallerTrust() != OEM_CERTIFY_TRUST) {
		pdwBufferLen = (PDWORD) MapCallerPtr(pdwBufferLen,sizeof(DWORD));
		pdwServiceEntries = (PDWORD) MapCallerPtr(pdwServiceEntries,sizeof(DWORD));
	}

	if (!pdwServiceEntries || !pdwBufferLen) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	if (pBuffer && *pdwBufferLen && (PSLGetCallerTrust() != OEM_CERTIFY_TRUST)) {
		pBuffer = (PBYTE) MapCallerPtr(pBuffer,*pdwBufferLen);

		if (!pBuffer) {
			SetLastError(ERROR_INVALID_PARAMETER);
			return FALSE;
		}
	}

	EnterCriticalSection(&g_ServCS);
	for (pTrav = (fsdev_t *)g_ActiveServiceList.Flink;
	     pTrav != (fsdev_t *)&g_ActiveServiceList;
	     pTrav = (fsdev_t *)pTrav->list.Flink) 
	{
		cbBytesRequired += (1+pTrav->dwDllNameLen)*sizeof(WCHAR);
		dwServiceEntries++;
	}
	cbBytesRequired += (sizeof(ServiceEnumInfo) * dwServiceEntries);

	*pdwServiceEntries = dwServiceEntries;

	if (!pBuffer) {
		*pdwBufferLen = cbBytesRequired;
		dwRet = ERROR_INSUFFICIENT_BUFFER;
		goto done;
	}
	else if (*pdwBufferLen < cbBytesRequired) {
		*pdwBufferLen = cbBytesRequired;
		dwRet = ERROR_MORE_DATA;
		goto done;
	}
	*pdwBufferLen = cbBytesRequired;

	pEnumTrav   = (ServiceEnumInfo*) pBuffer;
	szWriteTrav = (WCHAR*) (pBuffer + (dwServiceEntries * sizeof(ServiceEnumInfo)));

	for (pTrav = (fsdev_t *)g_ActiveServiceList.Flink;
	     pTrav != (fsdev_t *)&g_ActiveServiceList;
	     pTrav = (fsdev_t *)pTrav->list.Flink) 
	{
		FormatDeviceName(pEnumTrav->szPrefix,pTrav);
		pEnumTrav->hServiceHandle = (HANDLE) pTrav;

		wcscpy(szWriteTrav,pTrav->szDllName);
		pEnumTrav->szDllName = szWriteTrav;
		szWriteTrav += (pTrav->dwDllNameLen + 1);

		pEnumTrav++;
	}

	// save IOControls until the end because the g_ActiveServiceList may change
	// between calls since we have to yield CS, however pEnumTrav has all the
	// info we need.
	pEnumTrav = (ServiceEnumInfo*) pBuffer;

	for (i = 0; i < dwServiceEntries; i++) {
		DWORD dwServiceState;

		if (IsValidService((fsdev_t *)pEnumTrav->hServiceHandle)) {
			if (InternalServiceIOControl((fsdev_t *)pEnumTrav->hServiceHandle,IOCTL_SERVICE_STATUS,NULL,0,(PBYTE)&dwServiceState,sizeof(dwServiceState)))
				pEnumTrav->dwServiceState = dwServiceState;
			else
				pEnumTrav->dwServiceState = SERVICE_STATE_UNKNOWN;
		}
		else {
			// service has unloaded between calls.  Though we could remove this entry
			// from return list, probably not worth the effort.  Users should realize
			// that since services.exe is dynamic, services can change without warning.
			pEnumTrav->dwServiceState = SERVICE_STATE_UNKNOWN;
		}
		pEnumTrav++;
	}
	
	DEBUGCHK((CHAR*)pEnumTrav == (CHAR*)(pBuffer + (dwServiceEntries * sizeof(ServiceEnumInfo))));

	dwRet = ERROR_SUCCESS;
done:
	if (dwRet)
		SetLastError(dwRet);

	LeaveCriticalSection(&g_ServCS);
	return (dwRet == ERROR_SUCCESS) ? TRUE : FALSE;
}

// Takes a Service Prefix identifier and returns a service handle value
HANDLE SERV_GetServiceHandle(LPWSTR szPrefix, LPWSTR szDllName, DWORD *pdwDllBuf) {
	HANDLE  hService = INVALID_HANDLE_VALUE;
	DWORD   dwIndex = szPrefix ? szPrefix[3] - L'0' : 0;
	fsdev_t *pDevice;

	szPrefix = ValidatePWSTR(szPrefix,6);

	if (!szPrefix   || !szPrefix[0]         || !szPrefix[1]   || !szPrefix[2] ||
	    dwIndex > 9 ||  szPrefix[4] != L':' ||  szPrefix[5])    {
		SetLastError(ERROR_INVALID_PARAMETER);
		return hService;
	}

	if (szDllName && pdwDllBuf && (PSLGetCallerTrust() != OEM_CERTIFY_TRUST)) {
		pdwDllBuf = (PDWORD) MapCallerPtr(pdwDllBuf,sizeof(DWORD));
		if (pdwDllBuf)
			szDllName = (LPWSTR) MapCallerPtr(szDllName,*pdwDllBuf);

		if (! (szDllName && pdwDllBuf)) {
			SetLastError(ERROR_INVALID_PARAMETER);
			return hService;
		}
	}

	EnterCriticalSection(&g_ServCS);
	if (pDevice = FindPrefixInList(szPrefix,dwIndex,&g_ActiveServiceList)) {
		// szDllName is completly optional, no error if it's NULL
		if (szDllName) {
			if (!pdwDllBuf) {
				SetLastError(ERROR_INVALID_PARAMETER);
			}
			else if (*pdwDllBuf < (pDevice->dwDllNameLen+1)*sizeof(WCHAR)) {
				SetLastError(ERROR_MORE_DATA);
			}
			else {
				wcscpy(szDllName,pDevice->szDllName);
				hService = (HANDLE) pDevice;
			}
			if (pdwDllBuf)
				*pdwDllBuf = (pDevice->dwDllNameLen+1)*sizeof(WCHAR);
		}
		else
			hService = pDevice;
	}
	else {
		SetLastError(ERROR_NOT_FOUND);
	}
	LeaveCriticalSection(&g_ServCS);

	return hService;
}

BOOL SERV_ReadFile(fsopendev_t *fsodev, LPVOID buffer, DWORD nBytesToRead, LPDWORD lpNumBytesRead,
	LPOVERLAPPED lpOverlapped)
{
	BOOL retval = FALSE;
	DWORD dodec = 0;

	EnterCriticalSection(&g_ServCS);
	if (! IsValidFileHandle(fsodev) || !IsValidService(fsodev->lpDev)) {
		LeaveCriticalSection(&g_ServCS);
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!ReadFile (fsdev=0x%08x) fails, handle not valid\r\n",fsodev));
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}
	OpenAddRef(fsodev);
	LeaveCriticalSection(&g_ServCS);

	__try {
		*lpNumBytesRead = fsodev->lpDev->fnRead(fsodev->dwOpenData,buffer,nBytesToRead);
		if (*lpNumBytesRead == 0xffffffff) {
			retval = FALSE;
			*lpNumBytesRead = 0;
		} else
			retval = TRUE;
	}
	__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!xxx_ReadFile throws exception 0x%08x\r\n",GetExceptionCode()));
		SetLastError(ERROR_INVALID_PARAMETER);
	}

	EnterCriticalSection(&g_ServCS);
	OpenDelRef(fsodev);
	LeaveCriticalSection(&g_ServCS);

	DEBUGCHK((lpNumBytesRead==NULL) || (*lpNumBytesRead <= nBytesToRead));

	return retval;
}

BOOL SERV_WriteFile(fsopendev_t *fsodev, LPCVOID buffer, DWORD nBytesToWrite, LPDWORD lpNumBytesWritten,
	LPOVERLAPPED lpOverlapped)
{
	BOOL retval = FALSE;
	DWORD dodec = 0;

	EnterCriticalSection(&g_ServCS);
	if (! IsValidFileHandle(fsodev) || !IsValidService(fsodev->lpDev)) {
		LeaveCriticalSection(&g_ServCS);
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!WriteFile (fsdev=0x%08x) fails, handle not valid\r\n",fsodev));
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}
	OpenAddRef(fsodev);
	LeaveCriticalSection(&g_ServCS);

	__try {
		*lpNumBytesWritten = fsodev->lpDev->fnWrite(fsodev->dwOpenData,buffer,nBytesToWrite);
		if (*lpNumBytesWritten == 0xffffffff) {
			retval = FALSE;
			*lpNumBytesWritten = 0;
		} else
			retval = TRUE;
	}
	__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!xxx_WriteFile throws exception 0x%08x\r\n",GetExceptionCode()));
		SetLastError(ERROR_INVALID_PARAMETER);
	}

	EnterCriticalSection(&g_ServCS);
	OpenDelRef(fsodev);
	LeaveCriticalSection(&g_ServCS);

	return retval;
}

DWORD SERV_SetFilePointer(fsopendev_t *fsodev, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
	DWORD dwMoveMethod) {
	DWORD retval = 0xffffffff;

	EnterCriticalSection(&g_ServCS);
	if (! IsValidFileHandle(fsodev) || !IsValidService(fsodev->lpDev)) {
		LeaveCriticalSection(&g_ServCS);
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!SetFilePointer (fsdev=0x%08x) fails, handle not valid\r\n",fsodev));
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}
	OpenAddRef(fsodev);
	LeaveCriticalSection(&g_ServCS);

	__try {
		retval = fsodev->lpDev->fnSeek(fsodev->dwOpenData,lDistanceToMove,dwMoveMethod);
	}
	__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!xxx_SetFilePointer throws exception 0x%08x\r\n",GetExceptionCode()));
		SetLastError(ERROR_INVALID_PARAMETER);
	}

	EnterCriticalSection(&g_ServCS);
	OpenDelRef(fsodev);
	LeaveCriticalSection(&g_ServCS);

	return retval;
}

// Map the pointers automatically to save the service having to do this.
BOOL MapCmdLineParamPtrs(ServicesExeCommandLineParams *pPassed, ServicesExeCommandLineParams *pNew) {
	if (pPassed->dwArgs > SERVICES_EXE_MAX_CMD_LINE_OPTIONS) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}
	pNew->dwArgs = pPassed->dwArgs;

	for (DWORD i = 0; i < pPassed->dwArgs; i++) {
		if (NULL == (pNew->ppwszArgs[i] = (const WCHAR*) MapCallerPtr ((void*)pPassed->ppwszArgs[i],sizeof(WCHAR)))) {
			SetLastError(ERROR_INVALID_PARAMETER);
			return FALSE;
		}
	}
	return TRUE;
}


// This IOControl helper is used for app generated IO Control calls (not
// internal to services.exe).  Handles special case of starting and stopping service.
// Assumes that the service ref count has been increment and that lock is held when called.
BOOL ServIOControl(fsdev_t *lpdev, DWORD dwData, DWORD dwIoControlCode, LPVOID lpInBuf, DWORD nInBufSize, LPVOID lpOutBuf, DWORD nOutBufSize, LPDWORD lpBytesReturned) {
	BOOL fRet = FALSE;
	ServicesExeCommandLineParams cmdLine;


	if (IOCTL_SERVICE_COMMAND_LINE_PARAMS == dwIoControlCode) {
		if (! MapCmdLineParamPtrs((ServicesExeCommandLineParams*)lpInBuf,&cmdLine))
			return FALSE;
		lpInBuf = &cmdLine;
	}

	LeaveCriticalSection(&g_ServCS);
	
	__try {
		fRet = lpdev->fnControl(dwData,dwIoControlCode,(PBYTE)lpInBuf,nInBufSize,(PBYTE)lpOutBuf,nOutBufSize,lpBytesReturned);
	}
	__except (ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) {
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!xxx_DeviceIOControl throws exception 0x%08x\r\n",GetExceptionCode()));
		SetLastError(ERROR_INVALID_PARAMETER);
	}

	if (fRet && (lpdev->dwContext & SERVICE_INIT_STOPPED) && (dwIoControlCode == IOCTL_SERVICE_STOP)) {
		UnloadSuperServices(lpdev,FALSE,TRUE);
	}

	if (fRet && lpdev->szRegPath && (lpdev->dwContext & SERVICE_INIT_STOPPED) && (dwIoControlCode == IOCTL_SERVICE_START)) {
		StartSuperService(lpdev->szRegPath,(HANDLE) lpdev,FALSE);
	}

	EnterCriticalSection(&g_ServCS);

	// Event logging
	if (dwIoControlCode == IOCTL_SERVICE_STOP) {
		if (fRet)
			WriteEventLogSuccess(SERVICES_PAUSE_SUCCEED_MSG,lpdev->szFriendlyName);
		else
			WriteEventLogFailure(SERVICES_PAUSE_FAILED_MSG,lpdev->szFriendlyName);
	}
	else if (dwIoControlCode == IOCTL_SERVICE_START) {
		if (fRet)
			WriteEventLogSuccess(SERVICES_RESUME_SUCCEED_MSG,lpdev->szFriendlyName);
		else
			WriteEventLogFailure(SERVICES_RESUME_FAILED_MSG,lpdev->szFriendlyName);
	}
	
	return fRet;
}


// SERV_DeviceIoControl exepcts a valid file handle to be passed. (i.e. it
// was opened via CreateFile("XXX#:",...).
BOOL SERV_DeviceIoControl(fsopendev_t *fsodev, DWORD dwIoControlCode, LPVOID lpInBuf, DWORD nInBufSize, LPVOID lpOutBuf, DWORD nOutBufSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) {
	BOOL retval = FALSE;
	DWORD dodec = 0;

	EnterCriticalSection(&g_ServCS);
	if (! IsValidFileHandle(fsodev) || !IsValidService(fsodev->lpDev)) {
		LeaveCriticalSection(&g_ServCS);
		DEBUGMSG(ZONE_ERROR,(L"SERVICES!SetFilePointer (fsdev=0x%08x) fails, handle not valid\r\n",fsodev));
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	OpenAddRef(fsodev);
	retval = ServIOControl(fsodev->lpDev,fsodev->dwOpenData,dwIoControlCode,(PBYTE)lpInBuf,nInBufSize,(PBYTE)lpOutBuf,nOutBufSize,lpBytesReturned);
	OpenDelRef(fsodev);

	LeaveCriticalSection(&g_ServCS);
	return retval;
}

// Expects a service handle to be passed (i.e. value returned by GetServiceHandle,
// RegisterDevice, or ActivateDevice).  This function will be called by programmer
// whereas SERV_DeviceIoControl must be called into via the file system handler.
// This function is implemented for backward compatibility reasons, as versions of CE
// prior to 4.0 do not have the filesys support for services.exe.
BOOL SERV_ServiceIoControl(HANDLE hService, DWORD dwIoControlCode, LPVOID lpInBuf, DWORD nInBufSize, LPVOID lpOutBuf, DWORD nOutBufSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) {
	fsdev_t *lpdev = (fsdev_t *) hService;
	BOOL     fRet;
	BOOL     fFreeService;
	
	EnterCriticalSection(&g_ServCS);
	if (!IsValidService(lpdev)) {
		SetLastError(ERROR_INVALID_PARAMETER);
		LeaveCriticalSection(&g_ServCS);
		return FALSE;
	}
	lpdev->dwRefCnt++;

	fRet = ServIOControl(lpdev,lpdev->dwData,dwIoControlCode, lpInBuf, nInBufSize, lpOutBuf, nOutBufSize, lpBytesReturned);

	lpdev->dwRefCnt--;
	fFreeService = (lpdev->dwRefCnt == 0);
	LeaveCriticalSection(&g_ServCS);

	if (fFreeService)
		FreeService(lpdev);

	return fRet;
}

DWORD SERV_GetFileSize(fsopendev_t *fsodev, LPDWORD lpFileSizeHigh) {
	SetLastError(ERROR_INVALID_FUNCTION);
	return 0xffffffff;
}

BOOL SERV_GetFileInformationByHandle(fsopendev_t *fsodev, LPBY_HANDLE_FILE_INFORMATION lpFileInfo) {
	SetLastError(ERROR_INVALID_FUNCTION);
	return FALSE;
}

BOOL SERV_FlushFileBuffers(fsopendev_t *fsodev) {
	SetLastError(ERROR_INVALID_FUNCTION);
	return FALSE;
}

BOOL SERV_GetFileTime(fsopendev_t *fsodev, LPFILETIME lpCreation, LPFILETIME lpLastAccess, LPFILETIME lpLastWrite) {
	SetLastError(ERROR_INVALID_FUNCTION);
	return FALSE;
}

BOOL SERV_SetFileTime(fsopendev_t *fsodev, CONST FILETIME *lpCreation, CONST FILETIME *lpLastAccess, CONST FILETIME *lpLastWrite) {
	SetLastError(ERROR_INVALID_FUNCTION);
	return FALSE;
}

BOOL SERV_SetEndOfFile(fsopendev_t *fsodev) {
	SetLastError(ERROR_INVALID_FUNCTION);
	return FALSE;
}

void NotifyAllServices(DWORD dwIOCtl, BYTE *pInData, DWORD cbInData) {
	fsdev_t *   pTrav;
	int         icalls, i;
	HANDLE      h[20];
	HANDLE      *ph;

	EnterCriticalSection(&g_ServCS);

	// create a list of all services we need to send IOCTL to.  We can't just
	// run through list because on each call to the xxx_IOControl services.exe
	// releases its critical section, so list could be different between multiple calls.
	icalls = 0;

	for (pTrav = (fsdev_t *)g_ActiveServiceList.Flink;
	     pTrav != (fsdev_t *)&g_ActiveServiceList;
	     pTrav = (fsdev_t *)pTrav->list.Flink) 
	{
		icalls++;
	}

	if (icalls < (sizeof(h) / sizeof(h[0])))
		ph = h;
	else {
		ph = (HANDLE *)LocalAlloc (LMEM_FIXED, icalls * sizeof(HANDLE));
		if (ph == NULL) { // Run out memory. Do as much as we can
			ph = h;
			icalls= sizeof(h) / sizeof(h[0]);
		}
	}

	i = 0;

	for (pTrav = (fsdev_t *)g_ActiveServiceList.Flink;
	     pTrav != (fsdev_t *)&g_ActiveServiceList;
	     pTrav = (fsdev_t *)pTrav->list.Flink) 
	{
		if (i == icalls)
			break;

		ph[i++] = (fsdev_t *)pTrav;
	}
	LeaveCriticalSection (&g_ServCS);

	for (i = 0 ; i < icalls ; ++i) {
		// Use SERV_ServiceIoControl rather than faster InternalServiceIOControl because we don't own
		// critical section at this point, SERV_ServiceIoControl will verify that the service hasn't been unloaded since call started.
		SERV_ServiceIoControl (ph[i], dwIOCtl, pInData, cbInData, NULL, 0, NULL, NULL);
	}

	if (ph != h)
		LocalFree (ph);
}

static void DevPSLNotify (DWORD flags, HPROCESS proc, HTHREAD thread) {
	fsopendev_t *fsodev;
	int         icalls, i;
	HANDLE      h[20];
	HANDLE      *ph;

	DEVICE_PSL_NOTIFY pslPacket;

	EnterCriticalSection(&g_ServCS);

	fsodev = g_lpOpenDevs;

	icalls = 0;

	while (fsodev) {
		if (fsodev->hProc == proc)
			++icalls;
		fsodev = fsodev->nextptr;
	}

	if (icalls < sizeof(h) / sizeof(h[0]))
		ph = h;
	else {
		ph = (HANDLE *)LocalAlloc (LMEM_FIXED, icalls * sizeof(HANDLE));
		if (ph==NULL) { // Run out memory. Do as much as we can
			ph = h;
			icalls= sizeof(h) / sizeof(h[0]);
		};
	};

	i = 0;
	fsodev = g_lpOpenDevs;

	while (fsodev && i < icalls) {
		if (fsodev->hProc == proc)
			ph[i++] = fsodev->KHandle;
		fsodev = fsodev->nextptr;
	}

	LeaveCriticalSection (&g_ServCS);

	pslPacket.dwSize  = sizeof(pslPacket);
	pslPacket.dwFlags = flags;
	pslPacket.hProc   = proc;
	pslPacket.hThread = thread;

	for (i = 0 ; i < icalls ; ++i)
		DeviceIoControl (ph[i], IOCTL_PSL_NOTIFY, (LPVOID)&pslPacket, sizeof(pslPacket), NULL, 0, NULL, NULL);

	if (ph != h)
		LocalFree (ph);
}

void SERV_ProcNotify(DWORD flags, HPROCESS proc, HTHREAD thread) {
	switch (flags) {
		case DLL_PROCESS_EXITING:
			DevPSLNotify (flags, proc, thread);
			break;
//      case DLL_SYSTEM_STARTED:
//          break;
	}

	return;
}

void SERV_CloseAllServiceHandles(HPROCESS proc) {
	// Function support removed because it was unused and a security risk.
	return; // ERROR_CALL_NOT_IMPLEMENTED
}

void DeInitServicesDataStructures(void) {
	DeleteCriticalSection(&g_ServCS);
	CloseHandle(g_hCleanEvt);
}

void InitServicesDataStructures(void) {
	InitializeListHead(&g_ActiveServiceList);
	InitializeListHead(&g_NewServiceList);
	InitializeListHead(&g_DyingServiceList);
	InitializeCriticalSection(&g_ServCS);
	g_hCleanEvt = CreateEvent(0,0,0,0);
}

// int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nCmShow)
int wmain (int argc, WCHAR **argv)
{
	if (IsAPIReady(SH_SERVICES)) {
		return HandleServicesCommandLine(argc,argv);
	}
	
	DEBUGMSG(ZONE_INIT, (L"SERVICES: Starting\r\n"));

	InitServicesDataStructures();

	// required for main instance of services.exe.
	g_hDevApiHandle = CreateAPISet("SRVS", NUM_FDEV_APIS, FDevApiMethods, FDevApiSigs);
	RegisterAPISet(g_hDevApiHandle, SH_SERVICES);
	g_hDevFileApiHandle = CreateAPISet("W32S", NUM_FAPIS, DevFileApiMethods, DevFileApiSigs);
	RegisterAPISet(g_hDevFileApiHandle, HT_FILE | REGISTER_APISET_TYPE);

	if (! ServerLoadInit())
		return 1;

	SignalStarted(argc >=2 ? _wtol(argv[1]) : 0);

	while (1) {
		Sleep(INFINITE);
	}
	return 1;
}

#if defined (SDK_BUILD)
#define SERVICES_CMDLINE_BUFSIZE     4096
#define SERVICES_MAX_CMD_ARGS        10

BOOL CrackCmdLine(LPWSTR lpCmdLine, int *argc, WCHAR *argv[SERVICES_MAX_CMD_ARGS], WCHAR *szCmdLineBuf) {
	WCHAR *szTrav = szCmdLineBuf;
	BOOL  fDone = FALSE;

	// Crack cmd line so that it's in argv/argc format.
	argv[0] = SERVICE_SERVICES_EXE_PROCNAME;
	if (wcslen(lpCmdLine) > SERVICES_CMDLINE_BUFSIZE) {
		RETAILMSG(1,(L"SERVICES!Services command line passed is too large, must be < %d characters)",SERVICES_CMDLINE_BUFSIZE));
		return FALSE;
	}
	wcscpy(szCmdLineBuf,lpCmdLine);

	while (!fDone && (*argc < SERVICES_MAX_CMD_ARGS)) {
		int iPos;

		while (szTrav[0] == 0 && szTrav[0] == L'\t' && szTrav[0] == L' ') 
			++szTrav;

		if (szTrav[0] == 0)
			break;

		iPos  = wcscspn(szTrav,L" \t");
		fDone = (szTrav[iPos] == 0);

		szTrav[iPos] = 0;
		argv[(*argc)++] = szTrav;
		szTrav += (iPos + 1);
	}

	return TRUE;
}

int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nShowCmd)
{
	WCHAR *argv[SERVICES_MAX_CMD_ARGS];
	int argc = 1;
	WCHAR szCmdLineBuf[SERVICES_CMDLINE_BUFSIZE];

	if (! IsAPIReady(SH_SERVICES)) {
		return wmain(0,0);
	}

	if (! CrackCmdLine(lpCmdLine,&argc,argv,szCmdLineBuf))
		return 0;

	return HandleServicesCommandLine(argc,argv);
}
#endif  // SDK_BUILD



