2012-06-14 13:06:06 +00:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
2012-09-23 22:09:57 +00:00
#include "Root.h"
2012-06-14 13:06:06 +00:00
2014-07-17 19:13:23 +02:00
#include <exception>
#include <csignal>
#include <stdlib.h>
2012-06-14 13:06:06 +00:00
2013-05-25 14:59:41 +00:00
#ifdef _MSC_VER
2012-06-14 13:06:06 +00:00
#include <dbghelp.h>
2013-05-25 14:59:41 +00:00
#endif // _MSC_VER
2012-06-14 13:06:06 +00:00
2015-01-26 14:46:20 +01:00
#include "OSSupport/NetworkSingleton.h"
2015-05-04 09:07:03 +01:00
#include "BuildInfo.h"
2014-07-19 15:44:19 -07:00
2013-12-22 20:03:58 +00:00
2012-08-15 21:24:11 +00:00
2012-06-14 13:06:06 +00:00
2015-01-26 14:46:20 +01:00
/** If something has told the server to stop; checked periodically in cRoot */
bool cRoot :: m_TerminateEventRaised = false ;
/** Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console. */
static bool g_ServerTerminated = false ;
2012-06-14 13:06:06 +00:00
2014-01-26 17:54:18 +01:00
/** 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 ;
2014-01-24 23:03:48 +01:00
2015-03-31 14:50:03 +01:00
/** If set to true, binary will attempt to run as a service on Windows */
bool cRoot :: m_RunAsService = false ;
2014-01-24 23:03:48 +01:00
2015-04-05 17:07:10 +02:00
2015-03-31 14:50:03 +01:00
#if defined(_WIN32)
2015-04-05 17:07:10 +02:00
SERVICE_STATUS_HANDLE g_StatusHandle = NULL ;
HANDLE g_ServiceThread = INVALID_HANDLE_VALUE ;
#define SERVICE_NAME "MCServerService"
2015-03-31 14:50:03 +01:00
#endif
2014-01-24 23:03:48 +01:00
2015-04-05 17:07:10 +02:00
/** If defined, a thorough leak finder will be used (debug MSVC only); leaks will be output to the Output window */
2014-02-20 20:11:17 +01:00
// _X 2014_02_20: Disabled for canon repo, it makes the debug version too slow in MSVC2013
// and we haven't had a memory leak for over a year anyway.
// #define ENABLE_LEAK_FINDER
2012-06-14 13:06:06 +00:00
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
#pragma warning(push)
#pragma warning(disable:4100)
#include "LeakFinder.h"
#pragma warning(pop)
#endif
2014-07-17 16:33:09 +02:00
void NonCtrlHandler ( int a_Signal )
2012-06-14 13:06:06 +00:00
{
2013-12-22 20:03:58 +00:00
LOGD ( "Terminate event raised from std::signal" );
2014-07-19 15:44:19 -07:00
cRoot :: m_TerminateEventRaised = true ;
2012-06-14 13:06:06 +00:00
2013-12-22 20:03:58 +00:00
switch ( a_Signal )
{
case SIGSEGV :
{
std :: signal ( SIGSEGV , SIG_DFL );
2014-01-16 22:30:57 +00:00
LOGERROR ( " D: | MCServer has encountered an error and needs to close" );
LOGERROR ( "Details | SIGSEGV: Segmentation fault" );
2015-05-04 09:07:03 +01:00
#ifdef BUILD_ID
LOGERROR ( "MCServer " BUILD_SERIES_NAME " build id: " BUILD_ID );
LOGERROR ( "from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME );
#endif
2014-11-29 23:06:10 +01:00
PrintStackTrace ();
2014-06-01 17:23:02 +01:00
abort ();
2013-12-22 20:03:58 +00:00
}
2014-01-16 22:30:57 +00:00
case SIGABRT :
2014-01-17 11:13:35 +01:00
#ifdef SIGABRT_COMPAT
2014-01-16 22:30:57 +00:00
case SIGABRT_COMPAT :
2014-01-17 11:13:35 +01:00
#endif
2014-01-16 22:30:57 +00:00
{
std :: signal ( a_Signal , SIG_DFL );
LOGERROR ( " D: | MCServer has encountered an error and needs to close" );
LOGERROR ( "Details | SIGABRT: Server self-terminated due to an internal fault" );
2015-05-04 09:07:03 +01:00
#ifdef BUILD_ID
LOGERROR ( "MCServer " BUILD_SERIES_NAME " build id: " BUILD_ID );
LOGERROR ( "from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME );
#endif
2014-11-29 23:06:10 +01:00
PrintStackTrace ();
2014-06-01 17:23:02 +01:00
abort ();
2014-01-16 22:30:57 +00:00
}
2014-01-26 16:15:05 +00:00
case SIGINT :
2014-01-07 21:23:26 +00:00
case SIGTERM :
{
2014-07-17 19:13:23 +02:00
std :: signal ( a_Signal , SIG_IGN ); // Server is shutting down, wait for it...
2014-01-07 21:23:26 +00:00
break ;
}
2013-12-22 20:03:58 +00:00
default : break ;
}
2012-06-14 13:06:06 +00:00
}
2013-05-25 14:59:41 +00:00
#if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER)
2014-07-17 22:15:34 +02:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 13:06:06 +00:00
// 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
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
);
pMiniDumpWriteDump g_WriteMiniDump ; // The function in dbghlp DLL that creates dump files
char g_DumpFileName [ MAX_PATH ]; // Filename of the dump file; hes to be created before the dump handler kicks in
char g_ExceptionStack [ 128 * 1024 ]; // Substitute stack, just in case the handler kicks in because of "insufficient stack space"
MINIDUMP_TYPE g_DumpFlags = MiniDumpNormal ; // By default dump only the stack and some helpers
/** 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
*/
LONG WINAPI LastChanceExceptionFilter ( __in struct _EXCEPTION_POINTERS * a_ExceptionInfo )
{
char * newStack = & g_ExceptionStack [ sizeof ( g_ExceptionStack )];
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:
2014-10-20 21:55:07 +01:00
HANDLE dumpFile = CreateFile ( g_DumpFileName , GENERIC_WRITE , 0 , nullptr , CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , nullptr );
g_WriteMiniDump ( GetCurrentProcess (), GetCurrentProcessId (), dumpFile , g_DumpFlags , ( a_ExceptionInfo ) ? & ExcInformation : nullptr , nullptr , nullptr );
2012-06-14 13:06:06 +00:00
CloseHandle ( dumpFile );
2014-11-29 23:06:10 +01:00
// Print the stack trace for the basic debugging:
PrintStackTrace ();
2012-06-14 13:06:06 +00:00
// Revert to old stack:
_asm
{
mov esp , oldStack
}
return 0 ;
}
#endif // _WIN32 && !_WIN64
2015-04-05 17:07:10 +02:00
2013-12-22 20:03:58 +00:00
#ifdef _WIN32
// Handle CTRL events in windows, including console window close
BOOL CtrlHandler ( DWORD fdwCtrlType )
{
2014-07-19 15:44:19 -07:00
cRoot :: m_TerminateEventRaised = true ;
2013-12-22 20:03:58 +00:00
LOGD ( "Terminate event raised from the Windows CtrlHandler" );
2014-10-21 13:41:08 +01:00
while ( ! g_ServerTerminated )
2013-12-22 20:03:58 +00:00
{
2014-10-21 13:41:08 +01:00
std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 50 )); // Delay as much as possible to try to get the server to shut down cleanly
2013-12-22 20:03:58 +00:00
}
return TRUE ;
}
#endif
2015-04-05 17:07:10 +02:00
2015-03-31 14:50:03 +01:00
////////////////////////////////////////////////////////////////////////////////
// universalMain - Main startup logic for both standard running and as a service
void universalMain ()
{
#ifdef _WIN32
if ( ! SetConsoleCtrlHandler (( PHANDLER_ROUTINE ) CtrlHandler , TRUE ))
{
LOGERROR ( "Could not install the Windows CTRL handler!" );
}
#endif
// Initialize logging subsystem:
cLogger :: InitiateMultithreading ();
// Initialize LibEvent:
cNetworkSingleton :: Get ();
#if !defined(ANDROID_NDK)
try
#endif
{
2015-04-05 17:07:10 +02:00
cRoot Root ;
2015-03-31 14:50:03 +01:00
Root . Start ();
}
#if !defined(ANDROID_NDK)
catch ( std :: exception & e )
{
LOGERROR ( "Standard exception: %s" , e . what ());
}
catch (...)
{
LOGERROR ( "Unknown exception!" );
}
#endif
g_ServerTerminated = true ;
// Shutdown all of LibEvent:
cNetworkSingleton :: Get (). Terminate ();
}
#if defined(_WIN32)
////////////////////////////////////////////////////////////////////////////////
// serviceWorkerThread: Keep the service alive
DWORD WINAPI serviceWorkerThread ( LPVOID lpParam )
{
UNREFERENCED_PARAMETER ( lpParam );
// Do the normal startup
universalMain ();
return ERROR_SUCCESS ;
}
////////////////////////////////////////////////////////////////////////////////
// serviceSetState: Set the internal status of the service
void serviceSetState ( DWORD acceptedControls , DWORD newState , DWORD exitCode )
{
SERVICE_STATUS serviceStatus ;
ZeroMemory ( & serviceStatus , sizeof ( SERVICE_STATUS ));
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 " );
}
}
////////////////////////////////////////////////////////////////////////////////
// serviceCtrlHandler: Handle stop events from the Service Control Manager
void WINAPI serviceCtrlHandler ( DWORD CtrlCode )
{
switch ( CtrlCode )
{
case SERVICE_CONTROL_STOP :
{
2015-04-05 17:07:10 +02:00
cRoot :: m_ShouldStop = true ;
2015-03-31 14:50:03 +01:00
serviceSetState ( 0 , SERVICE_STOP_PENDING , 0 );
break ;
}
default :
{
break ;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// serviceMain: Startup logic for running as a service
void WINAPI serviceMain ( DWORD argc , TCHAR * argv [])
{
#if defined(_DEBUG) && defined(DEBUG_SERVICE_STARTUP)
Sleep ( 10000 );
#endif
char applicationFilename [ MAX_PATH ];
char applicationDirectory [ MAX_PATH ];
2015-04-06 22:01:25 +02:00
GetModuleFileName ( NULL , applicationFilename , sizeof ( applicationFilename )); // This binary's file path.
2015-03-31 14:50:03 +01:00
2015-04-06 22:01:25 +02:00
// Strip off the filename, keep only the path:
strncpy_s ( applicationDirectory , sizeof ( applicationDirectory ), applicationFilename , ( strrchr ( applicationFilename , '\\' ) - applicationFilename ));
2015-03-31 14:50:03 +01:00
applicationDirectory [ strlen ( applicationDirectory )] = '\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.
SetCurrentDirectory ( applicationDirectory );
g_StatusHandle = RegisterServiceCtrlHandler ( SERVICE_NAME , serviceCtrlHandler );
if ( g_StatusHandle == NULL )
{
2015-04-06 22:01:25 +02:00
OutputDebugStringA ( "RegisterServiceCtrlHandler() failed \n " );
2015-03-31 14:50:03 +01:00
serviceSetState ( 0 , SERVICE_STOPPED , GetLastError ());
return ;
}
serviceSetState ( SERVICE_ACCEPT_STOP , SERVICE_RUNNING , 0 );
g_ServiceThread = CreateThread ( NULL , 0 , serviceWorkerThread , NULL , 0 , NULL );
if ( g_ServiceThread == NULL )
{
2015-04-06 22:01:25 +02:00
OutputDebugStringA ( "CreateThread() failed \n " );
2015-03-31 14:50:03 +01:00
serviceSetState ( 0 , SERVICE_STOPPED , GetLastError ());
return ;
}
WaitForSingleObject ( g_ServiceThread , INFINITE ); // Wait here for a stop signal.
CloseHandle ( g_ServiceThread );
serviceSetState ( 0 , SERVICE_STOPPED , 0 );
}
#endif
2013-12-22 20:03:58 +00:00
2014-07-17 22:15:34 +02:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 13:06:06 +00:00
// main:
2014-07-21 15:19:48 +02:00
int main ( int argc , char ** argv )
2012-06-14 13:06:06 +00:00
{
2013-12-22 20:03:58 +00:00
UNUSED ( argc );
UNUSED ( argv );
2012-06-14 13:06:06 +00:00
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
InitLeakFinder ();
#endif
2014-11-26 11:00:21 +01:00
2012-06-14 13:06:06 +00:00
// Magic code to produce dump-files on Windows if the server crashes:
2013-05-25 14:59:41 +00:00
#if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER)
2012-06-14 13:06:06 +00:00
HINSTANCE hDbgHelp = LoadLibrary ( "DBGHELP.DLL" );
g_WriteMiniDump = ( pMiniDumpWriteDump ) GetProcAddress ( hDbgHelp , "MiniDumpWriteDump" );
2014-10-20 21:55:07 +01:00
if ( g_WriteMiniDump != nullptr )
2012-06-14 13:06:06 +00:00
{
_snprintf_s ( g_DumpFileName , ARRAYCOUNT ( g_DumpFileName ), _TRUNCATE , "crash_mcs_%x.dmp" , GetCurrentProcessId ());
SetUnhandledExceptionFilter ( LastChanceExceptionFilter );
// Parse arguments for minidump flags:
for ( int i = 0 ; i < argc ; i ++ )
{
2012-07-16 19:20:37 +00:00
if ( _stricmp ( argv [ i ], "/cdg" ) == 0 )
2012-06-14 13:06:06 +00:00
{
// Add globals to the dump
g_DumpFlags = ( MINIDUMP_TYPE )( g_DumpFlags | MiniDumpWithDataSegs );
}
2012-07-16 19:20:37 +00:00
else if ( _stricmp ( argv [ i ], "/cdf" ) == 0 )
2012-06-14 13:06:06 +00:00
{
// Add full memory to the dump (HUUUGE file)
g_DumpFlags = ( MINIDUMP_TYPE )( g_DumpFlags | MiniDumpWithFullMemory );
}
} // for i - argv[]
}
#endif // _WIN32 && !_WIN64
// End of dump-file magic
2013-12-22 20:03:58 +00:00
2012-06-14 13:06:06 +00:00
#if defined(_DEBUG) && defined(_MSC_VER)
2014-07-21 15:19:48 +02:00
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
2012-06-14 13:06:06 +00:00
// _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
#ifndef _DEBUG
2013-12-22 20:03:58 +00:00
std :: signal ( SIGSEGV , NonCtrlHandler );
std :: signal ( SIGTERM , NonCtrlHandler );
2014-01-24 23:03:48 +01:00
std :: signal ( SIGINT , NonCtrlHandler );
2014-01-26 16:15:05 +00:00
std :: signal ( SIGABRT , NonCtrlHandler );
#ifdef SIGABRT_COMPAT
std :: signal ( SIGABRT_COMPAT , NonCtrlHandler );
2014-07-17 19:13:23 +02:00
#endif // SIGABRT_COMPAT
2012-06-14 13:06:06 +00:00
#endif
// DEBUG: test the dumpfile creation:
// *((int *)0) = 0;
2014-01-24 23:03:48 +01:00
// Check if comm logging is to be enabled:
for ( int i = 0 ; i < argc ; i ++ )
{
2014-02-27 15:17:42 +01:00
AString Arg ( argv [ i ]);
2014-01-24 23:03:48 +01:00
if (
2014-02-27 15:17:42 +01:00
( NoCaseCompare ( Arg , "/commlog" ) == 0 ) ||
( NoCaseCompare ( Arg , "/logcomm" ) == 0 )
2014-01-24 23:03:48 +01:00
)
{
2014-01-26 17:54:18 +01:00
g_ShouldLogCommIn = true ;
g_ShouldLogCommOut = true ;
}
2014-02-27 15:17:42 +01:00
else if (
( NoCaseCompare ( Arg , "/commlogin" ) == 0 ) ||
( NoCaseCompare ( Arg , "/comminlog" ) == 0 ) ||
( NoCaseCompare ( Arg , "/logcommin" ) == 0 )
2014-01-26 17:54:18 +01:00
)
{
g_ShouldLogCommIn = true ;
}
2014-02-27 15:17:42 +01:00
else if (
( NoCaseCompare ( Arg , "/commlogout" ) == 0 ) ||
( NoCaseCompare ( Arg , "/commoutlog" ) == 0 ) ||
( NoCaseCompare ( Arg , "/logcommout" ) == 0 )
2014-01-26 17:54:18 +01:00
)
{
g_ShouldLogCommOut = true ;
2014-01-24 23:03:48 +01:00
}
2014-02-27 15:17:42 +01:00
else if ( NoCaseCompare ( Arg , "nooutbuf" ) == 0 )
{
2014-10-20 21:55:07 +01:00
setvbuf ( stdout , nullptr , _IONBF , 0 );
2014-02-27 15:17:42 +01:00
}
2015-03-31 14:50:03 +01:00
else if ( NoCaseCompare ( Arg , "/service" ) == 0 )
{
cRoot :: m_RunAsService = true ;
}
2014-02-27 15:17:42 +01:00
} // for i - argv[]
2015-03-31 14:50:03 +01:00
#if defined(_WIN32)
// Attempt to run as a service
if ( cRoot :: m_RunAsService )
2012-06-14 13:06:06 +00:00
{
2015-03-31 14:50:03 +01:00
SERVICE_TABLE_ENTRY ServiceTable [] =
{
{ SERVICE_NAME , ( LPSERVICE_MAIN_FUNCTION ) serviceMain },
{ NULL , NULL }
};
if ( StartServiceCtrlDispatcher ( ServiceTable ) == FALSE )
{
LOGERROR ( "Attempted, but failed, service startup." );
return GetLastError ();
}
2012-06-14 13:06:06 +00:00
}
2015-03-31 14:50:03 +01:00
else
#endif
2012-06-14 13:06:06 +00:00
{
2015-03-31 14:50:03 +01:00
// Not running as a service, do normal startup
universalMain ();
2012-06-14 13:06:06 +00:00
}
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
DeinitLeakFinder ();
#endif
2013-12-22 20:03:58 +00:00
return EXIT_SUCCESS ;
2012-06-14 13:06:06 +00:00
}