8d5b217266
r4169 | acceptthis | 2013-01-17 08:55:12 +0000 (Thu, 17 Jan 2013) | 31 lines removed MAX_VISEDICTS limit. PEXT2_REPLACEMENTDELTAS tweaked, now has 4 million entity limit. still not enabled by default. TE_BEAM now maps to a separate TEQW_BEAM to avoid conflicts with QW. added android multitouch emulation for windows/rawinput (in_simulatemultitouch). split topcolor/bottomcolor from scoreboard, for dp's colormap|1024 feature. now using utf-8 for windows consoles. qcc warnings/errors now give clickable console links for quick+easy editing. disabled menutint when the currently active item changes contrast or gamma (for OneManClan). Added support for drawfont/drawfontscale. tweaked the qcvm a little to reduce the number of pointers. .doll file loading. still experimental and will likely crash. requires csqc active, even if its a dummy progs. this will be fixed in time. Still other things that need cleaning up. windows: gl_font "?" shows the standard windows font-selection dialog, and can be used to select windows fonts. not all work. and you probably don't want to use windings. fixed splitscreen support when playing mvds. added mini-scoreboards to splitscreen. editor/debugger now shows asm if there's no linenumber info. also, pressing f1 for help shows the shortcuts. Added support for .framegroups files for psk(psa) and iqm formats. True support for ezquake's colour codes. Mutually exclusive with background colours. path command output slightly more readable. added support for digest_hex (MD4, SHA1, CRC16). skingroups now colourmap correctly. Fix terrain colour hints, and litdata from the wrong bsp. fix ftp dual-homed issue. support epsv command, and enable ipv6 (eprt still not supported). remove d3d11 compilation from the makefile. the required headers are not provided by mingw, and are not available to the build bot, so don't bother. fix v *= v.x and similar opcodes. fteqcc: fixed support for áéíóú type chars in names. utf-8 files now properly supported (even with the utf-8 bom/identifier). utf-16 also supported. fteqcc: fixed '#if 1 == 3 && 4' parsing. fteqcc: -Werror acts on the warning, rather than as a separate error. Line numbers are thus more readable. fteqcc: copyright message now includes compile date instead. fteqccgui: the treeview control is now coloured depending on whether there were warnings/errors in the last compile. fteqccgui: the output window is now focused and scrolls down as compilation progresses. pr_dumpplatform command dumps out some pragmas to convert more serious warnings to errors. This is to avoid the infamous 'fteqcc sucks cos my code sucks' issue. rewrote prespawn/modelist/soundlist code. server tracks progress now. ------------------------------------------------------------------------ git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4167 fc73d0e0-1445-4013-8a0c-d673dee63da5
1215 lines
31 KiB
C
1215 lines
31 KiB
C
#include "quakedef.h"
|
|
|
|
//SQLITE:
|
|
//should probably be compiled with -DSQLITE_OMIT_ATTACH -DSQLITE_OMIT_LOAD_EXTENSION which would mean we don't need to override authorisation.
|
|
|
|
#ifdef SQL
|
|
#include "sv_sql.h"
|
|
|
|
#ifdef USE_MYSQL
|
|
static void (VARGS *qmysql_library_end)(void);
|
|
static int (VARGS *qmysql_library_init)(int argc, char **argv, char **groups);
|
|
|
|
static my_ulonglong (VARGS *qmysql_affected_rows)(MYSQL *mysql);
|
|
static void (VARGS *qmysql_close)(MYSQL *sock);
|
|
static void (VARGS *qmysql_data_seek)(MYSQL_RES *result, my_ulonglong offset);
|
|
static unsigned int (VARGS *qmysql_errno)(MYSQL *mysql);
|
|
static const char *(VARGS *qmysql_error)(MYSQL *mysql);
|
|
static MYSQL_FIELD *(VARGS *qmysql_fetch_field_direct)(MYSQL_RES *res, unsigned int fieldnr);
|
|
static MYSQL_ROW (VARGS *qmysql_fetch_row)(MYSQL_RES *result);
|
|
static unsigned int (VARGS *qmysql_field_count)(MYSQL *mysql);
|
|
static void (VARGS *qmysql_free_result)(MYSQL_RES *result);
|
|
static const char *(VARGS *qmysql_get_client_info)(void);
|
|
static MYSQL *(VARGS *qmysql_init)(MYSQL *mysql);
|
|
static MYSQL_RES *(VARGS *qmysql_store_result)(MYSQL *mysql);
|
|
static unsigned int (VARGS *qmysql_num_fields)(MYSQL_RES *res);
|
|
static my_ulonglong (VARGS *qmysql_num_rows)(MYSQL_RES *res);
|
|
static int (VARGS *qmysql_options)(MYSQL *mysql, enum mysql_option option, const char *arg);
|
|
static int (VARGS *qmysql_query)(MYSQL *mysql, const char *q);
|
|
static MYSQL *(VARGS *qmysql_real_connect)(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag);
|
|
static unsigned long (VARGS *qmysql_real_escape_string)(MYSQL *mysql, char *to, const char *from, unsigned long length);
|
|
static void (VARGS *qmysql_thread_end)(void);
|
|
static my_bool (VARGS *qmysql_thread_init)(void);
|
|
static unsigned int (VARGS *qmysql_thread_safe)(void);
|
|
|
|
static dllfunction_t mysqlfuncs[] =
|
|
{
|
|
{(void*)&qmysql_library_end, "mysql_server_end"}, /* written as a define alias in mysql.h */
|
|
{(void*)&qmysql_library_init, "mysql_server_init"}, /* written as a define alias in mysql.h */
|
|
{(void*)&qmysql_affected_rows, "mysql_affected_rows"},
|
|
{(void*)&qmysql_close, "mysql_close"},
|
|
{(void*)&qmysql_data_seek, "mysql_data_seek"},
|
|
{(void*)&qmysql_errno, "mysql_errno"},
|
|
{(void*)&qmysql_error, "mysql_error"},
|
|
{(void*)&qmysql_fetch_field_direct, "mysql_fetch_field_direct"},
|
|
{(void*)&qmysql_fetch_row, "mysql_fetch_row"},
|
|
{(void*)&qmysql_field_count, "mysql_field_count"},
|
|
{(void*)&qmysql_free_result, "mysql_free_result"},
|
|
{(void*)&qmysql_get_client_info, "mysql_get_client_info"},
|
|
{(void*)&qmysql_init, "mysql_init"},
|
|
{(void*)&qmysql_store_result, "mysql_store_result"},
|
|
{(void*)&qmysql_num_fields, "mysql_num_fields"},
|
|
{(void*)&qmysql_num_rows, "mysql_num_rows"},
|
|
{(void*)&qmysql_options, "mysql_options"},
|
|
{(void*)&qmysql_query, "mysql_query"},
|
|
{(void*)&qmysql_real_connect, "mysql_real_connect"},
|
|
{(void*)&qmysql_real_escape_string, "mysql_real_escape_string"},
|
|
{(void*)&qmysql_thread_end, "mysql_thread_end"},
|
|
{(void*)&qmysql_thread_init, "mysql_thread_init"},
|
|
{(void*)&qmysql_thread_safe, "mysql_thread_safe"},
|
|
{NULL}
|
|
};
|
|
dllhandle_t *mysqlhandle;
|
|
#endif
|
|
|
|
#ifdef USE_SQLITE
|
|
#include "sqlite3.h"
|
|
SQLITE_API int (QDECL *qsqlite3_open)(const char *zFilename, sqlite3 **ppDb);
|
|
SQLITE_API const char *(QDECL *qsqlite3_libversion)(void);
|
|
SQLITE_API int (QDECL *qsqlite3_set_authorizer)(sqlite3*, int (QDECL *xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pUserData);
|
|
SQLITE_API int (QDECL *qsqlite3_enable_load_extension)(sqlite3 *db, int onoff);
|
|
SQLITE_API const char *(QDECL *qsqlite3_errmsg)(sqlite3 *db);
|
|
SQLITE_API int (QDECL *qsqlite3_close)(sqlite3 *db);
|
|
|
|
SQLITE_API int (QDECL *qsqlite3_prepare)(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail);
|
|
SQLITE_API int (QDECL *qsqlite3_column_count)(sqlite3_stmt *pStmt);
|
|
SQLITE_API const char *(QDECL *qsqlite3_column_name)(sqlite3_stmt *pStmt, int N);
|
|
SQLITE_API int (QDECL *qsqlite3_step)(sqlite3_stmt *pStmt);
|
|
SQLITE_API const unsigned char *(QDECL *qsqlite3_column_text)(sqlite3_stmt *pStmt, int i);
|
|
SQLITE_API int (QDECL *qsqlite3_finalize)(sqlite3_stmt *pStmt);
|
|
|
|
static dllfunction_t sqlitefuncs[] =
|
|
{
|
|
{(void*)&qsqlite3_open, "sqlite3_open"}, /* written as a define alias in mysql.h */
|
|
{(void*)&qsqlite3_libversion, "sqlite3_libversion"}, /* written as a define alias in mysql.h */
|
|
{(void*)&qsqlite3_set_authorizer, "sqlite3_set_authorizer"},
|
|
{(void*)&qsqlite3_enable_load_extension, "sqlite3_enable_load_extension"},
|
|
{(void*)&qsqlite3_errmsg, "sqlite3_errmsg"},
|
|
{(void*)&qsqlite3_close, "sqlite3_close"},
|
|
|
|
{(void*)&qsqlite3_prepare, "sqlite3_prepare"},
|
|
{(void*)&qsqlite3_column_count, "sqlite3_column_count"},
|
|
{(void*)&qsqlite3_column_name, "sqlite3_column_name"},
|
|
{(void*)&qsqlite3_step, "sqlite3_step"},
|
|
{(void*)&qsqlite3_column_text, "sqlite3_column_text"},
|
|
{(void*)&qsqlite3_finalize, "sqlite3_finalize"},
|
|
{NULL}
|
|
};
|
|
dllhandle_t *sqlitehandle;
|
|
#endif
|
|
|
|
cvar_t sql_driver = SCVARF("sv_sql_driver", "", CVAR_NOUNSAFEEXPAND);
|
|
cvar_t sql_host = SCVARF("sv_sql_host", "127.0.0.1", CVAR_NOUNSAFEEXPAND);
|
|
cvar_t sql_username = SCVARF("sv_sql_username", "", CVAR_NOUNSAFEEXPAND);
|
|
cvar_t sql_password = SCVARF("sv_sql_password", "", CVAR_NOUNSAFEEXPAND);
|
|
cvar_t sql_defaultdb = SCVARF("sv_sql_defaultdb", "", CVAR_NOUNSAFEEXPAND);
|
|
|
|
void SQL_PushResult(sqlserver_t *server, queryresult_t *qres)
|
|
{
|
|
Sys_LockMutex(server->resultlock);
|
|
qres->next = NULL;
|
|
if (!server->resultslast)
|
|
server->results = server->resultslast = qres;
|
|
else
|
|
server->resultslast = server->resultslast->next = qres;
|
|
Sys_UnlockMutex(server->resultlock);
|
|
}
|
|
|
|
queryresult_t *SQL_PullResult(sqlserver_t *server)
|
|
{
|
|
queryresult_t *qres;
|
|
Sys_LockMutex(server->resultlock);
|
|
qres = server->results;
|
|
if (qres)
|
|
{
|
|
server->results = qres->next;
|
|
if (!server->results)
|
|
server->resultslast = NULL;
|
|
}
|
|
Sys_UnlockMutex(server->resultlock);
|
|
|
|
return qres;
|
|
}
|
|
|
|
void SQL_PushRequest(sqlserver_t *server, queryrequest_t *qreq)
|
|
{
|
|
Sys_LockConditional(server->requestcondv);
|
|
qreq->next = NULL;
|
|
if (!server->requestslast)
|
|
server->requests = server->requestslast = qreq;
|
|
else
|
|
server->requestslast = server->requestslast->next = qreq;
|
|
Sys_UnlockConditional(server->requestcondv);
|
|
}
|
|
|
|
queryrequest_t *SQL_PullRequest(sqlserver_t *server, qboolean lock)
|
|
{
|
|
queryrequest_t *qreq;
|
|
if (lock)
|
|
Sys_LockConditional(server->requestcondv);
|
|
qreq = server->requests;
|
|
if (qreq)
|
|
{
|
|
server->requests = qreq->next;
|
|
if (!server->requests)
|
|
server->requestslast = NULL;
|
|
}
|
|
Sys_UnlockConditional(server->requestcondv);
|
|
|
|
return qreq;
|
|
}
|
|
|
|
sqlserver_t **sqlservers;
|
|
int sqlservercount;
|
|
int sqlavailable;
|
|
|
|
#ifdef USE_SQLITE
|
|
//this is to try to sandbox sqlite so it can only edit the file its originally opened with.
|
|
int QDECL mysqlite_authorizer(void *ctx, int action, const char *detail0, const char *detail1, const char *detail2, const char *detail3)
|
|
{
|
|
if (action == SQLITE_PRAGMA)
|
|
{
|
|
Sys_Printf("SQL: Rejecting pragma \"%s\"\n", detail0);
|
|
return SQLITE_DENY;
|
|
}
|
|
if (action == SQLITE_ATTACH)
|
|
{
|
|
Sys_Printf("SQL: Rejecting attach to \"%s\"\n", detail0);
|
|
return SQLITE_DENY;
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
#endif
|
|
|
|
int sql_serverworker(void *sref)
|
|
{
|
|
sqlserver_t *server = (sqlserver_t *)sref;
|
|
const char *error = NULL;
|
|
int tinit = -1, i;
|
|
qboolean needlock = false;
|
|
qboolean allokay = true;
|
|
|
|
switch(server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
{
|
|
my_bool reconnect = 1;
|
|
if (!qmysql_thread_init)
|
|
error = "MYSQL library not available";
|
|
else if (tinit = qmysql_thread_init())
|
|
error = "MYSQL thread init failed";
|
|
else if (!(server->mysql = qmysql_init(NULL)))
|
|
error = "MYSQL init failed";
|
|
else if (qmysql_options(server->mysql, MYSQL_OPT_RECONNECT, &reconnect))
|
|
error = "MYSQL reconnect options set failed";
|
|
else
|
|
{
|
|
int port = 0;
|
|
char *colon;
|
|
|
|
colon = strchr(server->connectparams[0], ':');
|
|
if (colon)
|
|
{
|
|
*colon = '\0';
|
|
port = atoi(colon + 1);
|
|
}
|
|
|
|
if (!(server->mysql = qmysql_real_connect(server->mysql, server->connectparams[0], server->connectparams[1], server->connectparams[2], server->connectparams[3], port, 0, 0)))
|
|
error = "MYSQL initial connect attempt failed";
|
|
|
|
if (colon)
|
|
*colon = ':';
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
if (qsqlite3_open(server->connectparams[3], &server->sqlite))
|
|
{
|
|
error = qsqlite3_errmsg(server->sqlite);
|
|
}
|
|
else
|
|
{
|
|
//disable extension loading, set up an authorizer hook.
|
|
qsqlite3_enable_load_extension(server->sqlite, false);
|
|
qsqlite3_set_authorizer(server->sqlite, mysqlite_authorizer, server);
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
error = "That driver is not enabled in this build.";
|
|
break;
|
|
}
|
|
|
|
if (error)
|
|
allokay = false;
|
|
|
|
while (allokay)
|
|
{
|
|
Sys_LockConditional(server->requestcondv);
|
|
if (!server->requests) // this is needed for thread startup and to catch any "lost" changes
|
|
Sys_ConditionWait(server->requestcondv);
|
|
needlock = false; // so we don't try to relock first round
|
|
|
|
while (1)
|
|
{
|
|
queryrequest_t *qreq = NULL;
|
|
queryresult_t *qres;
|
|
const char *qerror = NULL;
|
|
int rows = -1;
|
|
int columns = -1;
|
|
int qesize = 0;
|
|
void *res = NULL;
|
|
|
|
if (!(qreq = SQL_PullRequest(server, needlock)))
|
|
{
|
|
if (!server->active)
|
|
allokay = false;
|
|
break;
|
|
}
|
|
|
|
// pullrequest makes sure our condition is unlocked but we'll need
|
|
// a lock next round
|
|
needlock = true;
|
|
|
|
switch(server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
// perform the query and fill out the result structure
|
|
if (qmysql_query(server->mysql, qreq->query))
|
|
qerror = qmysql_error(server->mysql);
|
|
else // query succeeded
|
|
{
|
|
res = qmysql_store_result(server->mysql);
|
|
if (res) // result set returned
|
|
{
|
|
rows = qmysql_num_rows(res);
|
|
columns = qmysql_num_fields(res);
|
|
}
|
|
else if (qmysql_field_count(server->mysql) == 0) // no result set
|
|
{
|
|
rows = qmysql_affected_rows(server->mysql);
|
|
if (rows < 0)
|
|
rows = 0;
|
|
columns = 0;
|
|
}
|
|
else // error
|
|
qerror = qmysql_error(server->mysql);
|
|
|
|
}
|
|
|
|
if (qerror)
|
|
qesize = Q_strlen(qerror);
|
|
qres = (queryresult_t *)ZF_Malloc(sizeof(queryresult_t) + qesize);
|
|
if (qres)
|
|
{
|
|
if (qerror)
|
|
Q_strncpy(qres->error, qerror, qesize);
|
|
qres->result = res;
|
|
qres->rows = rows;
|
|
qres->columns = columns;
|
|
qres->request = qreq;
|
|
qres->eof = true; // store result has no more rows to read afterwards
|
|
qreq->next = NULL;
|
|
|
|
SQL_PushResult(server, qres);
|
|
}
|
|
else // we're screwed here so bomb out
|
|
{
|
|
server->active = false;
|
|
error = "MALLOC ERROR! Unable to allocate query result!";
|
|
break;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
{
|
|
int rc;
|
|
sqlite3_stmt *pStmt;
|
|
const char *trailingstring;
|
|
char *statementstring = qreq->query;
|
|
char **mat;
|
|
int matsize;
|
|
int rowspace;
|
|
int totalrows = 0;
|
|
qboolean keeplooping = true;
|
|
|
|
Sys_Printf("processing %s\n", statementstring);
|
|
// qsqlite3_mutex_enter(server->sqlite->mutex);
|
|
// while(*statementstring)
|
|
// {
|
|
if (qsqlite3_prepare(server->sqlite, statementstring, -1, &pStmt, &trailingstring) == SQLITE_OK)
|
|
{ //sql statement is valid, apparently.
|
|
columns = qsqlite3_column_count(pStmt);
|
|
|
|
rc = qsqlite3_step(pStmt);
|
|
while(keeplooping)
|
|
{
|
|
rowspace = 65;
|
|
|
|
matsize = columns * sizeof(char*);
|
|
|
|
qres = (queryresult_t *)ZF_Malloc(sizeof(queryresult_t) + columns * sizeof(char*) * rowspace);
|
|
mat = (char**)(qres + 1);
|
|
if (qres)
|
|
{
|
|
qres->result = mat;
|
|
qres->rows = 0;
|
|
qres->columns = columns;
|
|
qres->request = qreq;
|
|
qres->firstrow = totalrows;
|
|
qres->eof = false;
|
|
qreq->next = NULL;
|
|
|
|
//headers technically take a row.
|
|
for (i = 0; i < columns; i++)
|
|
{
|
|
mat[i] = strdup(qsqlite3_column_name(pStmt, i));
|
|
}
|
|
rowspace--;
|
|
mat += columns;
|
|
|
|
while(1)
|
|
{
|
|
if (rc == SQLITE_ROW)
|
|
{
|
|
if (!rowspace)
|
|
break;
|
|
|
|
//generate the row info
|
|
for (i = 0; i < columns; i++)
|
|
mat[i] = strdup(qsqlite3_column_text(pStmt, i));
|
|
qres->rows++;
|
|
totalrows++;
|
|
rowspace--;
|
|
mat += columns;
|
|
}
|
|
else if (rc == SQLITE_DONE)
|
|
{
|
|
//no more data to get.
|
|
keeplooping = false;
|
|
qres->eof = true; //this one was the ender.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf("sqlite error code %i: %s\n", rc, statementstring);
|
|
keeplooping = false;
|
|
qres->eof = true; //this one was the ender.
|
|
if (!qres->columns)
|
|
qres->columns = -1;
|
|
break;
|
|
}
|
|
|
|
rc = qsqlite3_step(pStmt);
|
|
}
|
|
}
|
|
else
|
|
keeplooping = false;
|
|
|
|
SQL_PushResult(server, qres);
|
|
}
|
|
qsqlite3_finalize(pStmt);
|
|
}
|
|
else
|
|
{
|
|
qres = (queryresult_t *)ZF_Malloc(sizeof(queryresult_t) + 18 + strlen(statementstring));
|
|
if (qres)
|
|
{
|
|
strcpy(qres->error, "Bad SQL statement ");
|
|
strcpy(qres->error+18, statementstring);
|
|
qres->result = NULL;
|
|
qres->rows = 0;
|
|
qres->columns = -1;
|
|
qres->firstrow = totalrows;
|
|
qres->request = qreq;
|
|
qres->eof = true;
|
|
qreq->next = NULL;
|
|
|
|
SQL_PushResult(server, qres);
|
|
}
|
|
break;
|
|
}
|
|
// statementstring = trailingstring;
|
|
// }
|
|
// qsqlite3_mutex_leave(server->sqlite->mutex);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
server->active = false;
|
|
|
|
switch(server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
if (server->mysql)
|
|
qmysql_close(server->mysql);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
qsqlite3_close(server->sqlite);
|
|
server->sqlite = NULL;
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
// if we have a server error we still need to put it on the queue
|
|
if (error)
|
|
{
|
|
int esize = Q_strlen(error);
|
|
queryresult_t *qres = (queryresult_t *)Z_Malloc(sizeof(queryresult_t) + esize);
|
|
if (qres)
|
|
{ // hopefully the qmysql_close gained us some memory otherwise we're pretty screwed
|
|
qres->rows = qres->columns = -1;
|
|
Q_strncpy(qres->error, error, esize);
|
|
|
|
SQL_PushResult(server, qres);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MYSQL
|
|
if (!tinit)
|
|
qmysql_thread_end();
|
|
#endif
|
|
|
|
server->terminated = true;
|
|
return 0;
|
|
}
|
|
|
|
sqlserver_t *SQL_GetServer (int serveridx, qboolean inactives)
|
|
{
|
|
if (serveridx < 0 || serveridx >= sqlservercount)
|
|
return NULL;
|
|
if (!sqlservers[serveridx])
|
|
return NULL;
|
|
if (!inactives && sqlservers[serveridx]->active == false)
|
|
return NULL;
|
|
return sqlservers[serveridx];
|
|
}
|
|
|
|
queryresult_t *SQL_GetQueryResult (sqlserver_t *server, int queryidx)
|
|
{
|
|
queryresult_t *qres;
|
|
|
|
qres = server->currentresult;
|
|
if (qres && qres->request && qres->request->num == queryidx)
|
|
return qres;
|
|
|
|
for (qres = server->persistresults; qres; qres = qres->next)
|
|
if (qres->request && qres->request->num == queryidx)
|
|
return qres;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void SQL_DeallocResult(sqlserver_t *server, queryresult_t *qres)
|
|
{
|
|
// deallocate current result
|
|
switch(server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
if (qres->result)
|
|
qmysql_free_result(qres->result);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
if (qres->result)
|
|
{
|
|
char **mat = qres->result;
|
|
int i;
|
|
for (i = 0; i < qres->columns * (qres->rows+1); i++)
|
|
free(mat[i]);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
if (qres->request)
|
|
Z_Free(qres->request);
|
|
|
|
Z_Free(qres);
|
|
}
|
|
|
|
void SQL_ClosePersistantResult(sqlserver_t *server, queryresult_t *qres)
|
|
{
|
|
queryresult_t *prev, *cur;
|
|
|
|
prev = server->persistresults;
|
|
if (prev == qres)
|
|
{
|
|
server->persistresults = prev->next;
|
|
SQL_DeallocResult(server, prev);
|
|
return;
|
|
}
|
|
|
|
for (cur = prev->next; cur; prev = cur, cur = prev->next)
|
|
{
|
|
if (cur == qres)
|
|
{
|
|
prev = cur->next;
|
|
SQL_DeallocResult(server, cur);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SQL_CloseResult(sqlserver_t *server, queryresult_t *qres)
|
|
{
|
|
if (!qres)
|
|
return;
|
|
if (qres == server->currentresult)
|
|
{
|
|
SQL_DeallocResult(server, server->currentresult);
|
|
server->currentresult = NULL;
|
|
return;
|
|
}
|
|
// else we have a persistant query
|
|
SQL_ClosePersistantResult(server, qres);
|
|
}
|
|
|
|
void SQL_CloseAllResults(sqlserver_t *server)
|
|
{
|
|
queryresult_t *oldqres, *qres;
|
|
|
|
// close orphaned results (we assume the lock is active or non-existant at this point)
|
|
qres = server->results;
|
|
while (qres)
|
|
{
|
|
oldqres = qres;
|
|
qres = qres->next;
|
|
SQL_DeallocResult(server, oldqres);
|
|
}
|
|
// close current
|
|
if (server->currentresult)
|
|
{
|
|
SQL_DeallocResult(server, server->currentresult);
|
|
server->currentresult = NULL;
|
|
}
|
|
// close persistant results
|
|
qres = server->persistresults;
|
|
while (qres)
|
|
{
|
|
oldqres = qres;
|
|
qres = qres->next;
|
|
SQL_DeallocResult(server, oldqres);
|
|
}
|
|
server->persistresults = NULL;
|
|
// close server result
|
|
if (server->serverresult)
|
|
{
|
|
SQL_DeallocResult(server, server->serverresult);
|
|
server->serverresult = NULL;
|
|
}
|
|
}
|
|
|
|
char *SQL_ReadField (sqlserver_t *server, queryresult_t *qres, int row, int col, qboolean fields)
|
|
{
|
|
if (!qres->result) // TODO: partial resultset logic not implemented yet
|
|
return NULL;
|
|
else
|
|
{ // store_result query
|
|
if (qres->rows < row || qres->columns < col || col < 0)
|
|
return NULL;
|
|
|
|
if (row < 0)
|
|
{ // fetch field name
|
|
if (fields) // but only if we asked for them
|
|
{
|
|
switch(server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
{
|
|
MYSQL_FIELD *field = qmysql_fetch_field_direct(qres->result, col);
|
|
|
|
if (!field)
|
|
return NULL;
|
|
else
|
|
return field->name;
|
|
}
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
{
|
|
char **mat = qres->result;
|
|
if (mat)
|
|
return mat[col];
|
|
}
|
|
return NULL;
|
|
#endif
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
else
|
|
{ // fetch data
|
|
switch(server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
{
|
|
MYSQL_ROW sqlrow;
|
|
|
|
qmysql_data_seek(qres->result, row);
|
|
sqlrow = qmysql_fetch_row(qres->result);
|
|
if (!sqlrow || !sqlrow[col])
|
|
return NULL;
|
|
else
|
|
return sqlrow[col];
|
|
}
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
{
|
|
char **mat = qres->result;
|
|
col += qres->columns * (row+1);
|
|
if (mat)
|
|
return mat[col];
|
|
}
|
|
return NULL;
|
|
#endif
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SQL_CleanupServer(sqlserver_t *server)
|
|
{
|
|
int i;
|
|
queryrequest_t *qreq, *oldqreq;
|
|
|
|
server->active = false; // set thread to kill itself
|
|
if (server->requestcondv)
|
|
Sys_ConditionBroadcast(server->requestcondv); // force condition check
|
|
if (server->thread)
|
|
Sys_WaitOnThread(server->thread); // wait on thread to die
|
|
|
|
// server resource deallocation (TODO: should this be done in the thread itself?)
|
|
if (server->requestcondv)
|
|
Sys_DestroyConditional(server->requestcondv);
|
|
if (server->resultlock)
|
|
Sys_DestroyMutex(server->resultlock);
|
|
|
|
// close orphaned requests
|
|
qreq = server->requests;
|
|
while (qreq)
|
|
{
|
|
oldqreq = qreq;
|
|
qreq = qreq->next;
|
|
Z_Free(oldqreq);
|
|
}
|
|
|
|
SQL_CloseAllResults(server);
|
|
|
|
for (i = SQL_CONNECT_STRUCTPARAMS; i < SQL_CONNECT_PARAMS; i++)
|
|
Z_Free(server->connectparams[i]);
|
|
if (server->connectparams)
|
|
BZ_Free(server->connectparams);
|
|
|
|
Z_Free(server);
|
|
}
|
|
|
|
int SQL_NewServer(char *driver, char **paramstr)
|
|
{
|
|
sqlserver_t *server;
|
|
int serverref;
|
|
int drvchoice;
|
|
int paramsize[SQL_CONNECT_PARAMS];
|
|
char nativepath[MAX_OSPATH];
|
|
int i, tsize;
|
|
|
|
//name matches
|
|
if (Q_strcasecmp(driver, "mysql") == 0)
|
|
drvchoice = SQLDRV_MYSQL;
|
|
else if (Q_strcasecmp(driver, "sqlite") == 0)
|
|
drvchoice = SQLDRV_SQLITE;
|
|
else if (!*driver && (sqlavailable & (1u<<SQLDRV_SQLITE)))
|
|
drvchoice = SQLDRV_SQLITE;
|
|
else if (!*driver && (sqlavailable & (1u<<SQLDRV_MYSQL)))
|
|
drvchoice = SQLDRV_MYSQL;
|
|
else // invalid driver choice so we bomb out
|
|
return -1;
|
|
|
|
if (!(sqlavailable & (1u<<drvchoice)))
|
|
return -1;
|
|
|
|
if (drvchoice == SQLDRV_SQLITE)
|
|
{
|
|
//sqlite can be sandboxed.
|
|
//explicitly allow 'temp' and 'memory' databases
|
|
if (*paramstr[3] && strcmp(paramstr[3], ":memory:"))
|
|
{
|
|
//anything else is sandboxed into a subdir/database.
|
|
char *qname = va("sqlite/%s.db", paramstr[3]);
|
|
if (!FS_NativePath(qname, FS_GAMEONLY, nativepath, sizeof(nativepath)))
|
|
return -1;
|
|
paramstr[3] = nativepath;
|
|
FS_CreatePath(qname, FS_GAMEONLY);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < SQL_CONNECT_PARAMS; i++)
|
|
paramsize[i] = Q_strlen(paramstr[i]);
|
|
|
|
// alloc or realloc sql servers array
|
|
if (sqlservers == NULL)
|
|
{
|
|
serverref = 0;
|
|
sqlservercount = 1;
|
|
sqlservers = (sqlserver_t **)BZ_Malloc(sizeof(sqlserver_t *));
|
|
}
|
|
else
|
|
{
|
|
serverref = sqlservercount;
|
|
sqlservercount++;
|
|
sqlservers = (sqlserver_t **)BZ_Realloc(sqlservers, sizeof(sqlserver_t *) * sqlservercount);
|
|
}
|
|
|
|
// assemble server structure
|
|
tsize = 0;
|
|
for (i = 0; i < SQL_CONNECT_STRUCTPARAMS; i++)
|
|
tsize += paramsize[i] + 1; // allocate extra space for host and user only
|
|
|
|
server = (sqlserver_t *)Z_Malloc(sizeof(sqlserver_t) + tsize);
|
|
server->connectparams = (char **)BZ_Malloc(sizeof(char *) * SQL_CONNECT_PARAMS);
|
|
|
|
tsize = 0;
|
|
for (i = 0; i < SQL_CONNECT_STRUCTPARAMS; i++)
|
|
{
|
|
server->connectparams[i] = ((char *)(server + 1)) + tsize;
|
|
Q_strncpy(server->connectparams[i], paramstr[i], paramsize[i]);
|
|
// string should be null-terminated due to Z_Malloc
|
|
tsize += paramsize[i] + 1;
|
|
}
|
|
for (i = SQL_CONNECT_STRUCTPARAMS; i < SQL_CONNECT_PARAMS; i++)
|
|
{
|
|
server->connectparams[i] = (char *)Z_Malloc(sizeof(char) * (paramsize[i] + 1));
|
|
Q_strncpy(server->connectparams[i], paramstr[i], paramsize[i]);
|
|
// string should be null-terminated due to Z_Malloc
|
|
}
|
|
|
|
sqlservers[serverref] = server;
|
|
|
|
server->driver = (sqldrv_t)drvchoice;
|
|
server->querynum = 1;
|
|
server->active = true;
|
|
server->requestcondv = Sys_CreateConditional();
|
|
server->resultlock = Sys_CreateMutex();
|
|
|
|
if (!server->requestcondv || !server->resultlock)
|
|
{
|
|
if (server->requestcondv)
|
|
Sys_DestroyConditional(server->requestcondv);
|
|
if (server->resultlock)
|
|
Sys_DestroyMutex(server->resultlock);
|
|
Z_Free(server);
|
|
sqlservercount--;
|
|
return -1;
|
|
}
|
|
|
|
server->thread = Sys_CreateThread("sqlworker", sql_serverworker, (void *)server, THREADP_NORMAL, 1024);
|
|
|
|
if (!server->thread)
|
|
{
|
|
Z_Free(server);
|
|
sqlservercount--;
|
|
return -1;
|
|
}
|
|
|
|
return serverref;
|
|
}
|
|
|
|
int SQL_NewQuery(sqlserver_t *server, int callfunc, int type, int self, float selfid, int other, float otherid, char *str)
|
|
{
|
|
int qsize = Q_strlen(str);
|
|
queryrequest_t *qreq;
|
|
int querynum;
|
|
|
|
qreq = (queryrequest_t *)ZF_Malloc(sizeof(queryrequest_t) + qsize);
|
|
if (qreq)
|
|
{
|
|
qreq->persistant = (type == 1);
|
|
qreq->callback = callfunc;
|
|
|
|
qreq->selfent = self;
|
|
qreq->selfid = selfid;
|
|
qreq->otherent = other;
|
|
qreq->otherid = otherid;
|
|
|
|
querynum = qreq->num = server->querynum;
|
|
// prevent the reference num from getting too big to prevent FP problems
|
|
if (++server->querynum > 1000000)
|
|
server->querynum = 1;
|
|
|
|
Q_strncpy(qreq->query, str, qsize);
|
|
|
|
SQL_PushRequest(server, qreq);
|
|
Sys_ConditionSignal(server->requestcondv);
|
|
|
|
return querynum;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void SQL_Disconnect(sqlserver_t *server)
|
|
{
|
|
server->active = false;
|
|
|
|
// force the threads to reiterate requests and hopefully terminate
|
|
Sys_ConditionBroadcast(server->requestcondv);
|
|
}
|
|
|
|
void SQL_Escape(sqlserver_t *server, char *src, char *dst, int dstlen)
|
|
{
|
|
switch (server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
{
|
|
int srclen = strlen(dst);
|
|
if (srclen > (dstlen / 2 - 1))
|
|
dst[0] = '\0';
|
|
else
|
|
qmysql_real_escape_string(server->mysql, dst, src, srclen);
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
{
|
|
dstlen--;
|
|
while (dstlen > 2 && *src)
|
|
{
|
|
if (*src == '\'')
|
|
{
|
|
*dst++ = *src;
|
|
dstlen--;
|
|
}
|
|
*dst++ = *src++;
|
|
dstlen--;
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
dst[0] = '\0';
|
|
}
|
|
}
|
|
|
|
const char *SQL_Info(sqlserver_t *server)
|
|
{
|
|
switch (server->driver)
|
|
{
|
|
#ifdef USE_MYSQL
|
|
case SQLDRV_MYSQL:
|
|
if (qmysql_get_client_info)
|
|
return va("mysql: %s", qmysql_get_client_info());
|
|
else
|
|
return "ERROR: mysql library not loaded";
|
|
break;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
case SQLDRV_SQLITE:
|
|
if (qsqlite3_libversion)
|
|
return va("sqlite: %s", qsqlite3_libversion());
|
|
else
|
|
return "ERROR: sqlite library not loaded";
|
|
#endif
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
qboolean SQL_Available(void)
|
|
{
|
|
return !!sqlavailable;
|
|
}
|
|
|
|
/* SQL related commands */
|
|
void SQL_Status_f(void)
|
|
{
|
|
int i;
|
|
|
|
if (!SQL_Available())
|
|
Con_Printf("No SQL library available.\n");
|
|
else
|
|
Con_Printf("%i connections\n", sqlservercount);
|
|
for (i = 0; i < sqlservercount; i++)
|
|
{
|
|
int reqnum = 0;
|
|
int resnum = 0;
|
|
queryrequest_t *qreq;
|
|
queryresult_t *qres;
|
|
|
|
sqlserver_t *server = sqlservers[i];
|
|
|
|
if (!server)
|
|
continue;
|
|
|
|
Sys_LockMutex(server->resultlock);
|
|
Sys_LockConditional(server->requestcondv);
|
|
for (qreq = server->requests; qreq; qreq = qreq->next)
|
|
reqnum++;
|
|
for (qres = server->results; qres; qres = qres->next)
|
|
resnum++;
|
|
|
|
switch(server->driver)
|
|
{
|
|
case SQLDRV_MYSQL:
|
|
Con_Printf("#%i %s@%s: %s\n",
|
|
i,
|
|
server->connectparams[1],
|
|
server->connectparams[0],
|
|
server->active ? "active" : "inactive");
|
|
break;
|
|
case SQLDRV_SQLITE:
|
|
Con_Printf("#%i %s: %s\n",
|
|
i,
|
|
server->connectparams[3],
|
|
server->active ? "active" : "inactive");
|
|
break;
|
|
}
|
|
|
|
if (reqnum)
|
|
{
|
|
Con_Printf ("- %i requests\n");
|
|
for (qreq = server->requests; qreq; qreq = qreq->next)
|
|
{
|
|
Con_Printf (" query #%i: %s\n",
|
|
qreq->num,
|
|
qreq->query);
|
|
// TODO: function lookup?
|
|
}
|
|
}
|
|
|
|
if (resnum)
|
|
{
|
|
Con_Printf ("- %i results\n");
|
|
for (qres = server->results; qres; qres = qres->next)
|
|
{
|
|
Con_Printf (" * %i rows, %i columns",
|
|
qres->rows,
|
|
qres->columns);
|
|
if (qres->error[0])
|
|
Con_Printf(", error %s\n", qres->error);
|
|
else
|
|
Con_Printf("\n");
|
|
// TODO: request info?
|
|
}
|
|
}
|
|
|
|
if (server->serverresult)
|
|
Con_Printf ("server result: error %s\n", server->serverresult->error);
|
|
|
|
// TODO: list all requests, results here
|
|
Sys_UnlockMutex(server->resultlock);
|
|
Sys_UnlockConditional(server->requestcondv);
|
|
}
|
|
}
|
|
|
|
void SQL_Kill_f (void)
|
|
{
|
|
sqlserver_t *server;
|
|
|
|
if (Cmd_Argc() < 2)
|
|
{
|
|
Con_Printf ("Syntax: %s serverid\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
|
|
server = SQL_GetServer(atoi(Cmd_Argv(1)), false);
|
|
if (server)
|
|
{
|
|
server->active = false;
|
|
Sys_ConditionBroadcast(server->requestcondv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SQL_Killall_f (void)
|
|
{
|
|
SQL_KillServers();
|
|
}
|
|
|
|
void SQL_ServerCycle (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sqlservercount; i++)
|
|
{
|
|
sqlserver_t *server = sqlservers[i];
|
|
queryresult_t *qres;
|
|
|
|
if (!server)
|
|
continue;
|
|
|
|
if (server->terminated)
|
|
{
|
|
sqlservers[i] = NULL;
|
|
SQL_CleanupServer(server);
|
|
continue;
|
|
}
|
|
|
|
while (qres = SQL_PullResult(server))
|
|
{
|
|
qres->next = NULL;
|
|
if (qres->request && qres->request->callback)
|
|
{
|
|
if (server->active)
|
|
{ // only process results to callback if server is active
|
|
edict_t *ent;
|
|
|
|
server->currentresult = qres;
|
|
G_FLOAT(OFS_PARM0) = i;
|
|
G_FLOAT(OFS_PARM1) = qres->request->num;
|
|
G_FLOAT(OFS_PARM2) = qres->rows;
|
|
G_FLOAT(OFS_PARM3) = qres->columns;
|
|
G_FLOAT(OFS_PARM4) = qres->eof;
|
|
G_FLOAT(OFS_PARM5) = qres->firstrow;
|
|
|
|
// recall self and other references
|
|
ent = PROG_TO_EDICT(prinst, qres->request->selfent);
|
|
if (ent->isfree || ent->xv->uniquespawnid != qres->request->selfid)
|
|
pr_global_struct->self = pr_global_struct->world;
|
|
else
|
|
pr_global_struct->self = qres->request->selfent;
|
|
ent = PROG_TO_EDICT(prinst, qres->request->otherent);
|
|
if (ent->isfree || ent->xv->uniquespawnid != qres->request->otherid)
|
|
pr_global_struct->other = pr_global_struct->world;
|
|
else
|
|
pr_global_struct->other = qres->request->otherent;
|
|
|
|
PR_ExecuteProgram(prinst, qres->request->callback);
|
|
|
|
if (server->currentresult)
|
|
{
|
|
if (server->currentresult->request && server->currentresult->request->persistant)
|
|
{
|
|
// move into persistant list
|
|
server->currentresult->next = server->persistresults;
|
|
server->persistresults = server->currentresult;
|
|
}
|
|
else // just close the query
|
|
SQL_CloseResult(server, server->currentresult);
|
|
}
|
|
}
|
|
}
|
|
else // error or server-only result
|
|
{
|
|
if (server->serverresult)
|
|
Z_Free(server->serverresult);
|
|
server->serverresult = qres;
|
|
}
|
|
}
|
|
server->currentresult = NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MYSQL
|
|
qboolean SQL_MYSQLInit(void)
|
|
{
|
|
if ((mysqlhandle = Sys_LoadLibrary("libmysql", mysqlfuncs)))
|
|
{
|
|
if (qmysql_thread_safe())
|
|
{
|
|
if (!qmysql_library_init(0, NULL, NULL))
|
|
{
|
|
Con_Printf("MYSQL backend loaded\n");
|
|
return true;
|
|
}
|
|
else
|
|
Con_Printf("MYSQL library init failed!\n");
|
|
}
|
|
else
|
|
Con_Printf("MYSQL client is not thread safe!\n");
|
|
|
|
Sys_CloseLibrary(mysqlhandle);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("mysql client didn't load\n");
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void SQL_Init(void)
|
|
{
|
|
sqlavailable = 0;
|
|
#ifdef USE_MYSQL
|
|
//mysql pokes network etc. there's no sandbox. people can use quake clients to pry upon private databases.
|
|
if (COM_CheckParm("-mysql"))
|
|
if (SQL_MYSQLInit())
|
|
sqlavailable |= 1u<<SQLDRV_MYSQL;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
//our sqlite implementation is sandboxed. we block database attachments, and restrict the master database name.
|
|
sqlitehandle = Sys_LoadLibrary("sqlite3", sqlitefuncs);
|
|
if (sqlitehandle)
|
|
{
|
|
sqlavailable |= 1u<<SQLDRV_SQLITE;
|
|
}
|
|
#endif
|
|
|
|
Cmd_AddCommand ("sqlstatus", SQL_Status_f);
|
|
Cmd_AddCommand ("sqlkill", SQL_Kill_f);
|
|
Cmd_AddCommand ("sqlkillall", SQL_Killall_f);
|
|
|
|
Cvar_Register(&sql_driver, SQLCVAROPTIONS);
|
|
Cvar_Register(&sql_host, SQLCVAROPTIONS);
|
|
Cvar_Register(&sql_username, SQLCVAROPTIONS);
|
|
Cvar_Register(&sql_password, SQLCVAROPTIONS);
|
|
Cvar_Register(&sql_defaultdb, SQLCVAROPTIONS);
|
|
}
|
|
|
|
void SQL_KillServers(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sqlservercount; i++)
|
|
{
|
|
sqlserver_t *server = sqlservers[i];
|
|
sqlservers[i] = NULL;
|
|
if (!server)
|
|
continue;
|
|
SQL_CleanupServer(server);
|
|
}
|
|
if (sqlservers)
|
|
Z_Free(sqlservers);
|
|
sqlservers = NULL;
|
|
sqlservercount = 0;
|
|
}
|
|
|
|
void SQL_DeInit(void)
|
|
{
|
|
sqlavailable = 0;
|
|
|
|
SQL_KillServers();
|
|
|
|
#ifdef USE_MYSQL
|
|
if (qmysql_library_end)
|
|
qmysql_library_end();
|
|
|
|
Sys_CloseLibrary(mysqlhandle);
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
Sys_CloseLibrary(sqlitehandle);
|
|
#endif
|
|
}
|
|
|
|
#endif
|