aboutsummaryrefslogtreecommitdiff
path: root/src/debug.cpp
blob: 8647160b1bed7c20cae3dae6e035bfc23bef521e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include "porting.h"
#include "debug.h"
#include "exceptions.h"
#include "threads.h"
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <map>
#include <sstream>
#include "threading/mutex.h"
#include "threading/mutex_auto_lock.h"
#include "config.h"

#ifdef _MSC_VER
	#include <dbghelp.h>
	#include "version.h"
	#include "filesys.h"
#endif

#if USE_CURSES
	#include "terminal_chat_console.h"
#endif

/*
	Assert
*/

void sanity_check_fn(const char *assertion, const char *file,
		unsigned int line, const char *function)
{
#if USE_CURSES
	g_term_console.stopAndWaitforThread();
#endif

	errorstream << std::endl << "In thread " << std::hex
		<< thr_get_current_thread_id() << ":" << std::endl;
	errorstream << file << ":" << line << ": " << function
		<< ": An engine assumption '" << assertion << "' failed." << std::endl;

	debug_stacks_print_to(errorstream);

	abort();
}

void fatal_error_fn(const char *msg, const char *file,
		unsigned int line, const char *function)
{
#if USE_CURSES
	g_term_console.stopAndWaitforThread();
#endif

	errorstream << std::endl << "In thread " << std::hex
		<< thr_get_current_thread_id() << ":" << std::endl;
	errorstream << file << ":" << line << ": " << function
		<< ": A fatal error occured: " << msg << std::endl;

	debug_stacks_print_to(errorstream);

	abort();
}

/*
	DebugStack
*/

struct DebugStack
{
	DebugStack(threadid_t id);
	void print(FILE *file, bool everything);
	void print(std::ostream &os, bool everything);

	threadid_t threadid;
	char stack[DEBUG_STACK_SIZE][DEBUG_STACK_TEXT_SIZE];
	int stack_i; // Points to the lowest empty position
	int stack_max_i; // Highest i that was seen
};

DebugStack::DebugStack(threadid_t id)
{
	threadid = id;
	stack_i = 0;
	stack_max_i = 0;
	memset(stack, 0, DEBUG_STACK_SIZE*DEBUG_STACK_TEXT_SIZE);
}

void DebugStack::print(FILE *file, bool everything)
{
	std::ostringstream os;
	os << threadid;
	fprintf(file, "DEBUG STACK FOR THREAD %s:\n",
		os.str().c_str());

	for(int i=0; i<stack_max_i; i++)
	{
		if(i == stack_i && everything == false)
			break;

		if(i < stack_i)
			fprintf(file, "#%d  %s\n", i, stack[i]);
		else
			fprintf(file, "(Leftover data: #%d  %s)\n", i, stack[i]);
	}

	if(stack_i == DEBUG_STACK_SIZE)
		fprintf(file, "Probably overflown.\n");
}

void DebugStack::print(std::ostream &os, bool everything)
{
	os<<"DEBUG STACK FOR THREAD "<<threadid<<": "<<std::endl;

	for(int i=0; i<stack_max_i; i++)
	{
		if(i == stack_i && everything == false)
			break;

		if(i < stack_i)
			os<<"#"<<i<<"  "<<stack[i]<<std::endl;
		else
			os<<"(Leftover data: #"<<i<<"  "<<stack[i]<<")"<<std::endl;
	}

	if(stack_i == DEBUG_STACK_SIZE)
		os<<"Probably overflown."<<std::endl;
}

// Note:  Using pthread_t (that is, threadid_t on POSIX platforms) as the key to
// a std::map is naughty.  Formally, a pthread_t may only be compared using
// pthread_equal() - pthread_t lacks the well-ordered property needed for
// comparisons in binary searches.  This should be fixed at some point by
// defining a custom comparator with an arbitrary but stable ordering of
// pthread_t, but it isn't too important since none of our supported platforms
// implement pthread_t as a non-ordinal type.
std::map<threadid_t, DebugStack*> g_debug_stacks;
Mutex g_debug_stacks_mutex;

void debug_stacks_init()
{
}

void debug_stacks_print_to(std::ostream &os)
{
	MutexAutoLock lock(g_debug_stacks_mutex);

	os<<"Debug stacks:"<<std::endl;

	for(std::map<threadid_t, DebugStack*>::iterator
			i = g_debug_stacks.begin();
			i != g_debug_stacks.end(); ++i)
	{
		i->second->print(os, false);
	}
}

