625 lines
14 KiB
C++
625 lines
14 KiB
C++
|
/**********************************************************************/
|
|||
|
/** Microsoft Windows NT **/
|
|||
|
/** Copyright(c) Microsoft Corp., 1993 **/
|
|||
|
/**********************************************************************/
|
|||
|
|
|||
|
/*
|
|||
|
main.cxx
|
|||
|
|
|||
|
This module contains the main startup code for the FTPD Service.
|
|||
|
|
|||
|
Functions exported by this module:
|
|||
|
|
|||
|
ServiceEntry
|
|||
|
|
|||
|
|
|||
|
FILE HISTORY:
|
|||
|
KeithMo 07-Mar-1993 Created.
|
|||
|
KeithMo 07-Jan-1994 Made it a DLL (part of TCPSVCS.EXE).
|
|||
|
MuraliK 21-March-1995 Modified it to use InternetServices
|
|||
|
common dll ( tcpsvcs.dll)
|
|||
|
MuraliK 11-April-1995 Added global ftp server config objects etc.
|
|||
|
( removed usage of Init and Terminate UserDatabase)
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
|
|||
|
#include <ftpdp.hxx>
|
|||
|
#include <apiutil.h>
|
|||
|
#include <inetsvcs.h>
|
|||
|
|
|||
|
//
|
|||
|
// Private constants.
|
|||
|
//
|
|||
|
|
|||
|
#define FTPD_MODULE_NAME_A "ftpsvc2.dll"
|
|||
|
#define DEFAULT_RECV_BUFFER_SIZE (8192)
|
|||
|
|
|||
|
//
|
|||
|
// Global variables for service info and debug variables.
|
|||
|
//
|
|||
|
|
|||
|
DEFINE_TSVC_INFO_INTERFACE( );
|
|||
|
DECLARE_DEBUG_PRINTS_OBJECT();
|
|||
|
#ifndef _NO_TRACING_
|
|||
|
#include <initguid.h>
|
|||
|
DEFINE_GUID(IisFtpGuid,
|
|||
|
0x784d891F, 0xaa8c, 0x11d2, 0x92, 0x5e, 0x00, 0xc0, 0x4f, 0x72, 0xd9, 0x0e);
|
|||
|
#else
|
|||
|
DECLARE_DEBUG_VARIABLE();
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// The following critical section synchronizes execution in ServiceEntry().
|
|||
|
// This is necessary because the NT Service Controller may reissue a service
|
|||
|
// start notification immediately after we have set our status to stopped.
|
|||
|
// This can lead to an unpleasant race condition in ServiceEntry() as one
|
|||
|
// thread cleans up global state as another thread is initializing it.
|
|||
|
//
|
|||
|
|
|||
|
CRITICAL_SECTION g_csServiceEntryLock;
|
|||
|
|
|||
|
//
|
|||
|
// Private prototypes.
|
|||
|
//
|
|||
|
|
|||
|
APIERR
|
|||
|
InitializeService(
|
|||
|
LPVOID lpContext
|
|||
|
);
|
|||
|
|
|||
|
APIERR
|
|||
|
TerminateService(
|
|||
|
LPVOID lpContext
|
|||
|
);
|
|||
|
|
|||
|
extern
|
|||
|
VOID
|
|||
|
FtpdNewConnection(
|
|||
|
IN SOCKET sNew,
|
|||
|
IN SOCKADDR_IN * psockaddr,
|
|||
|
IN PVOID EndpointContext,
|
|||
|
IN PVOID EndpointObject
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
extern
|
|||
|
VOID
|
|||
|
FtpdNewConnectionEx(
|
|||
|
IN PVOID patqContext,
|
|||
|
IN DWORD cbWritten,
|
|||
|
IN DWORD dwError,
|
|||
|
IN OVERLAPPED * lpo
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
PrintOutCurrentTime(
|
|||
|
IN CHAR * pszFile,
|
|||
|
IN int lineNum
|
|||
|
);
|
|||
|
|
|||
|
# ifdef CHECK_DBG
|
|||
|
# define PRINT_CURRENT_TIME_TO_DBG() PrintOutCurrentTime( __FILE__, __LINE__)
|
|||
|
# else
|
|||
|
# define PRINT_CURRENT_TIME_TO_DBG() ( NO_ERROR)
|
|||
|
# endif // CHECK_DBG
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
ServiceEntry(
|
|||
|
DWORD cArgs,
|
|||
|
LPSTR pArgs[],
|
|||
|
PTCPSVCS_GLOBAL_DATA pGlobalData // unused
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine:
|
|||
|
This is the "real" entrypoint for the service. When
|
|||
|
the Service Controller dispatcher is requested to
|
|||
|
start a service, it creates a thread that will begin
|
|||
|
executing this routine.
|
|||
|
|
|||
|
Arguments:
|
|||
|
cArgs - Number of command line arguments to this service.
|
|||
|
|
|||
|
pArgs - Pointers to the command line arguments.
|
|||
|
|
|||
|
Returns:
|
|||
|
None. Does not return until service is stopped.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
APIERR err = NO_ERROR;
|
|||
|
BOOL fInitSvcObject = FALSE;
|
|||
|
|
|||
|
EnterCriticalSection( &g_csServiceEntryLock );
|
|||
|
|
|||
|
if ( !InitCommonDlls() )
|
|||
|
{
|
|||
|
err = GetLastError();
|
|||
|
LeaveCriticalSection( &g_csServiceEntryLock );
|
|||
|
goto notify_scm;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the service status structure.
|
|||
|
//
|
|||
|
|
|||
|
g_pInetSvc = new FTP_IIS_SERVICE(
|
|||
|
FTPD_SERVICE_NAME,
|
|||
|
FTPD_MODULE_NAME_A,
|
|||
|
FTPD_PARAMETERS_KEY_A,
|
|||
|
INET_FTP_SVC_ID,
|
|||
|
INET_FTP_SVCLOC_ID,
|
|||
|
FALSE,
|
|||
|
0,
|
|||
|
FtpdNewConnection,
|
|||
|
FtpdNewConnectionEx,
|
|||
|
ProcessAtqCompletion
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// If we couldn't allocate memory for the service info structure,
|
|||
|
// then we're totally hozed.
|
|||
|
//
|
|||
|
|
|||
|
if( (g_pInetSvc != NULL) && g_pInetSvc->IsActive() ) {
|
|||
|
fInitSvcObject = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Start the service. This blocks until the service is shutdown.
|
|||
|
//
|
|||
|
|
|||
|
err = g_pInetSvc->StartServiceOperation(
|
|||
|
SERVICE_CTRL_HANDLER(),
|
|||
|
InitializeService,
|
|||
|
TerminateService
|
|||
|
);
|
|||
|
|
|||
|
if( err != NO_ERROR) {
|
|||
|
|
|||
|
//
|
|||
|
// The event has already been logged.
|
|||
|
//
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"FTP ServiceEntry: StartServiceOperation returned %d\n",
|
|||
|
err ));
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if ( g_pInetSvc ) {
|
|||
|
err = g_pInetSvc->QueryCurrentServiceError();
|
|||
|
} else {
|
|||
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if( g_pInetSvc != NULL ) {
|
|||
|
|
|||
|
//
|
|||
|
// delete the service object
|
|||
|
//
|
|||
|
|
|||
|
g_pInetSvc->CloseService( );
|
|||
|
g_pInetSvc = NULL;
|
|||
|
}
|
|||
|
|
|||
|
TerminateCommonDlls();
|
|||
|
LeaveCriticalSection( &g_csServiceEntryLock );
|
|||
|
|
|||
|
notify_scm:
|
|||
|
//
|
|||
|
// We need to tell the Service Control Manager that the service
|
|||
|
// is stopped if we haven't called g_pInetSvc->StartServiceOperation.
|
|||
|
// 1) InitCommonDlls fails, or
|
|||
|
// 2) new operator failed, or
|
|||
|
// 3) FTP_IIS_SERVICE constructor couldn't initialize properly
|
|||
|
//
|
|||
|
|
|||
|
if ( !fInitSvcObject ) {
|
|||
|
SERVICE_STATUS_HANDLE hsvcStatus;
|
|||
|
SERVICE_STATUS svcStatus;
|
|||
|
|
|||
|
hsvcStatus = RegisterServiceCtrlHandler( FTPD_SERVICE_NAME,
|
|||
|
SERVICE_CTRL_HANDLER() );
|
|||
|
|
|||
|
|
|||
|
if ( hsvcStatus != NULL_SERVICE_STATUS_HANDLE ) {
|
|||
|
svcStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
|
|||
|
svcStatus.dwCurrentState = SERVICE_STOPPED;
|
|||
|
svcStatus.dwWin32ExitCode = err;
|
|||
|
svcStatus.dwServiceSpecificExitCode = err;
|
|||
|
svcStatus.dwControlsAccepted = 0;
|
|||
|
svcStatus.dwCheckPoint = 0;
|
|||
|
svcStatus.dwWaitHint = 0;
|
|||
|
|
|||
|
SetServiceStatus( hsvcStatus, (LPSERVICE_STATUS) &svcStatus );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} // ServiceEntry()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Private functions.
|
|||
|
//
|
|||
|
|
|||
|
DWORD
|
|||
|
InitializeInstances(
|
|||
|
PFTP_IIS_SERVICE pService
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Read the instances from the registry
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
pService - Server instance added to.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Win32
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
DWORD err = NO_ERROR;
|
|||
|
DWORD i;
|
|||
|
DWORD cInstances = 0;
|
|||
|
MB mb( (IMDCOM*) pService->QueryMDObject() );
|
|||
|
BUFFER buff;
|
|||
|
CHAR szKeyName[MAX_PATH+1];
|
|||
|
BOOL fMigrateRoots = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Open the metabase for write to get an atomic snapshot
|
|||
|
//
|
|||
|
|
|||
|
if ( !mb.Open( "/LM/MSFTPSVC/",
|
|||
|
METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE ))
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"InitializeInstances: Cannot open path %s, error %lu\n",
|
|||
|
"/LM/MSFTPSVC/", GetLastError() ));
|
|||
|
|
|||
|
#if 1 // Temporary until setup is modified to create the instances in the metabase
|
|||
|
if ( !mb.Open( METADATA_MASTER_ROOT_HANDLE,
|
|||
|
METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE ) ||
|
|||
|
!mb.AddObject( "/LM/MSFTPSVC/" ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Unable to create service, error %d\n",
|
|||
|
GetLastError() ));
|
|||
|
|
|||
|
return GetLastError();
|
|||
|
}
|
|||
|
#else
|
|||
|
return GetLastError();
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
TryAgain:
|
|||
|
i = 0;
|
|||
|
while ( mb.EnumObjects( "",
|
|||
|
szKeyName,
|
|||
|
i++ ))
|
|||
|
{
|
|||
|
BOOL fRet;
|
|||
|
DWORD dwInstance;
|
|||
|
CHAR szRegKey[MAX_PATH+1];
|
|||
|
|
|||
|
//
|
|||
|
// Get the instance id
|
|||
|
//
|
|||
|
|
|||
|
dwInstance = atoi( szKeyName );
|
|||
|
if ( dwInstance == 0 ) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if ( buff.QuerySize() < (cInstances + 1) * sizeof(DWORD) )
|
|||
|
{
|
|||
|
if ( !buff.Resize( (cInstances + 10) * sizeof(DWORD)) )
|
|||
|
{
|
|||
|
return GetLastError();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
((DWORD *) buff.QueryPtr())[cInstances++] = dwInstance;
|
|||
|
}
|
|||
|
|
|||
|
if ( cInstances == 0 )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"No defined instances\n" ));
|
|||
|
|
|||
|
if ( !mb.AddObject( "1" ))
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Unable to create first instance, error %d\n",
|
|||
|
GetLastError() ));
|
|||
|
|
|||
|
return GetLastError();
|
|||
|
}
|
|||
|
|
|||
|
fMigrateRoots = TRUE; // Force reg->metabase migration of virtual directories
|
|||
|
goto TryAgain;
|
|||
|
}
|
|||
|
|
|||
|
DBG_REQUIRE( mb.Close() );
|
|||
|
|
|||
|
for ( i = 0; i < cInstances; i++ )
|
|||
|
{
|
|||
|
DWORD dwInstance = ((DWORD *)buff.QueryPtr())[i];
|
|||
|
|
|||
|
if( !g_pInetSvc->AddInstanceInfo( dwInstance, fMigrateRoots ) ) {
|
|||
|
|
|||
|
err = GetLastError();
|
|||
|
|
|||
|
DBGPRINTF((
|
|||
|
DBG_CONTEXT,
|
|||
|
"InitializeInstances: cannot create instance %lu, error %lu\n",
|
|||
|
dwInstance,
|
|||
|
err
|
|||
|
));
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
return err;
|
|||
|
|
|||
|
} // InitializeInstances
|
|||
|
|
|||
|
|
|||
|
APIERR
|
|||
|
InitializeService(
|
|||
|
LPVOID lpContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine:
|
|||
|
This function initializes the various FTPD Service components.
|
|||
|
|
|||
|
Arguments:
|
|||
|
lpContext - Pointer to the service object
|
|||
|
|
|||
|
Returns:
|
|||
|
NO_ERROR if successful, otherwise a Win32
|
|||
|
status code.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
APIERR err = NO_ERROR;
|
|||
|
PFTP_IIS_SERVICE pInetSvc = (PFTP_IIS_SERVICE)lpContext;
|
|||
|
PFTP_SERVER_INSTANCE pServer;
|
|||
|
|
|||
|
DBG_ASSERT( lpContext == g_pInetSvc);
|
|||
|
|
|||
|
IF_DEBUG( SERVICE_CTRL ) {
|
|||
|
DBGPRINTF(( DBG_CONTEXT,"Initializing ftp service\n" ));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize various components. The ordering of the
|
|||
|
// components is somewhat limited.
|
|||
|
// We should initialize connections as the last item,
|
|||
|
// since it kicks off the connection thread.
|
|||
|
//
|
|||
|
|
|||
|
err = PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
|
|||
|
if(( err = InitializeGlobals() ) != NO_ERROR ||
|
|||
|
( err = PRINT_CURRENT_TIME_TO_DBG()) != NO_ERROR ||
|
|||
|
( err = pInetSvc->InitializeSockets()) != NO_ERROR ||
|
|||
|
( err = PRINT_CURRENT_TIME_TO_DBG()) != NO_ERROR ||
|
|||
|
( err = pInetSvc->InitializeDiscovery( )) != NO_ERROR ||
|
|||
|
( err = PRINT_CURRENT_TIME_TO_DBG()) != NO_ERROR ) {
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"cannot initialize ftp service, error %lu\n",err ));
|
|||
|
|
|||
|
goto exit;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Success!
|
|||
|
//
|
|||
|
|
|||
|
DBG_ASSERT( err == NO_ERROR);
|
|||
|
|
|||
|
//
|
|||
|
// From discusssions with KeithMo, we decided to punt on the
|
|||
|
// default buffer size for now. Later on if performance is
|
|||
|
// critical, we will try to improve on this by proper values
|
|||
|
// for listen socket.
|
|||
|
//
|
|||
|
|
|||
|
g_SocketBufferSize = DEFAULT_RECV_BUFFER_SIZE;
|
|||
|
|
|||
|
IF_DEBUG( SERVICE_CTRL ) {
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT, " %s service initialized\n",
|
|||
|
pInetSvc->QueryServiceName())
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize all instances
|
|||
|
//
|
|||
|
InitializeInstances(pInetSvc);
|
|||
|
|
|||
|
|
|||
|
g_pFTPStats->UpdateStartTime();
|
|||
|
|
|||
|
exit:
|
|||
|
|
|||
|
PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
|
|||
|
return ( err);
|
|||
|
|
|||
|
} // InitializeService()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
APIERR
|
|||
|
TerminateService(
|
|||
|
LPVOID lpContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine:
|
|||
|
This function cleans up the various FTPD Service components.
|
|||
|
|
|||
|
Arguments:
|
|||
|
lpContext - Pointer to the service object
|
|||
|
|
|||
|
Returns:
|
|||
|
NO_ERROR if successful, otherwise a Win32
|
|||
|
status code.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
APIERR err = NO_ERROR;
|
|||
|
PFTP_IIS_SERVICE pInetSvc = (PFTP_IIS_SERVICE)lpContext;
|
|||
|
|
|||
|
DBG_ASSERT( lpContext == g_pInetSvc);
|
|||
|
|
|||
|
|
|||
|
IF_DEBUG( SERVICE_CTRL ) {
|
|||
|
DBGPRINTF(( DBG_CONTEXT, "terminating service\n" ));
|
|||
|
}
|
|||
|
|
|||
|
PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
|
|||
|
g_pFTPStats->UpdateStopTime();
|
|||
|
|
|||
|
//
|
|||
|
// Components should be terminated in reverse
|
|||
|
// initialization order.
|
|||
|
//
|
|||
|
|
|||
|
g_pInetSvc->ShutdownService( );
|
|||
|
|
|||
|
PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
IF_DEBUG( SERVICE_CTRL ) {
|
|||
|
DBGPRINTF(( DBG_CONTEXT, "Ftp service terminated\n" ));
|
|||
|
}
|
|||
|
|
|||
|
PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
err = pInetSvc->TerminateDiscovery();
|
|||
|
|
|||
|
if ( err != NO_ERROR) {
|
|||
|
DBGPRINTF( ( DBG_CONTEXT,
|
|||
|
"CleanupService( %s):"
|
|||
|
" TerminateDiscovery failed, err=%lu\n",
|
|||
|
pInetSvc->QueryServiceName(),
|
|||
|
err));
|
|||
|
}
|
|||
|
|
|||
|
PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
pInetSvc->CleanupSockets();
|
|||
|
|
|||
|
PRINT_CURRENT_TIME_TO_DBG();
|
|||
|
|
|||
|
TsCacheFlush( INET_FTP_SVC_ID );
|
|||
|
TsFlushMetaCache(METACACHE_FTP_SERVER_ID, TRUE);
|
|||
|
TerminateGlobals();
|
|||
|
|
|||
|
return ( err);
|
|||
|
|
|||
|
} // TerminateService()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# ifdef CHECK_DBG
|
|||
|
DWORD PrintOutCurrentTime(IN CHAR * pszFile, IN int lineNum)
|
|||
|
/*++
|
|||
|
This function generates the current time and prints it out to debugger
|
|||
|
for tracing out the path traversed, if need be.
|
|||
|
|
|||
|
Arguments:
|
|||
|
pszFile pointer to string containing the name of the file
|
|||
|
lineNum line number within the file where this function is called.
|
|||
|
|
|||
|
Returns:
|
|||
|
NO_ERROR always.
|
|||
|
--*/
|
|||
|
{
|
|||
|
CHAR szBuffer[1000];
|
|||
|
|
|||
|
sprintf( szBuffer, "[%u]( %40s, %10d) TickCount = %u\n",
|
|||
|
GetCurrentThreadId(),
|
|||
|
pszFile,
|
|||
|
lineNum,
|
|||
|
GetTickCount()
|
|||
|
);
|
|||
|
|
|||
|
OutputDebugString( szBuffer);
|
|||
|
|
|||
|
return ( NO_ERROR);
|
|||
|
|
|||
|
} // PrintOutCurrentTime()
|
|||
|
|
|||
|
# endif // CHECK_DBG
|
|||
|
|
|||
|
extern "C" {
|
|||
|
|
|||
|
BOOL
|
|||
|
WINAPI
|
|||
|
DLLEntry(
|
|||
|
HINSTANCE hDll,
|
|||
|
DWORD dwReason,
|
|||
|
LPVOID lpvReserved
|
|||
|
)
|
|||
|
{
|
|||
|
|
|||
|
|
|||
|
switch ( dwReason ) {
|
|||
|
|
|||
|
case DLL_PROCESS_ATTACH:
|
|||
|
|
|||
|
#ifdef _NO_TRACING_
|
|||
|
CREATE_DEBUG_PRINT_OBJECT( FTPD_SERVICE_NAME);
|
|||
|
#else
|
|||
|
CREATE_DEBUG_PRINT_OBJECT( FTPD_SERVICE_NAME, IisFtpGuid);
|
|||
|
#endif
|
|||
|
if ( !VALID_DEBUG_PRINT_OBJECT()) {
|
|||
|
return FALSE; // Nothing can be done. Debug Print object failed!
|
|||
|
}
|
|||
|
|
|||
|
DBG_REQUIRE( DisableThreadLibraryCalls( hDll ) );
|
|||
|
INITIALIZE_CRITICAL_SECTION( &g_csServiceEntryLock );
|
|||
|
break;
|
|||
|
|
|||
|
case DLL_PROCESS_DETACH:
|
|||
|
DELETE_DEBUG_PRINT_OBJECT();
|
|||
|
DeleteCriticalSection( &g_csServiceEntryLock );
|
|||
|
break;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
|
|||
|
} // DLLEntry
|
|||
|
|
|||
|
} // extern "C"
|
|||
|
|
|||
|
/************************ End Of File ************************/
|
|||
|
|
|||
|
|