summaryrefslogtreecommitdiffstats
path: root/src/OSSupport
diff options
context:
space:
mode:
Diffstat (limited to 'src/OSSupport')
-rw-r--r--src/OSSupport/CMakeLists.txt2
-rw-r--r--src/OSSupport/MiniDumpWriter.h139
-rw-r--r--src/OSSupport/StartAsService.h173
3 files changed, 314 insertions, 0 deletions
diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt
index 1b853c202..42ce6934d 100644
--- a/src/OSSupport/CMakeLists.txt
+++ b/src/OSSupport/CMakeLists.txt
@@ -28,12 +28,14 @@ target_sources(
HostnameLookup.h
IPLookup.h
IsThread.h
+ MiniDumpWriter.h
Network.h
NetworkLookup.h
NetworkSingleton.h
Queue.h
ServerHandleImpl.h
StackTrace.h
+ StartAsService.h
TCPLinkImpl.h
UDPEndpointImpl.h
WinStackWalker.h
diff --git a/src/OSSupport/MiniDumpWriter.h b/src/OSSupport/MiniDumpWriter.h
new file mode 100644
index 000000000..e5cd1a0ac
--- /dev/null
+++ b/src/OSSupport/MiniDumpWriter.h
@@ -0,0 +1,139 @@
+
+#pragma once
+
+
+
+
+
+/** Flags to control minidump contents on supported platforms. */
+enum class MiniDumpFlags
+{
+ WithDataSegments,
+ WithFullMemory
+};
+
+
+
+
+
+#if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER) // 32-bit Windows app compiled in MSVC
+
+#include <dbghelp.h>
+
+
+
+
+
+/** Windows 32-bit stuff:
+When the server crashes, create a "dump file" containing the callstack of each thread and some variables;
+let the user send us that crash file for analysis */
+class MiniDumpWriter
+{
+ typedef BOOL(WINAPI *pMiniDumpWriteDump)(
+ HANDLE hProcess,
+ DWORD ProcessId,
+ HANDLE hFile,
+ MINIDUMP_TYPE DumpType,
+ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ PMINIDUMP_CALLBACK_INFORMATION CallbackParam
+ );
+
+public:
+
+ MiniDumpWriter()
+ {
+ // Magic code to produce dump-files on Windows if the server crashes:
+
+ m_DbgHelp = LoadLibrary(L"DBGHELP.DLL");
+ if (m_DbgHelp == INVALID_HANDLE_VALUE)
+ {
+ return;
+ }
+
+ s_WriteMiniDump = (pMiniDumpWriteDump)GetProcAddress(m_DbgHelp, "MiniDumpWriteDump");
+ if (s_WriteMiniDump != nullptr)
+ {
+ ASSERT(swprintf(s_DumpFileName, ARRAYCOUNT(s_DumpFileName), L"crash_mcs_%x.dmp", GetCurrentProcessId()) > 0);
+ SetUnhandledExceptionFilter(LastChanceExceptionFilter);
+ }
+
+ // End of dump-file magic
+ }
+
+ void AddDumpFlags(const MiniDumpFlags a_Flags)
+ {
+ switch (a_Flags)
+ {
+ case MiniDumpFlags::WithDataSegments:
+ {
+ s_DumpFlags = static_cast<MINIDUMP_TYPE>(s_DumpFlags | MINIDUMP_TYPE::MiniDumpWithDataSegs);
+ break;
+ }
+ case MiniDumpFlags::WithFullMemory:
+ {
+ s_DumpFlags = static_cast<MINIDUMP_TYPE>(s_DumpFlags | MINIDUMP_TYPE::MiniDumpWithFullMemory);
+ break;
+ }
+ }
+ }
+
+ ~MiniDumpWriter()
+ {
+ FreeLibrary(m_DbgHelp);
+ }
+
+private:
+
+ /** This function gets called just before the "program executed an illegal instruction and will be terminated" or similar.
+ Its purpose is to create the crashdump using the dbghlp DLLs */
+ static LONG WINAPI LastChanceExceptionFilter(__in struct _EXCEPTION_POINTERS * a_ExceptionInfo)
+ {
+ char * newStack = &s_ExceptionStack[sizeof(s_ExceptionStack) - 1];
+ char * oldStack;
+
+ // Use the substitute stack:
+ // This code is the reason why we don't support 64-bit (yet)
+ _asm
+ {
+ mov oldStack, esp
+ mov esp, newStack
+ }
+
+ MINIDUMP_EXCEPTION_INFORMATION ExcInformation;
+ ExcInformation.ThreadId = GetCurrentThreadId();
+ ExcInformation.ExceptionPointers = a_ExceptionInfo;
+ ExcInformation.ClientPointers = 0;
+
+ // Write the dump file:
+ HANDLE dumpFile = CreateFile(s_DumpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ s_WriteMiniDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, s_DumpFlags, (a_ExceptionInfo) ? &ExcInformation : nullptr, nullptr, nullptr);
+ CloseHandle(dumpFile);
+
+ // Revert to old stack:
+ _asm
+ {
+ mov esp, oldStack
+ }
+
+ return 0;
+ }
+
+ HINSTANCE m_DbgHelp;
+
+ static inline pMiniDumpWriteDump s_WriteMiniDump; // The function in dbghlp DLL that creates dump files
+ static inline wchar_t s_DumpFileName[MAX_PATH]; // Filename of the dump file; hes to be created before the dump handler kicks in
+ static inline char s_ExceptionStack[128 * 1024]; // Substitute stack, just in case the handler kicks in because of "insufficient stack space"
+ static inline MINIDUMP_TYPE s_DumpFlags = MiniDumpNormal; // By default dump only the stack and some helpers
+};
+
+#else
+
+struct MiniDumpWriter
+{
+ void AddDumpFlags(const MiniDumpFlags)
+ {
+ }
+};
+
+#endif
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