/*++ Copyright (c) 1995 Microsoft Corporation Module Name: service.cxx Abstract: Process init and service controller interaction Author: Mario Goertzel [MarioGo] Revision History: MarioGo 06-14-95 Cloned RPCSS from the old endpoint mapper. jroberts 06-29-00 Cloned BITS from RPCSS --*/ #include "qmgrlib.h" #if !defined(BITS_V12_ON_NT4) #include "service.tmh" #endif #define CHECK_DLL_VERSION 1 // // We delay the expensive setup tasks for a few minutes, to avoid slowing the // boot or logon process. The delay time is expressed in milliseconds. // // #define STARTUP_DELAY (5 * 60 * 1000) #define STARTUP_DELAY (5 * 1000) // Array of service status blocks and pointers to service control // functions for each component service. #define SERVICE_NAME _T("BITS") #define DEVICE_PREFIX _T("\\\\.\\") VOID WINAPI ServiceMain(DWORD, LPTSTR*); VOID UpdateState(DWORD dwNewState); extern BOOL CatalogDllMain ( HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved ); SERVICE_TABLE_ENTRY gaServiceEntryTable[] = { { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain}, { NULL, NULL } }; HINSTANCE g_hInstance; SERVICE_STATUS gServiceStatus; SERVICE_STATUS_HANDLE ghServiceHandle; // This event is set when we receive a SERVICE_CONTROL_STOP/SHUTDOWN HANDLE g_hServiceStopEvent = NULL; // // When TRUE, the process is running outside the service controller. // BOOL g_fVisible = FALSE; typedef SERVICE_STATUS_HANDLE (*PREGISTER_FUNC)( LPCTSTR lpServiceName, LPHANDLER_FUNCTION_EX lpHandlerProc, LPVOID lpContext ); typedef VOID (*PSERVICE_MAIN_FUNC)( DWORD argc, LPTSTR *lpszArgv, PREGISTER_FUNC RegisterFunc ); extern "C" VOID BITSServiceMain( DWORD argc, LPTSTR *argv, PREGISTER_FUNC lpRegisterFunc ); #if CHECK_DLL_VERSION BOOL GetModuleVersion64( HMODULE hDll, ULONG64 * pVer ); BOOL GetFileVersion64( LPTSTR szFullPath, ULONG64 * pVer ); #endif LPHANDLER_FUNCTION_EX g_RealHandler = NULL; HINSTANCE g_RealLibrary = NULL; LONG g_RealLibraryRefs = 0; ULONG ServiceHandlerThunk( DWORD dwCode, DWORD dwEventType, PVOID EventData, PVOID pData ) { InterlockedIncrement( &g_RealLibraryRefs ); ULONG Result = g_RealHandler( dwCode, dwEventType, EventData, pData ); if (!InterlockedDecrement( &g_RealLibraryRefs ) ) { FreeLibrary( g_RealLibrary ); g_RealLibrary = NULL; } return Result; } SERVICE_STATUS_HANDLE RegisterServiceHandlerThunk( LPCTSTR lpServiceName, LPHANDLER_FUNCTION_EX lpHandlerProc, LPVOID lpContext ) { g_RealHandler = lpHandlerProc; return RegisterServiceCtrlHandlerEx( lpServiceName, ServiceHandlerThunk, lpContext ); } bool JumpToRealDLL( DWORD argc, LPTSTR *argv ) { #define MAX_DLLNAME (MAX_PATH+1) HKEY BitsKey = NULL; LONG Result = RegOpenKey( HKEY_LOCAL_MACHINE, C_QMGR_REG_KEY, &BitsKey ); if ( Result ) goto noload; static TCHAR DLLName[MAX_DLLNAME]; DWORD Type; DWORD NameSize = sizeof(DLLName); Result = RegQueryValueEx( BitsKey, C_QMGR_SERVICEDLL, NULL, &Type, (LPBYTE)DLLName, &NameSize ); if ( Result || (( Type != REG_SZ ) && (Type != REG_EXPAND_SZ)) ) { goto noload; } RegCloseKey( BitsKey ); BitsKey = NULL; if (Type == REG_EXPAND_SZ) { static TCHAR ExpandedDLLName[MAX_DLLNAME]; DWORD size = ExpandEnvironmentStrings( DLLName, ExpandedDLLName, MAX_DLLNAME ); if (size == 0) { // out of resources return true; } HRESULT hr; hr = StringCchCopy( DLLName, RTL_NUMBER_OF(DLLName), ExpandedDLLName ); if (FAILED(hr)) { // too long; must be badly formatted. Ignore it. goto noload; } } #if CHECK_DLL_VERSION // // At this point, we know that the registry specifies an alternate DLL. // See whether it has a later version than the current one. // ULONG64 AlternateDllVersion = 0; ULONG64 MyDllVersion = 0; if (!QMgrFileExists( DLLName )) { goto noload; } if (!GetFileVersion64( DLLName, &AlternateDllVersion )) { // can't ascertain the version. Don't start the service at all. return true; } if (!GetModuleVersion64( g_hInstance, &MyDllVersion )) { // can't ascertain the version. Don't start the service at all. return true; } if (MyDllVersion >= AlternateDllVersion) { goto noload; } #endif g_RealLibrary = LoadLibrary( DLLName ); if ( !g_RealLibrary ) goto noload; PSERVICE_MAIN_FUNC ServiceMainFunc = (PSERVICE_MAIN_FUNC)GetProcAddress( g_RealLibrary, "BITSServiceMain" ); if ( !ServiceMainFunc ) goto noload; g_RealLibraryRefs = 1; // Ok to call into real library now. ( *ServiceMainFunc ) ( argc, argv, RegisterServiceHandlerThunk ); if (!InterlockedDecrement( &g_RealLibraryRefs ) ) { FreeLibrary( g_RealLibrary ); g_RealLibrary = NULL; } return true; noload: if ( BitsKey ) RegCloseKey( BitsKey ); if ( g_RealLibrary ) FreeLibrary( g_RealLibrary ); return false; } VOID WINAPI ServiceMain( DWORD argc, LPTSTR *argv ) /*++ Routine Description: Callback by the service controller when starting this service. Arguments: argc - number of arguments, usually 1 argv - argv[0] is the name of the service. argv[>0] are arguments passed to the service. Return Value: None --*/ { volatile static LONG ThreadRunning = 0; if ( InterlockedCompareExchange( &ThreadRunning, 1, 0 ) == 1 ) { // A thread is already running ServiceMain, just exit. // The service controller has a bug where it can create multiple // threads to call ServiceMain in high stress conditions. return; } if (!JumpToRealDLL( argc, argv) ) { BITSServiceMain( argc, argv, RegisterServiceCtrlHandlerEx ); } ThreadRunning = 0; } DWORD g_LastServiceControl; ULONG WINAPI BITSServiceHandler( DWORD dwCode, DWORD dwEventType, PVOID EventData, PVOID pData ) /*++ Routine Description: Lowest level callback from the service controller to cause this service to change our status. (stop, start, pause, etc). Arguments: opCode - One of the service "Controls" value. SERVICE_CONTROL_{STOP, PAUSE, CONTINUE, INTERROGATE, SHUTDOWN}. Return Value: None --*/ { switch(dwCode) { case SERVICE_CONTROL_STOP: { LogService( "STOP request" ); // // only relevant in running state; damaging if we are stopping // and g_hServiceStopEvent is deleted. // if (gServiceStatus.dwCurrentState == SERVICE_RUNNING) { g_LastServiceControl = dwCode; UpdateState( SERVICE_STOP_PENDING ); SetEvent( g_hServiceStopEvent ); } break; } case SERVICE_CONTROL_INTERROGATE: // Service controller wants us to call SetServiceStatus. LogService( "INTERROGATE request" ); UpdateState(gServiceStatus.dwCurrentState); break ; case SERVICE_CONTROL_SHUTDOWN: // The machine is shutting down. We'll be killed once we return. LogService( "SHUTDOWN request" ); g_LastServiceControl = dwCode; UpdateState( SERVICE_STOP_PENDING ); SetEvent( g_hServiceStopEvent ); while (gServiceStatus.dwCurrentState == SERVICE_STOP_PENDING) { LogService( "service pending; sleeping..." ); Sleep(100); } break; #if !defined( BITS_V12_ON_NT4 ) case SERVICE_CONTROL_DEVICEEVENT: return DeviceEventCallback( dwEventType, EventData ); #endif case SERVICE_CONTROL_SESSIONCHANGE: { WTSSESSION_NOTIFICATION* pswtsi = (WTSSESSION_NOTIFICATION*) EventData; DWORD dwSessionId = pswtsi->dwSessionId; switch (dwEventType) { case WTS_SESSION_LOGON: { LogService("logon at session %d", dwSessionId); SessionLogonCallback( dwSessionId ); break; } case WTS_SESSION_LOGOFF: { LogService("logoff at session %d", dwSessionId); SessionLogoffCallback( dwSessionId ); break; } default: //Is there a default? break; } break; } default: LogError( "%!ts!: Unexpected service control message %d.\n", SERVICE_NAME, dwCode); return ERROR_CALL_NOT_IMPLEMENTED; } return NO_ERROR; } bool IsServiceShuttingDown() { return (gServiceStatus.dwCurrentState == SERVICE_STOP_PENDING); } VOID UpdateState( DWORD dwNewState ) /*++ Routine Description: Updates this services state with the service controller. Arguments: dwNewState - The next start for this service. One of SERVICE_START_PENDING SERVICE_RUNNING Return Value: None --*/ { DWORD status = ERROR_SUCCESS; LogService("state change: old %d new %d", gServiceStatus.dwCurrentState, dwNewState ); switch (dwNewState) { case SERVICE_RUNNING: case SERVICE_STOPPED: gServiceStatus.dwCheckPoint = 0; gServiceStatus.dwWaitHint = 0; break; case SERVICE_START_PENDING: case SERVICE_STOP_PENDING: ++gServiceStatus.dwCheckPoint; gServiceStatus.dwWaitHint = 30000L; break; default: ASSERT(0); status = ERROR_INVALID_SERVICE_CONTROL; break; } if (status == ERROR_SUCCESS) { gServiceStatus.dwCurrentState = dwNewState; if (!SetServiceStatus(ghServiceHandle, &gServiceStatus)) { status = GetLastError(); } } if (status != ERROR_SUCCESS) { LogError( "%!ts!: Failed to update service state: %d\n", SERVICE_NAME, status); } // We could return a status but how would we recover? Ignore it, the // worst thing is that services will kill us and there's nothing // we can about it if this call fails. LogInfo( "Finished updating service state to %u", dwNewState ); return; } extern "C" VOID BITSServiceMain( DWORD argc, LPTSTR *argv, PREGISTER_FUNC lpRegisterFunc ) /*++ Routine Description: Callback by the service controller when starting this service. Arguments: argc - number of arguments, usually 1 argv - argv[0] is the name of the service. argv[>0] are arguments passed to the service. Return Value: None --*/ { BOOL f = FALSE; HRESULT hr = S_OK; bool bGlobals = false; bool bQmgr = false; try { DWORD status = ERROR_SUCCESS; FILETIME ftStartTime; GetSystemTimeAsFileTime( &ftStartTime ); Log_Init(); Log_StartLogger(); LogInfo("Service started at %!TIMESTAMP!", FILETIMEToUINT64( ftStartTime ) ); // // Set up for service notifications. // gServiceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; gServiceStatus.dwCurrentState = SERVICE_START_PENDING; gServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; // The SESSIONCHANGE notification is only available on WindowsXP // and the service controller will become confused if this is given on windows 2000 if ( WINDOWSXP_PLATFORM == g_PlatformVersion ) gServiceStatus.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE; gServiceStatus.dwWin32ExitCode = 0; gServiceStatus.dwServiceSpecificExitCode = 0; gServiceStatus.dwCheckPoint = 0; gServiceStatus.dwWaitHint = 30000L; ghServiceHandle = (*lpRegisterFunc)( SERVICE_NAME, BITSServiceHandler, 0 ); if (0 == ghServiceHandle) { status = GetLastError(); ASSERT(status != ERROR_SUCCESS); LogError( "RegisterServiceCtrlHandlerEx failed %!winerr!", status); hr = HRESULT_FROM_WIN32( status ); } UpdateState(SERVICE_START_PENDING); // Set up an event that will be signaled when the service is // stopped or shutdown. g_hServiceStopEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if( NULL == g_hServiceStopEvent ) { status = GetLastError(); LogError( "CreateEvent failed %!winerr!", status ); THROW_HRESULT( HRESULT_FROM_WIN32( status )); } #if defined( BITS_V12_ON_NT4 ) if ( WINDOWS2000_PLATFORM == g_PlatformVersion || NT4_PLATFORM == g_PlatformVersion ) #else if ( WINDOWS2000_PLATFORM == g_PlatformVersion ) #endif { HRESULT Hr; bool CoInitCalled = false; Hr = CoInitializeEx( NULL, COINIT_MULTITHREADED ); if ( FAILED( Hr ) && ( Hr != RPC_E_CHANGED_MODE ) ) THROW_HRESULT( Hr ); CoInitCalled = true; Hr = CoInitializeSecurity( NULL, // pSecDesc -1, // cAuthSvc NULL, // asAuthSvc NULL, // pReserved RPC_C_AUTHN_LEVEL_PKT, // dwAuthnLevel RPC_C_IMP_LEVEL_IDENTIFY, // dwImpLevel NULL, // pReserved2 #if defined( BITS_V12_ON_NT4 ) 0, #else EOAC_NO_CUSTOM_MARSHAL | // dwCapabilities EOAC_DISABLE_AAA | EOAC_STATIC_CLOAKING, #endif NULL ); // pReserved3 if ( FAILED( Hr ) && ( Hr != RPC_E_TOO_LATE ) ) { LogError( "Unable to initialize security on Win2k, error %!winerr!", Hr ); if ( CoInitCalled ) CoUninitialize(); THROW_HRESULT( Hr ); } } LogInfo( "Initializing globalinfo\n" ); THROW_HRESULT( GlobalInfo::Init() ); bGlobals = true; LogInfo( "Initializing qmgr\n" ); THROW_HRESULT( InitQmgr() ); bQmgr = true; LogInfo( "Setting service to running."); // // Allow service controller to resume other duties. // UpdateState(SERVICE_RUNNING); // // wait for the stop signal. // if( WAIT_OBJECT_0 != WaitForSingleObject( g_hServiceStopEvent, INFINITE )) { status = GetLastError(); LogError( "ServiceMain failed waiting for stop signal %!winerr!", status); hr = HRESULT_FROM_WIN32( status ); } hr = S_OK; } catch ( ComError exception ) { hr = exception.Error(); } if (bQmgr) { HRESULT hr2 = UninitQmgr(); if (FAILED(hr2)) { LogError( "uninit Qmgr failed %!winerr!", hr2); } } if (bGlobals) { HRESULT hr2 = GlobalInfo::Uninit(); if (FAILED(hr2)) { LogError( "uninit GlobalInfo failed %!winerr!", hr2); } } if (g_hServiceStopEvent) { CloseHandle( g_hServiceStopEvent ); g_hServiceStopEvent = NULL; } if (FAILED(hr)) { gServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; gServiceStatus.dwServiceSpecificExitCode = hr; } LogService( "ServiceMain returning, hr = %x", hr ); Log_Close(); UpdateState(SERVICE_STOPPED); } enum { STARTUP_UNKNOWN, STARTUP_DEMAND, STARTUP_AUTO } g_ServiceStartupState = STARTUP_UNKNOWN; HRESULT SetServiceStartup( bool bAutoStart ) { LogService( "Setting startup to %s", bAutoStart ? ("Auto") : ("Demand") ); HRESULT Hr = S_OK; // // Changing the service state is expensive, so avoid it if possible. // // No need to monitor external changes to the startup state, though: // // If the admin changes our state from AUTO to DEMAND or DISABLED, then // the consequent lack of progress is his fault, and admins should know that. // // If the admin changes our state from DEMAND to AUTO, then we will // start more often but the result is otherwise harmless. // if ((g_ServiceStartupState == STARTUP_DEMAND && bAutoStart == FALSE) || (g_ServiceStartupState == STARTUP_AUTO && bAutoStart == TRUE)) { LogService( "startup state is already correct" ); return S_OK; } if (gServiceStatus.dwCurrentState != SERVICE_RUNNING) { LogService("can't change startup state in state %d", gServiceStatus.dwCurrentState); return S_OK; } SC_HANDLE hServiceManager = NULL; SC_HANDLE hService = NULL; try { try { hServiceManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); if ( !hServiceManager ) throw (DWORD)GetLastError(); hService = OpenService( hServiceManager, SERVICE_NAME, SERVICE_CHANGE_CONFIG ); if ( !hService ) throw (DWORD)GetLastError(); BOOL bResult = ChangeServiceConfig( hService, // service handle SERVICE_NO_CHANGE, // dwServiceType bAutoStart ? SERVICE_AUTO_START : SERVICE_DEMAND_START, // dwStartType SERVICE_NO_CHANGE, // dwErrorControl NULL, // lpBinaryPathName NULL, // lpLoadOrderGroup NULL, // lpdwTagId NULL, // lpDependencies NULL, // lpServiceStartName NULL, // lpPassword NULL); // lpDisplayName if ( !bResult ) throw (DWORD)GetLastError(); if (bAutoStart) { g_ServiceStartupState = STARTUP_AUTO; } else { g_ServiceStartupState = STARTUP_DEMAND; } } catch( DWORD dwException ) { throw (HRESULT)HRESULT_FROM_WIN32( dwException ); } } catch (HRESULT HrException) { Hr = HrException; LogError( "An error occurred setting service startup, %!winerr!", Hr ); } if ( hService ) { CloseServiceHandle( hService ); } if ( hServiceManager ) { CloseServiceHandle( hServiceManager ); } LogService( " HR: %!winerr!", Hr ); return Hr; } int InitializeBitsAllocator(); extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { g_hInstance = hInstance; DisableThreadLibraryCalls(hInstance); if (!InitCompilerLibrary()) { return FALSE; } if ( !DetectProductVersion() ) { UninitCompilerLibrary(); return FALSE; } if (0 != InitializeBitsAllocator()) { UninitCompilerLibrary(); return FALSE; } } else if ( dwReason == DLL_PROCESS_DETACH ) { UninitCompilerLibrary(); } return TRUE; // ok } #if CHECK_DLL_VERSION // // This ungainly typedef seems to have no global definition. There are several identical // definitions in the Windows NT sources, each of which has that bizarre bit-stripping // on szKey. I got mine from \nt\base\ntsetup\srvpack\update\splib\common.h. // typedef struct tagVERHEAD { WORD wTotLen; WORD wValLen; WORD wType; /* always 0 */ WCHAR szKey[(sizeof("VS_VERSION_INFO")+3)&~03]; VS_FIXEDFILEINFO vsf; } VERHEAD ; /* ** Purpose: ** Gets the file version values from the given file and sets the ** given ULONG64 variable. ** Arguments: ** szFullPath: a zero terminated character string containing the fully ** qualified path (including disk drive) to the file. ** Returns: ** fTrue if file and file version resource found and retrieved, ** fFalse if not. +++ ** Implementation: **************************************************************************/ BOOL GetFileVersion64( LPTSTR szFullPath, ULONG64 * pVer ) { BOOL fRet = false; DWORD dwHandle; DWORD InfoSize; try { // // Get the file version info size // if ((InfoSize = GetFileVersionInfoSize( szFullPath, &dwHandle)) == 0) { return (fRet); } // // Allocate enough size to hold version info // auto_ptr lpData ( LPTSTR(new byte[ InfoSize ])); // // Get the version info // fRet = GetFileVersionInfo( szFullPath, dwHandle, InfoSize, lpData.get()); if (fRet) { UINT dwLen; VS_FIXEDFILEINFO *pvsfi; fRet = VerQueryValue( lpData.get(), L"\\", (LPVOID *)&pvsfi, &dwLen ); // // Convert two DWORDs into a 64-bit integer. // if (fRet) { *pVer = ( ULONG64(pvsfi->dwFileVersionMS) << 32) | (pvsfi->dwFileVersionLS); } } return (fRet); } catch ( ComError err ) { return false; } } BOOL GetModuleVersion64( HMODULE hDll, ULONG64 * pVer ) { DWORD* pdwTranslation; VS_FIXEDFILEINFO* pFileInfo; UINT uiSize; HRSRC hrsrcVersion = FindResource( hDll, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); if (!hrsrcVersion) return false; HGLOBAL hglobalVersion = LoadResource(hDll, hrsrcVersion); if (!hglobalVersion) return false; VERHEAD * pVerHead = (VERHEAD *) LockResource(hglobalVersion); if (!pVerHead) return false; // I stole this code from \nt\com\complus\src\shared\util\svcerr.cpp, // and the comment is theirs: // // VerQueryValue will write to the memory, for some reason. // Therefore we must make a writable copy of the version // resource info before calling that API. auto_ptr pvVersionInfo ( new char[pVerHead->wTotLen + pVerHead->wTotLen/2] ); memcpy(pvVersionInfo.get(), pVerHead, pVerHead->wTotLen); // SEC: REVIEWED 2002-03-28 // Retrieve file version info BOOL fRet = VerQueryValue( pvVersionInfo.get(), L"\\", (void**)&pFileInfo, &uiSize); if (fRet) { *pVer = (ULONG64(pFileInfo->dwFileVersionMS) << 32) | (pFileInfo->dwFileVersionLS); } return fRet; } #endif