#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "BuildInfo.h"
#include "Logger.h"
#include "MemorySettingsRepository.h"
#include "OSSupport/NetworkSingleton.h"
#include "OSSupport/MiniDumpWriter.h"
#include "OSSupport/StartAsService.h"
#include "Root.h"
#include "tclap/CmdLine.h"
#include <csignal>
#include <cstdlib>
/** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile. */
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. */
bool g_RunAsService;
/** Global that registers itself as a last chance exception handler to write a minidump on crash. */
MiniDumpWriter g_MiniDumpWriter;
// Because SIG_DFL or SIG_IGN could be NULL instead of nullptr, we need to disable the Clang warning here
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif // __clang__
static void NonCtrlHandler(int a_Signal)
{
LOGD("Terminate event raised from std::signal");
switch (a_Signal)
{
case SIGSEGV:
{
PrintStackTrace();
LOGERROR("Failure report: \n\n"
" :( | Cuberite has encountered an error and needs to close\n"
" | SIGSEGV: Segmentation fault\n"
" |\n"
#ifdef BUILD_ID
" | Cuberite " BUILD_SERIES_NAME " (id: " BUILD_ID ")\n"
" | from commit " BUILD_COMMIT_ID "\n");
#endif
std::signal(SIGSEGV, SIG_DFL);
return;
}
case SIGABRT:
#ifdef SIGABRT_COMPAT
case SIGABRT_COMPAT:
#endif
{
PrintStackTrace();
LOGERROR("Failure report: \n\n"
" :( | Cuberite has encountered an error and needs to close\n"
" | SIGABRT: Server self-terminated due to an internal fault\n"
" |\n"
#ifdef BUILD_ID
" | Cuberite " BUILD_SERIES_NAME " (id: " BUILD_ID ")\n"
" | from commit " BUILD_COMMIT_ID "\n");
#endif
std::signal(SIGSEGV, SIG_DFL);
return;
}
case SIGINT:
case SIGTERM:
{
// Server is shutting down, wait for it...
cRoot::Stop();
return;
}
#ifdef SIGPIPE
case SIGPIPE:
{
// Ignore (PR #2487)
return;
}
#endif
}
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__
#ifdef _WIN32
// Handle CTRL events in windows, including console window close
static BOOL CtrlHandler(DWORD fdwCtrlType)
{
cRoot::Stop();
LOGD("Terminate event raised from the Windows CtrlHandler");
// Delay as much as possible to try to get the server to shut down cleanly - 10 seconds given by Windows
std::this_thread::sleep_for(std::chrono::seconds(10));
// Returning from main() automatically aborts this handler thread
return TRUE;
}
#endif
////////////////////////////////////////////////////////////////////////////////
// ParseArguments - Read the startup arguments and store into a settings object
static cMemorySettingsRepository ParseArguments(int argc, char ** argv)
{
// Parse the comand line args:
TCLAP::CmdLine cmd("Cuberite");
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);
TCLAP::ValueArg<AString> confArg ("c", "config-file", "Config file to use", false, "settings.ini", "string", cmd);
TCLAP::MultiArg<int> portsArg ("p", "port", "The port number the server should listen to", false, "port", cmd);
TCLAP::SwitchArg commLogArg ("", "log-comm", "Log server client communications to file", cmd);
TCLAP::SwitchArg commLogInArg ("", "log-comm-in", "Log inbound server client communications to file", cmd);
TCLAP::SwitchArg commLogOutArg ("", "log-comm-out", "Log outbound server client communications to file", cmd);
TCLAP::SwitchArg crashDumpFull ("", "crash-dump-full", "Crashdumps created by the server will contain full server memory", cmd);
TCLAP::SwitchArg crashDumpGlobals("", "crash-dump-globals", "Crashdumps created by the server will contain the global variables' values", cmd);
TCLAP::SwitchArg noBufArg ("", "no-output-buffering", "Disable output buffering", cmd);
TCLAP::SwitchArg noFileLogArg ("", "no-log-file", "Disable logging to file", cmd);
TCLAP::SwitchArg runAsServiceArg ("d", "service", "Run as a service on Windows, or daemon on UNIX like systems", cmd);
cmd.parse(argc, argv);
// Copy the parsed args' values into a settings repository:
cMemorySettingsRepository repo;
if (confArg.isSet())
{
AString conf_file = confArg.getValue();
repo.AddValue("Server", "ConfigFile", conf_file);
}
if (slotsArg.isSet())
{
int slots = slotsArg.getValue();
repo.AddValue("Server", "MaxPlayers", static_cast<Int64>(slots));
}
if (portsArg.isSet())
{
for (auto port: portsArg.getValue())
{
repo.AddValue("Server", "Ports", std::to_string(port));
}
}
if (noFileLogArg.getValue())
{
repo.AddValue("Server", "DisableLogFile", true);
}
if (commLogArg.getValue())
{
g_ShouldLogCommIn = true;
g_ShouldLogCommOut = true;
}
else
{
g_ShouldLogCommIn = commLogInArg.getValue();
g_ShouldLogCommOut = commLogOutArg.getValue();
}
if (noBufArg.getValue())
{
setvbuf(stdout, nullptr, _IONBF, 0);
}
repo.SetReadOnly();
if (runAsServiceArg.getValue())
{
g_RunAsService = true;
}
// Apply the CrashDump flags for platforms that support them:
if (crashDumpGlobals.getValue())
{
g_MiniDumpWriter.AddDumpFlags(MiniDumpFlags::WithDataSegments);
}
if (crashDumpFull.getValue())
{
g_MiniDumpWriter.AddDumpFlags(MiniDumpFlags::WithFullMemory);
}
return repo;
}
////////////////////////////////////////////////////////////////////////////////
// UniversalMain - Main startup logic for both standard running and as a service
static int UniversalMain(int argc, char * argv[], bool RunningAsService)
{
// Initialize logging subsystem:
cLogger::InitiateMultithreading();
struct NetworkRAII
{
NetworkRAII()
{
// Initialize LibEvent:
cNetworkSingleton::Get().Initialise();
}
~NetworkRAII()
{
// Shutdown all of LibEvent:
cNetworkSingleton::Get().Terminate();
}
};
try
{
// Make sure g_RunAsService is set correctly before checking it's value
auto Settings = ParseArguments(argc, argv);
// Attempt to run as a service
if (!RunningAsService && g_RunAsService)
{
// This will either fork or call UniversalMain again:
if (cStartAsService::MakeIntoService<&UniversalMain>())
{
return EXIT_SUCCESS;
}
}
while (true)
{
NetworkRAII LibEvent;
cRoot Root;
if (!Root.Run(Settings))
{
break;
}
}
return EXIT_SUCCESS;
}
catch (const fmt::format_error & Oops)
{
std::cerr << "Formatting exception: " << Oops.what() << '\n';
}
catch (const TCLAP::ArgException & Oops)
{
std::cerr << fmt::sprintf("Error reading command line {} for argument {}\n", Oops.error(), Oops.argId());
}
catch (const std::exception & Oops)
{
std::cerr << "Standard exception: " << Oops.what() << '\n';
}
catch (...)
{
std::cerr << "Unknown exception!\n";
}
return EXIT_FAILURE;
}
int main(int argc, char ** argv)
{
#if !defined(NDEBUG) && defined(_MSC_VER)
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
// _X: The simple built-in CRT leak finder - simply break when allocating the Nth block ({N} is listed in the leak output)
// Only useful when the leak is in the same sequence all the time
// _CrtSetBreakAlloc(85950);
#endif // _DEBUG && _MSC_VER
std::signal(SIGSEGV, NonCtrlHandler);
std::signal(SIGTERM, NonCtrlHandler);
std::signal(SIGINT, NonCtrlHandler);
std::signal(SIGABRT, NonCtrlHandler);
#ifdef SIGABRT_COMPAT
std::signal(SIGABRT_COMPAT, NonCtrlHandler);
#endif
#ifdef SIGPIPE
std::signal(SIGPIPE, SIG_IGN);
#endif
#ifdef _WIN32
VERIFY(SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(CtrlHandler), TRUE) == TRUE);
#endif
return UniversalMain(argc, argv, false);
}