mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-10 22:51:57 +00:00
53a7b3d47c
The ragdoll API is potentially usable now, but still really limited. Enabled SQL requests by default using sqlite. Note that you'll need the sqlite dll to use this. MySQL should still be usable, but I didn't try. MySQL requires -DUSE_MYSQL to compile it, and a dll and -mysql argument to enable it. Fixed nacl. NPFTE plugin now invokes an exe to run the game rather than running the game within the browser. externvalue builtin now accepts & prefix to return a pointer instead. Fixed vector autocvars. uri_get, bufstr_add, bufstr_free, now functional. QC debugger can now show asm if line numbers are not available. Added support for QC watchpoints. Use the watchpoint command. gl_specular now give specular even without rtlights, thankfully not as blatently, but its there. android will not crash due to supported audio formats, and gles2 can be selected via a cvar (requires full FTEDroidActivity/program restart). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4152 fc73d0e0-1445-4013-8a0c-d673dee63da5
1185 lines
30 KiB
C
1185 lines
30 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}
|
|
};
|
|
#endif
|
|
|
|
#ifdef USE_SQLITE
|
|
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}
|
|
};
|
|
#endif
|
|
|
|
cvar_t sql_driver = SCVARF("sv_sql_driver", "mysql", 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;
|
|
|
|
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)
|
|
server->active = false;
|
|
|
|
while (server->active)
|
|
{
|
|
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)))
|
|
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;
|
|
qboolean keeplooping = true;
|
|
|
|
// 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->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++;
|
|
rowspace--;
|
|
mat += columns;
|
|
}
|
|
else if (rc == SQLITE_DONE)
|
|
{
|
|
//no more data to get.
|
|
keeplooping = false;
|
|
qres->eof = true; //this one was the ender.
|
|
break;
|
|
}
|
|
|
|
rc = qsqlite3_step(pStmt);
|
|
}
|
|
}
|
|
else
|
|
keeplooping = false;
|
|
|
|
SQL_PushResult(server, qres);
|
|
}
|
|
qsqlite3_finalize(pStmt);
|
|
}
|
|
else
|
|
{
|
|
char *queryerror = va("Bad SQL statement %s", statementstring);
|
|
qres = (queryresult_t *)ZF_Malloc(sizeof(queryresult_t) + strlen(queryerror));
|
|
if (qres)
|
|
{
|
|
strcpy(qres->error, queryerror);
|
|
qres->result = NULL;
|
|
qres->rows = 0;
|
|
qres->columns = -1;
|
|
qres->request = qreq;
|
|
qres->eof = true;
|
|
qreq->next = NULL;
|
|
|
|
SQL_PushResult(server, qres);
|
|
}
|
|
break;
|
|
}
|
|
// statementstring = trailingstring;
|
|
// }
|
|
// qsqlite3_mutex_leave(server->sqlite->mutex);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (Q_strcasecmp(driver, "mysql") == 0)
|
|
drvchoice = SQLDRV_MYSQL;
|
|
else if (Q_strcasecmp(driver, "sqlite") == 0)
|
|
drvchoice = SQLDRV_SQLITE;
|
|
else // invalid driver choice so we bomb out
|
|
return -1;
|
|
|
|
if (!(sqlavailable & (1u<<drvchoice)))
|
|
return -1;
|
|
|
|
if (drvchoice == SQLDRV_SQLITE)
|
|
{
|
|
//explicitly allow 'temp' and 'memory' databases
|
|
if (*paramstr[3] && strcmp(paramstr[3], ":memory:"))
|
|
{
|
|
//anything else is sandboxed into a subdir/database.
|
|
char *qname = va("qdb/%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(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 (progfuncs_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;
|
|
|
|
// 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 (qres->eof)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
// TODO: else we move a request back into the queue?
|
|
}
|
|
}
|
|
else // error or server-only result
|
|
{
|
|
if (server->serverresult)
|
|
Z_Free(server->serverresult);
|
|
server->serverresult = qres;
|
|
}
|
|
}
|
|
server->currentresult = NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MYSQL
|
|
void SQL_MYSQLInit(void)
|
|
{
|
|
if (!(mysqlhandle = Sys_LoadLibrary("libmysql", mysqlfuncs)))
|
|
{
|
|
Con_Printf("mysql client didn't load\n");
|
|
return;
|
|
}
|
|
|
|
if (qmysql_thread_safe())
|
|
{
|
|
if (!qmysql_library_init(0, NULL, NULL))
|
|
{
|
|
Con_Printf("MYSQL backend loaded\n");
|
|
sqlavailable |= 1u<<SQLDRV_MYSQL;
|
|
return;
|
|
}
|
|
else
|
|
Con_Printf("MYSQL library init failed!\n");
|
|
}
|
|
else
|
|
Con_Printf("MYSQL client is not thread safe!\n");
|
|
|
|
Sys_CloseLibrary(mysqlhandle);
|
|
sqlavailable &= ~(1u<<SQLDRV_MYSQL);
|
|
}
|
|
#endif
|
|
|
|
void SQL_Init(void)
|
|
{
|
|
#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"))
|
|
SQL_MYSQLInit();
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
//our sqlite implementation is sandboxed. we block database attachments, and restrict the master database name.
|
|
if (Sys_LoadLibrary("sqlite3", sqlitefuncs))
|
|
{
|
|
if (!sqlavailable)
|
|
sql_driver.string = "sqlite"; //use this by default if its the only one available.
|
|
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 = false;
|
|
|
|
SQL_KillServers();
|
|
|
|
#ifdef USE_MYSQL
|
|
if (qmysql_library_end)
|
|
qmysql_library_end();
|
|
|
|
Sys_CloseLibrary(mysqlhandle);
|
|
#endif
|
|
}
|
|
|
|
#endif
|