e8aa715763
GL: r_dynamic -1 is now r_temporalscenecache 1, which makes menu options etc a little friendlier. fixed a serious memory leak. GL: Lightmaps are now uploaded using pbos to reduce cpu stalls (especially with temporalscenecache) and the resulting periodic framerate drops. Requires gl4.4. PM: moved manifest-downloads to the package manager. still needs some proper testing. PM: Fixed bug with downloading updates from every known mirror for that update. PM: Fixed bug with duplicate mirrors... PM: menuqc is now able to query available updates. engine's Draw_TextBox centers the text box more appropriately and easily. SV: added sv_autooffload cvar, when set the map command will automatically create a server in a separate process to reduce the effects of stutter in inefficient ssqc mods. Menu: menu_mods now shares data with getgamedirinfo builtin. MenuQC: Added some extra properties to the getgamedirinfo builtin. MenuQC: Added Menu_RendererRestarted entrypoint. MenuQC: _vid_renderer_opts cvar now has a value that actually reflects the windowing systems in the build, rather than just renderers. CQSC: Added getlocationname builtin. ALSA: device names are now more consistent with other audio drivers. SV: added unsavegame console command, to delete unwanted saved games. SV: hashtable entries are now saved into saved games. SV: reworked player remapping strategy when loading games. Player slots are now directly swapped serverside, not reconnected. SV: resend all csqc entity state when a client signals that it started recording a demo. SV: Added SOLID_BSPTRIGGER as a shapely alternative to the aabb SOLID_TRIGGER. modelindex must still be set for this to work. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5668 fc73d0e0-1445-4013-8a0c-d673dee63da5
647 lines
15 KiB
C
647 lines
15 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>
|
|
|
|
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_FILENO);
|
|
close(toslave[1]);
|
|
close(toslave[0]);
|
|
dup2(tomaster[1], STDOUT_FILENO);
|
|
|
|
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;
|
|
int l;
|
|
|
|
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
|
|
l = readlink("/proc/self/exe", exename, sizeof(exename)-1);
|
|
if (l <= 0)
|
|
return NULL;
|
|
exename[l] = 0;
|
|
#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_FILENO);
|
|
posix_spawn_file_actions_adddup2(&action, tomaster[1], STDOUT_FILENO);
|
|
// posix_spawn_file_actions_adddup2(&action, tomaster[1], STDERR_FILENO);
|
|
|
|
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_FILENO, cmd->data, cmd->cursize);
|
|
|
|
//FIXME: handle partial writes.
|
|
}
|
|
|
|
void SSV_CheckFromMaster(void)
|
|
{
|
|
static char inbuffer[1024];
|
|
static int inbufsize;
|
|
|
|
#if defined(__linux__) && defined(_DEBUG)
|
|
int fl = fcntl (STDIN_FILENO, F_GETFL, 0);
|
|
if (!(fl & FNDELAY))
|
|
{
|
|
fcntl(STDIN_FILENO, F_SETFL, fl | FNDELAY);
|
|
Sys_Printf(CON_WARNING "stdin flags became blocking - gdb bug?\n");
|
|
}
|
|
#endif
|
|
|
|
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_FILENO, 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
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef HAVEAUTOUPDATE
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
qboolean Sys_SetUpdatedBinary(const char *newbinary)
|
|
{
|
|
char enginebinary[MAX_OSPATH];
|
|
char tmpbinary[MAX_OSPATH];
|
|
// char enginebinarybackup[MAX_OSPATH+4];
|
|
// size_t len;
|
|
int i;
|
|
struct stat src, dst;
|
|
|
|
//windows is annoying. we can't delete a file that's in use (no orphaning)
|
|
//we can normally rename it to something else before writing a new file with the original name.
|
|
//then delete the old file later (sadly only on reboot)
|
|
|
|
//get the binary name
|
|
i = readlink("/proc/self/exe", enginebinary, sizeof(enginebinary)-1);
|
|
if (i <= 0)
|
|
return false;
|
|
enginebinary[i] = 0;
|
|
|
|
//generate the temp name
|
|
/*memcpy(enginebinarybackup, enginebinary, sizeof(enginebinary));
|
|
len = strlen(enginebinarybackup);
|
|
if (len > 4 && !strcasecmp(enginebinarybackup+len-4, ".bin"))
|
|
len -= 4; //swap its extension over, if we can.
|
|
strcpy(enginebinarybackup+len, ".bak");*/
|
|
|
|
//copy over file permissions (don't ignore the user)
|
|
if (stat(enginebinary, &dst)<0)
|
|
dst.st_mode = 0777;
|
|
if (stat(newbinary, &src)<0)
|
|
src.st_mode = 0777;
|
|
|
|
// if (src.st_dev != dst.st_dev)
|
|
{ //oops, its on a different filesystem. create a copy
|
|
Q_snprintfz(tmpbinary, sizeof(tmpbinary), "%s.new", enginebinary);
|
|
if (!FS_Copy(newbinary, tmpbinary, FS_SYSTEM, FS_SYSTEM))
|
|
return false;
|
|
newbinary = tmpbinary;
|
|
}
|
|
chmod(newbinary, dst.st_mode|S_IXUSR); //but make sure its executable, just in case...
|
|
|
|
//overwrite the name we were started through. this is supposed to be atomic.
|
|
if (rename(newbinary, enginebinary) < 0)
|
|
{
|
|
Con_Printf("Failed to overwrite %s with %s\n", enginebinary, newbinary);
|
|
return false; //failed
|
|
}
|
|
return true; //succeeded.
|
|
}
|
|
qboolean Sys_EngineMayUpdate(void)
|
|
{
|
|
char enginebinary[MAX_OSPATH];
|
|
char *e;
|
|
int len;
|
|
|
|
#define SVNREVISIONSTR STRINGIFY(SVNREVISION)
|
|
if (!COM_CheckParm("-allowupdate"))
|
|
{
|
|
//no revision info in this build, meaning its custom built and thus cannot check against the available updated versions.
|
|
if (!strcmp(SVNREVISIONSTR, "-"))
|
|
return false;
|
|
|
|
//svn revision didn't parse as an exact number. this implies it has an 'M' in it to mark it as modified.
|
|
//either way, its bad and autoupdates when we don't know what we're updating from is a bad idea.
|
|
strtoul(SVNREVISIONSTR, &e, 10);
|
|
if (!*SVNREVISIONSTR || *e)
|
|
return false;
|
|
}
|
|
|
|
//update blocked via commandline
|
|
if (COM_CheckParm("-noupdate") || COM_CheckParm("--noupdate") || COM_CheckParm("-noautoupdate") || COM_CheckParm("--noautoupdate"))
|
|
return false;
|
|
|
|
|
|
//check that we can actually do it.
|
|
len = readlink("/proc/self/exe", enginebinary, sizeof(enginebinary)-1);
|
|
if (len <= 0)
|
|
return false;
|
|
enginebinary[len] = 0;
|
|
if (access(enginebinary, R_OK|W_OK|X_OK) < 0)
|
|
return false; //can't write it. don't try downloading updates.
|
|
*COM_SkipPath(enginebinary) = 0;
|
|
if (access(enginebinary, R_OK|W_OK) < 0)
|
|
return false; //can't write to the containing directory. this does not bode well for moves/overwrites.
|
|
|
|
|
|
return true;
|
|
}
|
|
#endif
|