void debug_stacks_print()
{
	debug_stacks_print_to(errorstream);
}

DebugStacker::DebugStacker(const char *text)
{
	threadid_t threadid = thr_get_current_thread_id();

	MutexAutoLock lock(g_debug_stacks_mutex);

	std::map<threadid_t, DebugStack*>::iterator n;
	n = g_debug_stacks.find(threadid);
	if(n != g_debug_stacks.end())
	{
		m_stack = n->second;
	}
	else
	{
		/*DEBUGPRINT("Creating new debug stack for thread %x\n",
				(unsigned int)threadid);*/
		m_stack = new DebugStack(threadid);
		g_debug_stacks[threadid] = m_stack;
	}

	if(m_stack->stack_i >= DEBUG_STACK_SIZE)
	{
		m_overflowed = true;
	}
	else
	{
		m_overflowed = false;

		snprintf(m_stack->stack[m_stack->stack_i],
				DEBUG_STACK_TEXT_SIZE, "%s", text);
		m_stack->stack_i++;
		if(m_stack->stack_i > m_stack->stack_max_i)
			m_stack->stack_max_i = m_stack->stack_i;
	}
}

DebugStacker::~DebugStacker()
{
	MutexAutoLock lock(g_debug_stacks_mutex);

	if(m_overflowed == true)
		return;

	m_stack->stack_i--;

	if(m_stack->stack_i == 0)
	{
		threadid_t threadid = m_stack->threadid;
		/*DEBUGPRINT("Deleting debug stack for thread %x\n",
				(unsigned int)threadid);*/
		delete m_stack;
		g_debug_stacks.erase(threadid);
	}
}

#ifdef _MSC_VER

const char *Win32ExceptionCodeToString(DWORD exception_code)
{
	switch (exception_code) {
	case EXCEPTION_ACCESS_VIOLATION:
		return "Access violation";
	case EXCEPTION_DATATYPE_MISALIGNMENT:
		return "Misaligned data access";
	case EXCEPTION_BREAKPOINT:
		return "Breakpoint reached";
	case EXCEPTION_SINGLE_STEP:
		return "Single debug step";
	case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
		return "Array access out of bounds";
	case EXCEPTION_FLT_DENORMAL_OPERAND:
		return "Denormal floating point operand";
	case EXCEPTION_FLT_DIVIDE_BY_ZERO:
		return "Floating point division by zero";
	case EXCEPTION_FLT_INEXACT_RESULT:
		return "Inaccurate floating point result";
	case EXCEPTION_FLT_INVALID_OPERATION:
		return "Invalid floating point operation";
	case EXCEPTION_FLT_OVERFLOW:
		return "Floating point exponent overflow";
	case EXCEPTION_FLT_STACK_CHECK:
		return "Floating point stack overflow or underflow";
	case EXCEPTION_FLT_UNDERFLOW:
		return "Floating point exponent underflow";
	case EXCEPTION_INT_DIVIDE_BY_ZERO:
		return "Integer division by zero";
	case EXCEPTION_INT_OVERFLOW:
		return "Integer overflow";
	case EXCEPTION_PRIV_INSTRUCTION:
		return "Privileged instruction executed";
	case EXCEPTION_IN_PAGE_ERROR:
		return "Could not access or load page";
	case EXCEPTION_ILLEGAL_INSTRUCTION:
		return "Illegal instruction encountered";
	case EXCEPTION_NONCONTINUABLE_EXCEPTION:
		return "Attempted to continue after fatal exception";
	case EXCEPTION_STACK_OVERFLOW:
		return "Stack overflow";
	case EXCEPTION_INVALID_DISPOSITION:
		return "Invalid disposition returned to the exception dispatcher";
	case EXCEPTION_GUARD_PAGE:
		return "Attempted guard page access";
	case EXCEPTION_INVALID_HANDLE:
		return "Invalid handle";
	}

	return "Unknown exception";
}

