From 51891b766c733220b5267be1b4bcf6f04717e976 Mon Sep 17 00:00:00 2001 From: Anthony Birkett Date: Tue, 31 Mar 2015 14:50:03 +0100 Subject: [PATCH] Working as a Windows service. Starts and stops correctly. Added "/service" switch, to prompt the binary to attempt starting as a service. Added service* methods, to control service startup. Split up main() into universalMain(), which contains the startup code for both service and normal start. Added cRoot::m_RunningAsService bool, Added cRoot::SetStopping(bool) to allow a stop request to be sent by the service controller. Added cBlockIDMap::init() to avoid loading items.ini before the working directory has been set. --- MCServer/delete_windows_service.cmd | 4 + MCServer/install_windows_service.cmd | 7 + src/BlockID.cpp | 29 +++- src/Root.cpp | 12 +- src/Root.h | 4 + src/main.cpp | 230 ++++++++++++++++++++++----- 6 files changed, 248 insertions(+), 38 deletions(-) create mode 100644 MCServer/delete_windows_service.cmd create mode 100644 MCServer/install_windows_service.cmd diff --git a/MCServer/delete_windows_service.cmd b/MCServer/delete_windows_service.cmd new file mode 100644 index 000000000..ab6238e2f --- /dev/null +++ b/MCServer/delete_windows_service.cmd @@ -0,0 +1,4 @@ +@echo off +set SERVICENAME="MCServer" + +sc delete %SERVICENAME% \ No newline at end of file diff --git a/MCServer/install_windows_service.cmd b/MCServer/install_windows_service.cmd new file mode 100644 index 000000000..ba8a8c128 --- /dev/null +++ b/MCServer/install_windows_service.cmd @@ -0,0 +1,7 @@ +rem Alter this if you need to install multiple instances. +@echo off +set SERVICENAME="MCServer" + +set CURRENTDIR=%CD% +sc create %SERVICENAME% binPath= "%CURRENTDIR%\MCServer.exe /service" start= auto DisplayName= %SERVICENAME% +sc description %SERVICENAME% "Minecraft server instance" \ No newline at end of file diff --git a/src/BlockID.cpp b/src/BlockID.cpp index 06f4232d3..7f0db3cfc 100644 --- a/src/BlockID.cpp +++ b/src/BlockID.cpp @@ -26,8 +26,18 @@ class cBlockIDMap typedef std::map, Comparator> ItemMap; public: + static bool m_bHasRunInit; + cBlockIDMap(void) { + // Dont load items.ini on construct, this will search the wrong path when running as a service. + } + + + void init() + { + m_bHasRunInit = true; + cIniFile Ini; if (!Ini.ReadFile("items.ini")) { @@ -174,7 +184,7 @@ protected: - +bool cBlockIDMap::m_bHasRunInit = false; static cBlockIDMap gsBlockIDMap; @@ -209,6 +219,10 @@ int BlockStringToType(const AString & a_BlockTypeString) return res; } + if (!gsBlockIDMap.m_bHasRunInit) + { + gsBlockIDMap.init(); + } return gsBlockIDMap.Resolve(TrimString(a_BlockTypeString)); } @@ -222,6 +236,11 @@ bool StringToItem(const AString & a_ItemTypeString, cItem & a_Item) { ItemName = ItemName.substr(10); } + + if (!gsBlockIDMap.m_bHasRunInit) + { + gsBlockIDMap.init(); + } return gsBlockIDMap.ResolveItem(ItemName, a_Item); } @@ -231,6 +250,10 @@ bool StringToItem(const AString & a_ItemTypeString, cItem & a_Item) AString ItemToString(const cItem & a_Item) { + if (!gsBlockIDMap.m_bHasRunInit) + { + gsBlockIDMap.init(); + } return gsBlockIDMap.Desolve(a_Item.m_ItemType, a_Item.m_ItemDamage); } @@ -240,6 +263,10 @@ AString ItemToString(const cItem & a_Item) AString ItemTypeToString(short a_ItemType) { + if (!gsBlockIDMap.m_bHasRunInit) + { + gsBlockIDMap.init(); + } return gsBlockIDMap.Desolve(a_ItemType, -1); } diff --git a/src/Root.cpp b/src/Root.cpp index 27d87c717..87bc29627 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -84,7 +84,10 @@ void cRoot::InputThread(cRoot & a_Params) if (m_TerminateEventRaised || !std::cin.good()) { // We have come here because the std::cin has received an EOF / a terminate signal has been sent, and the server is still running; stop the server: - a_Params.m_bStop = true; + if (m_RunAsService) // HACK: Dont kill if running as a service + { + a_Params.m_bStop = true; + } } } @@ -268,6 +271,13 @@ void cRoot::Start(void) +void cRoot::SetStopping(bool a_Stopping) +{ + m_bStop = a_Stopping; +} + + + void cRoot::LoadGlobalSettings() { diff --git a/src/Root.h b/src/Root.h index fdaf444bd..d552466dc 100644 --- a/src/Root.h +++ b/src/Root.h @@ -46,6 +46,7 @@ public: // tolua_end static bool m_TerminateEventRaised; + static bool m_RunAsService; cRoot(void); @@ -53,6 +54,9 @@ public: void Start(void); + // Added so the service handler can request a stop + void SetStopping(bool a_Stopping); + // tolua_begin cServer * GetServer(void) { return m_Server; } cWorld * GetDefaultWorld(void); diff --git a/src/main.cpp b/src/main.cpp index 428e89e93..da8eb75d4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,8 @@ +/** Make the Root instance global, so it can be terminated from the worker threads */ +cRoot Root; /** If something has told the server to stop; checked periodically in cRoot */ @@ -29,8 +31,15 @@ bool g_ShouldLogCommIn; /** If set to true, the protocols will log each player's outgoing (S->C) communication to a per-connection logfile */ bool g_ShouldLogCommOut; +/** If set to true, binary will attempt to run as a service on Windows */ +bool cRoot::m_RunAsService = false; +#if defined(_WIN32) +SERVICE_STATUS_HANDLE g_StatusHandle = NULL; +HANDLE g_ServiceThread = INVALID_HANDLE_VALUE; +#define SERVICE_NAME "MCServerService" +#endif /// If defined, a thorough leak finder will be used (debug MSVC only); leaks will be output to the Output window @@ -179,6 +188,165 @@ BOOL CtrlHandler(DWORD fdwCtrlType) +//////////////////////////////////////////////////////////////////////////////// +// universalMain - Main startup logic for both standard running and as a service + +void universalMain() +{ + #ifdef _WIN32 + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE)) + { + LOGERROR("Could not install the Windows CTRL handler!"); + } + #endif + + // Initialize logging subsystem: + cLogger::InitiateMultithreading(); + + // Initialize LibEvent: + cNetworkSingleton::Get(); + + #if !defined(ANDROID_NDK) + try + #endif + { + Root.Start(); + } + #if !defined(ANDROID_NDK) + catch (std::exception & e) + { + LOGERROR("Standard exception: %s", e.what()); + } + catch (...) + { + LOGERROR("Unknown exception!"); + } + #endif + + g_ServerTerminated = true; + + // Shutdown all of LibEvent: + cNetworkSingleton::Get().Terminate(); +} + + + + +#if defined(_WIN32) +//////////////////////////////////////////////////////////////////////////////// +// serviceWorkerThread: Keep the service alive + +DWORD WINAPI serviceWorkerThread(LPVOID lpParam) +{ + UNREFERENCED_PARAMETER(lpParam); + + // Do the normal startup + universalMain(); + + return ERROR_SUCCESS; +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// serviceSetState: Set the internal status of the service + +void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode) +{ + SERVICE_STATUS serviceStatus; + ZeroMemory(&serviceStatus, sizeof(SERVICE_STATUS)); + serviceStatus.dwCheckPoint = 0; + serviceStatus.dwControlsAccepted = acceptedControls; + serviceStatus.dwCurrentState = newState; + serviceStatus.dwServiceSpecificExitCode = 0; + serviceStatus.dwServiceType = SERVICE_WIN32; + serviceStatus.dwWaitHint = 0; + serviceStatus.dwWin32ExitCode = exitCode; + + if (SetServiceStatus(g_StatusHandle, &serviceStatus) == FALSE) + { + LOGERROR("SetServiceStatus() failed\n"); + } +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// serviceCtrlHandler: Handle stop events from the Service Control Manager + +void WINAPI serviceCtrlHandler(DWORD CtrlCode) +{ + switch (CtrlCode) + { + case SERVICE_CONTROL_STOP: + { + Root.SetStopping(true); + serviceSetState(0, SERVICE_STOP_PENDING, 0); + break; + } + + default: + { + break; + } + } +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// serviceMain: Startup logic for running as a service + +void WINAPI serviceMain(DWORD argc, TCHAR *argv[]) +{ + #if defined(_DEBUG) && defined(DEBUG_SERVICE_STARTUP) + Sleep(10000); + #endif + + char applicationFilename[MAX_PATH]; + char applicationDirectory[MAX_PATH]; + + GetModuleFileName(NULL, applicationFilename, MAX_PATH); // This binaries fill path. + + // GetModuleFileName() returns the path and filename. Strip off the filename. + strncpy(applicationDirectory, applicationFilename, (strrchr(applicationFilename, '\\') - applicationFilename)); + applicationDirectory[strlen(applicationDirectory)] = '\0'; // Make sure new path is null terminated + + // Services are run by the SCM, and inherit its working directory - usually System32. + // Set the working directory to the same location as the binary. + SetCurrentDirectory(applicationDirectory); + + g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, serviceCtrlHandler); + + if (g_StatusHandle == NULL) + { + OutputDebugString("RegisterServiceCtrlHandler() failed\n"); + serviceSetState(0, SERVICE_STOPPED, GetLastError()); + return; + } + + serviceSetState(SERVICE_ACCEPT_STOP, SERVICE_RUNNING, 0); + + g_ServiceThread = CreateThread(NULL, 0, serviceWorkerThread, NULL, 0, NULL); + if (g_ServiceThread == NULL) + { + OutputDebugString("CreateThread() failed\n"); + serviceSetState(0, SERVICE_STOPPED, GetLastError()); + return; + } + WaitForSingleObject(g_ServiceThread, INFINITE); // Wait here for a stop signal. + + CloseHandle(g_ServiceThread); + + serviceSetState(0, SERVICE_STOPPED, 0); +} +#endif + + + //////////////////////////////////////////////////////////////////////////////// // main: @@ -219,13 +387,6 @@ int main( int argc, char **argv) #endif // _WIN32 && !_WIN64 // End of dump-file magic - #ifdef _WIN32 - if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE)) - { - LOGERROR("Could not install the Windows CTRL handler!"); - } - #endif - #if defined(_DEBUG) && defined(_MSC_VER) _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); @@ -280,42 +441,39 @@ int main( int argc, char **argv) { setvbuf(stdout, nullptr, _IONBF, 0); } + else if (NoCaseCompare(Arg, "/service") == 0) + { + cRoot::m_RunAsService = true; + } } // for i - argv[] - - // Initialize logging subsystem: - cLogger::InitiateMultithreading(); - - // Initialize LibEvent: - cNetworkSingleton::Get(); - - #if !defined(ANDROID_NDK) - try - #endif - { - cRoot Root; - Root.Start(); - } - #if !defined(ANDROID_NDK) - catch (std::exception & e) - { - LOGERROR("Standard exception: %s", e.what()); - } - catch (...) - { - LOGERROR("Unknown exception!"); - } - #endif + #if defined(_WIN32) + // Attempt to run as a service + if (cRoot::m_RunAsService) + { + SERVICE_TABLE_ENTRY ServiceTable[] = + { + { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)serviceMain }, + { NULL, NULL } + }; + + if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) + { + LOGERROR("Attempted, but failed, service startup."); + return GetLastError(); + } + } + else + #endif + { + // Not running as a service, do normal startup + universalMain(); + } #if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) DeinitLeakFinder(); #endif - g_ServerTerminated = true; - - // Shutdown all of LibEvent: - cNetworkSingleton::Get().Terminate(); - return EXIT_SUCCESS; }