Major changes to how data is stored on the server. Client changes will follow.

This commit is contained in:
Scott Duensing 2022-03-21 20:14:21 -05:00
parent ebea70dd0b
commit 049f81ed50
12 changed files with 671 additions and 183 deletions

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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) {

View file

@ -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;

View file

@ -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("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");

View file

@ -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,101 +89,162 @@ uint8_t dbDisconnect(void) {
}
uint8_t dbFileRealPathGet(char *vpath, char *value, uint32_t max) {
static DbFileInfoT *dbFileInfoGet(char *vpath) {
char statement[STATEMENT_MAX];
char *p = statement;
MYSQL_RES *result = NULL;
MYSQL_ROW row;
int count;
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;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
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);
DbFileInfoT *info = dbFileInfoGet(vpath);
if (info) {
strncpy(value, info->sha, max);
dbFileInfoRelease(&info);
return SUCCESS;
}
return FAIL;
}
uint8_t dbSettingsStringGet(char *host, char *key, char *value, uint32_t max) {
char statement[STATEMENT_MAX];
@ -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;

View file

@ -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);

View file

@ -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("");
}

View file

@ -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
View 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
View 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