diff --git a/get-sql.sh b/get-sql.sh index 1005c43..20fed86 100755 --- a/get-sql.sh +++ b/get-sql.sh @@ -1,3 +1,3 @@ #!/bin/bash -mysqldump -d -h192.168.0.3 -udosThing -p dosThing > schema.sql +mysqldump -d -h192.168.0.3 -ukangaworld -p kangaworld > schema.sql diff --git a/schema.sql b/schema.sql index 597ccca..41bf54b 100644 --- a/schema.sql +++ b/schema.sql @@ -1,6 +1,6 @@ --- MySQL dump 10.19 Distrib 10.3.32-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.19 Distrib 10.3.34-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: 192.168.0.3 Database: dosThing +-- Host: 192.168.0.3 Database: kangaworld -- ------------------------------------------------------ -- Server version 10.5.13-MariaDB-log @@ -15,6 +15,23 @@ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +-- +-- Table structure for table `client` +-- + +DROP TABLE IF EXISTS `client`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `client` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(32) NOT NULL, + `data` varchar(1024) NOT NULL, + `description` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `nameIDX` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `config` -- @@ -43,48 +60,58 @@ DROP TABLE IF EXISTS `files`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `files` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `realPath` varchar(1024) NOT NULL, - `virtualPath` varchar(512) NOT NULL, + `path` varchar(1024) NOT NULL, + `length` int(11) NOT NULL DEFAULT 0, `sha256` varchar(64) NOT NULL, - `description` varchar(255) NOT NULL, + `modified` datetime NOT NULL, + `description` varchar(255) NOT NULL DEFAULT '', + `touched` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), - UNIQUE KEY `virtualPath` (`virtualPath`), - UNIQUE KEY `realPath` (`realPath`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; + UNIQUE KEY `pathIndex` (`path`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `numbers` +-- Table structure for table `games` -- -DROP TABLE IF EXISTS `numbers`; +DROP TABLE IF EXISTS `games`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `numbers` ( +CREATE TABLE `games` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(32) NOT NULL, - `data` int(11) NOT NULL, - `description` varchar(255) NOT NULL, + `active` tinyint(1) NOT NULL DEFAULT 0, + `title` varchar(255) DEFAULT NULL, + `developer` varchar(255) DEFAULT NULL, + `publisher` varchar(255) DEFAULT NULL, + `description` text DEFAULT NULL, + `releaseDate` datetime DEFAULT NULL, + `stars` double DEFAULT NULL, + `starCount` int(11) DEFAULT NULL, + `rating` varchar(255) DEFAULT NULL, + `series` varchar(255) DEFAULT NULL, + `origin` varchar(255) DEFAULT NULL, + `region` varchar(255) DEFAULT NULL, + `shortName` varchar(8) DEFAULT NULL, + `type` varchar(8) DEFAULT NULL, + `maxPlayers` int(11) DEFAULT 2, + `joinable` tinyint(1) DEFAULT 0, + `mobyGames` varchar(255) DEFAULT NULL, + `wiki` varchar(255) DEFAULT NULL, + `manual` varchar(255) DEFAULT NULL, + `root` varchar(255) DEFAULT NULL, + `worksWith` varchar(255) DEFAULT NULL, + `configSys` text DEFAULT NULL, + `autoexecBat` text DEFAULT NULL, + `options` text DEFAULT NULL, + `mods` text DEFAULT NULL, + `notes` text DEFAULT NULL, + `added` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + `touched` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), - UNIQUE KEY `nameIDX` (`name`) -) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `strings` --- - -DROP TABLE IF EXISTS `strings`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `strings` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(32) NOT NULL, - `data` varchar(1024) NOT NULL, - `description` varchar(255) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `nameIDX` (`name`) -) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=latin1; + KEY `active_index` (`active`) +) ENGINE=InnoDB AUTO_INCREMENT=313 DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -118,4 +145,4 @@ CREATE TABLE `users` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2022-02-14 20:13:22 +-- Dump completed on 2022-03-21 20:14:03 diff --git a/server/server.pro b/server/server.pro index 7f5c246..2064f50 100644 --- a/server/server.pro +++ b/server/server.pro @@ -33,6 +33,7 @@ INCLUDEPATH += \ /usr/include/mariadb \ /usr/include/mariadb/mysql \ $$SHARED \ + $$PWD/src/thirdparty/sha256.c/deps \ $$PWD/src HEADERS = \ @@ -50,6 +51,7 @@ HEADERS = \ $$SHARED/packets.h \ $$SHARED/thirdparty/tiny-AES-c/aes.h \ $$SHARED/thirdparty/tiny-AES128-C/pkcs7_padding.h \ + src/thirdparty/sha256.c/sha256.h \ src/client.h \ src/client/file.h \ src/client/login.h \ @@ -63,7 +65,8 @@ HEADERS = \ src/rest.h \ src/database.h \ src/server.h \ - src/settings.h + src/settings.h \ + src/update.h SOURCES = \ $$SHARED/thirdparty/memwatch/memwatch.c \ @@ -75,6 +78,7 @@ SOURCES = \ $$SHARED/packet.c \ $$SHARED/thirdparty/tiny-AES-c/aes.c \ $$SHARED/thirdparty/tiny-AES128-C/pkcs7_padding.c \ + src/thirdparty/sha256.c/sha256.c \ src/client.c \ src/client/file.c \ src/client/login.c \ @@ -88,7 +92,8 @@ SOURCES = \ src/rest.c \ src/database.c \ src/server.c \ - src/settings.c + src/settings.c \ + src/update.c LIBS = \ -L/usr/lib/x86_64-linux-gnu/ \ @@ -96,11 +101,12 @@ LIBS = \ -lm \ -lmariadb \ -lpthread \ - -lgnutls \ -lcrypt \ -lcurl \ -ljson-c +#-lgnutls \ + OTHER_FILES = ASAN { diff --git a/server/src/client/file.c b/server/src/client/file.c index 9b11102..d82658b 100644 --- a/server/src/client/file.c +++ b/server/src/client/file.c @@ -76,7 +76,7 @@ static void clientApiFileRequestCheck(ClientThreadT *client, PacketDecodeDataT * uint16_t length = 0; char shaInDB[128] = { 0 }; char buffer[1024] = { 0 }; - char buffer2[2048] = { 0 }; + char buffer2[2064] = { 0 }; uint32_t temp = 0; // Is something still open? @@ -102,9 +102,18 @@ static void clientApiFileRequestCheck(ClientThreadT *client, PacketDecodeDataT * packetSend(client->packetThreadData, &encoded); DEL(packetData); } else { - // Get real path. - dbFileRealPathGet(path, buffer, FILE_VIRTUAL_PATH_MAX); - snprintf(buffer2, 2048, "%s%s", __settingsFile, buffer); + // Get real path by flipping colons to slashes. + temp = 0; + while (path[temp] != 0) { + if (path[temp] == ':') { + buffer[temp] = '/'; + } else { + buffer[temp] = path[temp]; + } + temp++; + } + buffer[temp] = 0; + snprintf(buffer2, 2064, "%s/files/%s", __settingsFile, buffer); // Open file & get file size. client->handle = fopen(buffer2, "rb"); if (!client->handle) { diff --git a/server/src/client/version.c b/server/src/client/version.c index 8e628f2..bb2bb38 100644 --- a/server/src/client/version.c +++ b/server/src/client/version.c @@ -31,89 +31,10 @@ void clientApiVersionBad(ClientThreadT *client, PacketDecodeDataT *data) { void clientApiVersionOkay(ClientThreadT *client, PacketDecodeDataT *data) { - uint64_t x = 0; - uint32_t y = 0; - uint16_t length = 0; - uint8_t *buffer = NULL; PacketEncodeDataT encoded = { 0 }; - DBTableT *record = NULL; - DBTableT **table = NULL; (void)data; - // Fetch string table from DB. - if (dbTableGet("strings", &table) == SUCCESS) { - while (arrlen(table)) { - record = table[0]; - arrdel(table, 0); - // Strings are encoded in a single buffer as: KEY\0DATA\0 - x = strlen(record->name); - y = strlen(record->data); - length = x + y + 2; - buffer = (uint8_t *)malloc(length); - if (!buffer) { - consoleMessageQueue("%ld: Unable to allocate buffer for string packet!\n", client->threadIndex); - break; - } - memcpy(buffer, record->name, x + 1); - memcpy(&buffer[x + 1], record->data, y + 1); - // Build packet. - encoded.control = PACKET_CONTROL_DAT; - encoded.packetType = PACKET_TYPE_STRING; - encoded.channel = data->channel; - encoded.encrypt = 0; - packetEncode(client->packetThreadData, &encoded, buffer, length); - // Send it. - packetSend(client->packetThreadData, &encoded); - DEL(buffer); - //consoleMessageQueue("PACKET_TYPE_STRING [%s] sent.\n", record->name); - DEL(record->name); - DEL(record->data); - DEL(record); - } - arrfree(table); - } else { - consoleMessageQueue("%ld: Unable to fetch strings!\n", client->threadIndex); - return; - } - - // Fetch number table from DB. - if (dbTableGet("numbers", &table) == SUCCESS) { - while (arrlen(table)) { - record = table[0]; - arrdel(table, 0); - // Integers are encoded in a single buffer as: 1234DATA\0 - // Integers are 64 bit until sent to the client when they are truncated to 32. - x = strlen(record->name); - y = atol(record->data); - length = x + 5; - buffer = (uint8_t *)malloc(length); - if (!buffer) { - consoleMessageQueue("%ld: Unable to allocate buffer for number packet!\n", client->threadIndex); - break; - } - memcpy(buffer, &y, 4); - memcpy(&buffer[4], record->name, x + 1); - // Build packet. - encoded.control = PACKET_CONTROL_DAT; - encoded.packetType = PACKET_TYPE_NUMBER; - encoded.channel = data->channel; - encoded.encrypt = 0; - packetEncode(client->packetThreadData, &encoded, buffer, length); - // Send it. - packetSend(client->packetThreadData, &encoded); - DEL(buffer); - //consoleMessageQueue("PACKET_TYPE_NUMBER [%s] sent.\n", record->name); - DEL(record->name); - DEL(record->data); - DEL(record); - } - arrfree(table); - } else { - consoleMessageQueue("%ld: Unable to fetch numbers!\n", client->threadIndex); - return; - } - // Build PROCEED packet. encoded.control = PACKET_CONTROL_DAT; encoded.packetType = PACKET_TYPE_PROCEED; diff --git a/server/src/console.c b/server/src/console.c index 6831e8e..a849122 100644 --- a/server/src/console.c +++ b/server/src/console.c @@ -24,6 +24,7 @@ #include "console.h" #include "array.h" #include "util.h" +#include "update.h" static struct termios _termios = { 0 }; @@ -61,6 +62,7 @@ void consoleRun(void) { char command[256] = { 0 }; uint16_t commandIndex = 0; char c = 0; + char *p = NULL; uint8_t commandOk = 0; struct timespec remaining = { 0, 0 }; struct timespec sleepTime = { 0, 1000000000/4 }; // 1/4th second @@ -114,15 +116,30 @@ void consoleRun(void) { printf("\n\r"); logWriteToFileOnly(">%s\n", command); commandOk = 0; + // Is there a space in the command line? + p = strstr(command, " "); + if (p) { + // Replace it with zero, move P to next character for parameters. + *p = 0; + p++; + } + // Help. if (!strcasecmp(command, "HELP") || !strcasecmp(command, "?")) { - sendToConsole("HELP or ? - This message.\n"); - sendToConsole("SHUTDOWN - Stop the server.\n"); + sendToConsole("HELP or ? - This message.\n"); + sendToConsole("SHUTDOWN - Stop the server.\n"); + sendToConsole("UPDATE [what] - Update client data.\n"); commandOk = 1; } + // Shutdown. if (!strcasecmp(command, "SHUTDOWN")) { __running = 0; commandOk = 1; } + // Update. + if (!strcasecmp(command, "UPDATE")) { + update(p); + commandOk = 1; + } // Did we grok it? if (!commandOk) { sendToConsole("Unknown command! Type HELP (or ?) for help.\n\r"); diff --git a/server/src/database.c b/server/src/database.c index 1b3b259..e518918 100644 --- a/server/src/database.c +++ b/server/src/database.c @@ -22,7 +22,12 @@ #include #include +#include "thirdparty/sha256.c/sha256.h" + #include "array.h" +#include "console.h" +#include "settings.h" + #include "database.h" @@ -32,10 +37,26 @@ #define STATEMENT_MAX 2048 +typedef struct DbFileInfoS { + uint64_t id; + char *path; + uint64_t length; + char *sha; + char *modified; + uint8_t touched; +} DbFileInfoT; + + static MYSQL *_sql = NULL; static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER; +static DbFileInfoT *dbFileInfoGet(char *vpath); +static void dbFileInfoRelease(DbFileInfoT **info); +static void dbFileInfoSet(DbFileInfoT *info); +static void dbFileSha256Create(char *file, char *buf); + + uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password) { uint8_t reconnect = 1; @@ -68,99 +89,160 @@ uint8_t dbDisconnect(void) { } -uint8_t dbFileRealPathGet(char *vpath, char *value, uint32_t max) { - char statement[STATEMENT_MAX]; - char *p = statement; - MYSQL_RES *result = NULL; - MYSQL_ROW row; - int count; +static DbFileInfoT *dbFileInfoGet(char *vpath) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + MYSQL_ROW row = NULL; + int32_t count = 0; + DbFileInfoT *info = NULL; pthread_mutex_lock(&_mutex); if (!_sql) { pthread_mutex_unlock(&_mutex); - return FAIL; + return NULL; } - p += sprintf(p, "SELECT realPath FROM files WHERE virtualPath='"); + // If they passed in a colon-separated path, flip it to slashes. + while (vpath[count] != 0) { + if (vpath[count] == ':') vpath[count] = '/'; + count++; + } + + p += sprintf(p, "SELECT id, path, length, sha256, modified, touched FROM files WHERE path='"); p += mysql_real_escape_string(_sql, p, vpath, strlen(vpath)); p += sprintf(p, "'"); if (mysql_real_query(_sql, statement, p - statement) != 0) { - logWrite("dbFileRealPathGet: %s\n", mysql_error(_sql)); + logWrite("dbFileInfoGet: %s\n", mysql_error(_sql)); pthread_mutex_unlock(&_mutex); - return FAIL; + return NULL; } result = mysql_store_result(_sql); count = mysql_num_rows(result); if (count != 1) { - logWrite("dbFileRealPathGet: Wrong number of rows returned: %d.\n", count); mysql_free_result(result); pthread_mutex_unlock(&_mutex); - return FAIL; + return NULL; } if ((row = mysql_fetch_row(result)) == NULL) { - logWrite("dbFileRealPathGet: %s\n", mysql_error(_sql)); + logWrite("dbFileInfoGet: %s\n", mysql_error(_sql)); pthread_mutex_unlock(&_mutex); - return FAIL; + return NULL; } - strncpy(value, row[0], max); + NEW(DbFileInfoT, info); + if (!info) { + logWrite("dbFileInfoGet: Unable to allocate DbFileInfoT.\n"); + pthread_mutex_unlock(&_mutex); + return NULL; + } + info->id = atol(row[0]); + info->path = strdup(row[1]); + info->length = atol(row[2]); + info->sha = strdup(row[3]); + info->modified = strdup(row[4]); + info->touched = atoi(row[5]); mysql_free_result(result); pthread_mutex_unlock(&_mutex); - return SUCCESS; + return info; +} + + +static void dbFileInfoRelease(DbFileInfoT **info) { + DbFileInfoT *i = *info; + + if (i) { + DEL(i->path); + DEL(i->modified); + DEL(i->sha); + DEL(i); + } +} + + +static void dbFileInfoSet(DbFileInfoT *info) { + char statement[STATEMENT_MAX]; + char *p = statement; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return; + } + + // If id is 0, it's a new file to insert. Otherwise update. + if (info->id == 0) { + // New record. + p += sprintf(p, "INSERT INTO files (path, length, sha256, modified, touched) VALUES ('"); + p += mysql_real_escape_string(_sql, p, info->path, strlen(info->path)); + p += sprintf(p, "', %ld, '", info->length); + p += mysql_real_escape_string(_sql, p, info->sha, strlen(info->sha)); + p += sprintf(p, "', '"); + p += mysql_real_escape_string(_sql, p, info->modified, strlen(info->modified)); + p += sprintf(p, "', %d)", info->touched); + } else { + // Update record. + p += sprintf(p, "UPDATE files SET length=%ld, sha256='", info->length); + p += mysql_real_escape_string(_sql, p, info->sha, strlen(info->sha)); + p += sprintf(p, "', modified='"); + p += mysql_real_escape_string(_sql, p, info->modified, strlen(info->modified)); + p += sprintf(p, "', touched=%d WHERE id=%ld", info->touched, info->id); + } + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbFileInfoSet: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return; + } + + pthread_mutex_unlock(&_mutex); +} + + +static void dbFileSha256Create(char *file, char *buf) { + FILE *f = NULL; + char buffer[1024] = { 0 }; + char hex[3] = { 0 }; + size_t bytes = 0; + sha256_t hash = { 0 }; + + // buf has to be at least 65 bytes. + buf[0] = 0; + + f = fopen(file, "rb"); + if (!f) return; + + sha256_init(&hash); + while (bytes = fread(buffer, 1, sizeof(buffer), f), bytes > 0) { + sha256_update(&hash, (unsigned char *)buffer, bytes); + } + fclose(f); + sha256_final(&hash, (unsigned char *)buffer); + + for (bytes = 0; bytes < 32; bytes++) { + sprintf(hex, "%0x", (unsigned char)buffer[bytes]); + strcat(buf, hex); + } + buf[64] = 0; } uint8_t dbFileSha256Get(char *vpath, char *value, uint32_t max) { - char statement[STATEMENT_MAX]; - char *p = statement; - MYSQL_RES *result = NULL; - MYSQL_ROW row; - int count; + DbFileInfoT *info = dbFileInfoGet(vpath); - pthread_mutex_lock(&_mutex); - - if (!_sql) { - pthread_mutex_unlock(&_mutex); - return FAIL; + if (info) { + strncpy(value, info->sha, max); + dbFileInfoRelease(&info); + return SUCCESS; } - p += sprintf(p, "SELECT sha256 FROM files WHERE virtualPath='"); - p += mysql_real_escape_string(_sql, p, vpath, strlen(vpath)); - p += sprintf(p, "'"); - if (mysql_real_query(_sql, statement, p - statement) != 0) { - logWrite("dbFileSha256Get: %s\n", mysql_error(_sql)); - pthread_mutex_unlock(&_mutex); - return FAIL; - } - - result = mysql_store_result(_sql); - count = mysql_num_rows(result); - if (count != 1) { - logWrite("dbFileSha256Get: Wrong number of rows returned: %d.\n", count); - mysql_free_result(result); - pthread_mutex_unlock(&_mutex); - return FAIL; - } - - if ((row = mysql_fetch_row(result)) == NULL) { - logWrite("dbFileSha256Get: %s\n", mysql_error(_sql)); - pthread_mutex_unlock(&_mutex); - return FAIL; - } - - strncpy(value, row[0], max); - - mysql_free_result(result); - - pthread_mutex_unlock(&_mutex); - - return SUCCESS; + return FAIL; } @@ -233,6 +315,8 @@ uint8_t dbTableGet(char *which, DBTableT ***table) { int count; DBTableT *record = NULL; + // By "table" we mean string or number table, not a database table. + pthread_mutex_lock(&_mutex); if (!_sql) { @@ -270,6 +354,136 @@ uint8_t dbTableGet(char *which, DBTableT ***table) { } +uint8_t dbUpdateFileData(char *file, uint64_t len, char *time) { + DbFileInfoT *info = NULL; + char prefix[1064] = { 0 }; + char *p = NULL; + unsigned char *buf[65] = { 0 }; + FILE *f = NULL; + + // This is the prefix we need to remove to match names in the database. + snprintf(prefix, 1064, "%s/files/", __settingsFile); + p = strstr(file, prefix); + if (!p || p != file) return FAIL; + p += strlen(prefix); + + // If the date or size change, re-calculate the SHA and update the record. + info = dbFileInfoGet(p); + if (info) { + if (info->length != len || strcasecmp(info->modified, time)) { + // File changed. Update & fix SHA. + dbFileSha256Create(file, (char *)buf); + DEL(info->sha); + info->sha = strdup((char *)buf); + DEL(info->modified); + info->modified = strdup(time); + info->length = 0; // Needs updated. + } + } else { + // File not in database, add it. + NEW(DbFileInfoT, info); + if (!info) return FAIL; + info->id = 0; // Indicates a new record. + info->modified = strdup(time); + info->path = strdup(p); + dbFileSha256Create(file, (char *)buf); + info->sha = strdup((char *)buf); + info->length = 0; // Needs updated. + } + + // Do we need to update the length? + if (info->length == 0) { + f = fopen(file, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + info->length = ftell(f); + fclose(f); + } + } + + // Every time we check a file, we set "touched" to 1. + info->touched = 1; + + dbFileInfoSet(info); + + dbFileInfoRelease(&info); + + return SUCCESS; +} + + +uint8_t dbUpdateFilesFinish(void) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + MYSQL_ROW row; + int32_t count = 0; + uint8_t status = FAIL; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + // We double-check to be sure not everything is zero - that would be bad. + p += sprintf(p, "SELECT COUNT(id) AS total FROM files WHERE touched=1"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUpdateFilesFinish: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + result = mysql_store_result(_sql); + count = mysql_num_rows(result); + + if (count == 1) { + if ((row = mysql_fetch_row(result)) != NULL) { + // Once the scan is complete, anything with "touched" set to 0 no longer exists. Delete the records. + p = statement; + p += sprintf(p, "DELETE FROM files WHERE touched=0"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUpdateFilesFinish: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + } + } + + mysql_free_result(result); + + pthread_mutex_unlock(&_mutex); + + return status; +} + + +uint8_t dbUpdateFilesStart(void) { + char statement[STATEMENT_MAX]; + char *p = statement; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + // We start by ensuring the "touched" column is zeroed. + p += sprintf(p, "UPDATE files SET touched=0"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUpdateFilesStart: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + pthread_mutex_unlock(&_mutex); + + return SUCCESS; +} + + uint8_t dbUserCreate(char *first, char *last, char *user, char *pass, char *email) { char statement[STATEMENT_MAX]; char *p = statement; diff --git a/server/src/database.h b/server/src/database.h index ebb45fb..28049e4 100644 --- a/server/src/database.h +++ b/server/src/database.h @@ -36,11 +36,13 @@ typedef struct DBTableS { uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password); uint8_t dbDisconnect(void); -uint8_t dbFileRealPathGet(char *vpath, char *value, uint32_t max); uint8_t dbFileSha256Get(char *vpath, char *value, uint32_t max); uint8_t dbSettingsStringGet(char *host, char *key, char *value, uint32_t max); uint8_t dbSettingsValueGet(char *host, char *key, int32_t *value); uint8_t dbTableGet(char *which, DBTableT ***table); +uint8_t dbUpdateFileData(char *file, uint64_t len, char *time); +uint8_t dbUpdateFilesFinish(void); +uint8_t dbUpdateFilesStart(void); uint8_t dbUserCreate(char *first, char *last, char *user, char *pass, char *email); uint8_t dbUserEmailExists(char *email); uint8_t dbUserLogin(char *user, char *password); diff --git a/server/src/main.c b/server/src/main.c index 1e77270..40d3604 100644 --- a/server/src/main.c +++ b/server/src/main.c @@ -65,8 +65,8 @@ static void configRead(char *file) { // String defaults. if (!_configServer) strdup("kanga.world"); - if (!_configDatabase) strdup("kpmpgsmkii"); - if (!_configUser) strdup(""); + if (!_configDatabase) strdup("kangaworld"); + if (!_configUser) strdup("kangaworld"); if (!_configPassword) strdup(""); } diff --git a/server/src/os.h b/server/src/os.h index 7784c3a..eadf141 100644 --- a/server/src/os.h +++ b/server/src/os.h @@ -47,6 +47,8 @@ #define SUCCESS 1 #define FAIL 0 +#define MAX_PATH 1024 + // Declared in main.c extern uint8_t __running; diff --git a/server/src/update.c b/server/src/update.c new file mode 100644 index 0000000..493c54d --- /dev/null +++ b/server/src/update.c @@ -0,0 +1,262 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * 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 3 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, see . + * + */ + + +#define _XOPEN_SOURCE 500 +#include +#include +#include + +#include "console.h" +#include "settings.h" +#include "database.h" +#include "array.h" + +#include "update.h" + + +typedef void *(*UpdateThreadT)(void *); + + +static pthread_t _updateThreadHandle = { 0 }; +static uint8_t _updateThreadRunning = 0; +static uint8_t _updateAllRunning = 0; + + +static void *updateAll(void *data); +static void *updateConfig(void *data); +static void *updateFiles(void *path); +static int32_t updateFileEntry(const char *filepath, const struct stat *info, const int typeflag, struct FTW *pathinfo); +static void *updateGames(void *data); + + + +void update(char *params) { + + char *p = NULL; + pthread_attr_t updateThreadAttributes = { 0 }; + UpdateThreadT updateThread = NULL; + + if (params == NULL) { + consoleMessageQueue("UPDATE [what] - where [what] is:\n"); + consoleMessageQueue(" ALL - Update everything.\n"); + consoleMessageQueue(" CONFIG - Rebuild client configuration.\n"); + consoleMessageQueue(" FILES [path] - Update file data from [path].\n"); + consoleMessageQueue(" GAMES - Rebuild game database.\n"); + return; + } + + // Is there a space in the parameters line? + p = strstr(params, " "); + if (p) { + // Replace it with zero, move P to next character for more parameters. + *p = 0; + p++; + } + + if (_updateThreadRunning) { + consoleMessageQueue("An update is already running. Wait until it completes.\n\r"); + return; + } + + if (!strcasecmp(params, "ALL")) { updateThread = updateAll; _updateAllRunning = 1; } + if (!strcasecmp(params, "CONFIG")) { updateThread = updateConfig; _updateAllRunning = 0; } + if (!strcasecmp(params, "FILES")) { updateThread = updateFiles; _updateAllRunning = 0; } + if (!strcasecmp(params, "GAMES")) { updateThread = updateGames; _updateAllRunning = 0; } + + if (!updateThread) { + consoleMessageQueue("Unknown UPDATE option! Type UPDATE for help.\n\r"); + return; + } + + // Start update thread. + _updateThreadRunning = 1; + if (pthread_attr_init(&updateThreadAttributes) != 0) utilDie("Unable to create update thread attributes.\n"); + pthread_attr_setdetachstate(&updateThreadAttributes, PTHREAD_CREATE_JOINABLE); + if (pthread_create(&_updateThreadHandle, &updateThreadAttributes, updateThread, (void *)p) != 0) utilDie("Unable to start update thread.\n"); + +} + + +static void *updateAll(void *data) { + (void)data; + + updateConfig(NULL); + updateFiles("/"); + updateGames(NULL); + + pthread_exit(NULL); +} + + +static void *updateConfig(void *data) { + DBTableT *record = NULL; + DBTableT **table = NULL; + char file[MAX_PATH] = { 0 }; + FILE *f = NULL; + + (void)data; + + consoleMessageQueue("Updating client configuration data.\n"); + + snprintf(file, MAX_PATH, "%s/files/generated/client.dat", __settingsFile); + f = fopen(file, "wb"); + if (f) { + // Fetch string table from DB. + if (dbTableGet("client", &table) == SUCCESS) { + while (arrlen(table)) { + record = table[0]; + arrdel(table, 0); + // Write to config file that is sent to the client. + fwrite(record->name, strlen(record->name) + 1, 1, f); + fwrite(record->data, strlen(record->data) + 1, 1, f); + DEL(record->name); + DEL(record->data); + DEL(record); + } + arrfree(table); + } + fclose(f); + } + + if (!_updateAllRunning) pthread_exit(NULL); + return NULL; +} + + +static void *updateFiles(void *data) { + char file[MAX_PATH] = { 0 }; + char *path = (char *)data; + int32_t result = 0; + + consoleMessageQueue("Updating file information starting from: %s\n", data == NULL ? "/" : (char *)data); + + if (!path) { + snprintf(file, MAX_PATH, "%s/files", __settingsFile); + } else { + if (path[0] == '/') { + snprintf(file, MAX_PATH, "%s/files%s", __settingsFile, path); + } else { + snprintf(file, MAX_PATH, "%s/files/%s", __settingsFile, path); + } + } + + if (dbUpdateFilesStart() == SUCCESS) { + result = nftw(file, updateFileEntry, 15, FTW_PHYS); + dbUpdateFilesFinish(); + } + + if (!_updateAllRunning) pthread_exit(NULL); + return NULL; +} + + +static int32_t updateFileEntry(const char *filepath, const struct stat *info, const int typeflag, struct FTW *pathinfo) { + // const char *const filename = filepath + pathinfo->base; + const double bytes = (double)info->st_size; // Not exact if large! + struct tm mtime; + char *target; + size_t maxlen = MAX_PATH; + ssize_t len; + char time[64]; + + (void)pathinfo; + + localtime_r(&(info->st_mtime), &mtime); + + snprintf(time, 64, "%04d-%02d-%02d %02d:%02d:%02d", + mtime.tm_year + 1900, mtime.tm_mon + 1, mtime.tm_mday, + mtime.tm_hour, mtime.tm_min, mtime.tm_sec); + + consoleMessageQueue("%s", time); + + if (bytes >= 1099511627776.0) { + consoleMessageQueue(" %9.3f TiB", bytes / 1099511627776.0); + } else { + if (bytes >= 1073741824.0) { + consoleMessageQueue(" %9.3f GiB", bytes / 1073741824.0); + } else { + if (bytes >= 1048576.0) { + consoleMessageQueue(" %9.3f MiB", bytes / 1048576.0); + } else { + if (bytes >= 1024.0) { + consoleMessageQueue(" %9.3f KiB", bytes / 1024.0); + } else { + consoleMessageQueue(" %9.0f B ", bytes); + } + } + } + } + + if (typeflag == FTW_SL) { + while (1) { + target = malloc(maxlen + 1); + if (target == NULL) return ENOMEM; + len = readlink(filepath, target, maxlen); + if (len == (ssize_t) - 1) { + const int saved_errno = errno; + free(target); + return saved_errno; + } + if (len >= (ssize_t)maxlen) { + free(target); + maxlen += 1024; + continue; + } + target[len] = '\0'; + break; + } + consoleMessageQueue(" %s -> %s", filepath, target); + dbUpdateFileData((char *)filepath, (uint64_t)bytes, time); + free(target); + } else { + if (typeflag == FTW_SLN) { + consoleMessageQueue(" %s (dangling symlink)", filepath); + } else { + if (typeflag == FTW_F) { + consoleMessageQueue(" %s", filepath); + dbUpdateFileData((char *)filepath, (uint64_t)bytes, time); + } else { + if (typeflag == FTW_D || typeflag == FTW_DP) { + consoleMessageQueue(" %s/", filepath); + } else { + if (typeflag == FTW_DNR) { + consoleMessageQueue(" %s/ (unreadable)", filepath); + } else { + consoleMessageQueue(" %s (unknown)", filepath); + } + } + } + } + } + + consoleMessageQueue("\n"); + + return 0; +} + + +static void *updateGames(void *data) { + (void)data; + + consoleMessageQueue("Updating client game database.\n"); + + if (!_updateAllRunning) pthread_exit(NULL); + return NULL; +} diff --git a/server/src/update.h b/server/src/update.h new file mode 100644 index 0000000..7bb9a0b --- /dev/null +++ b/server/src/update.h @@ -0,0 +1,28 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * 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 3 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, see . + * + */ + + +#ifndef UPDATE_H +#define UPDATE_H + + +void update(char *params); + + +#endif // UPDATE_H