summaryrefslogtreecommitdiffstats
path: root/src/OSSupport/StartAsService.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/OSSupport/StartAsService.h173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/OSSupport/StartAsService.h b/src/OSSupport/StartAsService.h
new file mode 100644
index 000000000..472184367
--- /dev/null
+++ b/src/OSSupport/StartAsService.h
@@ -0,0 +1,173 @@
+
+#pragma once
+
+
+
+
+
+#ifdef _WIN32
+
+#include <csignal>
+
+
+
+
+
+class cStartAsService
+{
+public:
+
+ /** Make a Windows service. */
+ template <auto UniversalMain>
+ static bool MakeIntoService()
+ {
+ SERVICE_TABLE_ENTRY ServiceTable[] =
+ {
+ { g_ServiceName, (LPSERVICE_MAIN_FUNCTION)serviceMain<UniversalMain> },
+ { 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 <auto MainFunction>
+ 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 = 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:
+ 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 = wcstombs(MultibyteArgV0, argv[0], OutputSize);
+
+ if (TranslateResult == static_cast<size_t>(-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 cStartAsService
+{
+ /** Make a UNIX daemon. */
+ template <auto>
+ 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