EQ2EMu/EQ2/source/common/database.cpp
Image 3ec034e172 db ini checks
Fixes #128

Also addresses telling the correct parameter that is missing from the ini
2020-06-06 07:58:17 -04:00

539 lines
No EOL
13 KiB
C++

/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator 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 3 of the License, or
(at your option) any later version.
EQ2Emulator 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 EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../common/debug.h"
#include <iostream>
using namespace std;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errmsg.h>
//#include <mysqld_error.h>
#include <limits.h>
#include <ctype.h>
#include <assert.h>
#include <map>
// Disgrace: for windows compile
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
#define snprintf _snprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else
#include "unix.h"
#include <netinet/in.h>
#endif
#include "database.h"
#include "EQStream.h"
#include "packet_functions.h"
#include "emu_opcodes.h"
#ifdef WORLD
#include "../WorldServer/WorldDatabase.h"
extern WorldDatabase database;
#endif
#ifdef LOGIN
#include "../LoginServer/LoginDatabase.h"
extern LoginDatabase database;
#endif
#ifdef PARSER
#include "../PacketParser/ParserDatabase.h"
extern ParserDatabase database;
#endif
#ifdef PATCHER
#include "../PatchServer/PatcherDatabase.h"
extern PatcherDatabase database;
#endif
#include "../common/EQEMuError.h"
#include "../common/packet_dump.h"
#include "../common/Log.h"
#ifdef WORLD
ThreadReturnType DBAsyncQueries(void* str)
{
// allow some buffer for multiple queries to collect
Sleep(10);
DBStruct* data = (DBStruct*)str;
database.RunAsyncQueries(data->queryid);
delete data;
THREAD_RETURN(NULL);
}
#endif
Database::Database()
{
InitVars();
}
bool Database::Init(bool silentLoad) {
char host[200], user[200], passwd[200], database[200];
int32 port=0;
bool compression = false;
bool items[6] = {false, false, false, false, false, false};
const char* exampleIni[] = { "[Database]", "host = localhost", "user = root", "password = pass", "database = dbname", "### --- Assure each parameter is on a new line!" };
if(!ReadDBINI(host, user, passwd, database, port, compression, items)) {
//exit(1);
return false;
}
if (!items[0] || !items[1] || !items[2] || !items[3])
{
LogWrite(DATABASE__ERROR, 0, "DB", "Database file %s is incomplete.", DB_INI_FILE);
int i;
for (i = 0; i < 4; i++)
{
if ( !items[i] )
LogWrite(DATABASE__ERROR, 0, "DB", "Could not find parameter %s", exampleIni[i+1]); // offset by 1 because the [Database] entry
}
LogWrite(DATABASE__ERROR, 0, "DB", "Example File:");
int length = sizeof exampleIni / sizeof exampleIni[0];
for(i=0;i<length;i++)
LogWrite(DATABASE__ERROR, 0, "DB", "%s", exampleIni[i]);
//exit (1);
return false;
}
int32 errnum = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
if (!Open(host, user, passwd, database,port, &errnum, errbuf))
{
LogWrite(DATABASE__ERROR, 0, "DB", "Failed to connect to database: Error: %s", errbuf);
HandleMysqlError(errnum);
//exit(1);
return false;
}
else
{
if (!silentLoad)
LogWrite(DATABASE__INFO, 0, "DB", "Using database '%s' at %s", database, host);
}
return true;
}
map<int16, int16> Database::GetVersions(){
map<int16, int16> opcodes;
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select distinct version_range1, version_range2 from opcodes");
while(result && (row = mysql_fetch_row(result))){
if(row[0] && row[1])
opcodes[atoi(row[0])] = atoi(row[1]);
}
return opcodes;
}
map<string, uint16> Database::GetOpcodes(int16 version){
map<string, uint16> opcodes;
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select name, opcode from opcodes where %i between version_range1 and version_range2 order by version_range1, id", version);
while(result && (row = mysql_fetch_row(result))){
opcodes[row[0]] = atoi(row[1]);
}
return opcodes;
}
void Database::HandleMysqlError(int32 errnum) {
switch(errnum) {
case 0:
break;
case 1045: // Access Denied
case 2001: {
AddEQEMuError(EQEMuError_Mysql_1405, true);
break;
}
case 2003: { // Unable to connect
AddEQEMuError(EQEMuError_Mysql_2003, true);
break;
}
case 2005: { // Unable to connect
AddEQEMuError(EQEMuError_Mysql_2005, true);
break;
}
case 2007: { // Unable to connect
AddEQEMuError(EQEMuError_Mysql_2007, true);
break;
}
}
}
void Database::InitVars() {
}
Database::~Database()
{
#ifdef WORLD
DBQueryMutex.writelock(__FUNCTION__, __LINE__);
activeQuerySessions.clear();
DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__);
DBAsyncMutex.writelock();
continueAsync = false;
map<int32, deque<Query*>>::iterator itr;
for (itr = asyncQueries.begin(); itr != asyncQueries.end(); itr++)
{
asyncQueriesMutex[itr->first]->writelock();
deque<Query*> queries = itr->second;
while (queries.size() > 0)
{
Query* cur = queries.front();
queries.pop_front();
safe_delete(cur);
}
asyncQueriesMutex[itr->first]->releasewritelock();
Mutex* mutex = asyncQueriesMutex[itr->first];
asyncQueriesMutex.erase(itr->first);
safe_delete(mutex);
}
asyncQueries.clear();
asyncQueriesMutex.clear();
DBAsyncMutex.releasewritelock();
PurgeDBInstances();
#endif
}
#ifdef WORLD
void Query::AddQueryAsync(int32 queryID, Database* db, QUERY_TYPE type, const char* format, ...) {
in_type = type;
va_list args;
va_start(args, format);
#ifdef WIN32
char* buffer;
int buf_len = _vscprintf(format, args) + 1;
buffer = new char[buf_len];
vsprintf(buffer, format, args);
#else
char* buffer;
int buf_len;
va_list argcopy;
va_copy(argcopy, args);
buf_len = vsnprintf(NULL, 0, format, argcopy) + 1;
va_end(argcopy);
buffer = new char[buf_len];
vsnprintf(buffer, buf_len, format, args);
#endif
va_end(args);
query = string(buffer);
Query* asyncQuery = new Query(this, queryID);
safe_delete_array(buffer);
db->AddAsyncQuery(asyncQuery);
}
void Query::RunQueryAsync(Database* db) {
db->RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry);
}
#endif
MYSQL_RES* Query::RunQuery2(QUERY_TYPE type, const char* format, ...){
va_list args;
va_start( args, format );
#ifdef WIN32
char * buffer;
int buf_len = _vscprintf( format, args ) + 1;
buffer = new char[buf_len];
vsprintf( buffer, format, args );
#else
char* buffer;
int buf_len;
va_list argcopy;
va_copy(argcopy, args);
buf_len = vsnprintf(NULL, 0, format, argcopy) + 1;
va_end(argcopy);
buffer = new char[buf_len];
vsnprintf(buffer, buf_len, format, args);
#endif
va_end(args);
query = string(buffer);
safe_delete_array( buffer );
return RunQuery2(query.c_str(), type);
}
MYSQL_RES* Query::RunQuery2(string in_query, QUERY_TYPE type){
switch(type){
case Q_SELECT:
break;
case Q_DBMS:
case Q_REPLACE:
case Q_DELETE:
case Q_UPDATE:
safe_delete(affected_rows);
affected_rows = new int32;
break;
case Q_INSERT:
safe_delete(last_insert_id);
last_insert_id = new int32;
}
if(result){
if(!multiple_results)
multiple_results = new vector<MYSQL_RES*>();
multiple_results->push_back(result);
}
query = in_query;
#if defined WORLD && defined _DEBUG
if (type == Q_UPDATE || type == Q_INSERT || type == Q_DELETE || type == Q_REPLACE)
{
char* filteredTables[] = { " characters", " character_", " `character_", " statistics", " variables", " char_colors", " `guild", " bugs" };
bool match = false;
for (int i = 0; i < sizeof(filteredTables) / sizeof(filteredTables[0]); i++)
{
if (query.find(filteredTables[i]) != std::string::npos) {
match = true;
break;
}
}
try
{
if (!match)
{
FILE* pFile;
pFile = fopen("sql_updates.sql", "a+");
fwrite(query.c_str(), 1, query.length(), pFile);
fwrite(";", sizeof(char), 1, pFile);
fwrite("\n", sizeof(char), 1, pFile);
fclose(pFile);
}
}
catch (...) {}
}
#endif
database.RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry);
return result;
}
#ifdef WORLD
void Database::RunAsyncQueries(int32 queryid)
{
Database* asyncdb = FindFreeInstance();
DBAsyncMutex.writelock();
map<int32, deque<Query*>>::iterator itr = asyncQueries.find(queryid);
if (itr == asyncQueries.end())
{
DBAsyncMutex.releasewritelock();
return;
}
asyncQueriesMutex[queryid]->writelock();
deque<Query*> queries;
while (itr->second.size())
{
Query* cur = itr->second.front();
queries.push_back(cur);
itr->second.pop_front();
}
itr->second.clear();
asyncQueries.erase(itr);
DBAsyncMutex.releasewritelock();
asyncQueriesMutex[queryid]->releasewritelock();
int32 count = 0;
while (queries.size() > 0)
{
Query* cur = queries.front();
cur->RunQueryAsync(asyncdb);
this->RemoveActiveQuery(cur);
queries.pop_front();
safe_delete(cur);
}
FreeDBInstance(asyncdb);
bool isActive = IsActiveQuery(queryid);
if (isActive)
{
continueAsync = true;
DBStruct* tmp = new DBStruct;
tmp->queryid = queryid;
#ifdef WIN32
_beginthread(DBAsyncQueries, 0, (void*)tmp);
#else
pthread_t t1;
pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp);
pthread_detach(t1);
#endif
}
}
void Database::AddAsyncQuery(Query* query)
{
DBAsyncMutex.writelock();
map<int32, Mutex*>::iterator mutexItr = asyncQueriesMutex.find(query->GetQueryID());
if (mutexItr == asyncQueriesMutex.end())
{
Mutex* queryMutex = new Mutex();
queryMutex->SetName("AsyncQuery" + query->GetQueryID());
asyncQueriesMutex.insert(make_pair(query->GetQueryID(), queryMutex));
}
map<int32, deque<Query*>>::iterator itr = asyncQueries.find(query->GetQueryID());
asyncQueriesMutex[query->GetQueryID()]->writelock();
if ( itr != asyncQueries.end())
itr->second.push_back(query);
else
{
deque<Query*> queue;
queue.push_back(query);
asyncQueries.insert(make_pair(query->GetQueryID(), queue));
}
AddActiveQuery(query);
asyncQueriesMutex[query->GetQueryID()]->releasewritelock();
DBAsyncMutex.releasewritelock();
bool isActive = IsActiveQuery(query->GetQueryID(), query);
if (!isActive)
{
continueAsync = true;
DBStruct* tmp = new DBStruct;
tmp->queryid = query->GetQueryID();
#ifdef WIN32
_beginthread(DBAsyncQueries, 0, (void*)tmp);
#else
pthread_t t1;
pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp);
pthread_detach(t1);
#endif
}
}
Database* Database::FindFreeInstance()
{
Database* db_inst = 0;
map<Database*, bool>::iterator itr;
DBInstanceMutex.writelock(__FUNCTION__, __LINE__);
for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) {
if (!itr->second)
{
db_inst = itr->first;
itr->second = true;
break;
}
}
if (!db_inst)
{
WorldDatabase* tmp = new WorldDatabase();
db_inst = (Database*)tmp;
tmp->Init();
tmp->ConnectNewDatabase();
dbInstances.insert(make_pair(db_inst, true));
}
DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__);
return db_inst;
}
void Database::PurgeDBInstances()
{
map<Database*, bool>::iterator itr;
DBInstanceMutex.writelock(__FUNCTION__, __LINE__);
for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) {
Database* tmpInst = itr->first;
safe_delete(tmpInst);
}
dbInstances.clear();
DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__);
}
void Database::PingAsyncDatabase()
{
map<Database*, bool>::iterator itr;
DBInstanceMutex.readlock(__FUNCTION__, __LINE__);
for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) {
Database* tmpInst = itr->first;
tmpInst->ping();
}
DBInstanceMutex.releasereadlock(__FUNCTION__, __LINE__);
}
void Database::FreeDBInstance(Database* cur)
{
DBInstanceMutex.writelock(__FUNCTION__, __LINE__);
dbInstances[cur] = false;
DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__);
}
void Database::RemoveActiveQuery(Query* query)
{
DBQueryMutex.writelock(__FUNCTION__, __LINE__);
vector<Query*>::iterator itr;
for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++)
{
Query* curQuery = *itr;
if (query == curQuery)
{
activeQuerySessions.erase(itr);
break;
}
}
DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__);
}
void Database::AddActiveQuery(Query* query)
{
DBQueryMutex.writelock(__FUNCTION__, __LINE__);
activeQuerySessions.push_back(query);
DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__);
}
bool Database::IsActiveQuery(int32 id, Query* skip)
{
bool isActive = false;
DBQueryMutex.readlock(__FUNCTION__, __LINE__);
vector<Query*>::iterator itr;
for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++)
{
Query* query = *itr;
if (query == skip)
continue;
if (query->GetQueryID() == id)
{
isActive = true;
break;
}
}
DBQueryMutex.releasereadlock(__FUNCTION__, __LINE__);
return isActive;
}
#endif