// StartAsService.h // Handles startup as a Windows Service or UNIX daemon. // This file MUST NOT be included from anywhere other than main.cpp. #ifdef _WIN32 class StartAsService { public: /** Make a Windows service. */ template static bool MakeIntoService() { SERVICE_TABLE_ENTRY ServiceTable[] = { { g_ServiceName, (LPSERVICE_MAIN_FUNCTION)serviceMain }, { nullptr, nullptr } }; if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) { throw std::system_error(GetLastError(), std::system_category()); } return true; } private: /** Set the internal status of the service */ static void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode) { SERVICE_STATUS serviceStatus = {}; 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"); } } /** Handle stop events from the Service Control Manager */ static void WINAPI serviceCtrlHandler(DWORD CtrlCode) { if (CtrlCode == SERVICE_CONTROL_STOP) { std::raise(SIGINT); serviceSetState(0, SERVICE_STOP_PENDING, 0); } } /* Startup logic for running as a service */ template static void WINAPI serviceMain(DWORD argc, TCHAR *argv[]) { wchar_t applicationFilename[MAX_PATH]; wchar_t applicationDirectory[MAX_PATH]; // Get this binary's file path: if (GetModuleFileName(nullptr, applicationFilename, std::size(applicationFilename)) == 0) { serviceSetState(0, SERVICE_STOPPED, GetLastError()); return; } const auto LastComponent = std::wcsrchr(applicationFilename, L'\\'); if (LastComponent == nullptr) { serviceSetState(0, SERVICE_STOPPED, E_UNEXPECTED); return; } const auto LengthToLastComponent = LastComponent - applicationFilename; // Strip off the filename, keep only the path: std::wcsncpy(applicationDirectory, applicationFilename, LengthToLastComponent); applicationDirectory[LengthToLastComponent] = L'\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. if (SetCurrentDirectory(applicationDirectory) == FALSE) { serviceSetState(0, SERVICE_STOPPED, GetLastError()); return; } g_StatusHandle = RegisterServiceCtrlHandler(g_ServiceName, serviceCtrlHandler); if (g_StatusHandle == nullptr) { OutputDebugStringA("RegisterServiceCtrlHandler() failed\n"); serviceSetState(0, SERVICE_STOPPED, GetLastError()); return; } serviceSetState(SERVICE_ACCEPT_STOP, SERVICE_RUNNING, 0); char MultibyteArgV0[MAX_PATH]; char * MultibyteArgV[] = { MultibyteArgV0 }; const auto OutputSize = std::size(MultibyteArgV0); const auto TranslateResult = std::wcstombs(MultibyteArgV0, argv[0], OutputSize); if (TranslateResult == static_cast(-1)) { // Translation failed entirely (!): MultibyteArgV0[0] = '\0'; } else if (TranslateResult == OutputSize) { // Output too small: MultibyteArgV0[OutputSize - 1] = '\0'; } const auto Result = MainFunction(1, MultibyteArgV, true); const auto Return = (Result == EXIT_SUCCESS) ? S_OK : E_FAIL; serviceSetState(0, SERVICE_STOPPED, Return); } static inline SERVICE_STATUS_HANDLE g_StatusHandle = nullptr; static inline HANDLE g_ServiceThread = INVALID_HANDLE_VALUE; static inline wchar_t g_ServiceName[] = L"Cuberite"; }; #else struct StartAsService { /** Make a UNIX daemon. */ template static bool MakeIntoService() { pid_t pid = fork(); // fork() returns a negative value on error. if (pid < 0) { throw std::system_error(errno, std::system_category()); } // Check if we are the parent or child process. Parent stops here. if (pid > 0) { return true; } // Child process now goes quiet, running in the background. close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); return false; } }; #endif