/* * Copyright (C) 2005,2006,2007 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** \file \ingroup mangosd */ #include "Master.h" #include "Network/SocketHandler.h" #include "Network/ListenSocket.h" #include "WorldSocket.h" #include "WorldSocketMgr.h" #include "WorldRunnable.h" #include "World.h" #include "Log.h" #include "Timer.h" #include #include "Policies/SingletonImp.h" #include "SystemConfig.h" #include "Config/ConfigEnv.h" #include "Database/DatabaseEnv.h" #include "CliRunnable.h" #include "RASocket.h" #include "ScriptCalls.h" #include "Network/TcpSocket.h" #include "Network/Utility.h" #include "Network/Parse.h" #include "Network/Socket.h" /// \todo Warning disabling not useful under VC++2005. Can somebody say on which compiler it is useful? #pragma warning(disable:4305) INSTANTIATE_SINGLETON_1( Master ); Master::Master() { } Master::~Master() { } /// Main function void Master::Run() { sLog.outString( "MaNGOS daemon %s", _FULLVERSION ); sLog.outString( " to stop.\n\n" ); sLog.outTitle( "MM MM MM MM MMMMM MMMM MMMMM"); sLog.outTitle( "MM MM MM MM MMM MMM MM MM MMM MMM"); sLog.outTitle( "MMM MMM MMM MM MMM MMM MM MM MMM"); sLog.outTitle( "MM M MM MMMM MM MMM MM MM MMM"); sLog.outTitle( "MM M MM MMMMM MM MMMM MMM MM MM MMM"); sLog.outTitle( "MM M MM M MMM MM MMM MMMMMMM MM MM MMM"); sLog.outTitle( "MM MM MMM MM MM MM MMM MM MM MMM"); sLog.outTitle( "MM MM MMMMMMM MM MM MMM MMM MM MM MMM MMM"); sLog.outTitle( "MM MM MM MMM MM MM MMMMMM MMMM MMMMM"); sLog.outTitle( " MM MMM http://www.mangosproject.org"); sLog.outTitle( " MMMMMM\n\n"); ///- Start the databases if (!_StartDB()) return; ///- Initialize the World sWorld.SetInitialWorldSettings(); ///- Launch the world listener socket port_t wsport = sWorld.getConfig(CONFIG_PORT_WORLD); SocketHandler h; ListenSocket worldListenSocket(h); if (worldListenSocket.Bind(wsport)) { clearOnlineAccounts(); sLog.outError("MaNGOS cannot bind to port %d", wsport); return; } h.Add(&worldListenSocket); ///- Catch termination signals _HookSignals(); ///- Launch WorldRunnable thread ZThread::Thread t(new WorldRunnable); t.setPriority ((ZThread::Priority )2); // set server online loginDatabase.PExecute("UPDATE `realmlist` SET `color` = 0, `population` = 0 WHERE `id` = '%d'",realmID); if (sConfig.GetBoolDefault("Console.Enable", 1)) { ///- Launch CliRunnable thread ZThread::Thread td1(new CliRunnable); } ///- Launch the RA listener socket ListenSocket RAListenSocket(h); if (sConfig.GetBoolDefault("Ra.Enable", 0)) { port_t raport = sConfig.GetIntDefault( "Ra.Port", 3443 ); std::string stringip = sConfig.GetStringDefault( "Ra.IP", "0.0.0.0" ); ipaddr_t raip; if(!Utility::u2ip(stringip, raip)) sLog.outError( "MaNGOS RA can not bind to ip %s", stringip.c_str()); else if (RAListenSocket.Bind(raip, raport)) sLog.outError( "MaNGOS RA can not bind to port %d on %s", raport, stringip.c_str()); else h.Add(&RAListenSocket); sLog.outString("Starting Remote access listner on port %d on %s", raport, stringip.c_str()); } ///- Handle affinity for multiple processors and process priority on Windows #ifdef WIN32 { HANDLE hProcess = GetCurrentProcess(); uint32 Aff = sConfig.GetIntDefault("UseProcessors", 0); if(Aff > 0) { DWORD appAff; DWORD sysAff; if(GetProcessAffinityMask(hProcess,&appAff,&sysAff)) { DWORD curAff = Aff & appAff; // remove non accessible processors if(!curAff ) { sLog.outError("Processors marked in UseProcessors bitmask (hex) %x not accessible for mangosd. Accessible processors bitmask (hex): %x",Aff,appAff); } else { if(SetProcessAffinityMask(hProcess,curAff)) sLog.outString("Using processors (bitmask, hex): %x", curAff); else sLog.outError("Can't set used processors (hex): %x",curAff); } } sLog.outString(); } uint32 Prio = sConfig.GetIntDefault("ProcessPriority", 0); if(Prio) { if(SetPriorityClass(hProcess,HIGH_PRIORITY_CLASS)) sLog.outString("mangosd process priority class set to HIGH"); else sLog.outError("ERROR: Can't set mangosd process priority class."); sLog.outString(); } } #endif uint32 realCurrTime, realPrevTime; realCurrTime = realPrevTime = getMSTime(); uint32 socketSelecttime = sWorld.getConfig(CONFIG_SOCKET_SELECTTIME); // maximum counter for next ping uint32 numLoops = (sConfig.GetIntDefault( "MaxPingTime", 30 ) * (MINUTE * 1000000 / socketSelecttime)); uint32 loopCounter = 0; ///- Wait for termination signal while (!World::m_stopEvent) { if (realPrevTime > realCurrTime) realPrevTime = 0; realCurrTime = getMSTime(); sWorldSocketMgr.Update( realCurrTime - realPrevTime ); realPrevTime = realCurrTime; h.Select(0, socketSelecttime); // ping if need if( (++loopCounter) == numLoops ) { loopCounter = 0; sLog.outDetail("Ping MySQL to keep connection alive"); delete WorldDatabase.Query("SELECT 1 FROM `command` LIMIT 1"); delete loginDatabase.Query("SELECT 1 FROM `realmlist` LIMIT 1"); delete CharacterDatabase.Query("SELECT 1 FROM `bugreport` LIMIT 1"); } } // set server offline loginDatabase.PExecute("UPDATE `realmlist` SET `color` = 2 WHERE `id` = '%d'",realmID); ///- Remove signal handling before leaving _UnhookSignals(); // when the main thread closes the singletons get unloaded // since worldrunnable uses them, it will crash if unloaded after master t.wait(); ///- Clean database before leaving clearOnlineAccounts(); ///- Wait for delay threads to end CharacterDatabase.HaltDelayThread(); WorldDatabase.HaltDelayThread(); loginDatabase.HaltDelayThread(); sLog.outString( "Halting process..." ); #ifdef WIN32 if (sConfig.GetBoolDefault("Console.Enable", 1)) { // this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API) //_exit(1); // send keyboard input to safely unblock the CLI thread INPUT_RECORD b[5]; HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); b[0].EventType = KEY_EVENT; b[0].Event.KeyEvent.bKeyDown = TRUE; b[0].Event.KeyEvent.uChar.AsciiChar = 'X'; b[0].Event.KeyEvent.wVirtualKeyCode = 'X'; b[0].Event.KeyEvent.wRepeatCount = 1; b[1].EventType = KEY_EVENT; b[1].Event.KeyEvent.bKeyDown = FALSE; b[1].Event.KeyEvent.uChar.AsciiChar = 'X'; b[1].Event.KeyEvent.wVirtualKeyCode = 'X'; b[1].Event.KeyEvent.wRepeatCount = 1; b[2].EventType = KEY_EVENT; b[2].Event.KeyEvent.bKeyDown = TRUE; b[2].Event.KeyEvent.dwControlKeyState = 0; b[2].Event.KeyEvent.uChar.AsciiChar = '\r'; b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; b[2].Event.KeyEvent.wRepeatCount = 1; b[2].Event.KeyEvent.wVirtualScanCode = 0x1c; b[3].EventType = KEY_EVENT; b[3].Event.KeyEvent.bKeyDown = FALSE; b[3].Event.KeyEvent.dwControlKeyState = 0; b[3].Event.KeyEvent.uChar.AsciiChar = '\r'; b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; b[3].Event.KeyEvent.wVirtualScanCode = 0x1c; b[3].Event.KeyEvent.wRepeatCount = 1; DWORD numb; BOOL ret = WriteConsoleInput(hStdIn, b, 4, &numb); } #endif // for some unknown reason, unloading scripts here and not in worldrunnable // fixes a memory leak related to detaching threads from the module UnloadScriptingModule(); return; } /// Initialize connection to the databases bool Master::_StartDB() { ///- Get world database info from configuration file std::string dbstring; if(!sConfig.GetString("WorldDatabaseInfo", &dbstring)) { sLog.outError("Database not specified in configuration file"); return false; } sLog.outString("World Database: %s", dbstring.c_str()); ///- Initialise the world database if(!WorldDatabase.Initialize(dbstring.c_str())) { sLog.outError("Cannot connect to world database %s",dbstring.c_str()); return false; } if(!sConfig.GetString("CharacterDatabaseInfo", &dbstring)) { sLog.outError("Character Database not specified in configuration file"); return false; } sLog.outString("Character Database: %s", dbstring.c_str()); ///- Initialise the Character database if(!CharacterDatabase.Initialize(dbstring.c_str())) { sLog.outError("Cannot connect to Character database %s",dbstring.c_str()); return false; } ///- Get login database info from configuration file if(!sConfig.GetString("LoginDatabaseInfo", &dbstring)) { sLog.outError("Login database not specified in configuration file"); return false; } ///- Initialise the login database sLog.outString("Login Database: %s", dbstring.c_str() ); if(!loginDatabase.Initialize(dbstring.c_str())) { sLog.outError("Cannot connect to login database %s",dbstring.c_str()); return false; } ///- Get the realm Id from the configuration file realmID = sConfig.GetIntDefault("RealmID", 0); if(!realmID) { sLog.outError("Realm ID not defined in configuration file"); return false; } sLog.outString("Realm running as realm ID %d", realmID); ///- Clean the database before starting clearOnlineAccounts(); QueryResult* result = WorldDatabase.Query("SELECT `version` FROM `db_version` LIMIT 1"); if(result) { Field* fields = result->Fetch(); sLog.outString("Using %s", fields[0].GetString()); delete result; } else sLog.outString("Using unknown world database."); return true; } /// Clear 'online' status for all accounts with characters in this realm void Master::clearOnlineAccounts() { // Cleanup online status for characters hosted at current realm /// \todo Only accounts with characters logged on *this* realm should have online status reset. Move the online column from 'account' to 'realmcharacters'? loginDatabase.PExecute( "UPDATE `account`,`realmcharacters` SET `account`.`online` = 0 " "WHERE `account`.`online` > 0 AND `account`.`id` = `realmcharacters`.`acctid` " " AND `realmcharacters`.`realmid` = '%d'",realmID); CharacterDatabase.Execute("UPDATE `character` SET `online` = 0"); } /// Handle termination signals /** Put the World::m_stopEvent to 'true' if a termination signal is caught **/ void Master::_OnSignal(int s) { switch (s) { case SIGINT: case SIGQUIT: case SIGTERM: case SIGABRT: #ifdef _WIN32 case SIGBREAK: #endif World::m_stopEvent = true; break; } signal(s, _OnSignal); } /// Define hook '_OnSignal' for all termination signals void Master::_HookSignals() { signal(SIGINT, _OnSignal); signal(SIGQUIT, _OnSignal); signal(SIGTERM, _OnSignal); signal(SIGABRT, _OnSignal); #ifdef _WIN32 signal(SIGBREAK, _OnSignal); #endif } /// Unhook the signals before leaving void Master::_UnhookSignals() { signal(SIGINT, 0); signal(SIGQUIT, 0); signal(SIGTERM, 0); signal(SIGABRT, 0); #ifdef _WIN32 signal(SIGBREAK, 0); #endif }