2005-10-09 20:24:00 +00:00
|
|
|
/*
|
2000-12-05 02:40:51 +00:00
|
|
|
* service control manager
|
2005-05-08 04:07:56 +00:00
|
|
|
*
|
2000-12-05 02:40:51 +00:00
|
|
|
* ReactOS Operating System
|
2005-05-08 04:07:56 +00:00
|
|
|
*
|
2000-12-05 02:40:51 +00:00
|
|
|
* --------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* This software is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This software is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Library General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this software; see the file COPYING.LIB. If not, write
|
|
|
|
* to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
|
2005-05-08 04:07:56 +00:00
|
|
|
* MA 02139, USA.
|
2000-12-05 02:40:51 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* NOTE:
|
|
|
|
* - Services.exe is NOT a native application, it is a GUI app.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
|
2002-06-07 20:11:03 +00:00
|
|
|
#include "services.h"
|
|
|
|
|
2004-04-11 16:10:05 +00:00
|
|
|
#define NDEBUG
|
2001-10-21 19:09:06 +00:00
|
|
|
#include <debug.h>
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-04-02 05:37:20 +00:00
|
|
|
int WINAPI RegisterServicesProcess(DWORD ServicesProcessId);
|
2002-06-07 20:11:03 +00:00
|
|
|
|
2000-12-05 02:40:51 +00:00
|
|
|
/* GLOBALS ******************************************************************/
|
|
|
|
|
2004-04-12 17:20:47 +00:00
|
|
|
#define PIPE_BUFSIZE 1024
|
|
|
|
#define PIPE_TIMEOUT 1000
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-30 13:13:53 +00:00
|
|
|
BOOL ScmShutdown = FALSE;
|
|
|
|
|
2000-12-05 02:40:51 +00:00
|
|
|
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
VOID
|
|
|
|
PrintString(LPCSTR fmt, ...)
|
2000-12-05 02:40:51 +00:00
|
|
|
{
|
2002-02-08 02:57:10 +00:00
|
|
|
#ifdef DBG
|
2005-10-22 19:46:00 +00:00
|
|
|
CHAR buffer[512];
|
|
|
|
va_list ap;
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
va_start(ap, fmt);
|
|
|
|
vsprintf(buffer, fmt, ap);
|
|
|
|
va_end(ap);
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
OutputDebugStringA(buffer);
|
2002-02-08 02:57:10 +00:00
|
|
|
#endif
|
2000-12-05 02:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-06-07 20:11:03 +00:00
|
|
|
BOOL
|
|
|
|
ScmCreateStartEvent(PHANDLE StartEvent)
|
2000-12-05 02:40:51 +00:00
|
|
|
{
|
2005-10-22 19:46:00 +00:00
|
|
|
HANDLE hEvent;
|
|
|
|
|
|
|
|
hEvent = CreateEvent(NULL,
|
|
|
|
TRUE,
|
2002-12-27 13:54:28 +00:00
|
|
|
FALSE,
|
2005-06-20 21:04:03 +00:00
|
|
|
TEXT("SvcctrlStartEvent_A3725DX"));
|
2005-10-22 19:46:00 +00:00
|
|
|
if (hEvent == NULL)
|
2005-04-15 22:02:37 +00:00
|
|
|
{
|
2005-10-22 19:46:00 +00:00
|
|
|
if (GetLastError() == ERROR_ALREADY_EXISTS)
|
|
|
|
{
|
|
|
|
hEvent = OpenEvent(EVENT_ALL_ACCESS,
|
|
|
|
FALSE,
|
|
|
|
TEXT("SvcctrlStartEvent_A3725DX"));
|
|
|
|
if (hEvent == NULL)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
2005-04-15 22:02:37 +00:00
|
|
|
}
|
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
*StartEvent = hEvent;
|
2005-04-15 22:02:37 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
return TRUE;
|
2000-12-05 02:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-10-21 19:09:06 +00:00
|
|
|
BOOL
|
2005-04-15 22:02:37 +00:00
|
|
|
ScmNamedPipeHandleRequest(PVOID Request,
|
2005-10-22 19:46:00 +00:00
|
|
|
DWORD RequestSize,
|
|
|
|
PVOID Reply,
|
|
|
|
LPDWORD ReplySize)
|
2001-10-21 19:09:06 +00:00
|
|
|
{
|
2005-10-22 19:46:00 +00:00
|
|
|
DbgPrint("SCM READ: %s\n", Request);
|
2004-04-12 17:14:55 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
*ReplySize = 0;
|
|
|
|
return FALSE;
|
2001-10-21 19:09:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
DWORD WINAPI
|
2002-12-27 13:54:28 +00:00
|
|
|
ScmNamedPipeThread(LPVOID Context)
|
2001-10-21 19:09:06 +00:00
|
|
|
{
|
2004-04-12 17:20:47 +00:00
|
|
|
CHAR chRequest[PIPE_BUFSIZE];
|
|
|
|
CHAR chReply[PIPE_BUFSIZE];
|
2002-12-27 13:54:28 +00:00
|
|
|
DWORD cbReplyBytes;
|
|
|
|
DWORD cbBytesRead;
|
|
|
|
DWORD cbWritten;
|
2005-10-22 19:46:00 +00:00
|
|
|
BOOL bSuccess;
|
2002-12-27 13:54:28 +00:00
|
|
|
HANDLE hPipe;
|
|
|
|
|
|
|
|
hPipe = (HANDLE)Context;
|
|
|
|
|
|
|
|
DPRINT("ScmNamedPipeThread(%x) - Accepting SCM commands through named pipe\n", hPipe);
|
2005-04-15 22:02:37 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
bSuccess = ReadFile(hPipe,
|
2004-04-12 17:20:47 +00:00
|
|
|
&chRequest,
|
|
|
|
PIPE_BUFSIZE,
|
2002-12-27 13:54:28 +00:00
|
|
|
&cbBytesRead,
|
|
|
|
NULL);
|
2005-10-22 19:46:00 +00:00
|
|
|
if (!bSuccess || cbBytesRead == 0)
|
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
break;
|
|
|
|
}
|
2005-10-22 19:46:00 +00:00
|
|
|
|
|
|
|
if (ScmNamedPipeHandleRequest(&chRequest, cbBytesRead, &chReply, &cbReplyBytes))
|
|
|
|
{
|
|
|
|
bSuccess = WriteFile(hPipe,
|
2004-04-12 17:20:47 +00:00
|
|
|
&chReply,
|
2002-12-27 13:54:28 +00:00
|
|
|
cbReplyBytes,
|
|
|
|
&cbWritten,
|
|
|
|
NULL);
|
2005-10-22 19:46:00 +00:00
|
|
|
if (!bSuccess || cbReplyBytes != cbWritten)
|
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2001-10-21 19:09:06 +00:00
|
|
|
}
|
2005-10-22 19:46:00 +00:00
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("ScmNamedPipeThread(%x) - Disconnecting named pipe connection\n", hPipe);
|
2005-10-22 19:46:00 +00:00
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
FlushFileBuffers(hPipe);
|
|
|
|
DisconnectNamedPipe(hPipe);
|
|
|
|
CloseHandle(hPipe);
|
2005-10-22 19:46:00 +00:00
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("ScmNamedPipeThread(%x) - Done.\n", hPipe);
|
2005-10-22 19:46:00 +00:00
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
return ERROR_SUCCESS;
|
2001-10-21 19:09:06 +00:00
|
|
|
}
|
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
|
|
|
|
BOOL
|
|
|
|
ScmCreateNamedPipe(VOID)
|
2001-10-21 19:09:06 +00:00
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
DWORD dwThreadId;
|
2005-10-22 19:46:00 +00:00
|
|
|
BOOL bConnected;
|
2002-12-27 13:54:28 +00:00
|
|
|
HANDLE hThread;
|
|
|
|
HANDLE hPipe;
|
|
|
|
|
|
|
|
DPRINT("ScmCreateNamedPipe() - CreateNamedPipe(\"\\\\.\\pipe\\Ntsvcs\")\n");
|
|
|
|
|
2005-06-20 21:04:03 +00:00
|
|
|
hPipe = CreateNamedPipe(TEXT("\\\\.\\pipe\\Ntsvcs"),
|
2002-12-27 13:54:28 +00:00
|
|
|
PIPE_ACCESS_DUPLEX,
|
|
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
|
|
|
PIPE_UNLIMITED_INSTANCES,
|
2004-04-12 17:20:47 +00:00
|
|
|
PIPE_BUFSIZE,
|
|
|
|
PIPE_BUFSIZE,
|
2002-12-27 13:54:28 +00:00
|
|
|
PIPE_TIMEOUT,
|
|
|
|
NULL);
|
2005-10-22 19:46:00 +00:00
|
|
|
if (hPipe == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("CreateNamedPipe() failed (%d)\n", GetLastError());
|
|
|
|
return FALSE;
|
2002-06-07 20:11:03 +00:00
|
|
|
}
|
2001-10-21 19:09:06 +00:00
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("CreateNamedPipe() - calling ConnectNamedPipe(%x)\n", hPipe);
|
2005-10-22 19:46:00 +00:00
|
|
|
bConnected = ConnectNamedPipe(hPipe,
|
|
|
|
NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
|
|
|
|
DPRINT("CreateNamedPipe() - ConnectNamedPipe() returned %d\n", bConnected);
|
2002-12-27 13:54:28 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
if (bConnected)
|
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("Pipe connected\n");
|
|
|
|
hThread = CreateThread(NULL,
|
|
|
|
0,
|
|
|
|
ScmNamedPipeThread,
|
|
|
|
(LPVOID)hPipe,
|
|
|
|
0,
|
|
|
|
&dwThreadId);
|
2005-10-22 19:46:00 +00:00
|
|
|
if (!hThread)
|
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("Could not create thread (%d)\n", GetLastError());
|
|
|
|
DisconnectNamedPipe(hPipe);
|
|
|
|
CloseHandle(hPipe);
|
|
|
|
DPRINT("CreateNamedPipe() - returning FALSE\n");
|
2005-10-22 19:46:00 +00:00
|
|
|
return FALSE;
|
2002-12-27 13:54:28 +00:00
|
|
|
}
|
2005-10-22 19:46:00 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("Pipe not connected\n");
|
|
|
|
CloseHandle(hPipe);
|
|
|
|
DPRINT("CreateNamedPipe() - returning FALSE\n");
|
|
|
|
return FALSE;
|
2002-06-07 20:11:03 +00:00
|
|
|
}
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("CreateNamedPipe() - returning TRUE\n");
|
|
|
|
return TRUE;
|
|
|
|
}
|
2001-10-21 19:09:06 +00:00
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
|
|
|
|
DWORD WINAPI
|
2002-12-27 13:54:28 +00:00
|
|
|
ScmNamedPipeListenerThread(LPVOID Context)
|
|
|
|
{
|
|
|
|
// HANDLE hPipe;
|
|
|
|
DPRINT("ScmNamedPipeListenerThread(%x) - aka SCM.\n", Context);
|
|
|
|
|
|
|
|
// hPipe = (HANDLE)Context;
|
2005-10-22 19:46:00 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
2003-08-28 13:38:24 +00:00
|
|
|
DPRINT("SCM: Waiting for new connection on named pipe...\n");
|
2002-12-27 13:54:28 +00:00
|
|
|
/* Create named pipe */
|
2005-10-22 19:46:00 +00:00
|
|
|
if (!ScmCreateNamedPipe())
|
|
|
|
{
|
2003-08-28 13:38:24 +00:00
|
|
|
DPRINT1("\nSCM: Failed to create named pipe\n");
|
2002-12-27 13:54:28 +00:00
|
|
|
break;
|
|
|
|
//ExitThread(0);
|
|
|
|
}
|
2003-08-28 13:38:24 +00:00
|
|
|
DPRINT("\nSCM: named pipe session created.\n");
|
2002-12-27 13:54:28 +00:00
|
|
|
Sleep(10);
|
2001-10-21 19:09:06 +00:00
|
|
|
}
|
2002-12-27 13:54:28 +00:00
|
|
|
DPRINT("\n\nWARNING: ScmNamedPipeListenerThread(%x) - Aborted.\n\n", Context);
|
|
|
|
return ERROR_SUCCESS;
|
2001-10-21 19:09:06 +00:00
|
|
|
}
|
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
BOOL
|
|
|
|
StartScmNamedPipeThreadListener(VOID)
|
|
|
|
{
|
2005-10-22 19:46:00 +00:00
|
|
|
DWORD dwThreadId;
|
|
|
|
HANDLE hThread;
|
2002-12-27 13:54:28 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
hThread = CreateThread(NULL,
|
|
|
|
0,
|
|
|
|
ScmNamedPipeListenerThread,
|
|
|
|
NULL, /*(LPVOID)hPipe,*/
|
|
|
|
0,
|
|
|
|
&dwThreadId);
|
|
|
|
if (!hThread)
|
|
|
|
{
|
|
|
|
DPRINT1("SERVICES: Could not create thread (Status %lx)\n", GetLastError());
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
2002-12-27 13:54:28 +00:00
|
|
|
}
|
2001-10-21 19:09:06 +00:00
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
|
2004-04-12 17:20:47 +00:00
|
|
|
VOID FASTCALL
|
|
|
|
AcquireLoadDriverPrivilege(VOID)
|
|
|
|
{
|
2005-10-22 19:46:00 +00:00
|
|
|
HANDLE hToken;
|
|
|
|
TOKEN_PRIVILEGES tkp;
|
|
|
|
|
|
|
|
/* Get a token for this process */
|
|
|
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
|
|
|
|
{
|
|
|
|
/* Get the LUID for the debug privilege */
|
|
|
|
LookupPrivilegeValue(NULL, SE_LOAD_DRIVER_NAME, &tkp.Privileges[0].Luid);
|
|
|
|
|
|
|
|
/* One privilege to set */
|
|
|
|
tkp.PrivilegeCount = 1;
|
|
|
|
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
|
|
|
|
|
|
/* Get the debug privilege for this process */
|
|
|
|
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
|
|
|
|
}
|
2004-04-12 17:20:47 +00:00
|
|
|
}
|
|
|
|
|
2005-04-15 22:02:37 +00:00
|
|
|
|
2005-10-30 13:13:53 +00:00
|
|
|
BOOL WINAPI
|
|
|
|
ShutdownHandlerRoutine(DWORD dwCtrlType)
|
|
|
|
{
|
|
|
|
DPRINT1("ShutdownHandlerRoutine() called\n");
|
|
|
|
|
|
|
|
if (dwCtrlType == CTRL_SHUTDOWN_EVENT)
|
|
|
|
{
|
|
|
|
DPRINT1("Shutdown event received!\n");
|
|
|
|
ScmShutdown = TRUE;
|
|
|
|
|
|
|
|
/* FIXME: Shut all services down */
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2000-12-05 02:40:51 +00:00
|
|
|
int STDCALL
|
|
|
|
WinMain(HINSTANCE hInstance,
|
2005-04-15 22:02:37 +00:00
|
|
|
HINSTANCE hPrevInstance,
|
|
|
|
LPSTR lpCmdLine,
|
|
|
|
int nShowCmd)
|
2000-12-05 02:40:51 +00:00
|
|
|
{
|
2005-10-22 19:46:00 +00:00
|
|
|
HANDLE hScmStartEvent;
|
|
|
|
HANDLE hEvent;
|
|
|
|
NTSTATUS Status;
|
2002-06-07 20:11:03 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
DPRINT("SERVICES: Service Control Manager\n");
|
2002-06-07 20:11:03 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Acquire privileges to load drivers */
|
|
|
|
AcquireLoadDriverPrivilege();
|
2004-04-12 17:20:47 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Create start event */
|
|
|
|
if (!ScmCreateStartEvent(&hScmStartEvent))
|
|
|
|
{
|
|
|
|
DPRINT1("SERVICES: Failed to create start event\n");
|
|
|
|
ExitThread(0);
|
|
|
|
}
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
DPRINT("SERVICES: created start event with handle %x.\n", hScmStartEvent);
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
// ScmInitThreadManager();
|
2005-04-15 22:02:37 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* FIXME: more initialization */
|
2000-12-05 02:40:51 +00:00
|
|
|
|
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Create the service database */
|
|
|
|
Status = ScmCreateServiceDataBase();
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
|
|
{
|
|
|
|
DPRINT1("SERVICES: failed to create SCM database (Status %lx)\n", Status);
|
|
|
|
ExitThread(0);
|
|
|
|
}
|
2002-06-07 20:11:03 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Update service database */
|
|
|
|
ScmGetBootAndSystemDriverState();
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Start the RPC server */
|
|
|
|
ScmStartRpcServer();
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Register service process with CSRSS */
|
2005-10-30 13:13:53 +00:00
|
|
|
RegisterServicesProcess(GetCurrentProcessId());
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
DPRINT("SERVICES: Initialized.\n");
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Signal start event */
|
|
|
|
SetEvent(hScmStartEvent);
|
2001-01-20 18:40:27 +00:00
|
|
|
|
2005-10-30 13:13:53 +00:00
|
|
|
/* Register event handler (used for system shutdown) */
|
|
|
|
SetConsoleCtrlHandler(ShutdownHandlerRoutine, TRUE);
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* Start auto-start services */
|
|
|
|
ScmAutoStartServices();
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
/* FIXME: more to do ? */
|
2000-12-05 02:40:51 +00:00
|
|
|
|
2002-06-07 20:11:03 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
DPRINT("SERVICES: Running.\n");
|
2002-06-07 20:11:03 +00:00
|
|
|
|
2002-12-27 13:54:28 +00:00
|
|
|
#if 1
|
2005-10-22 19:46:00 +00:00
|
|
|
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
WaitForSingleObject(hEvent, INFINITE);
|
2002-12-27 13:54:28 +00:00
|
|
|
#else
|
2005-10-22 19:46:00 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
NtYieldExecution();
|
|
|
|
}
|
2001-03-26 20:46:53 +00:00
|
|
|
#endif
|
2001-01-20 18:40:27 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
DPRINT("SERVICES: Finished.\n");
|
2001-01-20 18:40:27 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
ExitThread(0);
|
2005-04-15 22:02:37 +00:00
|
|
|
|
2005-10-22 19:46:00 +00:00
|
|
|
return 0;
|
2000-12-05 02:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* EOF */
|