Major changes to how data is stored on the server. Client changes will follow.
This commit is contained in:
parent
ebea70dd0b
commit
049f81ed50
12 changed files with 671 additions and 183 deletions
|
@ -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
|
||||
|
|
95
schema.sql
95
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -22,7 +22,12 @@
|
|||
#include <pthread.h>
|
||||
#include <crypt.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
#define SUCCESS 1
|
||||
#define FAIL 0
|
||||
|
||||
#define MAX_PATH 1024
|
||||
|
||||
|
||||
// Declared in main.c
|
||||
extern uint8_t __running;
|
||||
|
|
262
server/src/update.c
Normal file
262
server/src/update.c
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include <ftw.h>
|
||||
#include <errno.h>
|
||||
#include <strings.h>
|
||||
|
||||
#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;
|
||||
}
|
28
server/src/update.h
Normal file
28
server/src/update.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef UPDATE_H
|
||||
#define UPDATE_H
|
||||
|
||||
|
||||
void update(char *params);
|
||||
|
||||
|
||||
#endif // UPDATE_H
|
Loading…
Add table
Reference in a new issue