long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo)
{
	char buf[512];
	MINIDUMP_EXCEPTION_INFORMATION mdei;
	MINIDUMP_USER_STREAM_INFORMATION mdusi;
	MINIDUMP_USER_STREAM mdus;
	bool minidump_created = false;

	std::string dumpfile = porting::path_user + DIR_DELIM PROJECT_NAME ".dmp";

	std::string version_str(PROJECT_NAME " ");
	version_str += g_version_hash;

	HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE,
		FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		goto minidump_failed;

	if (SetEndOfFile(hFile) == FALSE)
		goto minidump_failed;

	mdei.ClientPointers	   = NULL;
	mdei.ExceptionPointers = pExceptInfo;
	mdei.ThreadId		   = GetCurrentThreadId();

	mdus.Type       = CommentStreamA;
	mdus.BufferSize = version_str.size();
	mdus.Buffer     = (PVOID)version_str.c_str();

	mdusi.UserStreamArray = &mdus;
	mdusi.UserStreamCount = 1;

	if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
			MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE)
		goto minidump_failed;

	minidump_created = true;

minidump_failed:

	CloseHandle(hFile);

	DWORD excode = pExceptInfo->ExceptionRecord->ExceptionCode;
	_snprintf(buf, sizeof(buf),
		" >> === FATAL ERROR ===\n"
		" >> %s (Exception 0x%08X) at 0x%p\n",
		Win32ExceptionCodeToString(excode), excode,
		pExceptInfo->ExceptionRecord->ExceptionAddress);
	dstream << buf;

	if (minidump_created)
		dstream << " >> Saved dump to " << dumpfile << std::endl;
	else
		dstream << " >> Failed to save dump" << std::endl;

	return EXCEPTION_EXECUTE_HANDLER;
}

#endif

void debug_set_exception_handler()
{
#ifdef _MSC_VER
	SetUnhandledExceptionFilter(Win32ExceptionHandler);
#endif
}

