engine/engine/server/sv_sql.c

1218 lines
31 KiB
C
Raw Normal View History

#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();
}
------------------------------------------------------------------------ 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
2013-03-12 15:29:40 -07:00
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();
if (mysqlhandle)
Sys_CloseLibrary(mysqlhandle);
#endif
#ifdef USE_SQLITE
if (sqlitehandle)
Sys_CloseLibrary(sqlitehandle);
#endif
}
#endif