2019 lines
56 KiB
C
2019 lines
56 KiB
C
/*++
|
||
|
||
Copyright (c) 1990-1992 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
scapi.c
|
||
|
||
Abstract:
|
||
|
||
Contains the Service-related API that are implemented solely in
|
||
DLL form. These include:
|
||
StartServiceCtrlDispatcherA
|
||
StartServiceCtrlDispatcherW
|
||
RegisterServiceCtrlHandlerW
|
||
RegisterServiceCtrlHandlerA
|
||
|
||
This file also contains the following local support routines:
|
||
ScDispatcherLoop
|
||
ScCreateDispatchTableW
|
||
ScCreateDispatchTableA
|
||
ScCreateThreadStartParms
|
||
ScConnectServiceController
|
||
ScExpungeMessage
|
||
ScGetPipeInput
|
||
ScGetDispatchEntry
|
||
ScNormalizeCmdLineArgs
|
||
ScSendResponse
|
||
ScSvcctrlThreadW
|
||
ScSvcctrlThreadA
|
||
|
||
Author:
|
||
|
||
Dan Lafferty (danl) 09 Apr-1991
|
||
|
||
Environment:
|
||
|
||
User Mode -Win32
|
||
|
||
Revision History:
|
||
|
||
03-Jun-1996 AnirudhS
|
||
ScGetPipeInput: If the message received from the service controller
|
||
is not a SERVICE_CONTROL_START message, don't allocate space for the
|
||
arguments, since there are none, and since that space never gets freed.
|
||
22-Sep-1995 AnirudhS
|
||
Return codes from InitializeStatusBinding were not being handled
|
||
correctly; success was sometimes reported on failure. Fixed this.
|
||
12-Aug-1993 Danl
|
||
ScGetDispatchEntry: When the first entry in the table is marked as
|
||
OwnProcess, then this function should just return the pointer to the
|
||
top of the table. It should ignore the ServiceName. In all cases,
|
||
when the service is started as an OWN_PROCESS, only the first entry
|
||
in the dispath table should be used.
|
||
04-Aug-1992 Danl
|
||
When starting a service, always pass the service name as the
|
||
first parameter in the argument list.
|
||
27-May-1992 JohnRo
|
||
RAID 9829: winsvc.h and related file cleanup.
|
||
09 Apr-1991 danl
|
||
created
|
||
|
||
--*/
|
||
|
||
//
|
||
// INCLUDES
|
||
//
|
||
|
||
#include <nt.h> // DbgPrint prototype
|
||
#include <ntrtl.h> // DbgPrint prototype
|
||
#include <rpc.h> // DataTypes and runtime APIs
|
||
#include <nturtl.h> // needed for winbase.h
|
||
#include <windef.h> // windows types needed for winbase.h
|
||
#include <winbase.h> // CreateFile
|
||
|
||
#include <string.h> // strcmp
|
||
#include <stdlib.h> // wide character c runtimes.
|
||
#include <tstr.h> // WCSSIZE().
|
||
|
||
#include <winsvc.h> // public Service Controller Interface.
|
||
|
||
#include "scbind.h" // InitializeStatusBinding
|
||
#include <valid.h> // MAX_SERVICE_NAME_LENGTH
|
||
#include <control.h> // CONTROL_PIPE_NAME
|
||
#include <scdebug.h> // STATIC
|
||
#include <sclib.h> // ScConvertToUnicode
|
||
|
||
//
|
||
// Internal Dispatch Table.
|
||
//
|
||
|
||
typedef union _START_ROUTINE_TYPE {
|
||
LPSERVICE_MAIN_FUNCTIONW U; // unicode type
|
||
LPSERVICE_MAIN_FUNCTIONA A; // ansi type
|
||
} START_ROUTINE_TYPE, *LPSTART_ROUTINE_TYPE;
|
||
|
||
typedef struct _INTERNAL_DISPATCH_ENTRY {
|
||
LPWSTR ServiceName;
|
||
START_ROUTINE_TYPE ServiceStartRoutine;
|
||
LPHANDLER_FUNCTION ControlHandler;
|
||
SERVICE_STATUS_HANDLE StatusHandle;
|
||
BOOL OwnProcess;
|
||
} INTERNAL_DISPATCH_ENTRY, *LPINTERNAL_DISPATCH_ENTRY;
|
||
|
||
|
||
//
|
||
// This structure is passed to the internal
|
||
// startup thread which calls the real user
|
||
// startup routine with argv, argc parameters.
|
||
//
|
||
|
||
typedef struct _THREAD_STARTUP_PARMSW {
|
||
DWORD NumArgs;
|
||
LPSERVICE_MAIN_FUNCTIONW ServiceStartRoutine;
|
||
LPWSTR VectorTable;
|
||
} THREAD_STARTUP_PARMSW, *LPTHREAD_STARTUP_PARMSW;
|
||
|
||
typedef struct _THREAD_STARTUP_PARMSA {
|
||
DWORD NumArgs;
|
||
LPSERVICE_MAIN_FUNCTIONA ServiceStartRoutine;
|
||
LPSTR VectorTable;
|
||
} THREAD_STARTUP_PARMSA, *LPTHREAD_STARTUP_PARMSA;
|
||
|
||
//
|
||
// The following is the amount of time we will wait for the named pipe
|
||
// to become available from the Service Controller.
|
||
//
|
||
#ifdef DEBUG
|
||
#define CONTROL_WAIT_PERIOD NMPWAIT_WAIT_FOREVER
|
||
#else
|
||
#define CONTROL_WAIT_PERIOD 15000 // 15 seconds
|
||
#endif
|
||
|
||
//
|
||
// This is the number of times we will continue to loop when pipe read
|
||
// failures occur. After this many tries, we cease to read the pipe.
|
||
//
|
||
#define MAX_RETRY_COUNT 30
|
||
|
||
//
|
||
// Globals
|
||
//
|
||
|
||
LPINTERNAL_DISPATCH_ENTRY DispatchTable=NULL; // table head.
|
||
|
||
//
|
||
// This flag is set to TRUE if the control dispatcher is to support
|
||
// ANSI calls. Otherwise the flag is set to FALSE.
|
||
//
|
||
BOOL AnsiFlag = FALSE;
|
||
|
||
//
|
||
// Internal Functions
|
||
//
|
||
|
||
DWORD
|
||
ScCreateDispatchTableW(
|
||
IN LPSERVICE_TABLE_ENTRYW UserDispatchTable,
|
||
OUT LPINTERNAL_DISPATCH_ENTRY *DispatchTablePtr
|
||
);
|
||
|
||
DWORD
|
||
ScCreateDispatchTableA(
|
||
IN LPSERVICE_TABLE_ENTRYA UserDispatchTable,
|
||
OUT LPINTERNAL_DISPATCH_ENTRY *DispatchTablePtr
|
||
);
|
||
|
||
DWORD
|
||
ScCreateThreadStartParms(
|
||
IN LPCTRL_MSG_HEADER Msg,
|
||
IN DWORD NumBytesRead,
|
||
OUT LPTHREAD_STARTUP_PARMSW *ThreadParmPtr,
|
||
OUT LPBYTE *TempArgPtr,
|
||
OUT LPDWORD ArgBytesRemaining
|
||
);
|
||
|
||
VOID
|
||
ScDispatcherLoop(
|
||
IN HANDLE PipeHandle,
|
||
IN LPCTRL_MSG_HEADER Msg,
|
||
IN DWORD BufferSize
|
||
);
|
||
|
||
DWORD
|
||
ScConnectServiceController (
|
||
OUT LPHANDLE pipeHandle
|
||
);
|
||
|
||
VOID
|
||
ScExpungeMessage(
|
||
IN HANDLE PipeHandle
|
||
);
|
||
|
||
DWORD
|
||
ScGetPipeInput (
|
||
IN HANDLE PipeHandle,
|
||
IN OUT LPCTRL_MSG_HEADER Msg,
|
||
IN DWORD BufferSize,
|
||
OUT LPTHREAD_STARTUP_PARMSW *ThreadParmPtr
|
||
);
|
||
|
||
DWORD
|
||
ScGetDispatchEntry (
|
||
OUT LPINTERNAL_DISPATCH_ENTRY *DispatchEntry,
|
||
IN LPWSTR ServiceName
|
||
);
|
||
|
||
VOID
|
||
ScNormalizeCmdLineArgs(
|
||
IN OUT LPCTRL_MSG_HEADER Msg,
|
||
IN OUT LPTHREAD_STARTUP_PARMSW ThreadStartupParms
|
||
);
|
||
|
||
VOID
|
||
ScSendResponse (
|
||
IN HANDLE pipeHandle,
|
||
IN DWORD Response
|
||
);
|
||
|
||
DWORD
|
||
ScSvcctrlThreadW(
|
||
IN LPTHREAD_STARTUP_PARMSW lpThreadStartupParms
|
||
);
|
||
|
||
DWORD
|
||
ScSvcctrlThreadA(
|
||
IN LPTHREAD_STARTUP_PARMSA lpThreadStartupParms
|
||
);
|
||
|
||
|
||
BOOL
|
||
WINAPI
|
||
StartServiceCtrlDispatcherA (
|
||
IN LPSERVICE_TABLE_ENTRYA lpServiceStartTable
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function provides the ANSI interface for the
|
||
StartServiceCtrlDispatcher function.
|
||
|
||
Arguments:
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
DWORD status;
|
||
NTSTATUS ntstatus;
|
||
HANDLE pipeHandle;
|
||
LPCTRL_MSG_HEADER msg = NULL;
|
||
DWORD bufferSize;
|
||
|
||
//
|
||
// Set the AnsiFlag to indicate that the control dispatcher must support
|
||
// ansi function calls only.
|
||
//
|
||
AnsiFlag = TRUE;
|
||
|
||
//
|
||
// Create an internal DispatchTable.
|
||
//
|
||
status = ScCreateDispatchTableA(
|
||
lpServiceStartTable,
|
||
(LPINTERNAL_DISPATCH_ENTRY *)&DispatchTable);
|
||
|
||
if (status != NO_ERROR) {
|
||
SetLastError(status);
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Allocate a buffer big enough to contain at least the control message
|
||
// header and a service name. This ensures that, if the message is not
|
||
// a START message, it can be read in a single ReadFile.
|
||
//
|
||
bufferSize = sizeof(CTRL_MSG_HEADER) +
|
||
(MAX_SERVICE_NAME_LENGTH+1) * sizeof(WCHAR);
|
||
|
||
msg = (LPCTRL_MSG_HEADER)LocalAlloc(LMEM_ZEROINIT, (UINT) bufferSize);
|
||
|
||
if (msg == NULL) {
|
||
SCC_LOG1(ERROR,"NetServiceStartCtrlDispatcherA:LocalAlloc failed rc = %d\n",
|
||
GetLastError());
|
||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
return(FALSE);
|
||
}
|
||
|
||
bufferSize = LocalSize(msg);
|
||
if (bufferSize == 0) {
|
||
SCC_LOG1(ERROR,"NetServiceStartCtrlDispatcherA:LocalSize failed rc = %d\n",
|
||
GetLastError());
|
||
ASSERT(0);
|
||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Connect to the Service Controller
|
||
//
|
||
|
||
status = ScConnectServiceController (&pipeHandle);
|
||
|
||
if (status != NO_ERROR) {
|
||
goto CleanExit;
|
||
}
|
||
|
||
//
|
||
// Initialize the binding for the status interface (NetServiceStatus).
|
||
//
|
||
|
||
SCC_LOG(TRACE,"Initialize the Status binding\n",0);
|
||
|
||
ntstatus = InitializeStatusBinding();
|
||
if (ntstatus != STATUS_SUCCESS) {
|
||
status = RtlNtStatusToDosError(ntstatus);
|
||
CloseHandle(pipeHandle);
|
||
goto CleanExit;
|
||
}
|
||
|
||
//
|
||
// Enter the dispatcher loop where we service control requests until
|
||
// all services in the service table have terminated.
|
||
//
|
||
|
||
ScDispatcherLoop (pipeHandle, msg, bufferSize);
|
||
|
||
CloseHandle(pipeHandle);
|
||
|
||
CleanExit:
|
||
|
||
//
|
||
// Clean up the dispatch table. Since we created unicode versions
|
||
// of all the service names, in ScCreateDispatchTableA, we now need to
|
||
// free them.
|
||
//
|
||
|
||
if (DispatchTable != NULL) {
|
||
|
||
LPINTERNAL_DISPATCH_ENTRY dispatchEntry;
|
||
|
||
for (dispatchEntry = DispatchTable;
|
||
dispatchEntry->ServiceName != NULL;
|
||
dispatchEntry++) {
|
||
|
||
LocalFree(dispatchEntry->ServiceName);
|
||
}
|
||
|
||
LocalFree(DispatchTable);
|
||
}
|
||
|
||
//
|
||
// Free the message buffer.
|
||
//
|
||
|
||
if (msg != NULL) {
|
||
LocalFree(msg);
|
||
}
|
||
|
||
if (status != NO_ERROR) {
|
||
SetLastError(status);
|
||
return(FALSE);
|
||
}
|
||
return(TRUE);
|
||
}
|
||
|
||
BOOL
|
||
WINAPI
|
||
StartServiceCtrlDispatcherW (
|
||
IN LPSERVICE_TABLE_ENTRYW lpServiceStartTable
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the Control Dispatcher thread. We do not return from this
|
||
function call until the Control Dispatcher is told to shut down.
|
||
The Control Dispatcher is responsible for connecting to the Service
|
||
Controller's control pipe, and receiving messages from that pipe.
|
||
The Control Dispatcher then dispatches the control messages to the
|
||
correct control handling routine.
|
||
|
||
Arguments:
|
||
|
||
lpServiceStartTable - This is a pointer to the top of a service dispatch
|
||
table that the service main process passes in. Each table entry
|
||
contains pointers to the ServiceName, and the ServiceStartRotuine.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - The Control Dispatcher successfully terminated.
|
||
|
||
ERROR_INVALID_DATA - The specified dispatch table does not contain
|
||
entries in the proper format.
|
||
|
||
ERROR_FAILED_SERVICE_CONTROLLER_CONNECT - The Control Dispatcher
|
||
could not connect with the Service Controller.
|
||
|
||
--*/
|
||
{
|
||
DWORD status;
|
||
NTSTATUS ntStatus;
|
||
HANDLE pipeHandle;
|
||
LPCTRL_MSG_HEADER msg;
|
||
DWORD bufferSize;
|
||
|
||
//
|
||
// Create the Real Dispatch Table
|
||
//
|
||
|
||
try {
|
||
status = ScCreateDispatchTableW(lpServiceStartTable, &DispatchTable);
|
||
}
|
||
except (EXCEPTION_EXECUTE_HANDLER) {
|
||
status = GetExceptionCode();
|
||
if (status != EXCEPTION_ACCESS_VIOLATION) {
|
||
SCC_LOG(ERROR,"StartServiceCtrlDispatcherW:Unexpected Exception 0x%lx\n",status);
|
||
}
|
||
}
|
||
if (status != NO_ERROR) {
|
||
SetLastError(status);
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Allocate a buffer big enough to contain at least the control message
|
||
// header and a service name. This ensures that, if the message is not
|
||
// a START message, it can be read in a single ReadFile.
|
||
//
|
||
bufferSize = sizeof(CTRL_MSG_HEADER) +
|
||
(MAX_SERVICE_NAME_LENGTH+1) * sizeof(WCHAR);
|
||
|
||
msg = (LPCTRL_MSG_HEADER)LocalAlloc(LMEM_ZEROINIT, (UINT) bufferSize);
|
||
|
||
if (msg == NULL) {
|
||
SCC_LOG1(ERROR,"NetServiceStartCtrlDispatcher:LocalAlloc failed rc = %d\n",
|
||
GetLastError());
|
||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
return(FALSE);
|
||
}
|
||
|
||
bufferSize = LocalSize(msg);
|
||
if (bufferSize == 0) {
|
||
SCC_LOG1(ERROR,"NetServiceStartCtrlDispatcher:LocalSize failed rc = %d\n",
|
||
GetLastError());
|
||
ASSERT(0);
|
||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Connect to the Service Controller
|
||
//
|
||
|
||
status = ScConnectServiceController (&pipeHandle);
|
||
if (status != NO_ERROR) {
|
||
LocalFree(DispatchTable);
|
||
LocalFree(msg);
|
||
SetLastError(status);
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Initialize the binding for the status interface (NetServiceStatus).
|
||
//
|
||
|
||
SCC_LOG(TRACE,"Initialize the Status binding\n",0);
|
||
|
||
ntStatus = InitializeStatusBinding();
|
||
if (ntStatus != STATUS_SUCCESS) {
|
||
status = RtlNtStatusToDosError(ntStatus);
|
||
CloseHandle(pipeHandle);
|
||
LocalFree(DispatchTable);
|
||
LocalFree(msg);
|
||
SetLastError(status);
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Enter the dispatcher loop where we service control requests until
|
||
// all services in the service table have terminated.
|
||
//
|
||
|
||
ScDispatcherLoop (pipeHandle, msg, bufferSize);
|
||
|
||
CloseHandle(pipeHandle);
|
||
|
||
return(TRUE);
|
||
}
|
||
|
||
VOID
|
||
ScDispatcherLoop(
|
||
IN HANDLE PipeHandle,
|
||
IN LPCTRL_MSG_HEADER Msg,
|
||
IN DWORD BufferSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the input loop that the Control Dispatcher stays in through-out
|
||
its life. Only two types of events will cause us to leave this loop:
|
||
|
||
1) The service controller instructed the dispatcher to exit.
|
||
2) The dispatcher can no longer communicate with the the
|
||
service controller.
|
||
|
||
Arguments:
|
||
|
||
PipeHandle: This is a handle to the pipe over which control
|
||
requests are received.
|
||
|
||
Return Value:
|
||
|
||
none
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
DWORD status;
|
||
DWORD controlStatus;
|
||
BOOL continueDispatch;
|
||
LPWSTR serviceName;
|
||
LPTHREAD_STARTUP_PARMSW threadStartupParmsW;
|
||
LPTHREAD_STARTUP_PARMSA threadStartupParmsA;
|
||
LPTHREAD_START_ROUTINE threadAddress;
|
||
LPVOID threadParms;
|
||
LPINTERNAL_DISPATCH_ENTRY dispatchEntry;
|
||
DWORD threadId;
|
||
HANDLE threadHandle;
|
||
DWORD i;
|
||
DWORD errorCount = 0;
|
||
|
||
//
|
||
// Input Loop
|
||
//
|
||
|
||
continueDispatch = TRUE;
|
||
|
||
do {
|
||
//
|
||
// Wait for input
|
||
//
|
||
|
||
controlStatus = ScGetPipeInput (
|
||
PipeHandle,
|
||
Msg,
|
||
BufferSize,
|
||
&threadStartupParmsW);
|
||
|
||
SCC_LOG(TRACE,"A control message has been received!\n",0);
|
||
|
||
//
|
||
// If we received good input, check to see if we are to shut down
|
||
// the ControlDispatcher. If not, then obtain the dispatchEntry
|
||
// from the dispatch table.
|
||
//
|
||
|
||
if (controlStatus == NO_ERROR) {
|
||
|
||
//
|
||
// Clear the error count
|
||
//
|
||
errorCount = 0;
|
||
|
||
|
||
serviceName = (LPWSTR) ((LPBYTE)Msg + Msg->ServiceNameOffset);
|
||
|
||
if ((serviceName[0] == L'\0') &&
|
||
(Msg->OpCode == SERVICE_STOP)) {
|
||
|
||
//
|
||
// The Dispatcher is being asked to shut down.
|
||
// (security check not required for this operation)
|
||
// Although perhaps it would be a good idea to verify
|
||
// that the request came from the Service Controller.
|
||
//
|
||
controlStatus = NO_ERROR;
|
||
continueDispatch = FALSE;
|
||
}
|
||
else {
|
||
dispatchEntry = DispatchTable;
|
||
|
||
|
||
//
|
||
// If this is a request to start a service that is in its
|
||
// own process, then we expect that there is only one entry
|
||
// in the dispatch table. We remember the fact that this
|
||
// service is alone in this process.
|
||
//
|
||
// It should be obvious at this point that we if there
|
||
// were other entries in the dispatch table, they will
|
||
// be ignored because when SERVICE_CONTROL_START_OWN
|
||
// is received. The moral of the story is: "Be sure
|
||
// the configuration information in the registry is correct".
|
||
//
|
||
|
||
if (Msg->OpCode == SERVICE_CONTROL_START_OWN) {
|
||
dispatchEntry->OwnProcess = TRUE;
|
||
}
|
||
|
||
//
|
||
// If the request is for a service that is in it's own
|
||
// process, then we don't check the service name.
|
||
// Since there is only one dispatch entry, we use the one
|
||
// at the top of the table.
|
||
//
|
||
|
||
if (!dispatchEntry->OwnProcess) {
|
||
controlStatus = ScGetDispatchEntry(&dispatchEntry, serviceName);
|
||
|
||
if (controlStatus != NO_ERROR) {
|
||
SCC_LOG(TRACE,"Service Name not in Dispatch Table\n",0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
if (controlStatus != ERROR_NOT_ENOUGH_MEMORY) {
|
||
|
||
//
|
||
// If an error occured and it is not an out-of-memory error,
|
||
// then the pipe read must have failed.
|
||
// In this case we Increment the error count.
|
||
// When this count reaches the MAX_RETRY_COUNT, then
|
||
// the service controller must be gone. We want to log an
|
||
// error and notify an administrator. Then go to sleep forever.
|
||
// Only a re-boot will solve this problem.
|
||
//
|
||
// We should be able to report out-of-memory errors back to
|
||
// the caller. It should be noted that out-of-memory errors
|
||
// do not clear the error count. But they don't add to it
|
||
// either.
|
||
//
|
||
|
||
errorCount++;
|
||
if (errorCount > MAX_RETRY_COUNT) {
|
||
|
||
//
|
||
// BUGBUG: Add eventlog call here.
|
||
//
|
||
|
||
Sleep(0xffffffff);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Dispatch the request
|
||
//
|
||
|
||
if ((continueDispatch == TRUE) && (controlStatus == NO_ERROR)) {
|
||
|
||
status = NO_ERROR;
|
||
|
||
switch(Msg->OpCode) {
|
||
|
||
case SERVICE_CONTROL_START_SHARE:
|
||
case SERVICE_CONTROL_START_OWN:
|
||
|
||
//
|
||
// Update the StatusHandle in the dispatch entry table
|
||
//
|
||
dispatchEntry->StatusHandle = Msg->StatusHandle;
|
||
|
||
//
|
||
// The Control Dispatcher is to start a service.
|
||
// start the new thread.
|
||
//
|
||
threadStartupParmsW->ServiceStartRoutine =
|
||
dispatchEntry->ServiceStartRoutine.U;
|
||
|
||
threadAddress = (LPTHREAD_START_ROUTINE)ScSvcctrlThreadW;
|
||
threadParms = (LPVOID)threadStartupParmsW;
|
||
//
|
||
// If the service needs to be called with ansi parameters,
|
||
// then do the conversion here.
|
||
//
|
||
if (AnsiFlag) {
|
||
threadStartupParmsA =
|
||
(LPTHREAD_STARTUP_PARMSA)threadStartupParmsW;
|
||
|
||
for (i=0; i < threadStartupParmsW->NumArgs; i++) {
|
||
if(!ScConvertToAnsi(
|
||
*(&threadStartupParmsA->VectorTable + i),
|
||
*(&threadStartupParmsW->VectorTable + i))) {
|
||
|
||
//
|
||
// Conversion error occured.
|
||
//
|
||
SCC_LOG(ERROR,"Could not convert args to ansi\n",0);
|
||
status = ERROR_SERVICE_NO_THREAD;
|
||
}
|
||
}
|
||
threadAddress = (LPTHREAD_START_ROUTINE)ScSvcctrlThreadA;
|
||
threadParms = (LPVOID)threadStartupParmsA;
|
||
}
|
||
|
||
if (status == NO_ERROR){
|
||
//
|
||
// Create the new thread
|
||
//
|
||
threadHandle = CreateThread (
|
||
NULL, // Thread Attributes.
|
||
0L, // Stack Size
|
||
threadAddress, // lpStartAddress
|
||
threadParms, // lpParameter
|
||
0L, // Creation Flags
|
||
&threadId); // lpThreadId
|
||
|
||
if (threadHandle == (HANDLE) NULL) {
|
||
SCC_LOG(ERROR,
|
||
"NetServiceStartCtrlDispatcher:CreateThread failed %d\n",
|
||
GetLastError());
|
||
status = ERROR_SERVICE_NO_THREAD;
|
||
}
|
||
else {
|
||
CloseHandle(threadHandle);
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
//
|
||
// Call the proper ControlHandler routine.
|
||
//
|
||
|
||
if (dispatchEntry->ControlHandler != NULL) {
|
||
try{
|
||
#if defined(_X86_)
|
||
//
|
||
// The Windows NT 3.1 SDK didn't prototype control
|
||
// handler functions as WINAPI, so a number of
|
||
// existing 3rd-party services have their control
|
||
// handler functions built as __cdecl instead. This
|
||
// is a workaround.
|
||
//
|
||
DWORD SaveEdi;
|
||
_asm mov SaveEdi, edi;
|
||
_asm mov edi, esp; // called function preserves EDI
|
||
#endif
|
||
dispatchEntry->ControlHandler(
|
||
Msg->
|
||
OpCode);
|
||
#if defined(_X86_)
|
||
_asm mov esp, edi;
|
||
_asm mov edi, SaveEdi;
|
||
#endif
|
||
}
|
||
except (EXCEPTION_EXECUTE_HANDLER) {
|
||
status = GetExceptionCode();
|
||
if (status != EXCEPTION_ACCESS_VIOLATION) {
|
||
SCC_LOG(ERROR,
|
||
"StartServiceCtrlDispatcherW:Unexpected Exception 0x%lx\n",
|
||
status);
|
||
}
|
||
status = ERROR_EXCEPTION_IN_SERVICE;
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// The control could not be delivered because there
|
||
// is no control handling routine registered for this
|
||
// service.
|
||
//
|
||
status = ERROR_SERVICE_CANNOT_ACCEPT_CTRL;
|
||
}
|
||
//
|
||
// If status is not good here, then an exception occured
|
||
// either because the pointer to the control handling
|
||
// routine was bad, or because an exception occured
|
||
// inside the control handling routine.
|
||
//
|
||
// ??EVENTLOG??
|
||
//
|
||
|
||
} // end switch.
|
||
|
||
//
|
||
// Send the status back to the sevice controller.
|
||
//
|
||
ScSendResponse (PipeHandle, status);
|
||
}
|
||
else {
|
||
|
||
//
|
||
// The controlStatus indicates failure, we always want to try
|
||
// to send the status back to the Service Controller.
|
||
//
|
||
|
||
ScSendResponse (PipeHandle, controlStatus);
|
||
|
||
switch (controlStatus) {
|
||
|
||
case ERROR_SERVICE_DOES_NOT_EXIST:
|
||
case ERROR_SERVICE_NO_THREAD:
|
||
|
||
//
|
||
// The Service Name is not in this .exe's dispatch table.
|
||
// Or a thread for a new service couldn't be created.
|
||
// ignore it. The Service Controller will tell us to
|
||
// shut down if necessary.
|
||
//
|
||
controlStatus = NO_ERROR;
|
||
break;
|
||
|
||
default:
|
||
|
||
//
|
||
// If the error is not specifically recognized, continue.
|
||
//
|
||
controlStatus = NO_ERROR;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
while (continueDispatch == TRUE);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
SERVICE_STATUS_HANDLE
|
||
WINAPI
|
||
RegisterServiceCtrlHandlerW (
|
||
IN LPCWSTR ServiceName,
|
||
IN LPHANDLER_FUNCTION ControlHandler
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function enters a pointer to a control handling routine and a
|
||
pointer to a security descriptor into the Control Dispatcher's
|
||
dispatch table.
|
||
|
||
Arguments:
|
||
|
||
ServiceName - This is a pointer to the Service Name string.
|
||
|
||
ControlHandler - This is a pointer to the service's control handling
|
||
routine.
|
||
|
||
Return Value:
|
||
|
||
This function returns a handle to the service that is to be used in
|
||
subsequent calls to SetServiceStatus. If the return value is NULL,
|
||
an error has occured, and GetLastError can be used to obtain the
|
||
error value. Possible values for error are:
|
||
|
||
NO_ERROR - If the operation was successful.
|
||
|
||
ERROR_INVALID_PARAMETER - The pointer to the control handler function
|
||
is NULL.
|
||
|
||
ERROR_INVALID_DATA -
|
||
|
||
ERROR_SERVICE_DOES_NOT_EXIST - The serviceName could not be found in
|
||
the dispatch table. This indicates that the configuration database
|
||
says the serice is in this process, but the service name doesn't
|
||
exist in the dispatch table.
|
||
|
||
--*/
|
||
{
|
||
|
||
DWORD status;
|
||
LPINTERNAL_DISPATCH_ENTRY dispatchEntry;
|
||
|
||
//
|
||
// Find the service in the dispatch table.
|
||
//
|
||
|
||
dispatchEntry = DispatchTable;
|
||
try {
|
||
status = ScGetDispatchEntry(&dispatchEntry, (LPWSTR) ServiceName);
|
||
}
|
||
except (EXCEPTION_EXECUTE_HANDLER) {
|
||
status = GetExceptionCode();
|
||
if (status != EXCEPTION_ACCESS_VIOLATION) {
|
||
SCC_LOG(ERROR,"RegisterServiceCtrlHandlerW:Unexpected Exception 0x%lx\n",status);
|
||
}
|
||
}
|
||
|
||
if(status != NO_ERROR) {
|
||
SCC_LOG(ERROR,
|
||
"RegisterServiceCtrlHandlerW: can't find dispatch entry\n",0);
|
||
|
||
SetLastError(status);
|
||
return(0L);
|
||
}
|
||
|
||
//
|
||
// Insert the ControlHandler and SecurityDescriptor pointers
|
||
//
|
||
|
||
if (ControlHandler == NULL) {
|
||
SCC_LOG(ERROR,
|
||
"RegisterServiceCtrlHandlerW: Ptr to ctrlhandler is NULL\n",0);
|
||
|
||
SetLastError(ERROR_INVALID_PARAMETER);
|
||
return(0L);
|
||
}
|
||
|
||
//
|
||
// Insert the entries into the table
|
||
//
|
||
|
||
dispatchEntry->ControlHandler = ControlHandler;
|
||
|
||
|
||
return(dispatchEntry->StatusHandle);
|
||
}
|
||
|
||
|
||
SERVICE_STATUS_HANDLE
|
||
WINAPI
|
||
RegisterServiceCtrlHandlerA (
|
||
IN LPCSTR ServiceName,
|
||
IN LPHANDLER_FUNCTION ControlHandler
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the ansi entry point for RegisterServiceCtrlHandler.
|
||
|
||
Arguments:
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
LPWSTR ServiceNameW;
|
||
SERVICE_STATUS_HANDLE statusHandle;
|
||
|
||
if(!ScConvertToUnicode(&ServiceNameW, ServiceName)) {
|
||
//
|
||
// The only thing that could happen to cause this to fail is
|
||
// a failure in LocalAlloc. (or else the ansi string is garbage).
|
||
//
|
||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
return(0L);
|
||
}
|
||
statusHandle = RegisterServiceCtrlHandlerW( ServiceNameW, ControlHandler);
|
||
|
||
LocalFree(ServiceNameW);
|
||
|
||
return(statusHandle);
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScCreateDispatchTableW(
|
||
IN LPSERVICE_TABLE_ENTRYW lpServiceStartTable,
|
||
OUT LPINTERNAL_DISPATCH_ENTRY *DispatchTablePtr
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine allocates space for the Control Dispatchers Dispatch Table.
|
||
It also initializes the table with the data that the service main
|
||
routine passed in with the lpServiceStartTable parameter.
|
||
|
||
This routine expects that pointers in the user's dispatch table point
|
||
to valid information. And that that information will stay valid and
|
||
fixed through out the life of the Control Dispatcher. In otherwords,
|
||
the ServiceName string better not move or get cleared.
|
||
|
||
Arguments:
|
||
|
||
lpServiceStartTable - This is a pointer to the first entry in the
|
||
dispatch table that the service's main routine passed in .
|
||
|
||
DispatchTablePtr - This is a pointer to the location where the
|
||
Service Controller's dispatch table is to be stored.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - The operation was successful.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - The memory allocation failed.
|
||
|
||
ERROR_INVALID_PARAMETER - There are no entries in the dispatch table.
|
||
|
||
--*/
|
||
{
|
||
DWORD numEntries;
|
||
LPINTERNAL_DISPATCH_ENTRY dispatchTable;
|
||
LPSERVICE_TABLE_ENTRYW entryPtr;
|
||
|
||
//
|
||
// Count the number of entries in the user dispatch table
|
||
//
|
||
|
||
numEntries = 0;
|
||
entryPtr = lpServiceStartTable;
|
||
|
||
while (entryPtr->lpServiceName != NULL) {
|
||
numEntries++;
|
||
entryPtr++;
|
||
}
|
||
|
||
if (numEntries == 0) {
|
||
SCC_LOG(ERROR,"ScCreateDispatchTable:No entries in Dispatch table!\n",0);
|
||
return(ERROR_INVALID_PARAMETER);
|
||
}
|
||
|
||
//
|
||
// Allocate space for the Control Dispatcher's Dispatch Table
|
||
//
|
||
|
||
dispatchTable = (LPINTERNAL_DISPATCH_ENTRY)LocalAlloc(LMEM_ZEROINIT,
|
||
sizeof(INTERNAL_DISPATCH_ENTRY) * (numEntries + 1));
|
||
|
||
if (dispatchTable == NULL) {
|
||
SCC_LOG(ERROR,"ScCreateDispatchTable: Local Alloc failed rc = %d\n",
|
||
GetLastError());
|
||
return (ERROR_NOT_ENOUGH_MEMORY);
|
||
}
|
||
|
||
//
|
||
// Move user dispatch info into the Control Dispatcher's table.
|
||
//
|
||
|
||
*DispatchTablePtr = dispatchTable;
|
||
entryPtr = lpServiceStartTable;
|
||
|
||
while (entryPtr->lpServiceName != NULL) {
|
||
dispatchTable->ServiceName = entryPtr->lpServiceName;
|
||
dispatchTable->ServiceStartRoutine.U= entryPtr->lpServiceProc;
|
||
dispatchTable->ControlHandler = NULL;
|
||
dispatchTable->StatusHandle = (SERVICE_STATUS_HANDLE)0;
|
||
dispatchTable->OwnProcess = FALSE;
|
||
entryPtr++;
|
||
dispatchTable++;
|
||
}
|
||
|
||
return (NO_ERROR);
|
||
}
|
||
|
||
DWORD
|
||
ScCreateDispatchTableA(
|
||
IN LPSERVICE_TABLE_ENTRYA lpServiceStartTable,
|
||
OUT LPINTERNAL_DISPATCH_ENTRY *DispatchTablePtr
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine allocates space for the Control Dispatchers Dispatch Table.
|
||
It also initializes the table with the data that the service main
|
||
routine passed in with the lpServiceStartTable parameter.
|
||
|
||
This routine expects that pointers in the user's dispatch table point
|
||
to valid information. And that that information will stay valid and
|
||
fixed through out the life of the Control Dispatcher. In otherwords,
|
||
the ServiceName string better not move or get cleared.
|
||
|
||
Arguments:
|
||
|
||
lpServiceStartTable - This is a pointer to the first entry in the
|
||
dispatch table that the service's main routine passed in .
|
||
|
||
DispatchTablePtr - This is a pointer to the location where the
|
||
Service Controller's dispatch table is to be stored.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - The operation was successful.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - The memory allocation failed.
|
||
|
||
ERROR_INVALID_PARAMETER - There are no entries in the dispatch table.
|
||
|
||
--*/
|
||
{
|
||
DWORD numEntries;
|
||
DWORD status = NO_ERROR;
|
||
LPINTERNAL_DISPATCH_ENTRY dispatchTable;
|
||
LPSERVICE_TABLE_ENTRYA entryPtr;
|
||
|
||
//
|
||
// Count the number of entries in the user dispatch table
|
||
//
|
||
|
||
numEntries = 0;
|
||
entryPtr = lpServiceStartTable;
|
||
|
||
while (entryPtr->lpServiceName != NULL) {
|
||
numEntries++;
|
||
entryPtr++;
|
||
}
|
||
|
||
if (numEntries == 0) {
|
||
SCC_LOG(ERROR,"ScCreateDispatchTable:No entries in Dispatch table!\n",0);
|
||
return(ERROR_INVALID_PARAMETER);
|
||
}
|
||
|
||
//
|
||
// Allocate space for the Control Dispatcher's Dispatch Table
|
||
//
|
||
|
||
dispatchTable = (LPINTERNAL_DISPATCH_ENTRY)LocalAlloc(LMEM_ZEROINIT,
|
||
sizeof(INTERNAL_DISPATCH_ENTRY) * (numEntries + 1));
|
||
|
||
if (dispatchTable == NULL) {
|
||
SCC_LOG(ERROR,"ScCreateDispatchTableA: Local Alloc failed rc = %d\n",
|
||
GetLastError());
|
||
return (ERROR_NOT_ENOUGH_MEMORY);
|
||
}
|
||
|
||
//
|
||
// Move user dispatch info into the Control Dispatcher's table.
|
||
//
|
||
|
||
*DispatchTablePtr = dispatchTable;
|
||
entryPtr = lpServiceStartTable;
|
||
|
||
while (entryPtr->lpServiceName != NULL) {
|
||
|
||
//
|
||
// Convert the service name to unicode
|
||
//
|
||
|
||
try {
|
||
if (!ScConvertToUnicode(
|
||
&(dispatchTable->ServiceName),
|
||
entryPtr->lpServiceName)) {
|
||
|
||
//
|
||
// The convert failed.
|
||
//
|
||
SCC_LOG(ERROR,"ScCreateDispatcherTableA:ScConvertToUnicode failed\n",0);
|
||
|
||
//
|
||
// This is the only reason for failure that I can think of.
|
||
//
|
||
status = ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
}
|
||
except (EXCEPTION_EXECUTE_HANDLER) {
|
||
status = GetExceptionCode();
|
||
if (status != EXCEPTION_ACCESS_VIOLATION) {
|
||
SCC_LOG(ERROR,
|
||
"ScCreateDispatchTableA: Unexpected Exception 0x%lx\n",status);
|
||
}
|
||
}
|
||
if (status != NO_ERROR) {
|
||
//
|
||
// If an error occured, free up the allocated resources.
|
||
//
|
||
dispatchTable = *DispatchTablePtr;
|
||
|
||
while (dispatchTable->ServiceName != NULL) {
|
||
LocalFree(dispatchTable->ServiceName);
|
||
dispatchTable++;
|
||
}
|
||
LocalFree(*DispatchTablePtr);
|
||
return(status);
|
||
}
|
||
|
||
//
|
||
// Fill in the rest of the dispatch entry.
|
||
//
|
||
dispatchTable->ServiceStartRoutine.A= entryPtr->lpServiceProc;
|
||
dispatchTable->ControlHandler = NULL;
|
||
dispatchTable->StatusHandle = (SERVICE_STATUS_HANDLE)0;
|
||
dispatchTable->OwnProcess = FALSE;
|
||
entryPtr++;
|
||
dispatchTable++;
|
||
}
|
||
|
||
return (NO_ERROR);
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScCreateThreadStartParms(
|
||
IN LPCTRL_MSG_HEADER Msg,
|
||
IN DWORD NumBytesRead,
|
||
OUT LPTHREAD_STARTUP_PARMSW *ThreadParmPtr,
|
||
OUT LPBYTE *TempArgPtr,
|
||
OUT LPDWORD ArgBytesRemaining
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine calculates the number of bytes needed for the services
|
||
thread startup parameters by using the arg count information in the
|
||
message header. The startup parameter structure is allocated and
|
||
as many bytes of argument information as has been captured so far
|
||
is placed into the buffer. A second read of the pipe may be necessary
|
||
to obtain the remaining bytes of argument information.
|
||
|
||
NOTE: This function allocates enough space in the startup parameter
|
||
buffer for the service name and pointer as well as the rest of the
|
||
arguments. However, it does not put the service name into the argument
|
||
list. This is because it may take two calls to this routine to
|
||
get all the argument information. We can't insert the service name
|
||
string until we have all the rest of the argument data.
|
||
|
||
[serviceNamePtr][argv1]argv2]...[argv1Str][argv2Str]...[serviceNameStr]
|
||
|
||
|
||
Arguments:
|
||
|
||
Msg - A pointer to the pipe message header.
|
||
|
||
NumBytesRead - The number of bytes read in the first pipe read.
|
||
|
||
ThreadParmPtr - A pointer to a location where the pointer to the
|
||
thread startup parameter structure is to be placed.
|
||
|
||
TempArgPtr - A location that will contain the pointer to where
|
||
more argument data can be placed by a second read of the pipe.
|
||
|
||
ArgBytesRemaining - Returns with a count of the number of argument bytes
|
||
that remain to be read from the pipe.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - If the operation was successful.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - If the memory allocation was unsuccessful.
|
||
|
||
Note:
|
||
|
||
|
||
--*/
|
||
{
|
||
DWORD nameSize; // num bytes in ServiceName.
|
||
DWORD parmBufSize; // num bytes for threadStartupParm buffer
|
||
DWORD argBytesCaptured; // number of arg bytes in first read.
|
||
|
||
LPTHREAD_STARTUP_PARMSW threadStartupParms;
|
||
|
||
|
||
SCC_LOG(TRACE,"ScCreateThreadStartParms: Get Cmd Line Args from pipe\n",0);
|
||
|
||
//
|
||
// Note: Here we assume that the service name was read into the buffer
|
||
// in its entirety.
|
||
//
|
||
nameSize = WCSSIZE((LPWSTR)((LPBYTE)Msg + Msg->ServiceNameOffset));
|
||
|
||
//
|
||
// Calculate the size of buffer needed. This will consist of an
|
||
// THREAD_STARTUP_PARMS structure, plus the service name and a pointer
|
||
// for it, plus the rest of the arg info sent in
|
||
// the message (We are wasting 4 bytes here since the first pointer in
|
||
// the vector table is accounted for twice - but what the heck!).
|
||
//
|
||
|
||
parmBufSize = Msg->Count -
|
||
sizeof(CTRL_MSG_HEADER) +
|
||
sizeof(THREAD_STARTUP_PARMSW) +
|
||
sizeof(LPWSTR);
|
||
|
||
//
|
||
// Allocate the memory for the thread parameter list.
|
||
//
|
||
|
||
threadStartupParms = (LPTHREAD_STARTUP_PARMSW)LocalAlloc(
|
||
LMEM_ZEROINIT,
|
||
(UINT)parmBufSize);
|
||
|
||
if (threadStartupParms == NULL) {
|
||
|
||
SCC_LOG(ERROR,"ScCreateThreadStartParms: LocalAlloc failed rc=%d\n",
|
||
GetLastError());
|
||
|
||
return(ERROR_NOT_ENOUGH_MEMORY);
|
||
}
|
||
|
||
argBytesCaptured = NumBytesRead - sizeof(CTRL_MSG_HEADER) - nameSize;
|
||
*ArgBytesRemaining = Msg->Count - NumBytesRead;
|
||
|
||
*TempArgPtr = (LPBYTE)&threadStartupParms->VectorTable;
|
||
|
||
//
|
||
// Leave the first vector location blank for the service name
|
||
// pointer.
|
||
//
|
||
(*TempArgPtr) += sizeof(LPWSTR);
|
||
|
||
if (Msg->NumCmdArgs != 0) {
|
||
|
||
//
|
||
// adjust argBytesCaptured to remove any extra bytes that are there
|
||
// for alignment. It should be noted that the initial buffer size is
|
||
// always large enough contain all alignment bytes. Therefore,
|
||
// the adjusted argBytesCaptured can never be a negative number.
|
||
//
|
||
// If a name that is not in the dispatch table is passed in, it could
|
||
// be larger than our buffer. This could cause argBytesCaptured to
|
||
// become negative. However it should fail safe anyway since the
|
||
// name simply won't be recognized and an error will be returned.
|
||
//
|
||
argBytesCaptured -= (Msg->ArgvOffset - Msg->ServiceNameOffset - nameSize);
|
||
|
||
//
|
||
// Copy any portion of the command arg info from the first read
|
||
// into the buffer that is to be used for the second read.
|
||
//
|
||
|
||
if (argBytesCaptured > 0) {
|
||
memcpy(
|
||
*TempArgPtr,
|
||
(LPBYTE)Msg + Msg->ArgvOffset,
|
||
(UINT)argBytesCaptured);
|
||
|
||
*TempArgPtr += argBytesCaptured;
|
||
}
|
||
}
|
||
else {
|
||
*TempArgPtr = NULL;
|
||
}
|
||
*ThreadParmPtr = threadStartupParms;
|
||
|
||
return (NO_ERROR);
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScConnectServiceController (
|
||
OUT LPHANDLE PipeHandle
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function connects to the Service Controller Pipe.
|
||
|
||
Arguments:
|
||
|
||
PipeHandle - This is a pointer to the location where the PipeHandle
|
||
is to be placed.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - if the operation was successful.
|
||
|
||
ERROR_FAILED_SERVICE_CONTROLLER_CONNECT - if we failed to connect.
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
BOOL status;
|
||
DWORD apiStatus;
|
||
DWORD response;
|
||
DWORD pipeMode;
|
||
DWORD numBytesWritten;
|
||
|
||
status = WaitNamedPipeW (
|
||
CONTROL_PIPE_NAME,
|
||
CONTROL_WAIT_PERIOD);
|
||
|
||
if (status != TRUE) {
|
||
SCC_LOG(ERROR,"ScConnectServiceController:WaitNamedPipe failed rc = %d\n",
|
||
GetLastError());
|
||
}
|
||
|
||
SCC_LOG(TRACE,"ScConnectServiceController:WaitNamedPipe success\n",0);
|
||
|
||
|
||
*PipeHandle = CreateFileW(
|
||
CONTROL_PIPE_NAME, // lpFileName
|
||
GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess
|
||
FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode
|
||
NULL, // lpSecurityAttributes
|
||
OPEN_EXISTING, // dwCreationDisposition
|
||
FILE_ATTRIBUTE_NORMAL, // dwFileAttributes
|
||
0L); // hTemplateFile
|
||
|
||
if (*PipeHandle == (HANDLE)-1) {
|
||
SCC_LOG(ERROR,"ScConnectServiceController:CreateFile failed rc = %d\n",
|
||
GetLastError());
|
||
return(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT);
|
||
}
|
||
|
||
SCC_LOG(TRACE,"ScConnectServiceController:CreateFile success\n",0);
|
||
|
||
|
||
|
||
//
|
||
// Set pipe mode
|
||
//
|
||
|
||
pipeMode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
|
||
status = SetNamedPipeHandleState (
|
||
*PipeHandle,
|
||
&pipeMode,
|
||
NULL,
|
||
NULL);
|
||
|
||
if (status != TRUE) {
|
||
SCC_LOG(ERROR,"ScConnectServiceController:SetNamedPipeHandleState failed rc = %d\n",
|
||
GetLastError());
|
||
return(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT);
|
||
|
||
}
|
||
else {
|
||
|
||
SCC_LOG(TRACE,
|
||
"ScConnectServiceController SetNamedPipeHandleState Success\n",0);
|
||
|
||
}
|
||
|
||
//
|
||
// Send initial status - This is the process Id for the service process.
|
||
//
|
||
|
||
response = GetCurrentProcessId();
|
||
|
||
apiStatus = WriteFile (
|
||
*PipeHandle,
|
||
&response,
|
||
sizeof(response),
|
||
&numBytesWritten,
|
||
NULL);
|
||
|
||
if (apiStatus != TRUE) {
|
||
//
|
||
// If this fails, there is a chance that the pipe is still in good
|
||
// shape. So we just go on.
|
||
//
|
||
// ??EVENTLOG??
|
||
//
|
||
SCC_LOG(ERROR,"ScConnectServiceController: WriteFile failed, rc= %d\n", GetLastError());
|
||
}
|
||
else {
|
||
|
||
SCC_LOG(TRACE,
|
||
"ScConnectServiceController: WriteFile success, bytes Written= %d\n",
|
||
numBytesWritten);
|
||
|
||
}
|
||
|
||
return(NO_ERROR);
|
||
}
|
||
|
||
|
||
VOID
|
||
ScExpungeMessage(
|
||
IN HANDLE PipeHandle
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine cleans the remaining portion of a message out of the pipe.
|
||
It is called in response to an unsuccessful attempt to allocate the
|
||
correct buffer size from the heap. In this routine a small buffer is
|
||
allocated on the stack, and successive reads are made until a status
|
||
other than ERROR_MORE_DATA is received.
|
||
|
||
Arguments:
|
||
|
||
PipeHandle - This is a handle to the pipe in which the message resides.
|
||
|
||
Return Value:
|
||
|
||
none - If this operation fails, there is not much I can do about
|
||
the data in the pipe.
|
||
|
||
--*/
|
||
{
|
||
#define EXPUNGE_BUF_SIZE 100
|
||
|
||
DWORD status;
|
||
DWORD numBytesRead=0;
|
||
BYTE msg[EXPUNGE_BUF_SIZE];
|
||
|
||
|
||
do {
|
||
status = ReadFile (
|
||
PipeHandle,
|
||
msg,
|
||
EXPUNGE_BUF_SIZE,
|
||
&numBytesRead,
|
||
NULL);
|
||
}
|
||
while( status == ERROR_MORE_DATA);
|
||
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScGetPipeInput (
|
||
IN HANDLE PipeHandle,
|
||
IN OUT LPCTRL_MSG_HEADER Msg,
|
||
IN DWORD BufferSize,
|
||
OUT LPTHREAD_STARTUP_PARMSW *ThreadParmPtr
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine reads a control message from the pipe and places it into
|
||
a message buffer. This routine also allocates a structure for
|
||
the service thread information. This structure will eventually
|
||
contain everything that is needed to invoke the service startup
|
||
routine in the context of a new thread. Items contained in the
|
||
structure are:
|
||
1) The pointer to the startup routine,
|
||
2) The number of arguments, and
|
||
3) The table of vectors to the arguments.
|
||
Since this routine has knowledge about the buffer size needed for
|
||
the arguments, the allocation is done here.
|
||
|
||
Arguments:
|
||
|
||
PipeHandle - This is the handle for the pipe that is to be read.
|
||
|
||
Msg - This is a pointer to a buffer where the data is to be
|
||
placed.
|
||
|
||
BufferSize - This is the size (in bytes) of the buffer that data is to
|
||
be placed in.
|
||
|
||
CmdArgPtr - This is the location where the pointer to the Command
|
||
arg pointers is to be placed upon return. If there are no Command
|
||
Args, then this will return a NULL.
|
||
|
||
NumArgs - This is the location where the number of CmdArgs is to be
|
||
placed. If there are no command args, this will return a 0.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - if the operation was successful.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to create a large
|
||
enough buffer for the command line arguments.
|
||
|
||
ERROR_INVALID_DATA - This is returned if we did not receive a complete
|
||
CTRL_MESSAGE_HEADER on the first read.
|
||
|
||
|
||
Any error that ReadFile might return could be returned by this function.
|
||
(We may want to return something more specific like ERROR_READ_FAULT)
|
||
|
||
--*/
|
||
{
|
||
DWORD status;
|
||
BOOL readStatus;
|
||
DWORD numBytesRead=0;
|
||
DWORD argBytesRemaining;
|
||
LPBYTE tempArgPtr;
|
||
|
||
*ThreadParmPtr = NULL;
|
||
|
||
//
|
||
// Read the header and name string from the pipe.
|
||
// NOTE: The number of bytes for the name string is determined by
|
||
// the longest service name in the service process. If the actual
|
||
// string read is shorter, then the beginning of the command arg
|
||
// data may be read with this read.
|
||
// Also note: The buffer is large enough to accommodate the longest
|
||
// permissible service name.
|
||
//
|
||
|
||
readStatus = ReadFile (
|
||
PipeHandle,
|
||
Msg,
|
||
BufferSize,
|
||
&numBytesRead,
|
||
NULL);
|
||
|
||
|
||
SCC_LOG(TRACE,"ScGetPipeInput:ReadFile buffer size = %ld\n",BufferSize);
|
||
SCC_LOG(TRACE,"ScGetPipeInput:ReadFile numBytesRead = %ld\n",numBytesRead);
|
||
|
||
|
||
if ((readStatus == TRUE) && (numBytesRead > sizeof(CTRL_MSG_HEADER))) {
|
||
|
||
//
|
||
// The Read File read the complete message in one read. So we
|
||
// can return with the data.
|
||
//
|
||
|
||
SCC_LOG(TRACE,"ScGetPipeInput:Success!\n",0);
|
||
|
||
if (Msg->OpCode == SERVICE_CONTROL_START_OWN ||
|
||
Msg->OpCode == SERVICE_CONTROL_START_SHARE) {
|
||
|
||
//
|
||
// Allocate storage for thread startup parameters.
|
||
// Place any arguments that have been captured into the
|
||
// structure.
|
||
//
|
||
|
||
status = ScCreateThreadStartParms(
|
||
Msg,
|
||
numBytesRead,
|
||
ThreadParmPtr,
|
||
&tempArgPtr,
|
||
&argBytesRemaining);
|
||
|
||
if (status != NO_ERROR) {
|
||
return(status);
|
||
}
|
||
//
|
||
// Change the offsets back into pointers.
|
||
//
|
||
ScNormalizeCmdLineArgs(Msg, *ThreadParmPtr);
|
||
}
|
||
else {
|
||
|
||
ASSERT(Msg->NumCmdArgs == 0);
|
||
*ThreadParmPtr = NULL;
|
||
}
|
||
|
||
return(NO_ERROR);
|
||
}
|
||
else {
|
||
//
|
||
// An error was returned from ReadFile. ERROR_MORE_DATA
|
||
// means that we need to read some arguments from the buffer.
|
||
// Any other error is unexpected, and generates an internal error.
|
||
//
|
||
|
||
if (readStatus != TRUE) {
|
||
status = GetLastError();
|
||
if (status != ERROR_MORE_DATA) {
|
||
|
||
SCC_LOG(ERROR,"ScGetPipeInput:Unexpected return code, rc= %ld\n",
|
||
status);
|
||
|
||
return(status);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// The read was successful, but we didn't get a complete
|
||
// CTRL_MESSAGE_HEADER.
|
||
//
|
||
return(ERROR_INVALID_DATA);
|
||
}
|
||
}
|
||
|
||
//
|
||
// We must have received an ERROR_MORE_DATA to go down this
|
||
// path. This means that the message contains more data. Namely,
|
||
// command line arguments must be present. Therefore, the pipe must
|
||
// be read again. Since the header indicates how many bytes are
|
||
// needed, we will allocate a buffer large enough to hold all the
|
||
// command args.
|
||
//
|
||
// If a portion of the command line args was read in the first read,
|
||
// they will be put in this new buffer. That is so that all the
|
||
// command line arg info is in one place.
|
||
//
|
||
|
||
//
|
||
// Allocate storage for thread startup parameters.
|
||
// Place any arguments that have been captured into the
|
||
// structure.
|
||
//
|
||
|
||
status = ScCreateThreadStartParms(
|
||
Msg,
|
||
numBytesRead,
|
||
ThreadParmPtr,
|
||
&tempArgPtr,
|
||
&argBytesRemaining);
|
||
|
||
|
||
if (status != NO_ERROR) {
|
||
ScExpungeMessage(PipeHandle);
|
||
|
||
return(status);
|
||
}
|
||
|
||
readStatus = ReadFile (
|
||
PipeHandle,
|
||
tempArgPtr,
|
||
argBytesRemaining,
|
||
&numBytesRead,
|
||
NULL);
|
||
|
||
if ((readStatus != TRUE) || (numBytesRead < argBytesRemaining)) {
|
||
if (readStatus != TRUE) {
|
||
status = GetLastError();
|
||
SCC_LOG(ERROR,"ScGetPipeInput: ReadFile error (2nd read), rc = %ld\n",
|
||
status);
|
||
}
|
||
else {
|
||
status = ERROR_BAD_LENGTH;
|
||
}
|
||
SCC_LOG(ERROR,"ScGetPipeInput: ReadFile read:%d,", numBytesRead);
|
||
SCC_LOG(ERROR," expected:%d\n", argBytesRemaining);
|
||
LocalFree(*ThreadParmPtr);
|
||
return(status);
|
||
}
|
||
|
||
//
|
||
// Change the offsets back into pointers.
|
||
//
|
||
ScNormalizeCmdLineArgs(Msg, *ThreadParmPtr);
|
||
|
||
return(NO_ERROR);
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScGetDispatchEntry (
|
||
IN OUT LPINTERNAL_DISPATCH_ENTRY *DispatchEntryPtr,
|
||
IN LPWSTR ServiceName
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Finds an entry in the Dispatch Table for a particular service which
|
||
is identified by a service name string.
|
||
|
||
Arguments:
|
||
|
||
DispatchEntryPtr - As an input, the is a location where a pointer to
|
||
the top of the DispatchTable is placed. On return, this is the
|
||
location where the pointer to the specific dispatch entry is to
|
||
be placed. This is an opaque pointer because it could be either
|
||
ansi or unicode depending on the operational state of the dispatcher.
|
||
|
||
ServiceName - This is a pointer to the service name string.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - The operation was successful.
|
||
|
||
ERROR_SERVICE_DOES_NOT_EXIST - The serviceName could not be found in
|
||
the dispatch table. This indicates that the configuration database
|
||
says the serice is in this process, but the service name doesn't
|
||
exist in the dispatch table.
|
||
|
||
--*/
|
||
{
|
||
LPINTERNAL_DISPATCH_ENTRY entryPtr;
|
||
DWORD found = FALSE;
|
||
|
||
//
|
||
// BUGBUG: I need to create a lock for the dispatch table. I must
|
||
// get the lock before updating the table.
|
||
//
|
||
|
||
entryPtr = *DispatchEntryPtr;
|
||
if (entryPtr->OwnProcess){
|
||
return(NO_ERROR);
|
||
}
|
||
|
||
while (entryPtr->ServiceName != NULL) {
|
||
if (_wcsicmp(entryPtr->ServiceName, ServiceName) == 0) {
|
||
found = TRUE;
|
||
break;
|
||
}
|
||
entryPtr++;
|
||
}
|
||
if (found) {
|
||
*DispatchEntryPtr = entryPtr;
|
||
}
|
||
else {
|
||
SCC_LOG(ERROR,"ScGetDispatchEntry: DispatchEntry not found\n",0);
|
||
SCC_LOG(ERROR,"There is a configuration error - the service is not in this .exe file\n",0);
|
||
return(ERROR_SERVICE_DOES_NOT_EXIST);
|
||
}
|
||
|
||
return(NO_ERROR);
|
||
}
|
||
|
||
|
||
VOID
|
||
ScNormalizeCmdLineArgs(
|
||
IN OUT LPCTRL_MSG_HEADER Msg,
|
||
IN OUT LPTHREAD_STARTUP_PARMSW ThreadStartupParms
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Normalizes the command line argument information that came across in
|
||
the pipe. The argument information is stored in a buffer that consists
|
||
of an array of string pointers followed by the strings themselves.
|
||
However, in the pipe, the pointers are replaced with offsets. This
|
||
routine transforms the offsets into real pointers.
|
||
|
||
This routine also puts the service name into the array of argument
|
||
vectors, and adds the service name string to the end of the
|
||
buffer (space has already been allocated for it).
|
||
|
||
Arguments:
|
||
|
||
Msg - This is a pointer to the Message. Useful information from this
|
||
includes the NumCmdArgs and the service name.
|
||
|
||
ThreadStartupParms - A pointer to the thread startup parameter structure.
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
DWORD i;
|
||
LPWSTR *argv;
|
||
DWORD numCmdArgs;
|
||
LPWSTR *serviceNameVector;
|
||
LPWSTR serviceNamePtr;
|
||
|
||
numCmdArgs = Msg->NumCmdArgs;
|
||
|
||
argv = &(ThreadStartupParms->VectorTable);
|
||
|
||
//
|
||
// Save the first argv for the service name.
|
||
//
|
||
serviceNameVector = argv;
|
||
argv++;
|
||
|
||
//
|
||
// Normalize the Command Line Argument information by replacing
|
||
// offsets in buffer with pointers.
|
||
//
|
||
// NOTE: The elaborate casting that takes place here is because we
|
||
// are taking some (pointer sized) offsets, and turning them back
|
||
// into pointers to strings. The offsets are in bytes, and are
|
||
// relative to the beginning of the vector table which contains
|
||
// pointers to the various command line arg strings.
|
||
//
|
||
|
||
for (i=0; i<(numCmdArgs); i++) {
|
||
argv[i] = (LPWSTR)((LPBYTE)argv + (DWORD)argv[i]);
|
||
}
|
||
|
||
|
||
//
|
||
// If we are starting a service, then we need to add the service name
|
||
// to the argument vectors.
|
||
//
|
||
if ((Msg->OpCode == SERVICE_CONTROL_START_SHARE) ||
|
||
(Msg->OpCode == SERVICE_CONTROL_START_OWN)) {
|
||
|
||
numCmdArgs++;
|
||
|
||
if (numCmdArgs > 1) {
|
||
//
|
||
// Find the location for the service name string by finding
|
||
// the pointer to the last argument adding its string length
|
||
// to it.
|
||
//
|
||
serviceNamePtr = argv[i-1];
|
||
serviceNamePtr += (wcslen(serviceNamePtr) + 1);
|
||
}
|
||
else {
|
||
serviceNamePtr = (LPWSTR)argv;
|
||
}
|
||
wcscpy(serviceNamePtr, (LPWSTR) ((LPBYTE)Msg + Msg->ServiceNameOffset));
|
||
*serviceNameVector = serviceNamePtr;
|
||
}
|
||
|
||
ThreadStartupParms->NumArgs = numCmdArgs;
|
||
}
|
||
|
||
|
||
VOID
|
||
ScSendResponse (
|
||
IN HANDLE PipeHandle,
|
||
IN DWORD Response
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine sends a status response to the Service Controller's pipe.
|
||
|
||
Arguments:
|
||
|
||
Response - This is the status message that is to be sent.
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
DWORD numBytesWritten;
|
||
DWORD status;
|
||
|
||
status = WriteFile (
|
||
PipeHandle,
|
||
&Response,
|
||
sizeof(Response),
|
||
&numBytesWritten,
|
||
NULL);
|
||
|
||
if (status != TRUE) {
|
||
SCC_LOG(ERROR,"ScSendResponse: WriteFile failed, rc= %d\n",
|
||
GetLastError());
|
||
//
|
||
// BUGBUG: I probably need to do more for an error condition here.
|
||
// If the response doesn't make it back to the Service Controller,
|
||
// we may have lost the connection. Perhaps I need to re-open it.
|
||
// Check into this...
|
||
//
|
||
}
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScSvcctrlThreadW(
|
||
IN LPTHREAD_STARTUP_PARMSW lpThreadStartupParms
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the thread for the newly started service. This code
|
||
calls the service's main thread with parameters from the
|
||
ThreadStartupParms structure.
|
||
|
||
NOTE: The first item in the argument vector table is the pointer to
|
||
the service registry path string.
|
||
|
||
Arguments:
|
||
|
||
lpThreadStartupParms - This is a pointer to the ThreadStartupParms
|
||
structure. (This is a unicode structure);
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
//
|
||
// Call the Service's Main Routine.
|
||
//
|
||
((LPSERVICE_MAIN_FUNCTIONW)lpThreadStartupParms->ServiceStartRoutine) (
|
||
lpThreadStartupParms->NumArgs,
|
||
&lpThreadStartupParms->VectorTable);
|
||
|
||
LocalFree(lpThreadStartupParms);
|
||
|
||
ExitThread(0);
|
||
|
||
return(0);
|
||
}
|
||
|
||
DWORD
|
||
ScSvcctrlThreadA(
|
||
IN LPTHREAD_STARTUP_PARMSA lpThreadStartupParms
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the thread for the newly started service. This code
|
||
calls the service's main thread with parameters from the
|
||
ThreadStartupParms structure.
|
||
|
||
NOTE: The first item in the argument vector table is the pointer to
|
||
the service registry path string.
|
||
|
||
Arguments:
|
||
|
||
lpThreadStartupParms - This is a pointer to the ThreadStartupParms
|
||
structure. (This is a unicode structure);
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Call the Service's Main Routine.
|
||
//
|
||
// NOTE: The first item in the argument vector table is the pointer to
|
||
// the service registry path string.
|
||
//
|
||
((LPSERVICE_MAIN_FUNCTIONA)lpThreadStartupParms->ServiceStartRoutine) (
|
||
lpThreadStartupParms->NumArgs,
|
||
&lpThreadStartupParms->VectorTable);
|
||
|
||
LocalFree(lpThreadStartupParms);
|
||
|
||
ExitThread(0);
|
||
|
||
return(0);
|
||
}
|