8197e0875f
Fix up r_ignoreentpvs 0 to check areas properly. checkpvs builtin can no longer mess up area checks elsewhere. Write out foo.db files for release builds, in the hopes of at least getting function names from release-build crashes. Implement _skyroom worldspawn field, still needs a few tweaks though. Try to fix android surface-related crashes, AGAIN. Separate parsing of connect requests, in preparation for formal logins (and removal of the old ranking code). A few tweaks to try to improve compatibility with q3 mods. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5484 fc73d0e0-1445-4013-8a0c-d673dee63da5
541 lines
12 KiB
C
541 lines
12 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
//well, linux or cygwin (windows with posix emulation layer), anyway...
|
|
|
|
#define _GNU_SOURCE
|
|
#include "quakedef.h"
|
|
|
|
#ifdef MULTITHREAD
|
|
#include <limits.h>
|
|
#include <pthread.h>
|
|
/* Thread creation calls */
|
|
typedef void *(*pfunction_t)(void *);
|
|
|
|
static pthread_t mainthread;
|
|
|
|
void Sys_ThreadsInit(void)
|
|
{
|
|
mainthread = pthread_self();
|
|
}
|
|
qboolean Sys_IsThread(void *thread)
|
|
{
|
|
return pthread_equal(pthread_self(), *(pthread_t*)thread);
|
|
}
|
|
qboolean Sys_IsMainThread(void)
|
|
{
|
|
return Sys_IsThread(&mainthread);
|
|
}
|
|
void Sys_ThreadAbort(void)
|
|
{
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
#if 1
|
|
typedef struct {
|
|
int (*func)(void *);
|
|
void *args;
|
|
} qthread_t;
|
|
static void *Sys_CreatedThread(void *v)
|
|
{
|
|
qthread_t *qthread = v;
|
|
qintptr_t r;
|
|
|
|
r = qthread->func(qthread->args);
|
|
|
|
return (void*)r;
|
|
}
|
|
|
|
void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority, int stacksize)
|
|
{
|
|
pthread_t *thread;
|
|
qthread_t *qthread;
|
|
pthread_attr_t attr;
|
|
|
|
thread = (pthread_t *)malloc(sizeof(pthread_t)+sizeof(qthread_t));
|
|
if (!thread)
|
|
return NULL;
|
|
|
|
qthread = (qthread_t*)(thread+1);
|
|
qthread->func = func;
|
|
qthread->args = args;
|
|
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
|
if (stacksize != -1)
|
|
{
|
|
if (stacksize < PTHREAD_STACK_MIN*2)
|
|
stacksize = PTHREAD_STACK_MIN*2;
|
|
if (stacksize < PTHREAD_STACK_MIN+65536*16)
|
|
stacksize = PTHREAD_STACK_MIN+65536*16;
|
|
pthread_attr_setstacksize(&attr, stacksize);
|
|
}
|
|
if (pthread_create(thread, &attr, (pfunction_t)Sys_CreatedThread, qthread))
|
|
{
|
|
free(thread);
|
|
thread = NULL;
|
|
}
|
|
pthread_attr_destroy(&attr);
|
|
#if defined(DEBUG) && defined(__USE_GNU) && defined(__GLIBC_PREREQ)
|
|
#if __GLIBC_PREREQ(2,12)
|
|
pthread_setname_np(*thread, name);
|
|
#endif
|
|
#endif
|
|
|
|
return (void *)thread;
|
|
}
|
|
#else
|
|
void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority, int stacksize)
|
|
{
|
|
pthread_t *thread;
|
|
pthread_attr_t attr;
|
|
|
|
thread = (pthread_t *)malloc(sizeof(pthread_t));
|
|
if (!thread)
|
|
return NULL;
|
|
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
|
if (stacksize < PTHREAD_STACK_MIN*2)
|
|
stacksize = PTHREAD_STACK_MIN*2;
|
|
pthread_attr_setstacksize(&attr, stacksize);
|
|
if (pthread_create(thread, &attr, (pfunction_t)func, args))
|
|
{
|
|
free(thread);
|
|
thread = NULL;
|
|
}
|
|
pthread_attr_destroy(&attr);
|
|
|
|
#if defined(DEBUG) && defined(__USE_GNU) && defined(__GLIBC_PREREQ)
|
|
#if __GLIBC_PREREQ(2,12)
|
|
pthread_setname_np(*thread, name);
|
|
#endif
|
|
#endif
|
|
|
|
return (void *)thread;
|
|
}
|
|
#endif
|
|
|
|
void Sys_WaitOnThread(void *thread)
|
|
{
|
|
int err;
|
|
err = pthread_join(*(pthread_t *)thread, NULL);
|
|
if (err)
|
|
printf("pthread_join(%p) failed, error %s\n", thread, strerror(err));
|
|
|
|
free(thread);
|
|
}
|
|
|
|
/* Mutex calls */
|
|
void *Sys_CreateMutex(void)
|
|
{
|
|
pthread_mutex_t *mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
|
|
|
|
if (mutex && !pthread_mutex_init(mutex, NULL))
|
|
return mutex;
|
|
return NULL;
|
|
}
|
|
|
|
qboolean Sys_TryLockMutex(void *mutex)
|
|
{
|
|
return !pthread_mutex_trylock(mutex);
|
|
}
|
|
|
|
qboolean Sys_LockMutex(void *mutex)
|
|
{
|
|
return !pthread_mutex_lock(mutex);
|
|
}
|
|
|
|
qboolean Sys_UnlockMutex(void *mutex)
|
|
{
|
|
return !pthread_mutex_unlock(mutex);
|
|
}
|
|
|
|
void Sys_DestroyMutex(void *mutex)
|
|
{
|
|
pthread_mutex_destroy(mutex);
|
|
free(mutex);
|
|
}
|
|
|
|
/* Conditional wait calls */
|
|
typedef struct condvar_s
|
|
{
|
|
pthread_mutex_t *mutex;
|
|
pthread_cond_t *cond;
|
|
} condvar_t;
|
|
|
|
void *Sys_CreateConditional(void)
|
|
{
|
|
condvar_t *condv;
|
|
pthread_mutex_t *mutex;
|
|
pthread_cond_t *cond;
|
|
|
|
condv = (condvar_t *)malloc(sizeof(condvar_t));
|
|
if (!condv)
|
|
return NULL;
|
|
|
|
mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
|
|
if (!mutex)
|
|
return NULL;
|
|
|
|
cond = (pthread_cond_t *)malloc(sizeof(pthread_cond_t));
|
|
if (!cond)
|
|
return NULL;
|
|
|
|
if (!pthread_mutex_init(mutex, NULL))
|
|
{
|
|
if (!pthread_cond_init(cond, NULL))
|
|
{
|
|
condv->cond = cond;
|
|
condv->mutex = mutex;
|
|
|
|
return (void *)condv;
|
|
}
|
|
else
|
|
pthread_mutex_destroy(mutex);
|
|
}
|
|
|
|
free(cond);
|
|
free(mutex);
|
|
free(condv);
|
|
return NULL;
|
|
}
|
|
|
|
qboolean Sys_LockConditional(void *condv)
|
|
{
|
|
return !pthread_mutex_lock(((condvar_t *)condv)->mutex);
|
|
}
|
|
|
|
qboolean Sys_UnlockConditional(void *condv)
|
|
{
|
|
return !pthread_mutex_unlock(((condvar_t *)condv)->mutex);
|
|
}
|
|
|
|
qboolean Sys_ConditionWait(void *condv)
|
|
{
|
|
return !pthread_cond_wait(((condvar_t *)condv)->cond, ((condvar_t *)condv)->mutex);
|
|
}
|
|
|
|
qboolean Sys_ConditionSignal(void *condv)
|
|
{
|
|
return !pthread_cond_signal(((condvar_t *)condv)->cond);
|
|
}
|
|
|
|
qboolean Sys_ConditionBroadcast(void *condv)
|
|
{
|
|
return !pthread_cond_broadcast(((condvar_t *)condv)->cond);
|
|
}
|
|
|
|
void Sys_DestroyConditional(void *condv)
|
|
{
|
|
condvar_t *cv = (condvar_t *)condv;
|
|
|
|
pthread_cond_destroy(cv->cond);
|
|
pthread_mutex_destroy(cv->mutex);
|
|
free(cv->cond);
|
|
free(cv->mutex);
|
|
free(cv);
|
|
}
|
|
#endif
|
|
|
|
void Sys_Sleep (double seconds)
|
|
{
|
|
struct timespec ts;
|
|
|
|
ts.tv_sec = (time_t)seconds;
|
|
seconds -= ts.tv_sec;
|
|
ts.tv_nsec = seconds * 1000000000.0;
|
|
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef SUBSERVERS
|
|
#include <spawn.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <sys/wait.h>
|
|
#define STDIN 0
|
|
#define STDOUT 1
|
|
#define STDERR 2
|
|
|
|
typedef struct slaveserver_s
|
|
{
|
|
pubsubserver_t pub;
|
|
|
|
int inpipe;
|
|
int outpipe;
|
|
pid_t pid; //so we don't end up with zombie processes
|
|
|
|
qbyte inbuffer[2048];
|
|
int inbufsize;
|
|
} linsubserver_t;
|
|
|
|
static void Sys_InstructSlave(pubsubserver_t *ps, sizebuf_t *cmd)
|
|
{
|
|
//FIXME: this is blocking. this is bad if the target is also blocking while trying to write to us.
|
|
//FIXME: merge buffering logic with SSV_InstructMaster, and allow for failure if full
|
|
linsubserver_t *s = (linsubserver_t*)ps;
|
|
if (s->outpipe == -1)
|
|
return; //it already died.
|
|
cmd->data[0] = cmd->cursize & 0xff;
|
|
cmd->data[1] = (cmd->cursize>>8) & 0xff;
|
|
write(s->outpipe, cmd->data, cmd->cursize);
|
|
}
|
|
|
|
static int Sys_SubServerRead(pubsubserver_t *ps)
|
|
{
|
|
linsubserver_t *s = (linsubserver_t*)ps;
|
|
|
|
if (s->inbufsize < sizeof(s->inbuffer) && s->inpipe != -1)
|
|
{
|
|
ssize_t avail = read(s->inpipe, s->inbuffer+s->inbufsize, sizeof(s->inbuffer)-s->inbufsize);
|
|
if (!avail)
|
|
{ //eof
|
|
close(s->inpipe);
|
|
close(s->outpipe);
|
|
Con_Printf("%i:%s has died\n", s->pub.id, s->pub.name);
|
|
s->inpipe = -1;
|
|
s->outpipe = -1;
|
|
waitpid(s->pid, NULL, 0);
|
|
}
|
|
else if (avail < 0)
|
|
{
|
|
int e = errno;
|
|
if (e == EAGAIN || e == EWOULDBLOCK)
|
|
;
|
|
else
|
|
perror("subserver read");
|
|
}
|
|
else
|
|
s->inbufsize += avail;
|
|
}
|
|
|
|
if(s->inbufsize >= 2)
|
|
{
|
|
unsigned short len = s->inbuffer[0] | (s->inbuffer[1]<<8);
|
|
if (s->inbufsize >= len && len>=2)
|
|
{
|
|
memcpy(net_message.data, s->inbuffer+2, len-2);
|
|
net_message.cursize = len-2;
|
|
memmove(s->inbuffer, s->inbuffer+len, s->inbufsize - len);
|
|
s->inbufsize -= len;
|
|
MSG_BeginReading (msg_nullnetprim);
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
else if (s->inpipe == -1)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef SQL
|
|
#include "sv_sql.h"
|
|
#endif
|
|
|
|
pubsubserver_t *Sys_ForkServer(void)
|
|
{
|
|
#ifdef SERVERONLY
|
|
// extern jmp_buf host_abort;
|
|
|
|
int toslave[2];
|
|
int tomaster[2];
|
|
linsubserver_t *ctx;
|
|
pid_t pid;
|
|
|
|
//make sure we're fully synced, so that workers can't mess up
|
|
Cvar_Set(Cvar_FindVar("worker_count"), "0");
|
|
COM_WorkerFullSync();
|
|
#ifdef WEBCLIENT
|
|
DL_DeThread();
|
|
#endif
|
|
#ifdef SQL
|
|
SQL_KillServers(NULL); //FIXME: this is bad...
|
|
#endif
|
|
//FIXME: we should probably use posix_atfork for those.
|
|
|
|
pipe(toslave);
|
|
pipe(tomaster);
|
|
|
|
//make the reads non-blocking.
|
|
fcntl(toslave[1], F_SETFL, fcntl(toslave[1], F_GETFL, 0)|O_NONBLOCK);
|
|
fcntl(tomaster[0], F_SETFL, fcntl(tomaster[0], F_GETFL, 0)|O_NONBLOCK);
|
|
|
|
pid = fork();
|
|
|
|
if (!pid)
|
|
{ //this is the child
|
|
dup2(toslave[0], STDIN);
|
|
close(toslave[1]);
|
|
close(toslave[0]);
|
|
dup2(tomaster[1], STDOUT);
|
|
|
|
isClusterSlave = true;
|
|
|
|
FS_UnloadPackFiles(); //these handles got wiped. make sure they're all properly wiped before loading new handles.
|
|
NET_Shutdown();
|
|
|
|
FS_ReloadPackFiles();
|
|
|
|
return NULL; //lets hope the caller can cope.
|
|
//jump out into the main work loop
|
|
// longjmp(host_abort, 1);
|
|
// exit(0); //err...
|
|
}
|
|
else
|
|
{ //this is the parent
|
|
close(toslave[0]);
|
|
close(tomaster[1]);
|
|
if (pid == -1)
|
|
{ //fork failed. make sure everything is destroyed.
|
|
close(toslave[1]);
|
|
close(tomaster[0]);
|
|
return NULL;
|
|
}
|
|
|
|
Con_DPrintf("Forked new server node\n");
|
|
ctx = Z_Malloc(sizeof(*ctx));
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
int toslave[2];
|
|
int tomaster[2];
|
|
char exename[MAX_OSPATH];
|
|
posix_spawn_file_actions_t action;
|
|
linsubserver_t *ctx;
|
|
char *argv[64];
|
|
int argc = 0;
|
|
|
|
argv[argc++] = exename;
|
|
argv[argc++] = "-clusterslave";
|
|
argc += FS_GetManifestArgv(argv+argc, countof(argv)-argc-1);
|
|
argv[argc++] = NULL;
|
|
|
|
#if 0
|
|
strcpy(exename, "/bin/ls");
|
|
args[1] = NULL;
|
|
#elif 0
|
|
strcpy(exename, "/tmp/ftedbg/fteqw.sv");
|
|
#else
|
|
memset(exename, 0, sizeof(exename)); //having problems with valgrind being stupid.
|
|
if (readlink("/proc/self/exe", exename, sizeof(exename)-1) <= 0)
|
|
return NULL;
|
|
#endif
|
|
Con_DPrintf("Execing %s\n", exename);
|
|
|
|
ctx = Z_Malloc(sizeof(*ctx));
|
|
|
|
pipe(toslave);
|
|
pipe(tomaster);
|
|
|
|
//make the reads non-blocking.
|
|
fcntl(toslave[1], F_SETFL, fcntl(toslave[1], F_GETFL, 0)|O_NONBLOCK);
|
|
fcntl(tomaster[0], F_SETFL, fcntl(tomaster[0], F_GETFL, 0)|O_NONBLOCK);
|
|
|
|
posix_spawn_file_actions_init(&action);
|
|
posix_spawn_file_actions_addclose(&action, toslave[1]);
|
|
posix_spawn_file_actions_addclose(&action, tomaster[0]);
|
|
|
|
posix_spawn_file_actions_adddup2(&action, toslave[0], STDIN);
|
|
posix_spawn_file_actions_adddup2(&action, tomaster[1], STDOUT);
|
|
// posix_spawn_file_actions_adddup2(&action, tomaster[1], STDERR);
|
|
|
|
posix_spawn_file_actions_addclose(&action, toslave[0]);
|
|
posix_spawn_file_actions_addclose(&action, tomaster[1]);
|
|
|
|
posix_spawn(&ctx->pid, exename, &action, NULL, argv, NULL);
|
|
#endif
|
|
|
|
ctx->inpipe = tomaster[0];
|
|
close(tomaster[1]);
|
|
close(toslave[0]);
|
|
ctx->outpipe = toslave[1];
|
|
|
|
ctx->pub.funcs.InstructSlave = Sys_InstructSlave;
|
|
ctx->pub.funcs.SubServerRead = Sys_SubServerRead;
|
|
return &ctx->pub;
|
|
}
|
|
|
|
void Sys_InstructMaster(sizebuf_t *cmd)
|
|
{
|
|
write(STDOUT, cmd->data, cmd->cursize);
|
|
|
|
//FIXME: handle partial writes.
|
|
}
|
|
|
|
void SSV_CheckFromMaster(void)
|
|
{
|
|
static char inbuffer[1024];
|
|
static int inbufsize;
|
|
for(;;)
|
|
{
|
|
if(inbufsize >= 2)
|
|
{
|
|
unsigned short len = inbuffer[0] | (inbuffer[1]<<8);
|
|
if (inbufsize >= len && len>=2)
|
|
{
|
|
memcpy(net_message.data, inbuffer+2, len-2);
|
|
net_message.cursize = len-2;
|
|
memmove(inbuffer, inbuffer+len, inbufsize - len);
|
|
inbufsize -= len;
|
|
MSG_BeginReading (msg_nullnetprim);
|
|
|
|
SSV_ReadFromControlServer();
|
|
|
|
continue; //keep trying to handle it
|
|
}
|
|
}
|
|
|
|
if (inbufsize == sizeof(inbuffer))
|
|
{ //fatal: we can't easily recover from this.
|
|
SV_FinalMessage("Cluster message too large\n");
|
|
Cmd_ExecuteString("quit force", RESTRICT_LOCAL);
|
|
break;
|
|
}
|
|
|
|
{
|
|
ssize_t avail = read(STDIN, inbuffer+inbufsize, sizeof(inbuffer)-inbufsize);
|
|
if (!avail)
|
|
{ //eof
|
|
SV_FinalMessage("Cluster shut down\n");
|
|
Cmd_ExecuteString("quit force", RESTRICT_LOCAL);
|
|
break;
|
|
}
|
|
else if (avail < 0)
|
|
{
|
|
int e = errno;
|
|
if (e == EAGAIN || e == EWOULDBLOCK)
|
|
;
|
|
else
|
|
perror("master read");
|
|
break;
|
|
}
|
|
else
|
|
inbufsize += avail;
|
|
}
|
|
}
|
|
}
|
|
#endif
|