From 57bba6d83f49d659625e3e1f7e7fb118e525cf62 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Mon, 30 Jun 2008 02:34:21 +0000 Subject: [PATCH] Import of gamedata daemon. I love imported stuff. --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%402304 --- tools/daemon/Makefile | 76 ++++++ tools/daemon/smud.cpp | 130 ++++++++++ tools/daemon/smud.h | 25 ++ tools/daemon/smud_connections.cpp | 396 ++++++++++++++++++++++++++++++ tools/daemon/smud_connections.h | 109 ++++++++ tools/daemon/smud_threads.cpp | 86 +++++++ tools/daemon/smud_threads.h | 41 ++++ 7 files changed, 863 insertions(+) create mode 100644 tools/daemon/Makefile create mode 100644 tools/daemon/smud.cpp create mode 100644 tools/daemon/smud.h create mode 100644 tools/daemon/smud_connections.cpp create mode 100644 tools/daemon/smud_connections.h create mode 100644 tools/daemon/smud_threads.cpp create mode 100644 tools/daemon/smud_threads.h diff --git a/tools/daemon/Makefile b/tools/daemon/Makefile new file mode 100644 index 00000000..0ede6c57 --- /dev/null +++ b/tools/daemon/Makefile @@ -0,0 +1,76 @@ +# (C)2004-2008 SourceMod Development Team +# Makefile written by David "BAILOPAN" Anderson + + +##################################### +### EDIT BELOW FOR OTHER PROJECTS ### +##################################### + +OBJECTS = smud.cpp smud_connections.cpp smud_threads.cpp + +############################################## +### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### +############################################## + +C_OPT_FLAGS = -DNDEBUG -O3 -funroll-loops -pipe -fno-strict-aliasing +C_DEBUG_FLAGS = -D_DEBUG -DDEBUG -g -ggdb3 +C_GCC4_FLAGS = -fvisibility=hidden +CPP_GCC4_FLAGS = -fvisibility-inlines-hidden +CPP = gcc-4.1 + +BINARY = daemon + +LINK += -lpthread -static-libgcc + +INCLUDE += -I. + +CFLAGS += -D_LINUX -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp \ + -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -Wall -Werror \ + -Wno-uninitialized -mfpmath=sse -msse -DHAVE_STDINT_H -DSM_DEFAULT_THREADER -m32 +CPPFLAGS += -Wno-non-virtual-dtor -fno-exceptions -fno-rtti + +################################################ +### DO NOT EDIT BELOW HERE FOR MOST PROJECTS ### +################################################ + +ifeq "$(DEBUG)" "true" + BIN_DIR = Debug + CFLAGS += $(C_DEBUG_FLAGS) +else + BIN_DIR = Release + CFLAGS += $(C_OPT_FLAGS) +endif + +GCC_VERSION := $(shell $(CPP) -dumpversion >&1 | cut -b1) +ifeq "$(GCC_VERSION)" "4" + CFLAGS += $(C_GCC4_FLAGS) + CPPFLAGS += $(CPP_GCC4_FLAGS) +endif + +OBJ_LINUX := $(OBJECTS:%vm_engine.cpp=$(BIN_DIR)/%vm_engine.o) +OBJ_LINUX := $(OBJ_LINUX:%.cpp=$(BIN_DIR)/%.o) +OBJ_LINUX := $(OBJ_LINUX:%.c=$(BIN_DIR)/%.o) + +$(BIN_DIR)/%vm_engine.o: %vm_engine.cpp + $(CPP) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +$(BIN_DIR)/%.o: %.cpp + $(CPP) $(INCLUDE) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +$(BIN_DIR)/%.o: %.c + $(CPP) $(INCLUDE) $(CFLAGS) -o $@ -c $< + +all: + mkdir -p $(BIN_DIR) + $(MAKE) -f Makefile sourcemod + +sourcemod: $(OBJ_LINUX) + $(CPP) $(INCLUDE) $(OBJ_LINUX) $(LINK) -m32 -lstdc++ -o$(BIN_DIR)/$(BINARY) + +debug: + $(MAKE) -f Makefile all DEBUG=true + +default: all + +clean: + rm -rf $(BIN_DIR)/$(BINARY) diff --git a/tools/daemon/smud.cpp b/tools/daemon/smud.cpp new file mode 100644 index 00000000..8ed6c3a6 --- /dev/null +++ b/tools/daemon/smud.cpp @@ -0,0 +1,130 @@ +#include "smud.h" +#include "smud_threads.h" + +#define LISTEN_PORT 6500 +#define LISTEN_QUEUE_LENGTH 6 + +char fileNames[NUM_FILES][30] = { + "core.games.txt", + "sdktools.games.txt", + "sdktools.games.ep2.txt", + "sm-cstrike.games.txt", + "sm-tf2.games.txt", +}; + +void *fileLocations[NUM_FILES]; +int fileLength[NUM_FILES]; + +int main(int argc, char **argv) +{ + ThreadPool *pool; + struct protoent *pProtocol; + struct sockaddr_in serverAddress; + struct sockaddr_in clientAddress; + int serverSocket; + int clientSocket; + int addressLen; + int opts; + int file; + char filename[100]; + struct stat sbuf; + + printf("Loading Gamedata files into memory\n"); + + for (int i=0; iStart()) + { + return 1; + } + + printf("Create Server Socket\n"); + + memset(&serverAddress, 0, sizeof(serverAddress)); + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = INADDR_ANY; + serverAddress.sin_port = htons(LISTEN_PORT); + + pProtocol = getprotobyname("tcp"); + + if (pProtocol == NULL) + { + return 1; + } + + serverSocket = socket(AF_INET, SOCK_STREAM, pProtocol->p_proto); + + if (serverSocket < 0) + { + return 1; + } + + opts = 1; + setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + + if (bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) + { + return 1; + } + + if (listen(serverSocket, LISTEN_QUEUE_LENGTH) < 0) + { + return 1; + } + + printf("Entering Main Loop\n"); + + while (1) + { + addressLen = sizeof(clientAddress); + + if ( (clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, (socklen_t *)&addressLen)) < 0) + { + continue; + } + + opts = fcntl(clientSocket, F_GETFL, 0); + if (fcntl(clientSocket, F_SETFL, opts|O_NONBLOCK) < 0) + { + closesocket(clientSocket); + continue; + } + + + printf("Connection Received!\n"); + + pool->AddConnection(clientSocket); + } + + delete pool; +} + + + diff --git a/tools/daemon/smud.h b/tools/daemon/smud.h new file mode 100644 index 00000000..468024ca --- /dev/null +++ b/tools/daemon/smud.h @@ -0,0 +1,25 @@ +#ifndef _INCLUDE_SMUD_MAIN_H_ +#define _INCLUDE_SMUD_MAIN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define closesocket close + +#define NUM_FILES 5 + +extern char fileNames[NUM_FILES][30]; +extern void *fileLocations[NUM_FILES]; +extern int fileLength[NUM_FILES]; + +#endif //_INCLUDE_SMUD_MAIN_H_ diff --git a/tools/daemon/smud_connections.cpp b/tools/daemon/smud_connections.cpp new file mode 100644 index 00000000..e401e607 --- /dev/null +++ b/tools/daemon/smud_connections.cpp @@ -0,0 +1,396 @@ +#include "smud_connections.h" +#include "smud.h" + +ConnectionPool::ConnectionPool() +{ + pthread_mutex_init(&m_AddLock, NULL); + m_timeOut = 1000; +} + +ConnectionPool::~ConnectionPool() +{ + pthread_mutex_destroy(&m_AddLock); +} + +void ConnectionPool::AddConnection( int fd ) +{ + smud_connection *connection = new smud_connection(fd); + + pthread_mutex_lock(&m_AddLock); + m_AddQueue.push_back(connection); + pthread_mutex_unlock(&m_AddLock); + + printf("New Connection Added\n"); +} + +void ConnectionPool::Process( bool *terminate ) +{ + struct timespec ts_wait; + + ts_wait.tv_sec = 0; + ts_wait.tv_nsec = 50000000; /* 50ms */ + + std::list::iterator iter; + smud_connection *con = NULL; + + while (1) + { + if (*terminate) + { + return; + } + + iter = m_Links.begin(); + + QueryResult result = QueryResult_Continue; + int pollReturn = 0; + + /* Add all connections that want processing to the sets */ + while (iter != m_Links.end()) + { + con = (smud_connection *)*iter; + + pollReturn = poll(&(con->pollData), 1, 0); + + if (pollReturn == -1) + { + //Something went badly wrong or the connection closed. + result = QueryResult_Complete; + } + else if (pollReturn == 1) + { + //Poll returns the number of sockets available (which can only ever be 1) + result = ProcessConnection(con); + } + + if (result == QueryResult_Complete) + { + iter = m_Links.erase(iter); + closesocket(con->fd); + delete con; + + printf("Connection Completed!\n"); + + continue; + } + + iter++; + } + + /* Add new items to process */ + + iter = m_Links.end(); + pthread_mutex_lock(&m_AddLock); + m_Links.splice(iter, m_AddQueue); + pthread_mutex_unlock(&m_AddLock); + + nanosleep(&ts_wait, NULL); + } +} + +QueryResult ConnectionPool::ProcessConnection( smud_connection *con ) +{ + switch (con->state) + { + case ConnectionState_ReadQueryHeader: + { + ReadQueryHeader(con); + break; + } + + case ConnectionState_ReadQueryData: + { + ReadQueryContent(con); + break; + } + + case ConnectionState_ReplyQuery: + { + ReplyQuery(con); + break; + } + + case ConnectionState_SendingFiles: + { + SendFile(con); + break; + } + + case ConnectionState_SendUnknownList: + { + SendUnknownList(con); + break; + } + + case ConnectionState_Complete: + { + break; + } + } + + if (con->state == ConnectionState_Complete) + { + printf("Ending connection because it marked itself as finished\n"); + return QueryResult_Complete; + } + + if (con->start + m_timeOut < time(NULL)) + { + printf("Ending connection because it has passed maximum allowed time\n"); + return QueryResult_Complete; + } + + return QueryResult_Continue; +} + +void ConnectionPool::ReadQueryHeader( smud_connection *con ) +{ + char data[11]; + + if (recv(con->fd, data, sizeof(data), 0) == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + con->state = ConnectionState_Complete; + } + + return; + } + + if (data[0] != 'A' || data[1] != 'G') + { + con->state = ConnectionState_Complete; + return; + } + + //Ignore the next 8 bytes for the moment. Versioning data is currently unused + // uint16[4] - source version major/minor/something/rev + + con->sentSums = data[10]; + + con->state = ConnectionState_ReadQueryData; + printf("Query Header Read Complete, %i md5's expected\n", con->sentSums); +} + +void ConnectionPool::ReplyQuery(smud_connection *con) +{ + char data[12]; + data[0] = 'A'; + data[1] = 'G'; + + data[2] = (char)Update_Unknown; //unused versioning crap + *(short *)&data[3] = 1; + *(short *)&data[5] = 0; + *(short *)&data[7] = 0; + *(short *)&data[9] = 3; + + data[11] = (char)con->sendCount; + + if (send(con->fd, data, sizeof(data), 0) == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + con->state = ConnectionState_Complete; + } + + return; + } + + //Now we need more send sub functions for all the damn files. Geh. + //Alternatively we could just send all at once here. Could make for a damn big query. 100k anyone? + + con->state = ConnectionState_SendingFiles; + printf("Query Reply Header Complete\n"); +} + +void ConnectionPool::ReadQueryContent( smud_connection *con ) +{ + char *data = new char[16*(con->sentSums)](); + + if (recv(con->fd, data, 16*(con->sentSums), 0) == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + con->state = ConnectionState_Complete; + } + + delete [] data; + return; + } + + con->shouldSend = new MD5Status[con->sentSums](); + con->fileLocation = new int[con->sentSums](); + con->headerSent = new bool[con->sentSums](); + + for (int i=0; isentSums; i++) + { + con->fileLocation[i] = -1; + con->shouldSend[i] = GetMD5UpdateStatus(data + (16*i), con, i); + + if (con->shouldSend[i] == MD5Status_NeedsUpdate) + { + printf("File %i needs updating\n", i); + con->sendCount++; + con->headerSent[i] = false; + continue; + } + + if (con->shouldSend[i] == MD5Status_Unknown) + { + printf("File %i is unknown\n", i); + con->unknownCount++; + } + } + + con->state = ConnectionState_ReplyQuery; + con->pollData.events = POLLOUT; + delete [] data; + printf("Query Data Read Complete\n"); +} + +MD5Status ConnectionPool::GetMD5UpdateStatus( const char *md5 , smud_connection *con, int fileNum) +{ + //Try find a file with this name in some directory. + char path[100] = "./md5/"; + char temp[4]; + char md5String[33] = ""; + + for (int i=0; i<16; i++) + { + snprintf(temp, sizeof(temp), "%02x", (unsigned char)md5[i]); + strcat(md5String, temp); + } + + strcat(path, md5String); + + printf("checking for file \"%s\"\n", path); + + FILE *file = fopen(path, "r"); + + if (file == NULL) + { + printf("Couldn't find file!\n"); + return MD5Status_Unknown; + } + + char latestMD5[33]; + fgets(latestMD5, 33, file); + printf("Latest md5 is: %s\n", latestMD5); + + if (strcmp(latestMD5, md5String) == 0) + { + return MD5Status_Current; + } + + char filename[100]; + fgets(filename, sizeof(filename), file); + if (filename[strlen(filename)-1] == '\n') + { + filename[strlen(filename)-1] = '\0'; + } + + printf("Filename is %s\n", filename); + + //We now need to match this filename with one of our mmap'd files in memory and store it until send gets called. + for (int i=0; ifileLocation[fileNum] = i; + printf("File %i mapped to local file %i\n", fileNum, i); + return MD5Status_NeedsUpdate; + } + } + + return MD5Status_Unknown; +} + +void ConnectionPool::SendFile( smud_connection *con ) +{ + //Find the next file to send. + while (con->currentFile < con->sentSums && + con->shouldSend[con->currentFile] != MD5Status_NeedsUpdate) + { + con->currentFile++; + } + + //All files have been sent. + if (con->currentFile >= con->sentSums) + { + printf("All files sent!\n"); + con->state = ConnectionState_SendUnknownList; + return; + } + + void *file = fileLocations[con->fileLocation[con->currentFile]]; + int filelength = fileLength[con->fileLocation[con->currentFile]]; + + printf("Sending file of length %i\n", filelength); + printf("Current file index is: %i, maps to file index: %i\n", con->currentFile, con->fileLocation[con->currentFile]); + + if (!con->headerSent[con->currentFile]) + { + char buffer[5]; + buffer[0] = con->currentFile; + *((int *)&buffer[1]) = filelength; + + if (send(con->fd, buffer, 5, 0) == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + con->state = ConnectionState_Complete; + } + + return; + } + + con->headerSent[con->currentFile] = true; + } + + if (send(con->fd, file, filelength, 0) == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + con->state = ConnectionState_Complete; + } + + return; + } + + con->currentFile++; + printf("Sent a file!: %s\n", fileNames[con->fileLocation[con->currentFile-1]]); +} + +void ConnectionPool::SendUnknownList( smud_connection *con ) +{ + int size = con->unknownCount+1; + char *packet = new char[size](); + + packet[0] = con->unknownCount; + + printf("%i Files are unknown\n", con->unknownCount); + + int i=1; + + for (int j=0; jsentSums; j++) + { + if (con->shouldSend[j] == MD5Status_Unknown) + { + packet[i] = j; + i++; + } + } + + if (send(con->fd, packet, size, 0) == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + con->state = ConnectionState_Complete; + } + + return; + } + + con->state = ConnectionState_Complete; + printf("Unknown's Sent\n"); +} diff --git a/tools/daemon/smud_connections.h b/tools/daemon/smud_connections.h new file mode 100644 index 00000000..5fe29d68 --- /dev/null +++ b/tools/daemon/smud_connections.h @@ -0,0 +1,109 @@ +#ifndef _INCLUDE_SMUD_CONNECTION_H_ +#define _INCLUDE_SMUD_CONNECTION_H_ + +#include "smud.h" +#include +#include "poll.h" + +enum ConnectionState +{ + ConnectionState_ReadQueryHeader, + ConnectionState_ReadQueryData, + ConnectionState_ReplyQuery, + ConnectionState_SendingFiles, + ConnectionState_SendUnknownList, + ConnectionState_Complete, +}; + +enum QueryResult +{ + QueryResult_Continue, + QueryResult_Complete, +}; + +enum MD5Status +{ + MD5Status_Unknown, + MD5Status_Current, + MD5Status_NeedsUpdate, +}; + +enum UpdateStatus +{ + Update_Unknown = 0, /* Version wasn't recognised or version querying is unsupported */ + Update_Current = 1, /* Server is running latest version */ + Update_NewBuild = 2, /* Server is on a svn release and a newer version is available */ + Update_MinorAvailable = 3, /* Server is on a release and a minor release has superceeded it */ + Update_MajorAvailable = 4, /* Server is on a release and a major release has superceeded it */ + Update_CriticalAvailable = 5, /* A critical update has been released (security fixes etc) */ +}; + +struct smud_connection +{ + smud_connection(int fd) + { + shouldSend = NULL; + fileLocation = NULL; + headerSent = NULL; + sentSums = 0; + sendCount = 0; + currentFile = 0; + unknownCount = 0; + pollData.events = POLLIN; + start = time(NULL); + state = ConnectionState_ReadQueryHeader; + this->fd = fd; + pollData.fd = fd; + } + + ~smud_connection() + { + if (shouldSend != NULL) + delete [] shouldSend; + if (fileLocation != NULL) + delete [] fileLocation; + if (headerSent != NULL) + delete [] headerSent; + } + + int fd; /** Socket file descriptor */ + time_t start; /** The time this connection was received (for timeouts) */ + ConnectionState state; /** How far through processing the connection we are */ + uint8_t sentSums; /** Number of MD5 Sums sent from the client */ + MD5Status *shouldSend; /** Arrays of statuses for each sum */ + int *fileLocation; /** Array of indexes into the global file list for each sum (only valid if shouldSend[i] == MD5Status_NeedsUpdate) */ + bool *headerSent; /** Has the header been sent yet for each sum? Header == file index and size */ + int sendCount; /** Number of files that need to be sent */ + int unknownCount; /** Number of files that were unknown */ + int currentFile; /** Current file being sent (index into the above 3 arrays) */ + pollfd pollData; /** Data to be passed into poll() */ +}; + + + +class ConnectionPool +{ +public: + ConnectionPool(); + ~ConnectionPool(); +public: + void AddConnection(int fd); + void Process(bool *terminate); +private: + QueryResult ProcessConnection(smud_connection *con); + void ReadQueryHeader(smud_connection *con); + void ReadQueryContent(smud_connection *con); + void ReplyQuery(smud_connection *con); + void SendFile(smud_connection *con); + void SendUnknownList(smud_connection *con); + + MD5Status GetMD5UpdateStatus(const char *md5, smud_connection *con, int fileNum); +private: + std::list m_Links; + std::list m_AddQueue; + pthread_mutex_t m_AddLock; + time_t m_timeOut; +}; + +#endif //_INCLUDE_SMUD_CONNECTION_H_ + diff --git a/tools/daemon/smud_threads.cpp b/tools/daemon/smud_threads.cpp new file mode 100644 index 00000000..1bb41156 --- /dev/null +++ b/tools/daemon/smud_threads.cpp @@ -0,0 +1,86 @@ +#include "smud_threads.h" + +ThreadPool::ThreadPool() +{ +} + +ThreadPool::~ThreadPool() +{ +} + +bool ThreadPool::Start() +{ + m_pWorker = new ThreadWorker(); + + if (!m_pWorker->Start()) + { + delete m_pWorker; + return false; + } + + return true; +} + +void ThreadPool::Stop() +{ + m_pWorker->CancelAndWait(); + delete m_pWorker; +} + +void ThreadPool::AddConnection(int fd) +{ + m_pWorker->AddConnection(fd); +} + +ThreadWorker::ThreadWorker() : m_bShouldCancel(false) +{ +} + +ThreadWorker::~ThreadWorker() +{ +} + +bool ThreadWorker::Start() +{ + m_pPool = new ConnectionPool(); + pthread_mutex_init(&m_NotifyLock, NULL); + pthread_cond_init(&m_Notify, NULL); + + if (pthread_create(&m_Thread, NULL, ThreadCallback, this) != 0) + { + return false; + } + + return true; +} + +void ThreadWorker::CancelAndWait() +{ + m_bShouldCancel = true; + + pthread_join(m_Thread, NULL); + + pthread_cond_destroy(&m_Notify); + pthread_mutex_destroy(&m_NotifyLock); + + delete m_pPool; +} + +void ThreadWorker::AddConnection( int fd ) +{ + m_pPool->AddConnection(fd); +} + +void ThreadWorker::Process() +{ + m_pPool->Process(&m_bShouldCancel); +} + +void *ThreadCallback(void *data) +{ + ((ThreadWorker *)data)->Process(); + + pthread_exit(NULL); +} + + diff --git a/tools/daemon/smud_threads.h b/tools/daemon/smud_threads.h new file mode 100644 index 00000000..1897b160 --- /dev/null +++ b/tools/daemon/smud_threads.h @@ -0,0 +1,41 @@ +#ifndef _INCLUDE_SMUD_H_ +#define _INCLUDE_SMUD_H_ + +#include "smud.h" +#include "smud_connections.h" + +void *ThreadCallback(void *data); + +class ThreadWorker +{ +public: + ThreadWorker(); + ~ThreadWorker(); +public: + bool Start(); + void CancelAndWait(); + void AddConnection(int fd); + void Process(); +private: + ConnectionPool *m_pPool; + pthread_t m_Thread; + pthread_mutex_t m_NotifyLock; + pthread_cond_t m_Notify; + bool m_bShouldCancel; +}; + +class ThreadPool +{ +public: + ThreadPool(); + ~ThreadPool(); +public: + void AddConnection(int fd); + bool Start(); + void Stop(); +private: + ThreadWorker *m_pWorker; +}; + +#endif //_INCLUDE_SMUD_H_ +