From 80dabf4406e29360acfa52e62214151a91cc13bb Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 18 Dec 2020 00:17:35 +0100 Subject: [PATCH] Finalize ALL the statements * Fixed not being able to modify the DB externally while the script is running * Made most DB-related errors print the appropriate error string * Fixed the settings looking for dbsaveinterval in the shard category instead of the login category * Added -shr and -wal to .gitignore (even though we're not actually using WAL mode) Hopefully the DB code is now free of resource leaks and undefined behaviour. --- .gitignore | 2 + src/Database.cpp | 117 +++++++++++++++++++++++++++++++++++------------ src/settings.cpp | 2 +- 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index c109250..7ce449c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ build/ .vs/ .idea/ *.db +*-shm +*-wal version.h infer-out gmon.out diff --git a/src/Database.cpp b/src/Database.cpp index f77727f..ed7cd17 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -31,6 +31,10 @@ void Database::open() { // foreign keys in sqlite are off by default; enable them sqlite3_exec(db, "PRAGMA foreign_keys=ON;", NULL, NULL, NULL); + + // just in case a DB operation collides with an external manual modification + sqlite3_busy_timeout(db, 2000); + checkMetaTable(); createTables(); @@ -63,13 +67,14 @@ void Database::checkMetaTable() { sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (sqlite3_step(stmt) != SQLITE_ROW) { + std::cout << "[FATAL] Failed to check meta table" << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); - std::cout << "[FATAL] Failed to check meta table" << std::endl; exit(1); } int count = sqlite3_column_int(stmt, 0); if (count == 0) { + sqlite3_finalize(stmt); // check if there's other non-internal tables first sql = R"( SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'; @@ -86,6 +91,8 @@ void Database::checkMetaTable() { return createMetaTable(); } + sqlite3_finalize(stmt); + // check protocol version sql = R"( SELECT Value FROM Meta WHERE Key = 'ProtocolVersion'; @@ -93,8 +100,8 @@ void Database::checkMetaTable() { sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (sqlite3_step(stmt) != SQLITE_ROW) { + std::cout << "[FATAL] Failed to check DB Protocol Version: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); - std::cout << "[FATAL] Failed to check DB Protocol Version" << std::endl; exit(1); } @@ -104,13 +111,15 @@ void Database::checkMetaTable() { exit(1); } + sqlite3_finalize(stmt); + sql = R"( SELECT Value FROM Meta WHERE Key = 'DatabaseVersion'; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (sqlite3_step(stmt) != SQLITE_ROW) { - std::cout << "[FATAL] Failed to check DB Version" << std::endl; + std::cout << "[FATAL] Failed to check DB Version: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); exit(1); } @@ -140,11 +149,12 @@ void Database::createMetaTable() { sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); - std::cout << "[FATAL] Failed to create meta table" << std::endl; exit(1); } + sqlite3_finalize(stmt); sql = R"( INSERT INTO Meta (Key, Value) @@ -155,9 +165,9 @@ void Database::createMetaTable() { sqlite3_bind_int(stmt, 2, PROTOCOL_VERSION); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); - std::cout << "[FATAL] Failed to create meta table" << std::endl; exit(1); } @@ -166,10 +176,9 @@ void Database::createMetaTable() { sqlite3_bind_int(stmt, 2, DATABASE_VERSION); int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); - if (rc != SQLITE_DONE) { + std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); - std::cout << "[FATAL] Failed to create meta table" << std::endl; exit(1); } @@ -370,6 +379,7 @@ void Database::findAccount(Account* account, std::string login) { sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_text(stmt, 1, login.c_str(), -1, NULL); + int rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { account->AccountID = sqlite3_column_int(stmt, 0); @@ -394,13 +404,14 @@ int Database::addAccount(std::string login, std::string password) { std::string hashedPassword = BCrypt::generateHash(password); sqlite3_bind_text(stmt, 2, hashedPassword.c_str(), -1, NULL); sqlite3_bind_int(stmt, 3, settings::ACCLEVEL); + int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); - if (rc != SQLITE_DONE) { std::cout << "[WARN] Database: failed to add new account" << std::endl; return 0; } + return sqlite3_last_insert_rowid(db); } @@ -419,7 +430,7 @@ void Database::banAccount(int accountId, int days) { sqlite3_bind_int(stmt, 1, days * 86400); // convert days to seconds sqlite3_bind_int(stmt, 2, accountId); if (sqlite3_step(stmt) != SQLITE_DONE) { - std::cout << "[WARN] Database: failed to ban player" << std::endl; + std::cout << "[WARN] Database: failed to ban player: " << sqlite3_errmsg(db) << std::endl; } sqlite3_finalize(stmt); } @@ -448,7 +459,7 @@ void Database::updateSelected(int accountId, int slot) { sqlite3_finalize(stmt); if (rc != SQLITE_DONE) - std::cout << "[WARN] Database fail on updateSelected(). Error Code " << rc << std::endl; + std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl; } bool Database::validateCharacter(int characterID, int userID) { @@ -498,7 +509,7 @@ bool Database::isSlotFree(int accountId, int slotNum) { std::lock_guard lock(dbCrit); if (slotNum < 1 || slotNum > 4) { - std::cout << "[WARN] Invalid slot number passed to isSlotFree()! "<PCStyle.iHeight = sqlite3_column_int(stmt, 34); plr->PCStyle.iSkinColor = sqlite3_column_int(stmt, 35); + sqlite3_finalize(stmt); + // get inventory sql = R"( SELECT Slot, Type, ID, Opt, TimeLimit @@ -1005,6 +1030,8 @@ void Database::getPlayer(Player* plr, int id) { item->iTimeLimit = sqlite3_column_int(stmt, 4); } + sqlite3_finalize(stmt); + Database::removeExpiredVehicles(plr); // get quest inventory @@ -1027,6 +1054,7 @@ void Database::getPlayer(Player* plr, int id) { item->iOpt = sqlite3_column_int(stmt, 2); } + sqlite3_finalize(stmt); // get nanos sql = R"( @@ -1051,6 +1079,8 @@ void Database::getPlayer(Player* plr, int id) { nano->iStamina = sqlite3_column_int(stmt, 2); } + sqlite3_finalize(stmt); + // get active quests sql = R"( SELECT @@ -1074,6 +1104,8 @@ void Database::getPlayer(Player* plr, int id) { i++; } + sqlite3_finalize(stmt); + // get buddies sql = R"( SELECT PlayerAID, PlayerBID @@ -1095,6 +1127,8 @@ void Database::getPlayer(Player* plr, int id) { i++; } + sqlite3_finalize(stmt); + // get blocked players sql = R"( SELECT BlockedPlayerID FROM Blocks @@ -1165,12 +1199,14 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 21, player->iID); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } + sqlite3_finalize(stmt); + // update inventory sql = R"( DELETE FROM Inventory WHERE PlayerID = ?; @@ -1179,6 +1215,8 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 1, player->iID); int rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + sql = R"( INSERT INTO Inventory (PlayerID, Slot, Type, Opt, ID, Timelimit) @@ -1200,9 +1238,9 @@ void Database::updatePlayer(Player *player) { rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } sqlite3_reset(stmt); @@ -1220,9 +1258,9 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } sqlite3_reset(stmt); @@ -1240,14 +1278,15 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } sqlite3_reset(stmt); } + sqlite3_finalize(stmt); // Update Quest Inventory sql = R"( @@ -1257,6 +1296,8 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 1, player->iID); sqlite3_step(stmt); + sqlite3_finalize(stmt); + sql = R"( INSERT INTO QuestItems (PlayerID, Slot, Opt, ID) VALUES (?, ?, ?, ?); @@ -1273,14 +1314,16 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 4, player->QInven[i].iID); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } sqlite3_reset(stmt); } + sqlite3_finalize(stmt); + // Update Nanos sql = R"( DELETE FROM Nanos WHERE PlayerID = ?; @@ -1289,6 +1332,8 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 1, player->iID); sqlite3_step(stmt); + sqlite3_finalize(stmt); + sql = R"( INSERT INTO Nanos (PlayerID, ID, SKill, Stamina) VALUES (?, ?, ?, ?); @@ -1305,14 +1350,16 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } sqlite3_reset(stmt); } + sqlite3_finalize(stmt); + // Update Running Quests sql = R"( DELETE FROM RunningQuests WHERE PlayerID = ?; @@ -1321,6 +1368,8 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 1, player->iID); sqlite3_step(stmt); + sqlite3_finalize(stmt); + sql = R"( INSERT INTO RunningQuests (PlayerID, TaskID, RemainingNPCCount1, RemainingNPCCount2, RemainingNPCCount3) @@ -1338,9 +1387,9 @@ void Database::updatePlayer(Player *player) { sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]); if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); - std::cout << "[WARN] Database: Failed to save player to database" << std::endl; return; } sqlite3_reset(stmt); @@ -1401,6 +1450,8 @@ int Database::getNumBuddies(Player* player) { sqlite3_step(stmt); int result = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + sql = R"( SELECT COUNT(*) FROM Blocks @@ -1430,7 +1481,7 @@ void Database::addBuddyship(int playerA, int playerB) { sqlite3_bind_int(stmt, 2, playerB); if (sqlite3_step(stmt) != SQLITE_DONE) - std::cout << "[WARN] Database: failed to add buddyship" << std::endl; + std::cout << "[WARN] Database: failed to add buddyship: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); } @@ -1466,7 +1517,7 @@ void Database::addBlock(int playerId, int blockedPlayerId) { sqlite3_bind_int(stmt, 2, blockedPlayerId); if (sqlite3_step(stmt) != SQLITE_DONE) - std::cout << "[WARN] Database: failed to block player" << std::endl; + std::cout << "[WARN] Database: failed to block player: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); } @@ -1496,7 +1547,11 @@ int Database::getUnreadEmailCount(int playerID) { sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, playerID); sqlite3_step(stmt); - return sqlite3_column_int(stmt, 0); + int ret = sqlite3_column_int(stmt, 0); + + sqlite3_finalize(stmt); + + return ret; } std::vector Database::getEmails(int playerID, int page) { @@ -1612,6 +1667,7 @@ sItemBase* Database::getEmailAttachments(int playerID, int index) { items[slot].iTimeLimit = sqlite3_column_int(stmt, 4); } + sqlite3_finalize(stmt); return items; } @@ -1632,6 +1688,8 @@ void Database::updateEmailContent(EmailData* data) { data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically + sqlite3_finalize(stmt); + sql = R"( UPDATE EmailData SET @@ -1666,7 +1724,7 @@ void Database::updateEmailContent(EmailData* data) { sqlite3_bind_int(stmt, 14, data->MsgIndex); if (sqlite3_step(stmt) != SQLITE_DONE) - std::cout << "[WARN] Database: failed to update email" << std::endl; + std::cout << "[WARN] Database: failed to update email: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); } @@ -1692,7 +1750,7 @@ void Database::deleteEmailAttachments(int playerID, int index, int slot) { sqlite3_bind_int(stmt, 3, slot); if (sqlite3_step(stmt) != SQLITE_DONE) - std::cout << "[WARN] Database: Failed to delete email attachments" << std::endl; + std::cout << "[WARN] Database: Failed to delete email attachments: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); } @@ -1712,7 +1770,7 @@ void Database::deleteEmails(int playerID, int64_t* indices) { sqlite3_bind_int(stmt, 1, playerID); sqlite3_bind_int64(stmt, 2, indices[i]); if (sqlite3_step(stmt) != SQLITE_DONE) { - std::cout << "[WARN] Database: Failed to delete an emil" << std::endl; + std::cout << "[WARN] Database: Failed to delete an email: " << sqlite3_errmsg(db) << std::endl; } sqlite3_reset(stmt); } @@ -1737,6 +1795,7 @@ int Database::getNextEmailIndex(int playerID) { sqlite3_step(stmt); int index = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); return (index > 0 ? index + 1 : 1); } @@ -1769,12 +1828,14 @@ bool Database::sendEmail(EmailData* data, std::vector attachments) { sqlite3_bind_int64(stmt, 12, data->DeleteTime); if (sqlite3_step(stmt) != SQLITE_DONE) { - std::cout << "[WARN] Database: Failed to send email" << std::endl; + std::cout << "[WARN] Database: Failed to send email: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); return false; } + sqlite3_finalize(stmt); + // send attachments int slot = 1; for (sItemBase item : attachments) { @@ -1795,7 +1856,7 @@ bool Database::sendEmail(EmailData* data, std::vector attachments) { sqlite3_bind_int(stmt, 7, item.iTimeLimit); if (sqlite3_step(stmt) != SQLITE_DONE) { - std::cout << "[WARN] Database: Failed to send email" << std::endl; + std::cout << "[WARN] Database: Failed to send email: " << sqlite3_errmsg(db) << std::endl; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_finalize(stmt); return false; diff --git a/src/settings.cpp b/src/settings.cpp index 56db5c3..0cb5950 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -57,8 +57,8 @@ void settings::init() { VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT); + DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1"); - DBSAVEINTERVAL = reader.GetInteger("shard", "dbsaveinterval", DBSAVEINTERVAL); TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT); VIEWDISTANCE = reader.GetInteger("shard", "viewdistance", VIEWDISTANCE); SIMULATEMOBS = reader.GetBoolean("shard", "simulatemobs", SIMULATEMOBS);