e(m_database), "Failed to close database"); } /* * Map database */ MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir): Database_SQLite3(savedir, "map"), MapDatabase() { } MapDatabaseSQLite3::~MapDatabaseSQLite3() { FINALIZE_STATEMENT(m_stmt_read) FINALIZE_STATEMENT(m_stmt_write) FINALIZE_STATEMENT(m_stmt_list) FINALIZE_STATEMENT(m_stmt_delete) } void MapDatabaseSQLite3::createDatabase() { assert(m_database); // Pre-condition SQLOK(sqlite3_exec(m_database, "CREATE TABLE IF NOT EXISTS `blocks` (\n" " `pos` INT PRIMARY KEY,\n" " `data` BLOB\n" ");\n", NULL, NULL, NULL), "Failed to create database table"); } void MapDatabaseSQLite3::initStatements() { PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); #ifdef __ANDROID__ PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #else PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #endif PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); verbosestream << "ServerMap: SQLite3 database opened." << std::endl; } inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) { SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)), "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); } bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos) { verifyDatabase(); bindPos(m_stmt_delete, pos); bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE; sqlite3_reset(m_stmt_delete); if (!good) { warningstream << "deleteBlock: Block failed to delete " << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl; } return good; } bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data) { verifyDatabase(); #ifdef __ANDROID__ /** * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android, * deleting them and then inserting works. */ bindPos(m_stmt_read, pos); if (sqlite3_step(m_stmt_read) == SQLITE_ROW) { deleteBlock(pos); } sqlite3_reset(m_stmt_read); #endif bindPos(m_stmt_write, pos); SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL), "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block") sqlite3_reset(m_stmt_write); return true; } void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) { verifyDatabase(); bindPos(m_stmt_read, pos); if (sqlite3_step(m_stmt_read) != SQLITE_ROW) { sqlite3_reset(m_stmt_read); return; } const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); size_t len = sqlite3_column_bytes(m_stmt_read, 0); *block = (data) ? std::string(data, len) : ""; sqlite3_step(m_stmt_read); // We should never get more than 1 row, so ok to reset sqlite3_reset(m_stmt_read); } void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst) { verifyDatabase(); while (sqlite3_step(m_stmt_list) == SQLITE_ROW) dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); sqlite3_reset(m_stmt_list); } /* * Player Database */ PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir): Database_SQLite3(savedir, "players"), PlayerDatabase() { } PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3() { FINALIZE_STATEMENT(m_stmt_player_load) FINALIZE_STATEMENT(m_stmt_player_add) FINALIZE_STATEMENT(m_stmt_player_update) FINALIZE_STATEMENT(m_stmt_player_remove) FINALIZE_STATEMENT(m_stmt_player_list) FINALIZE_STATEMENT(m_stmt_player_add_inventory) FINALIZE_STATEMENT(m_stmt_player_add_inventory_items) FINALIZE_STATEMENT(m_stmt_player_remove_inventory) FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items) FINALIZE_STATEMENT(m_stmt_player_load_inventory) FINALIZE_STATEMENT(m_stmt_player_load_inventory_items) FINALIZE_STATEMENT(m_stmt_player_metadata_load) FINALIZE_STATEMENT(m_stmt_player_metadata_add) FINALIZE_STATEMENT(m_stmt_player_metadata_remove) }; void PlayerDatabaseSQLite3::createDatabase() { assert(m_database); // Pre-condition SQLOK(sqlite3_exec(m_database, "CREATE TABLE IF NOT EXISTS `player` (" "`name` VARCHAR(50) NOT NULL," "`pitch` NUMERIC(11, 4) NOT NULL," "`yaw` NUMERIC(11, 4) NOT NULL," "`posX` NUMERIC(11, 4) NOT NULL," "`posY` NUMERIC(11, 4) NOT NULL," "`posZ` NUMERIC(11, 4) NOT NULL," "`hp` INT NOT NULL," "`breath` INT NOT NULL," "`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," "`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," "PRIMARY KEY (`name`));", NULL, NULL, NULL), "Failed to create player table"); SQLOK(sqlite3_exec(m_database, "CREATE TABLE IF NOT EXISTS `player_metadata` (" " `player` VARCHAR(50) NOT NULL," " `metadata` VARCHAR(256) NOT NULL," " `value` TEXT," " PRIMARY KEY(`player`, `metadata`)," " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", NULL, NULL, NULL), "Failed to create player metadata table"); SQLOK(sqlite3_exec(m_database, "CREATE TABLE IF NOT EXISTS `player_inventories` (" " `player` VARCHAR(50) NOT NULL," " `inv_id` INT NOT NULL," " `inv_width` INT NOT NULL," " `inv_name` TEXT NOT NULL DEFAULT ''," " `inv_size` INT NOT NULL," " PRIMARY KEY(player, inv_id)," " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", NULL, NULL, NULL), "Failed to create player inventory table"); SQLOK(sqlite3_exec(m_database, "CREATE TABLE `player_inventory_items` (" " `player` VARCHAR(50) NOT NULL," " `inv_id` INT NOT NULL," " `slot_id` INT NOT NULL," " `item` TEXT NOT NULL DEFAULT ''," " PRIMARY KEY(player, inv_id, slot_id)," " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", NULL, NULL, NULL), "Failed to create player inventory items table"); } void PlayerDatabaseSQLite3::initStatements() { PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, " "`breath`" "FROM `player` WHERE `name` = ?") PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, " "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, " "`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, " "`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?") PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?") PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`") PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` " "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)") PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` " "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)") PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` " "WHERE `player` = ?") PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` " "WHERE `player` = ?") PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, " "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id") PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` " "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?") PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM " "`player_metadata` WHERE `player` = ?") PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` " "(`player`, `metadata`, `value`) VALUES (?, ?, ?)") PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` " "WHERE `player` = ?") verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl; } bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name) { verifyDatabase(); str_to_sqlite(m_stmt_player_load, 1, name); bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW); sqlite3_reset(m_stmt_player_load); return res; } void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) { PlayerSAO* sao = player->getPlayerSAO(); sanity_check(sao); const v3f &pos = sao->getBasePosition(); // Begin save in brace is mandatory if (!playerDataExists(player->getName())) { beginSave(); str_to_sqlite(m_stmt_player_add, 1, player->getName()); double_to_sqlite(m_stmt_player_add, 2, sao->getLookPitch()); double_to_sqlite(m_stmt_player_add, 3, sao->getRotation().Y); double_to_sqlite(m_stmt_player_add, 4, pos.X); double_to_sqlite(m_stmt_player_add, 5, pos.Y); double_to_sqlite(m_stmt_player_add, 6, pos.Z); int64_to_sqlite(m_stmt_player_add, 7, sao->getHP()); int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath()); sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE); sqlite3_reset(m_stmt_player_add); } else { beginSave(); double_to_sqlite(m_stmt_player_update, 1, sao->getLookPitch()); double_to_sqlite(m_stmt_player_update, 2, sao->getRotation().Y); double_to_sqlite(m_stmt_player_update, 3, pos.X); double_to_sqlite(m_stmt_player_update, 4, pos.Y); double_to_sqlite(m_stmt_player_update, 5, pos.Z); int64_to_sqlite(m_stmt_player_update, 6, sao->getHP()); int64_to_sqlite(m_stmt_player_update, 7, sao->getBreath()); str_to_sqlite(m_stmt_player_update, 8, player->getName()); sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE); sqlite3_reset(m_stmt_player_update); } // Write player inventories str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName()); sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove_inventory); str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName()); sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove_inventory_items); std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory, 2, i); int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth()); str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName()); int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize()); sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE); sqlite3_reset(m_stmt_player_add_inventory); for (u32 j = 0; j < list->getSize(); j++) { std::ostringstream os; list->getItem(j).serialize(os); std::string itemStr = os.str(); str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); int_to_sqlite(m_stmt_player_add_inventory_items, 3, j); str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr); sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE); sqlite3_reset(m_stmt_player_add_inventory_items); } } str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName()); sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE); sqlite3_reset(m_stmt_player_metadata_remove); const StringMap &attrs = sao->getMeta().getStrings(); for (const auto &attr : attrs) { str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName()); str_to_sqlite(m_stmt_player_metadata_add, 2, attr.first); str_to_sqlite(m_stmt_player_metadata_add, 3, attr.second); sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE); sqlite3_reset(m_stmt_player_metadata_add); } endSave(); player->onSuccessfulSave(); } bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) { verifyDatabase(); str_to_sqlite(m_stmt_player_load, 1, player->getName()); if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) { sqlite3_reset(m_stmt_player_load); return false; } sao->setLookPitch(sqlite_to_float(m_stmt_player_load, 0)); sao->setPlayerYaw(sqlite_to_float(m_stmt_player_load, 1)); sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2)); sao->setHPRaw((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), U16_MAX)); sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false); sqlite3_reset(m_stmt_player_load); // Load inventory str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName()); while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) { InventoryList *invList = player->inventory.addList( sqlite_to_string(m_stmt_player_load_inventory, 2), sqlite_to_uint(m_stmt_player_load_inventory, 3)); invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1)); u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0); str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName()); int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId); while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) { const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1); if (itemStr.length() > 0) { ItemStack stack; stack.deSerialize(itemStr); invList->changeItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack); } } sqlite3_reset(m_stmt_player_load_inventory_items); } sqlite3_reset(m_stmt_player_load_inventory); str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName()); while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) { std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0); std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1); sao->getMeta().setString(attr, value); } sao->getMeta().setModified(false); sqlite3_reset(m_stmt_player_metadata_load); return true; } bool PlayerDatabaseSQLite3::removePlayer(const std::string &name) { if (!playerDataExists(name)) return false; str_to_sqlite(m_stmt_player_remove, 1, name); sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove); return true; } void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res) { verifyDatabase(); while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW) res.push_back(sqlite_to_string(m_stmt_player_list, 0)); sqlite3_reset(m_stmt_player_list); } /* * Auth database */ AuthDatabaseSQLite3::AuthDatabaseSQLite3(const std::string &savedir) : Database_SQLite3(savedir, "auth"), AuthDatabase() { } AuthDatabaseSQLite3::~AuthDatabaseSQLite3() { FINALIZE_STATEMENT(m_stmt_read) FINALIZE_STATEMENT(m_stmt_write) FINALIZE_STATEMENT(m_stmt_create) FINALIZE_STATEMENT(m_stmt_delete) FINALIZE_STATEMENT(m_stmt_list_names) FINALIZE_STATEMENT(m_stmt_read_privs) FINALIZE_STATEMENT(m_stmt_write_privs) FINALIZE_STATEMENT(m_stmt_delete_privs) FINALIZE_STATEMENT(m_stmt_last_insert_rowid) } void AuthDatabaseSQLite3::createDatabase() { assert(m_database); // Pre-condition SQLOK(sqlite3_exec(m_database, "CREATE TABLE IF NOT EXISTS `auth` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT," "`name` VARCHAR(32) UNIQUE," "`password` VARCHAR(512)," "`last_login` INTEGER" ");", NULL, NULL, NULL), "Failed to create auth table"); SQLOK(sqlite3_exec(m_database, "CREATE TABLE IF NOT EXISTS `user_privileges` (" "`id` INTEGER," "`privilege` VARCHAR(32)," "PRIMARY KEY (id, privilege)" "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE" ");", NULL, NULL, NULL), "Failed to create auth privileges table"); } void AuthDatabaseSQLite3::initStatements() { PREPARE_STATEMENT(read, "SELECT id, name, password, last_login FROM auth WHERE name = ?"); PREPARE_STATEMENT(write, "UPDATE auth set name = ?, password = ?, last_login = ? WHERE id = ?"); PREPARE_STATEMENT(create, "INSERT INTO auth (name, password, last_login) VALUES (?, ?, ?)"); PREPARE_STATEMENT(delete, "DELETE FROM auth WHERE name = ?"); PREPARE_STATEMENT(list_names, "SELECT name FROM auth ORDER BY name DESC"); PREPARE_STATEMENT(read_privs, "SELECT privilege FROM user_privileges WHERE id = ?"); PREPARE_STATEMENT(write_privs, "INSERT OR IGNORE INTO user_privileges (id, privilege) VALUES (?, ?)"); PREPARE_STATEMENT(delete_privs, "DELETE FROM user_privileges WHERE id = ?"); PREPARE_STATEMENT(last_insert_rowid, "SELECT last_insert_rowid()"); } bool AuthDatabaseSQLite3::getAuth(const std::string &name, AuthEntry &res) { verifyDatabase(); str_to_sqlite(m_stmt_read, 1, name); if (sqlite3_step(m_stmt_read) != SQLITE_ROW) { sqlite3_reset(m_stmt_read); return false; } res.id = sqlite_to_uint(m_stmt_read, 0); res.name = sqlite_to_string(m_stmt_read, 1); res.password = sqlite_to_string(m_stmt_read, 2); res.last_login = sqlite_to_int64(m_stmt_read, 3); sqlite3_reset(m_stmt_read); int64_to_sqlite(m_stmt_read_privs, 1, res.id); while (sqlite3_step(m_stmt_read_privs) == SQLITE_ROW) { res.privileges.emplace_back(sqlite_to_string(m_stmt_read_privs, 0)); } sqlite3_reset(m_stmt_read_privs); return true; } bool AuthDatabaseSQLite3::saveAuth(const AuthEntry &authEntry) { beginSave(); str_to_sqlite(m_stmt_write, 1, authEntry.name); str_to_sqlite(m_stmt_write, 2, authEntry.password); int64_to_sqlite(m_stmt_write, 3, authEntry.last_login); int64_to_sqlite(m_stmt_write, 4, authEntry.id); sqlite3_vrfy(sqlite3_step(m_stmt_write), SQLITE_DONE); sqlite3_reset(m_stmt_write); writePrivileges(authEntry); endSave(); return true; } bool AuthDatabaseSQLite3::createAuth(AuthEntry &authEntry) { beginSave(); // id autoincrements str_to_sqlite(m_stmt_create, 1, authEntry.name); str_to_sqlite(m_stmt_create, 2, authEntry.password); int64_to_sqlite(m_stmt_create, 3, authEntry.last_login); sqlite3_vrfy(sqlite3_step(m_stmt_create), SQLITE_DONE); sqlite3_reset(m_stmt_create); // obtain id and write back to original authEntry sqlite3_step(m_stmt_last_insert_rowid); authEntry.id = sqlite_to_uint(m_stmt_last_insert_rowid, 0); sqlite3_reset(m_stmt_last_insert_rowid); writePrivileges(authEntry); endSave(); return true; } bool AuthDatabaseSQLite3::deleteAuth(const std::string &name) { verifyDatabase(); str_to_sqlite(m_stmt_delete, 1, name); sqlite3_vrfy(sqlite3_step(m_stmt_delete), SQLITE_DONE); int changes = sqlite3_changes(m_database); sqlite3_reset(m_stmt_delete); // privileges deleted by foreign key on delete cascade return changes > 0; } void AuthDatabaseSQLite3::listNames(std::vector<std::string> &res) { verifyDatabase(); while (sqlite3_step(m_stmt_list_names) == SQLITE_ROW) { res.push_back(sqlite_to_string(m_stmt_list_names, 0)); } sqlite3_reset(m_stmt_list_names); } void AuthDatabaseSQLite3::reload() { // noop for SQLite } void AuthDatabaseSQLite3::writePrivileges(const AuthEntry &authEntry) { int64_to_sqlite(m_stmt_delete_privs, 1, authEntry.id); sqlite3_vrfy(sqlite3_step(m_stmt_delete_privs), SQLITE_DONE); sqlite3_reset(m_stmt_delete_privs); for (const std::string &privilege : authEntry.privileges) { int64_to_sqlite(m_stmt_write_privs, 1, authEntry.id); str_to_sqlite(m_stmt_write_privs, 2, privilege); sqlite3_vrfy(sqlite3_step(m_stmt_write_privs), SQLITE_DONE); sqlite3_reset(m_stmt_write_privs); } }