Cuberite
A lightweight, fast and extensible game server for Minecraft
main.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
3 
4 #include "Root.h"
5 #include "tclap/CmdLine.h"
6 
7 #include <exception>
8 #include <csignal>
9 #include <stdlib.h>
10 
11 
12 
13 #ifdef ANDROID
14  // Workaround for Android NDK builds that do not support std::to_string
15  namespace std
16  {
17  template <typename T>
18  std::string to_string(T Value)
19  {
20  std::ostringstream TempStream;
21  TempStream << Value;
22  return TempStream.str();
23  }
24  }
25 #endif
26 
27 #ifdef _MSC_VER
28  #include <dbghelp.h>
29 #endif // _MSC_VER
30 
32 #include "BuildInfo.h"
33 #include "Logger.h"
34 
36 
37 
38 
39 
40 // Forward declarations to satisfy Clang's -Wmissing-variable-declarations:
41 extern bool g_ShouldLogCommIn;
42 extern bool g_ShouldLogCommOut;
43 
44 
45 
46 
47 
50 
53 
56 
58 bool cRoot::m_RunAsService = false;
59 
60 
61 
62 
63 
64 #if defined(_WIN32)
65  SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
66  HANDLE g_ServiceThread = INVALID_HANDLE_VALUE;
67  #define SERVICE_NAME L"CuberiteService"
68 #endif
69 
70 
71 
72 
73 
74 #ifndef _DEBUG
75 // Because SIG_DFL or SIG_IGN could be NULL instead of nullptr, we need to disable the Clang warning here
76 #ifdef __clang__
77  #pragma clang diagnostic push
78  #pragma clang diagnostic ignored "-Wunknown-warning-option"
79  #pragma clang diagnostic ignored "-Wunknown-pragmas"
80  #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
81 #endif // __clang__
82 
83 static void NonCtrlHandler(int a_Signal)
84 {
85  LOGD("Terminate event raised from std::signal");
87 
88  switch (a_Signal)
89  {
90  case SIGSEGV:
91  {
92  std::signal(SIGSEGV, SIG_DFL);
93  LOGERROR(" D: | Cuberite has encountered an error and needs to close");
94  LOGERROR("Details | SIGSEGV: Segmentation fault");
95  #ifdef BUILD_ID
96  LOGERROR("Cuberite " BUILD_SERIES_NAME " build id: " BUILD_ID);
97  LOGERROR("from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME);
98  #endif
100  abort();
101  }
102  case SIGABRT:
103  #ifdef SIGABRT_COMPAT
104  case SIGABRT_COMPAT:
105  #endif
106  {
107  std::signal(a_Signal, SIG_DFL);
108  LOGERROR(" D: | Cuberite has encountered an error and needs to close");
109  LOGERROR("Details | SIGABRT: Server self-terminated due to an internal fault");
110  #ifdef BUILD_ID
111  LOGERROR("Cuberite " BUILD_SERIES_NAME " build id: " BUILD_ID);
112  LOGERROR("from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME);
113  #endif
114  PrintStackTrace();
115  abort();
116  }
117  case SIGINT:
118  case SIGTERM:
119  {
120  std::signal(a_Signal, SIG_IGN); // Server is shutting down, wait for it...
121  break;
122  }
123  default: break;
124  }
125 }
126 
127 #ifdef __clang__
128  #pragma clang diagnostic pop
129 #endif // __clang__
130 #endif // _DEBUG
131 
132 
133 
134 
135 
136 #if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER)
137 // 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
139 
140 typedef BOOL (WINAPI *pMiniDumpWriteDump)(
141  HANDLE hProcess,
142  DWORD ProcessId,
143  HANDLE hFile,
144  MINIDUMP_TYPE DumpType,
145  PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
146  PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
147  PMINIDUMP_CALLBACK_INFORMATION CallbackParam
148 );
149 
150 static pMiniDumpWriteDump g_WriteMiniDump; // The function in dbghlp DLL that creates dump files
151 
152 static wchar_t g_DumpFileName[MAX_PATH]; // Filename of the dump file; hes to be created before the dump handler kicks in
153 static char g_ExceptionStack[128 * 1024]; // Substitute stack, just in case the handler kicks in because of "insufficient stack space"
154 static MINIDUMP_TYPE g_DumpFlags = MiniDumpNormal; // By default dump only the stack and some helpers
155 
156 
157 
158 
159 
163 static LONG WINAPI LastChanceExceptionFilter(__in struct _EXCEPTION_POINTERS * a_ExceptionInfo)
164 {
165  char * newStack = &g_ExceptionStack[sizeof(g_ExceptionStack) - 1];
166  char * oldStack;
167 
168  // Use the substitute stack:
169  // This code is the reason why we don't support 64-bit (yet)
170  _asm
171  {
172  mov oldStack, esp
173  mov esp, newStack
174  }
175 
176  MINIDUMP_EXCEPTION_INFORMATION ExcInformation;
177  ExcInformation.ThreadId = GetCurrentThreadId();
178  ExcInformation.ExceptionPointers = a_ExceptionInfo;
179  ExcInformation.ClientPointers = 0;
180 
181  // Write the dump file:
182  HANDLE dumpFile = CreateFile(g_DumpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
183  g_WriteMiniDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, g_DumpFlags, (a_ExceptionInfo) ? &ExcInformation : nullptr, nullptr, nullptr);
184  CloseHandle(dumpFile);
185 
186  // Print the stack trace for the basic debugging:
187  PrintStackTrace();
188 
189  // Revert to old stack:
190  _asm
191  {
192  mov esp, oldStack
193  }
194 
195  return 0;
196 }
197 
198 #endif // _WIN32 && !_WIN64
199 
200 
201 
202 
203 
204 #ifdef _WIN32
205 // Handle CTRL events in windows, including console window close
206 static BOOL CtrlHandler(DWORD fdwCtrlType)
207 {
209  LOGD("Terminate event raised from the Windows CtrlHandler");
210 
211  std::this_thread::sleep_for(std::chrono::seconds(10)); // Delay as much as possible to try to get the server to shut down cleanly - 10 seconds given by Windows
212  // Returning from main() automatically aborts this handler thread
213 
214  return TRUE;
215 }
216 #endif
217 
218 
219 
220 
221 
223 // UniversalMain - Main startup logic for both standard running and as a service
224 
225 static void UniversalMain(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo)
226 {
227  // Initialize logging subsystem:
229 
230  // Initialize LibEvent:
232 
233  try
234  {
235  cRoot Root;
236  Root.Start(std::move(a_OverridesRepo));
237  }
238  catch (const fmt::FormatError & exc)
239  {
240  cRoot::m_TerminateEventRaised = true;
241  FLOGERROR("Formatting exception: {0}", exc.what());
242  }
243  catch (const std::exception & exc)
244  {
245  cRoot::m_TerminateEventRaised = true;
246  LOGERROR("Standard exception: %s", exc.what());
247  }
248  catch (...)
249  {
250  cRoot::m_TerminateEventRaised = true;
251  LOGERROR("Unknown exception!");
252  }
253 
254  // Shutdown all of LibEvent:
256 }
257 
258 
259 
260 
261 
262 #if defined(_WIN32) // Windows service support.
263 // serviceWorkerThread: Keep the service alive
265 
266 static DWORD WINAPI serviceWorkerThread(LPVOID lpParam)
267 {
268  UNREFERENCED_PARAMETER(lpParam);
269 
270  while (!cRoot::m_TerminateEventRaised)
271  {
272  // Do the normal startup
273  UniversalMain(cpp14::make_unique<cMemorySettingsRepository>());
274  }
275 
276  return ERROR_SUCCESS;
277 }
278 
279 
280 
281 
282 
284 // serviceSetState: Set the internal status of the service
285 
286 static void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode)
287 {
288  SERVICE_STATUS serviceStatus = {};
289  serviceStatus.dwCheckPoint = 0;
290  serviceStatus.dwControlsAccepted = acceptedControls;
291  serviceStatus.dwCurrentState = newState;
292  serviceStatus.dwServiceSpecificExitCode = 0;
293  serviceStatus.dwServiceType = SERVICE_WIN32;
294  serviceStatus.dwWaitHint = 0;
295  serviceStatus.dwWin32ExitCode = exitCode;
296 
297  if (SetServiceStatus(g_StatusHandle, &serviceStatus) == FALSE)
298  {
299  LOGERROR("SetServiceStatus() failed\n");
300  }
301 }
302 
303 
304 
305 
307 // serviceCtrlHandler: Handle stop events from the Service Control Manager
308 
309 static void WINAPI serviceCtrlHandler(DWORD CtrlCode)
310 {
311  switch (CtrlCode)
312  {
313  case SERVICE_CONTROL_STOP:
314  {
316  serviceSetState(0, SERVICE_STOP_PENDING, 0);
317  break;
318  }
319  default:
320  {
321  break;
322  }
323  }
324 }
325 
326 
327 
328 
330 // serviceMain: Startup logic for running as a service
331 
332 static void WINAPI serviceMain(DWORD argc, TCHAR *argv[])
333 {
334  wchar_t applicationFilename[MAX_PATH];
335  wchar_t applicationDirectory[MAX_PATH];
336 
337  GetModuleFileName(nullptr, applicationFilename, sizeof(applicationFilename)); // This binary's file path.
338 
339  // Strip off the filename, keep only the path:
340  wcsncpy_s(applicationDirectory, sizeof(applicationDirectory), applicationFilename, (wcsrchr(applicationFilename, '\\') - applicationFilename));
341  applicationDirectory[wcslen(applicationDirectory)] = '\0'; // Make sure new path is null terminated
342 
343  // Services are run by the SCM, and inherit its working directory - usually System32.
344  // Set the working directory to the same location as the binary.
345  SetCurrentDirectory(applicationDirectory);
346 
347  g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, serviceCtrlHandler);
348  if (g_StatusHandle == nullptr)
349  {
350  OutputDebugStringA("RegisterServiceCtrlHandler() failed\n");
351  serviceSetState(0, SERVICE_STOPPED, GetLastError());
352  return;
353  }
354 
355  serviceSetState(SERVICE_ACCEPT_STOP, SERVICE_RUNNING, 0);
356 
357  DWORD ThreadID;
358  g_ServiceThread = CreateThread(nullptr, 0, serviceWorkerThread, nullptr, 0, &ThreadID);
359  if (g_ServiceThread == nullptr)
360  {
361  OutputDebugStringA("CreateThread() failed\n");
362  serviceSetState(0, SERVICE_STOPPED, GetLastError());
363  return;
364  }
365  WaitForSingleObject(g_ServiceThread, INFINITE); // Wait here for a stop signal.
366 
367  CloseHandle(g_ServiceThread);
368 
369  serviceSetState(0, SERVICE_STOPPED, 0);
370 }
371 #endif // Windows service support.
372 
373 
374 
375 
376 
377 static std::unique_ptr<cMemorySettingsRepository> ParseArguments(int argc, char ** argv)
378 {
379  try
380  {
381  // Parse the comand line args:
382  TCLAP::CmdLine cmd("Cuberite");
383  TCLAP::ValueArg<int> slotsArg ("s", "max-players", "Maximum number of slots for the server to use, overrides setting in setting.ini", false, -1, "number", cmd);
384  TCLAP::ValueArg<AString> confArg ("c", "config-file", "Config file to use", false, "settings.ini", "string", cmd);
385  TCLAP::MultiArg<int> portsArg ("p", "port", "The port number the server should listen to", false, "port", cmd);
386  TCLAP::SwitchArg commLogArg ("", "log-comm", "Log server client communications to file", cmd);
387  TCLAP::SwitchArg commLogInArg ("", "log-comm-in", "Log inbound server client communications to file", cmd);
388  TCLAP::SwitchArg commLogOutArg ("", "log-comm-out", "Log outbound server client communications to file", cmd);
389  TCLAP::SwitchArg crashDumpFull ("", "crash-dump-full", "Crashdumps created by the server will contain full server memory", cmd);
390  TCLAP::SwitchArg crashDumpGlobals("", "crash-dump-globals", "Crashdumps created by the server will contain the global variables' values", cmd);
391  TCLAP::SwitchArg noBufArg ("", "no-output-buffering", "Disable output buffering", cmd);
392  TCLAP::SwitchArg noFileLogArg ("", "no-log-file", "Disable logging to file", cmd);
393  TCLAP::SwitchArg runAsServiceArg ("d", "service", "Run as a service on Windows, or daemon on UNIX like systems", cmd);
394  cmd.parse(argc, argv);
395 
396  // Copy the parsed args' values into a settings repository:
397  auto repo = cpp14::make_unique<cMemorySettingsRepository>();
398  if (confArg.isSet())
399  {
400  AString conf_file = confArg.getValue();
401  repo->AddValue("Server", "ConfigFile", conf_file);
402  }
403  if (slotsArg.isSet())
404  {
405  int slots = slotsArg.getValue();
406  repo->AddValue("Server", "MaxPlayers", static_cast<Int64>(slots));
407  }
408  if (portsArg.isSet())
409  {
410  for (auto port: portsArg.getValue())
411  {
412  repo->AddValue("Server", "Ports", std::to_string(port));
413  }
414  }
415  if (noFileLogArg.getValue())
416  {
417  repo->AddValue("Server", "DisableLogFile", true);
418  }
419  if (commLogArg.getValue())
420  {
421  g_ShouldLogCommIn = true;
422  g_ShouldLogCommOut = true;
423  }
424  else
425  {
426  g_ShouldLogCommIn = commLogInArg.getValue();
427  g_ShouldLogCommOut = commLogOutArg.getValue();
428  }
429  if (noBufArg.getValue())
430  {
431  setvbuf(stdout, nullptr, _IONBF, 0);
432  }
433  repo->SetReadOnly();
434 
435  // Set the service flag directly to cRoot:
436  if (runAsServiceArg.getValue())
437  {
438  cRoot::m_RunAsService = true;
439  }
440 
441  // Apply the CrashDump flags for platforms that support them:
442  #if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER) // 32-bit Windows app compiled in MSVC
443  if (crashDumpGlobals.getValue())
444  {
445  g_DumpFlags = static_cast<MINIDUMP_TYPE>(g_DumpFlags | MiniDumpWithDataSegs);
446  }
447  if (crashDumpFull.getValue())
448  {
449  g_DumpFlags = static_cast<MINIDUMP_TYPE>(g_DumpFlags | MiniDumpWithFullMemory);
450  }
451  #endif // 32-bit Windows app compiled in MSVC
452 
453  return repo;
454  }
455  catch (const TCLAP::ArgException & exc)
456  {
457  fmt::print("Error reading command line {0} for arg {1}", exc.error(), exc.argId());
458  return cpp14::make_unique<cMemorySettingsRepository>();
459  }
460 }
461 
462 
463 
464 
465 
467 // main:
468 
469 int main(int argc, char ** argv)
470 {
471  // Magic code to produce dump-files on Windows if the server crashes:
472  #if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER) // 32-bit Windows app compiled in MSVC
473  HINSTANCE hDbgHelp = LoadLibrary(L"DBGHELP.DLL");
474  g_WriteMiniDump = (pMiniDumpWriteDump)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");
475  if (g_WriteMiniDump != nullptr)
476  {
477  _snwprintf_s(g_DumpFileName, ARRAYCOUNT(g_DumpFileName), _TRUNCATE, L"crash_mcs_%x.dmp", GetCurrentProcessId());
478  SetUnhandledExceptionFilter(LastChanceExceptionFilter);
479  }
480  #endif // 32-bit Windows app compiled in MSVC
481  // End of dump-file magic
482 
483 
484  #if defined(_DEBUG) && defined(_MSC_VER)
485  _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
486 
487  // _X: The simple built-in CRT leak finder - simply break when allocating the Nth block ({N} is listed in the leak output)
488  // Only useful when the leak is in the same sequence all the time
489  // _CrtSetBreakAlloc(85950);
490 
491  #endif // _DEBUG && _MSC_VER
492 
493  #ifndef _DEBUG
494  std::signal(SIGSEGV, NonCtrlHandler);
495  std::signal(SIGTERM, NonCtrlHandler);
496  std::signal(SIGINT, NonCtrlHandler);
497  std::signal(SIGABRT, NonCtrlHandler);
498  #ifdef SIGABRT_COMPAT
499  std::signal(SIGABRT_COMPAT, NonCtrlHandler);
500  #endif // SIGABRT_COMPAT
501  #endif
502 
503 
504  #ifdef __unix__
505  std::signal(SIGPIPE, SIG_IGN);
506  #endif
507 
508  #ifdef _WIN32
509  if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
510  {
511  LOGERROR("Could not install the Windows CTRL handler!");
512  }
513  #endif
514 
515  // Make sure m_RunAsService is set correctly before checking it's value
516  ParseArguments(argc, argv);
517 
518  // Attempt to run as a service
519  if (cRoot::m_RunAsService)
520  {
521  #if defined(_WIN32) // Windows service.
522  SERVICE_TABLE_ENTRY ServiceTable[] =
523  {
524  { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)serviceMain },
525  { nullptr, nullptr }
526  };
527 
528  if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
529  {
530  LOGERROR("Attempted, but failed, service startup.");
531  return GetLastError();
532  }
533  #else // UNIX daemon.
534  pid_t pid = fork();
535 
536  // fork() returns a negative value on error.
537  if (pid < 0)
538  {
539  LOGERROR("Could not fork process.");
540  return EXIT_FAILURE;
541  }
542 
543  // Check if we are the parent or child process. Parent stops here.
544  if (pid > 0)
545  {
546  return EXIT_SUCCESS;
547  }
548 
549  // Child process now goes quiet, running in the background.
550  close(STDIN_FILENO);
551  close(STDOUT_FILENO);
552  close(STDERR_FILENO);
553 
554  while (!cRoot::m_TerminateEventRaised)
555  {
556  UniversalMain(ParseArguments(argc, argv));
557  }
558  #endif
559  }
560  else
561  {
562  while (!cRoot::m_TerminateEventRaised)
563  {
564  // Not running as a service, do normal startup
565  UniversalMain(ParseArguments(argc, argv));
566  }
567  }
568  return EXIT_SUCCESS;
569 }
Definition: FastNBT.h:131
bool g_ShouldLogCommOut
If set to true, the protocols will log each player&#39;s outgoing (S->C) communication to a per-connectio...
Definition: main.cpp:55
void Terminate(void)
Terminates all network-related threads.
static void NonCtrlHandler(int a_Signal)
Definition: main.cpp:83
void QueueExecuteConsoleCommand(const AString &a_Cmd, cCommandOutputCallback &a_Output)
Queues a console command for execution through the cServer class.
Definition: Root.cpp:693
void LOGERROR(const char *a_Format, fmt::ArgList a_ArgList)
Definition: Logger.cpp:183
void FLOGERROR(const char *a_Format, fmt::ArgList a_ArgList)
Definition: Logger.cpp:147
static void UniversalMain(std::unique_ptr< cSettingsRepositoryInterface > a_OverridesRepo)
Definition: main.cpp:225
int main(int argc, char **argv)
Definition: main.cpp:469
static bool m_TerminateEventRaised
If something has told the server to stop; checked periodically in cRoot.
Definition: Root.h:54
static bool m_RunAsService
If set to true, binary will attempt to run as a service on Windows.
Definition: Root.h:55
#define LOGD(...)
Definition: LoggerSimple.h:40
bool g_ShouldLogCommIn
If set to true, the protocols will log each player&#39;s incoming (C->S) communication to a per-connectio...
Definition: main.cpp:52
The root of the object hierarchy.
Definition: Root.h:48
std::string AString
Definition: StringUtils.h:13
static cNetworkSingleton & Get(void)
Returns the singleton instance of this class.
static cRoot * Get()
Definition: Root.h:51
void PrintStackTrace(void)
Prints the stacktrace for the current thread.
Definition: StackTrace.cpp:21
void Start(std::unique_ptr< cSettingsRepositoryInterface > a_OverridesRepo)
Definition: Root.cpp:134
static void InitiateMultithreading()
Definition: Logger.cpp:24
static std::unique_ptr< cMemorySettingsRepository > ParseArguments(int argc, char **argv)
Definition: main.cpp:377
#define ARRAYCOUNT(X)
Evaluates to the number of elements in an array (compile-time!)
Definition: Globals.h:290
void Initialise(void)
Initialises all network-related threads.