811bce25f1
Change revision displays, use the SVN commit date instead of using __DATE__ (when there's no local changes). This should allow reproducible builds. Added s_al_disable cvar, to block openal and all the various problems people have had with it, without having to name an explicit fallback (which would vary by system). Add mastervolume cvar (for ss). Add r_shadows 2 (aka fake shadows - for ss). Add scr_loadingscreen_aspect -1 setting, to disable levelshots entirely, also disables the progress bar (for ss). Better support for some effectinfo hacks (for ss). Added dpcompat_nocsqcwarnings (because of lazy+buggy mods like ss). Rework the dpcsqc versions of project+unproject builtins for better compat (for ss). Added dpcompat_csqcinputeventtypes to block unexpected csqc input events (for ss). Better compat with DP's loadfont console command (for ss). Added dpcompat_smallerfonts cvar to replicate a DP bug (for ss). Detect dp's m_draw extension, to work around it (for ss). Cvar dpcompat_ignoremodificationtimes added. A value of 0 favour the most recently modified file, 1 will use DP-like alphabetically sorted preferences (for ss). loadfont builtin can now accept outline=1 in the sizes arg for slightly more readable fonts. Fix bbox calcs for rotated entities, fix needed for r_ignorenetpvs 0. Hackily parse emoji.json to provide 💩 etc suggestions. Skip prediction entirely when there's no local entity info. This fixes stair-smoothing in xonotic. screenshot_cubemap will now capture half-float images when saving to ktx or dds files. Fix support for xcf files larger than 4gb, mostly to avoid compiler warnings. Fixed size of gfx/loading.lmp when replacement textures are used. Added mipmap support for rg8 and l8a8 textures. r_hdr_framebuffer cvar updated to support format names instead of random negative numbers. Description updated to name some interesting ones. Perform autoupdate _checks_ ONLY with explicit user confirmation (actual updating already needed user confirmation, but this extra step should reduce the chances of us getting wrongly accused of exfiltrating user data if we're run in a sandbox - we ONLY ever included the updating engine's version in the checks, though there's nothing we can do to avoid sending the user's router's IP). Removed the 'summon satan all over your harddrive' quit message, in case paranoid security researchers are idiots and don't bother doing actual research. Removed the triptohell.info and fte.triptohell.info certificates, they really need to stop being self-signed. The updates domain is still self-signed for autoupdates. Video drivers are now able to report supported video resolutions, visible to menuqc. Currently only works with SDL2 builds. Added setmousepos builtin. Should work with glx+win32 build. VF_SKYROOM_CAMERA can now accept an extra two args, setviewprop(VF_SKYROOM_CAMERA, org, axis, degrees). Removed v_skyroom_origin+v_skyroom_orientation cvars in favour just v_skyroom, which should make it behave more like the 'fog' command (used when csqc isn't overriding). Added R_EndPolygonRibbon builtin to make it faster+easier to generate textured ribbon/cable/etc wide lines (for TW). sdl: Fix up sys_sdl.c's file enumeration to support wildcards in directories. edit command now displays end1.bin/end2.bin correctly, because we can. Finally add support for f_modified - though ruleset_allow_larger_models and ruleset_allow_overlong_sounds generally make it redundant. Fix threading race condition in sha1 lookups. Updated f_ruleset to include the same extra flags reported by ezquake. A mod's default.fmf file can now contain an eg 'mainconfig config.cfg' line (to explicitly set the main config saved with cfg_save_auto 1 etc). fmf: basegame steam:GameName/GameDir can be used to try to load a mod directory from an installed steam game. The resulting gamedir will be read-only. HOMEDIR CHANGE: use homedirs only if the basedir cannot be written or a homedir already exists, which should further reduce the probability of microsoft randomly uploading our data to their cloud (but mostly because its annoying to never know where your data is written). Fixed buf_cvarlist, should work in xonotic now, and without segfaults. Added an extra arg to URI_Get_Callback calls - the response size, also changed the tempstring to contain all bytes of the response, you need to be careful about nulls though. Try to work around nvidia's forced-panning bug on x11 when changing video modes. This might screw with other programs. sdl: support custom icons. sdl: support choosing a specific display. Added some documentation to menuqc builtins. menusys: use outlines for slightly more readable fonts. menusys: switch vid_width and vid_height combos into a single video mode combo to set both according to reported video modes. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5581 fc73d0e0-1445-4013-8a0c-d673dee63da5
1251 lines
27 KiB
C
1251 lines
27 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.
|
|
|
|
*/
|
|
#ifdef __linux__
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE //we need this in order to fix up broken backtraces. make sure its defined only where needed so we still some posixy conformance test on one plat.
|
|
#endif
|
|
#endif
|
|
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <dlfcn.h>
|
|
#include "quakedef.h"
|
|
|
|
|
|
#undef malloc
|
|
|
|
#ifdef NeXT
|
|
#include <libc.h>
|
|
#endif
|
|
|
|
#include <sys/stat.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef MULTITHREAD
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
//if we're chrooting, then we need to poke a few other systems to ensure libraries are loaded
|
|
#include "netinc.h"
|
|
#endif
|
|
|
|
|
|
// callbacks
|
|
void Sys_Linebuffer_Callback (struct cvar_s *var, char *oldvalue);
|
|
|
|
cvar_t sys_nostdout = CVAR("sys_nostdout","0");
|
|
cvar_t sys_extrasleep = CVAR("sys_extrasleep","0");
|
|
cvar_t sys_colorconsole = CVAR("sys_colorconsole", "1");
|
|
cvar_t sys_linebuffer = CVARC("sys_linebuffer", "1", Sys_Linebuffer_Callback);
|
|
|
|
qboolean stdin_ready;
|
|
|
|
struct termios orig, changes;
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
REQUIRED SYS FUNCTIONS
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
============
|
|
Sys_mkdir
|
|
|
|
============
|
|
*/
|
|
void Sys_mkdir (const char *path)
|
|
{
|
|
if (mkdir (path, 0755) != -1)
|
|
return;
|
|
// if (errno != EEXIST)
|
|
// Sys_Error ("mkdir %s: %s",path, strerror(errno));
|
|
}
|
|
qboolean Sys_rmdir (const char *path)
|
|
{
|
|
if (rmdir (path) == 0)
|
|
return true;
|
|
if (errno == ENOENT)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
qboolean Sys_remove (const char *path)
|
|
{
|
|
#ifdef __unix__
|
|
return unlink(path);
|
|
#else
|
|
return system(va("rm \"%s\"", path));
|
|
#endif
|
|
}
|
|
|
|
qboolean Sys_Rename (const char *oldfname, const char *newfname)
|
|
{
|
|
return !rename(oldfname, newfname);
|
|
}
|
|
|
|
|
|
#if _POSIX_C_SOURCE >= 200112L
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
qboolean Sys_GetFreeDiskSpace(const char *path, quint64_t *freespace)
|
|
{
|
|
#if _POSIX_C_SOURCE >= 200112L
|
|
//posix 2001
|
|
struct statvfs inf;
|
|
if(0==statvfs(path, &inf))
|
|
{
|
|
*freespace = inf.f_bsize*(quint64_t)inf.f_bavail;
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
int Sys_DebugLog(char *file, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char data[1024];
|
|
int fd;
|
|
size_t result;
|
|
|
|
va_start(argptr, fmt);
|
|
vsnprintf (data,sizeof(data)-1, fmt, argptr);
|
|
va_end(argptr);
|
|
|
|
if (strlen(data) >= sizeof(data)-1)
|
|
Sys_Error("Sys_DebugLog was stomped\n");
|
|
|
|
fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0666);
|
|
if (fd)
|
|
{
|
|
result = write(fd, data, strlen(data)); // do something with the result
|
|
|
|
if (result != strlen(data))
|
|
Con_Printf("Sys_DebugLog() write: Filename: %s, expected %lu, result was %lu (%s)\n",file,(unsigned long)strlen(data),(unsigned long)result,strerror(errno));
|
|
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Sys_Milliseconds
|
|
================
|
|
*/
|
|
unsigned int Sys_Milliseconds (void)
|
|
{
|
|
struct timeval tp;
|
|
struct timezone tzp;
|
|
static int secbase;
|
|
|
|
gettimeofday(&tp, &tzp);
|
|
|
|
if (!secbase)
|
|
{
|
|
secbase = tp.tv_sec;
|
|
return tp.tv_usec/1000;
|
|
}
|
|
return (tp.tv_sec - secbase)*1000 + tp.tv_usec/1000;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Sys_DoubleTime
|
|
================
|
|
*/
|
|
double Sys_DoubleTime (void)
|
|
{
|
|
struct timeval tp;
|
|
struct timezone tzp;
|
|
static int secbase;
|
|
|
|
gettimeofday(&tp, &tzp);
|
|
|
|
if (!secbase)
|
|
{
|
|
secbase = tp.tv_sec;
|
|
return tp.tv_usec/1000000.0;
|
|
}
|
|
|
|
return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Sys_Error
|
|
================
|
|
*/
|
|
void Sys_Error (const char *error, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[1024];
|
|
|
|
va_start (argptr,error);
|
|
vsnprintf (string,sizeof(string)-1, error,argptr);
|
|
va_end (argptr);
|
|
COM_WorkerAbort(string);
|
|
printf ("Fatal error: %s\n",string);
|
|
|
|
tcsetattr(STDIN_FILENO, TCSADRAIN, &orig);
|
|
|
|
//we used to fire sigsegv. this resulted in people reporting segfaults and not the error message that appeared above. resulting in wasted debugging.
|
|
//abort should trigger a SIGABRT and still give us the same stack trace. should be more useful that way.
|
|
abort();
|
|
|
|
exit (1);
|
|
}
|
|
|
|
static qboolean useansicolours;
|
|
static int ansiremap[8] = {0, 4, 2, 6, 1, 5, 3, 7};
|
|
static void ApplyColour(unsigned int chr)
|
|
{
|
|
static int oldchar = CON_WHITEMASK;
|
|
int bg, fg;
|
|
chr &= CON_FLAGSMASK;
|
|
|
|
if (oldchar == chr)
|
|
return;
|
|
oldchar = chr;
|
|
if (!useansicolours) //don't spew weird chars when redirected to a file.
|
|
return;
|
|
|
|
printf("\e[0;"); // reset
|
|
|
|
if (chr & CON_BLINKTEXT)
|
|
printf("5;"); // set blink
|
|
|
|
bg = (chr & CON_BGMASK) >> CON_BGSHIFT;
|
|
fg = (chr & CON_FGMASK) >> CON_FGSHIFT;
|
|
|
|
// don't handle intensive bit for background
|
|
// as terminals differ too much in displaying \e[1;7;3?m
|
|
bg &= 0x7;
|
|
|
|
if (chr & CON_NONCLEARBG)
|
|
{
|
|
if (fg & 0x8) // intensive bit set for foreground
|
|
{
|
|
printf("1;"); // set bold/intensity ansi flag
|
|
fg &= 0x7; // strip intensive bit
|
|
}
|
|
|
|
// set foreground and background colors
|
|
printf("3%i;4%im", ansiremap[fg], ansiremap[bg]);
|
|
}
|
|
else
|
|
{
|
|
switch(fg)
|
|
{
|
|
//to get around wierd defaults (like a white background) we have these special hacks for colours 0 and 7
|
|
case COLOR_BLACK:
|
|
printf("7m"); // set inverse
|
|
break;
|
|
case COLOR_GREY:
|
|
printf("1;30m"); // treat as dark grey
|
|
break;
|
|
case COLOR_WHITE:
|
|
printf("m"); // set nothing else
|
|
break;
|
|
default:
|
|
if (fg & 0x8) // intensive bit set for foreground
|
|
{
|
|
printf("1;"); // set bold/intensity ansi flag
|
|
fg &= 0x7; // strip intensive bit
|
|
}
|
|
|
|
printf("3%im", ansiremap[fg]); // set foreground
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define putch(c) putc(c, stdout);
|
|
/*static void Sys_PrintColouredChar(unsigned int chr)
|
|
{
|
|
ApplyColour(chr);
|
|
|
|
chr = chr & CON_CHARMASK;
|
|
|
|
if ((chr > 128 || chr < 32) && chr != 10 && chr != 13 && chr != 9)
|
|
printf("[%02x]", chr);
|
|
else
|
|
chr &= ~0x80;
|
|
|
|
putch(chr);
|
|
}*/
|
|
|
|
/*
|
|
================
|
|
Sys_Printf
|
|
================
|
|
*/
|
|
#define MAXPRINTMSG 4096
|
|
char coninput_text[256];
|
|
int coninput_len;
|
|
void Sys_Printf (char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
if (sys_nostdout.value)
|
|
return;
|
|
|
|
if (1)
|
|
{
|
|
char msg[MAXPRINTMSG];
|
|
unsigned char *t;
|
|
|
|
va_start (argptr,fmt);
|
|
vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
#ifdef SUBSERVERS
|
|
if (SSV_IsSubServer())
|
|
{
|
|
SSV_PrintToMaster(msg);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
//if we're not linebuffered, kill the currently displayed input line, add the new text, and add more output.
|
|
if (!sys_linebuffer.value)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < coninput_len; i++)
|
|
putch('\b');
|
|
putch('\b');
|
|
for (i = 0; i < coninput_len; i++)
|
|
putch(' ');
|
|
putch(' ');
|
|
for (i = 0; i < coninput_len; i++)
|
|
putch('\b');
|
|
putch('\b');
|
|
}
|
|
|
|
|
|
if (sys_colorconsole.value)
|
|
{
|
|
wchar_t w;
|
|
conchar_t *e, *c;
|
|
conchar_t ctext[MAXPRINTMSG];
|
|
unsigned int codeflags, codepoint;
|
|
e = COM_ParseFunString(CON_WHITEMASK, msg, ctext, sizeof(ctext), false);
|
|
for (c = ctext; c < e; )
|
|
{
|
|
c = Font_Decode(c, &codeflags, &codepoint);
|
|
if (codeflags & CON_HIDDEN)
|
|
continue;
|
|
|
|
if ((codeflags&CON_RICHFORECOLOUR) || (codepoint == '\n' && (codeflags&CON_NONCLEARBG)))
|
|
codeflags = CON_WHITEMASK; //make sure we don't get annoying backgrounds on other lines.
|
|
ApplyColour(codeflags);
|
|
w = codepoint;
|
|
if (w >= 0xe000 && w < 0xe100)
|
|
{
|
|
/*not all quake chars are ascii compatible, so map those control chars to safe ones so we don't mess up anyone's xterm*/
|
|
if ((w & 0x7f) > 0x20)
|
|
putc(w&0x7f, stdout);
|
|
else if (w & 0x80)
|
|
{
|
|
static char tab[32] = "---#@.@@@@ # >.." "[]0123456789.---";
|
|
putc(tab[w&31], stdout);
|
|
}
|
|
else
|
|
{
|
|
static char tab[32] = ".####.#### # >.." "[]0123456789.---";
|
|
putc(tab[w&31], stdout);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*putwc doesn't like me. force it in utf8*/
|
|
if (w >= 0x80)
|
|
{
|
|
if (w > 0x800)
|
|
{
|
|
putc(0xe0 | ((w>>12)&0x0f), stdout);
|
|
putc(0x80 | ((w>>6)&0x3f), stdout);
|
|
}
|
|
else
|
|
putc(0xc0 | ((w>>6)&0x1f), stdout);
|
|
putc(0x80 | (w&0x3f), stdout);
|
|
}
|
|
else
|
|
putc(w, stdout);
|
|
}
|
|
}
|
|
ApplyColour(CON_WHITEMASK);
|
|
}
|
|
else
|
|
{
|
|
for (t = (unsigned char*)msg; *t; t++)
|
|
{
|
|
if (*t >= 146 && *t < 156)
|
|
*t = *t - 146 + '0';
|
|
if (*t >= 0x12 && *t <= 0x1b)
|
|
*t = *t - 0x12 + '0';
|
|
if (*t == 143)
|
|
*t = '.';
|
|
if (*t == 157 || *t == 158 || *t == 159)
|
|
*t = '-';
|
|
if (*t >= 128)
|
|
*t -= 128;
|
|
if (*t == 16)
|
|
*t = '[';
|
|
if (*t == 17)
|
|
*t = ']';
|
|
if (*t == 0x1c)
|
|
*t = 249;
|
|
|
|
*t &= 0x7f;
|
|
if ((*t > 128 || *t < 32) && *t != 10 && *t != 13 && *t != 9)
|
|
printf("[%02x]", *t);
|
|
else
|
|
putc(*t, stdout);
|
|
}
|
|
}
|
|
|
|
//and put the input line back
|
|
if (!sys_linebuffer.value)
|
|
{
|
|
if (coninput_len)
|
|
printf("]%s", coninput_text);
|
|
else
|
|
putch(']');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
va_start (argptr,fmt);
|
|
vprintf (fmt,argptr);
|
|
va_end (argptr);
|
|
}
|
|
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
/*
|
|
================
|
|
Sys_Printf
|
|
================
|
|
*/
|
|
void Sys_Printf (char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
static char text[2048];
|
|
unsigned char *p;
|
|
|
|
if (sys_nostdout.value || SSV_IsSubServer())
|
|
return;
|
|
|
|
va_start (argptr,fmt);
|
|
vsnprintf (text,sizeof(text)-1, fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
if (strlen(text) > sizeof(text))
|
|
Sys_Error("memory overwrite in Sys_Printf");
|
|
|
|
for (p = (unsigned char *)text; *p; p++) {
|
|
*p &= 0x7f;
|
|
if ((*p > 128 || *p < 32) && *p != 10 && *p != 13 && *p != 9)
|
|
printf("[%02x]", *p);
|
|
else
|
|
putc(*p, stdout);
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
================
|
|
Sys_Quit
|
|
================
|
|
*/
|
|
void Sys_Quit (void)
|
|
{
|
|
tcsetattr(STDIN_FILENO, TCSADRAIN, &orig);
|
|
exit (0); // appkit isn't running
|
|
}
|
|
|
|
static int do_stdin = 1;
|
|
|
|
#if 1
|
|
static char *Sys_LineInputChar(char *line)
|
|
{
|
|
char c;
|
|
while(*line)
|
|
{
|
|
c = *line++;
|
|
if (c == '\r' || c == '\n')
|
|
{
|
|
coninput_text[coninput_len] = 0;
|
|
putch ('\n');
|
|
putch (']');
|
|
coninput_len = 0;
|
|
fflush(stdout);
|
|
return coninput_text;
|
|
}
|
|
if (c == 8)
|
|
{
|
|
if (coninput_len)
|
|
{
|
|
putch (c);
|
|
putch (' ');
|
|
putch (c);
|
|
coninput_len--;
|
|
coninput_text[coninput_len] = 0;
|
|
}
|
|
continue;
|
|
}
|
|
if (c == '\t')
|
|
{
|
|
int i;
|
|
char *s = Cmd_CompleteCommand(coninput_text, true, true, 0, NULL);
|
|
if(s)
|
|
{
|
|
for (i = 0; i < coninput_len; i++)
|
|
putch('\b');
|
|
for (i = 0; i < coninput_len; i++)
|
|
putch(' ');
|
|
for (i = 0; i < coninput_len; i++)
|
|
putch('\b');
|
|
|
|
strcpy(coninput_text, s);
|
|
coninput_len = strlen(coninput_text);
|
|
printf("%s", coninput_text);
|
|
}
|
|
continue;
|
|
}
|
|
putch (c);
|
|
coninput_text[coninput_len] = c;
|
|
coninput_len++;
|
|
coninput_text[coninput_len] = 0;
|
|
if (coninput_len == sizeof(coninput_text))
|
|
coninput_len = 0;
|
|
}
|
|
fflush(stdout);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
/*
|
|
================
|
|
Sys_ConsoleInput
|
|
|
|
Checks for a complete line of text typed in at the console, then forwards
|
|
it to the host command processor
|
|
================
|
|
*/
|
|
void Sys_Linebuffer_Callback (struct cvar_s *var, char *oldvalue)
|
|
{
|
|
changes = orig;
|
|
if (var->value)
|
|
{
|
|
changes.c_lflag |= (ICANON|ECHO);
|
|
}
|
|
else
|
|
{
|
|
changes.c_lflag &= ~(ICANON|ECHO);
|
|
changes.c_cc[VTIME] = 0;
|
|
changes.c_cc[VMIN] = 1;
|
|
}
|
|
tcsetattr(STDIN_FILENO, TCSADRAIN, &changes);
|
|
}
|
|
|
|
char *Sys_ConsoleInput (void)
|
|
{
|
|
static char text[256];
|
|
int len;
|
|
|
|
#ifdef SUBSERVERS
|
|
if (SSV_IsSubServer())
|
|
{
|
|
SSV_CheckFromMaster();
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
if (!stdin_ready || !do_stdin)
|
|
return NULL; // the select didn't say it was ready
|
|
stdin_ready = false;
|
|
|
|
if (sys_linebuffer.value == 0)
|
|
{
|
|
text[0] = getc(stdin);
|
|
text[1] = 0;
|
|
len = 1;
|
|
return Sys_LineInputChar(text);
|
|
}
|
|
else
|
|
{
|
|
len = read (0, text, sizeof(text)-1);
|
|
if (len == 0)
|
|
{
|
|
// end of file
|
|
do_stdin = 0;
|
|
return NULL;
|
|
}
|
|
if (len < 1)
|
|
return NULL;
|
|
text[len-1] = 0; // rip off the /n and terminate
|
|
|
|
return text;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Sys_Init
|
|
|
|
Quake calls this so the system can register variables before host_hunklevel
|
|
is marked
|
|
=============
|
|
*/
|
|
void Sys_Init (void)
|
|
{
|
|
Cvar_Register (&sys_nostdout, "System configuration");
|
|
Cvar_Register (&sys_extrasleep, "System configuration");
|
|
|
|
Cvar_Register (&sys_colorconsole, "System configuration");
|
|
Cvar_Register (&sys_linebuffer, "System configuration");
|
|
}
|
|
|
|
void Sys_Shutdown (void)
|
|
{
|
|
}
|
|
|
|
#if defined(__linux__) && defined(__GNUC__)
|
|
#include <execinfo.h>
|
|
#ifdef __i386__
|
|
#include <ucontext.h>
|
|
#endif
|
|
static void Friendly_Crash_Handler(int sig, siginfo_t *info, void *vcontext)
|
|
{
|
|
int fd;
|
|
void *array[10];
|
|
size_t size;
|
|
int firstframe = 0;
|
|
char signame[32];
|
|
|
|
switch(sig)
|
|
{
|
|
case SIGILL: strcpy(signame, "SIGILL"); break;
|
|
case SIGFPE: strcpy(signame, "SIGFPE"); break;
|
|
case SIGBUS: strcpy(signame, "SIGBUS"); break;
|
|
case SIGABRT: strcpy(signame, "SIGABRT"); break;
|
|
case SIGSEGV: Q_snprintfz(signame, sizeof(signame), "SIGSEGV (%p)", info->si_addr); break;
|
|
default: Q_snprintfz(signame, sizeof(signame), "%i", sig); break;
|
|
}
|
|
|
|
// get void*'s for all entries on the stack
|
|
size = backtrace(array, 10);
|
|
|
|
#if defined(__i386__)
|
|
//x86 signals don't leave the stack in a clean state, so replace the signal handler with the real crash address, and hide this function
|
|
ucontext_t *uc = vcontext;
|
|
array[1] = (void*)uc->uc_mcontext.gregs[REG_EIP];
|
|
firstframe = 1;
|
|
#elif defined(__amd64__)
|
|
//amd64 is sane enough, but this function and the libc signal handler are on the stack, and should be ignored.
|
|
firstframe = 2;
|
|
#endif
|
|
|
|
// print out all the frames to stderr
|
|
fprintf(stderr, "Error: signal %s:\n", signame);
|
|
backtrace_symbols_fd(array+firstframe, size-firstframe, 2);
|
|
|
|
fd = open("crash.log", O_WRONLY|O_CREAT|O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP);
|
|
if (fd != -1)
|
|
{
|
|
time_t rawtime;
|
|
struct tm * timeinfo;
|
|
char buffer [80];
|
|
|
|
time (&rawtime);
|
|
timeinfo = localtime (&rawtime);
|
|
strftime (buffer, sizeof(buffer), "Time: %Y-%m-%d %H:%M:%S\n",timeinfo);
|
|
write(fd, buffer, strlen(buffer));
|
|
|
|
Q_snprintfz(buffer, sizeof(buffer), "Ver: %i.%02i%s\n", FTE_VER_MAJOR, FTE_VER_MINOR,
|
|
#ifdef OFFICIAL_RELEASE
|
|
" (official)");
|
|
#else
|
|
"");
|
|
#endif
|
|
write(fd, buffer, strlen(buffer));
|
|
|
|
#if defined(SVNREVISION) && defined(SVNDATE)
|
|
Q_snprintfz(buffer, sizeof(buffer), "Revision: %s\nBinary: %s\n", STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE));
|
|
#else
|
|
Q_snprintfz(buffer, sizeof(buffer),
|
|
#ifdef SVNREVISION
|
|
"Revision: "STRINGIFY(SVNREVISION)"\n"
|
|
#endif
|
|
"Binary: "__DATE__" "__TIME__"\n");
|
|
#endif
|
|
write(fd, buffer, strlen(buffer));
|
|
|
|
backtrace_symbols_fd(array + firstframe, size - firstframe, fd);
|
|
write(fd, "\n", 1);
|
|
close(fd);
|
|
}
|
|
exit(1);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_GNUTLS
|
|
qboolean SSL_InitGlobal(qboolean isserver);
|
|
#endif
|
|
#ifdef SQL
|
|
#include "sv_sql.h"
|
|
#endif
|
|
static int Sys_CheckChRoot(void)
|
|
{ //also warns if run as root.
|
|
int ret = false;
|
|
#ifdef __linux__
|
|
//three ways to use this:
|
|
//nonroot-with-SUID-root -- chroots+drops to a fixed path when run as a regular user. the homedir mechanism can be used for writing files.
|
|
//root -chroot foo -uid bar -- requires root, changes the filesystem and then switches user rights before starting the game itself.
|
|
//root -chroot foo -- requires root, changes the filesystem and leaves the process with far far too many rights
|
|
|
|
uid_t ruid, euid, suid;
|
|
int arg = COM_CheckParm("-chroot");
|
|
const char *newroot = arg?com_argv[arg+1]:NULL;
|
|
const char *newhome;
|
|
|
|
getresuid(&ruid, &euid, &suid);
|
|
// printf("ruid %u, euid %u, suid %u\n", ruid, euid, suid);
|
|
if (!euid && ruid != euid)
|
|
{ //if we're running SUID-root then assume the admin set it up that way in order to use chroot without making any libraries available inside the jail.
|
|
//however, chroot needs a certain level of sandboxing to prevent somehow running suid programs with eg a custom /etc/passwd, etc.
|
|
//this means we can't allow
|
|
//FIXME other games. should use the list in fs.c
|
|
if (COM_CheckParm("-quake"))
|
|
newroot = "/usr/share/games/quake";
|
|
else if (COM_CheckParm("-quake2"))
|
|
newroot = "/usr/share/games/quake2";
|
|
else if (COM_CheckParm("-quake3"))
|
|
newroot = "/usr/share/games/quake3";
|
|
else if (COM_CheckParm("-hexen2") || COM_CheckParm("-portals"))
|
|
newroot = "/usr/share/games/hexen2";
|
|
else
|
|
#ifdef GAME_SHORTNAME
|
|
newroot = "/usr/share/games/" GAME_SHORTNAME;
|
|
#else
|
|
newroot = "/usr/share/games/quake";
|
|
#endif
|
|
|
|
//just read the environment name
|
|
newhome = getenv("USER");
|
|
}
|
|
else
|
|
{
|
|
newhome = NULL;
|
|
arg = COM_CheckParm("-uid");
|
|
if (arg)
|
|
ruid = strtol(com_argv[arg+1], NULL, 0);
|
|
}
|
|
|
|
if (newroot)
|
|
{ //chroot requires running as root, which sucks.
|
|
//make sure there's no suid programs in the new root dir that might get confused by /etc/ being something else.
|
|
//this binary MUST NOT be inside the new root.
|
|
|
|
//make sure we don't crash on any con_printfs.
|
|
#ifdef MULTITHREAD
|
|
Sys_ThreadsInit();
|
|
#endif
|
|
|
|
//FIXME: should we temporarily try swapping uid+euid so we don't have any more access than a non-suid binary for this initial init stuff?
|
|
{
|
|
struct addrinfo *info;
|
|
if (getaddrinfo("master.quakeservers.net", NULL, NULL, &info) == 0) //make sure we've loaded /etc/resolv.conf etc, otherwise any dns requests are going to fail, which would mean no masters.
|
|
freeaddrinfo(info);
|
|
}
|
|
|
|
#if defined(SQL) && defined(HAVE_SERVER)
|
|
SQL_Available();
|
|
#endif
|
|
#ifdef HAVE_GNUTLS
|
|
SSL_InitGlobal(false); //we need to load the known CA certs while we still can, as well as any shared objects
|
|
//SSL_InitGlobal(true); //make sure we load our public cert from outside the sandbox. an exploit might still be able to find it in memory though. FIXME: disabled in case this reads from somewhere bad - we're still root.
|
|
#endif
|
|
|
|
{ //this protects against stray setuid programs like su reading passwords from /etc/passwd et al
|
|
//there shouldn't be anyway so really this is pure paranoia.
|
|
//(the length thing is to avoid overflows inside va giving false negatives.)
|
|
struct stat s;
|
|
if (strlen(newroot) > 4096 || lstat(va("%s/etc/", newroot), &s) != -1)
|
|
{
|
|
printf("refusing to chroot to %s - contains an /etc directory\n", newroot);
|
|
return -1;
|
|
}
|
|
if (strlen(newroot) > 4096 || lstat(va("%s/proc/", newroot), &s) != -1)
|
|
{
|
|
printf("refusing to chroot to %s - contains a /proc directory\n", newroot);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
printf("Changing root dir to \"%s\"\n", newroot);
|
|
if (chroot(newroot))
|
|
{
|
|
printf("chroot call failed\n");
|
|
return -1;
|
|
}
|
|
chdir("/"); //chroot does NOT change the working directory, so we need to make sure that happens otherwise still a way out.
|
|
|
|
//signal to the fs.c code to use an explicit base home dir.
|
|
if (newhome)
|
|
setenv("FTEHOME", va("/user/%s", newhome), true);
|
|
else
|
|
setenv("FTEHOME", va("/user/%i", ruid), true);
|
|
|
|
//these paths are no longer valid.
|
|
setenv("HOME", "", true);
|
|
setenv("XDG_DATA_HOME", "", true);
|
|
|
|
setenv("PWD", "/", true);
|
|
|
|
ret = true;
|
|
}
|
|
|
|
if (ruid != euid || newroot)
|
|
{
|
|
if (setresuid(ruid, ruid, ruid)) //go back to our original user, assuming we were SUIDed
|
|
{
|
|
printf("error dropping priveledges\n");
|
|
return -1;
|
|
}
|
|
getresuid(&ruid, &euid, &suid);
|
|
|
|
if (setuid(0) != -1 || errno != EPERM)
|
|
{
|
|
printf("priveledges were not dropped...\n");
|
|
return -1;
|
|
}
|
|
getresuid(&ruid, &euid, &suid);
|
|
}
|
|
|
|
|
|
if (!ruid || !euid || !suid)
|
|
printf("WARNING: you should NOT be running this as root!\n");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
main
|
|
=============
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
float maxsleep;
|
|
quakeparms_t parms;
|
|
// fd_set fdset;
|
|
// extern int net_socket;
|
|
char bindir[MAX_OSPATH];
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
tcgetattr(STDIN_FILENO, &orig);
|
|
changes = orig;
|
|
|
|
memset (&parms, 0, sizeof(parms));
|
|
|
|
COM_InitArgv (argc, (const char **)argv);
|
|
parms.argc = com_argc;
|
|
parms.argv = com_argv;
|
|
#ifdef CONFIG_MANIFEST_TEXT
|
|
parms.manifest = CONFIG_MANIFEST_TEXT;
|
|
#endif
|
|
|
|
//decide if we should be printing colours to the stdout or not.
|
|
if (COM_CheckParm("-nocolour")||COM_CheckParm("-nocolor"))
|
|
useansicolours = false;
|
|
else
|
|
useansicolours = (isatty(STDOUT_FILENO) || COM_CheckParm("-colour") || COM_CheckParm("-color"));
|
|
|
|
switch(Sys_CheckChRoot())
|
|
{
|
|
case true:
|
|
parms.basedir = "/";
|
|
break;
|
|
case false:
|
|
parms.basedir = "./";
|
|
#ifdef __linux__
|
|
{ //attempt to figure out where the exe is located
|
|
int l = readlink("/proc/self/exe", bindir, sizeof(bindir)-1);
|
|
if (l > 0)
|
|
{
|
|
bindir[l] = 0;
|
|
*COM_SkipPath(bindir) = 0;
|
|
printf("Binary is located at \"%s\"\n", bindir);
|
|
parms.binarydir = bindir;
|
|
}
|
|
}
|
|
/*#elif defined(__bsd__)
|
|
{ //attempt to figure out where the exe is located
|
|
int l = readlink("/proc/self/exe", bindir, sizeof(bindir)-1);
|
|
if (l > 0)
|
|
{
|
|
bindir[l] = 0;
|
|
*COM_SkipPath(bindir) = 0;
|
|
printf("Binary is located at "%s"\n", bindir);
|
|
parms.binarydir = bindir;
|
|
}
|
|
}
|
|
*/
|
|
#endif
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
#if defined(__linux__) && defined(__GNUC__)
|
|
if (!COM_CheckParm("-nodumpstack"))
|
|
{
|
|
struct sigaction act;
|
|
memset(&act, 0, sizeof(act));
|
|
act.sa_sigaction = Friendly_Crash_Handler;
|
|
act.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
sigaction(SIGILL, &act, NULL);
|
|
sigaction(SIGFPE, &act, NULL);
|
|
sigaction(SIGSEGV, &act, NULL);
|
|
sigaction(SIGABRT, &act, NULL);
|
|
sigaction(SIGBUS, &act, NULL);
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef SUBSERVERS
|
|
if (COM_CheckParm("-clusterslave"))
|
|
isClusterSlave = true;
|
|
#endif
|
|
|
|
TL_InitLanguages(parms.basedir);
|
|
|
|
SV_Init (&parms);
|
|
|
|
// run one frame immediately for first heartbeat
|
|
maxsleep = SV_Frame();
|
|
|
|
//
|
|
// main loop
|
|
//
|
|
while (1)
|
|
{
|
|
if (do_stdin)
|
|
stdin_ready = NET_Sleep(maxsleep, true);
|
|
else
|
|
{
|
|
NET_Sleep(maxsleep, false);
|
|
stdin_ready = false;
|
|
}
|
|
|
|
maxsleep = SV_Frame();
|
|
|
|
// extrasleep is just a way to generate a fucked up connection on purpose
|
|
if (sys_extrasleep.value)
|
|
usleep (sys_extrasleep.value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char *match, int (*func)(const char *, qofs_t, time_t modtime, void *, searchpathfuncs_t *), void *parm, searchpathfuncs_t *spath)
|
|
{
|
|
DIR *dir;
|
|
char file[MAX_OSPATH];
|
|
const char *s;
|
|
struct dirent *ent;
|
|
struct stat st;
|
|
const char *wild;
|
|
const char *apath = truepath+apathofs;
|
|
|
|
//if there's a * in a system path, then we need to scan its parent directory to figure out what the * expands to.
|
|
//we can just recurse quicklyish to try to handle it.
|
|
wild = strchr(apath, '*');
|
|
if (!wild)
|
|
wild = strchr(apath, '?');
|
|
if (wild)
|
|
{
|
|
char subdir[MAX_OSPATH];
|
|
for (s = wild+1; *s && *s != '/'; s++)
|
|
;
|
|
while (wild > truepath)
|
|
{
|
|
if (*(wild-1) == '/')
|
|
break;
|
|
wild--;
|
|
}
|
|
memcpy(file, truepath, wild-truepath);
|
|
file[wild-truepath] = 0;
|
|
|
|
dir = opendir(file);
|
|
memcpy(subdir, wild, s-wild);
|
|
subdir[s-wild] = 0;
|
|
if (dir)
|
|
{
|
|
do
|
|
{
|
|
ent = readdir(dir);
|
|
if (!ent)
|
|
break;
|
|
if (*ent->d_name != '.')
|
|
{
|
|
if (wildcmp(subdir, ent->d_name))
|
|
{
|
|
memcpy(file, truepath, wild-truepath);
|
|
Q_snprintfz(file+(wild-truepath), sizeof(file)-(wild-truepath), "%s%s", ent->d_name, s);
|
|
if (!Sys_EnumerateFiles2(file, apathofs, match, func, parm, spath))
|
|
{
|
|
closedir(dir);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} while(1);
|
|
closedir(dir);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
dir = opendir(truepath);
|
|
if (!dir)
|
|
{
|
|
Con_DLPrintf((errno==ENOENT)?2:1, "Failed to open dir %s\n", truepath);
|
|
return true;
|
|
}
|
|
do
|
|
{
|
|
ent = readdir(dir);
|
|
if (!ent)
|
|
break;
|
|
if (*ent->d_name != '.')
|
|
{
|
|
if (wildcmp(match, ent->d_name))
|
|
{
|
|
Q_snprintfz(file, sizeof(file), "%s/%s", truepath, ent->d_name);
|
|
|
|
if (stat(file, &st) == 0)
|
|
{
|
|
Q_snprintfz(file, sizeof(file), "%s%s%s", apath, ent->d_name, S_ISDIR(st.st_mode)?"/":"");
|
|
|
|
if (!func(file, st.st_size, st.st_mtime, parm, spath))
|
|
{
|
|
Con_DPrintf("giving up on search after finding %s\n", file);
|
|
closedir(dir);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
printf("Stat failed for \"%s\"\n", file);
|
|
}
|
|
}
|
|
} while(1);
|
|
closedir(dir);
|
|
return true;
|
|
}
|
|
int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const char *, qofs_t, time_t modtime, void *, searchpathfuncs_t *), void *parm, searchpathfuncs_t *spath)
|
|
{
|
|
char apath[MAX_OSPATH];
|
|
char truepath[MAX_OSPATH];
|
|
char *s;
|
|
|
|
if (!gpath)
|
|
gpath = "";
|
|
*apath = '\0';
|
|
|
|
Q_strncpyz(apath, match, sizeof(apath));
|
|
for (s = apath+strlen(apath)-1; s >= apath; s--)
|
|
{
|
|
if (*s == '/')
|
|
{
|
|
s[1] = '\0';
|
|
match += s - apath+1;
|
|
break;
|
|
}
|
|
}
|
|
if (s < apath) //didn't find a '/'
|
|
*apath = '\0';
|
|
|
|
Q_snprintfz(truepath, sizeof(truepath), "%s/%s", gpath, apath);
|
|
return Sys_EnumerateFiles2(truepath, strlen(gpath)+1, match, func, parm, spath);
|
|
}
|
|
|
|
void Sys_CloseLibrary(dllhandle_t *lib)
|
|
{
|
|
if (sys_nounload)
|
|
return;
|
|
dlclose((void*)lib);
|
|
}
|
|
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
|
|
{
|
|
int i;
|
|
dllhandle_t *lib;
|
|
|
|
lib = dlopen (name, RTLD_LAZY);
|
|
if (!lib && !strstr(name, ".so"))
|
|
lib = dlopen (va("%s.so", name), RTLD_LAZY);
|
|
if (!lib)
|
|
{
|
|
const char *err = dlerror();
|
|
//I hate this string check
|
|
Con_DLPrintf(strstr(err, "No such file or directory")?2:0,"%s\n", err);
|
|
return NULL;
|
|
}
|
|
|
|
if (funcs)
|
|
{
|
|
for (i = 0; funcs[i].name; i++)
|
|
{
|
|
*funcs[i].funcptr = dlsym(lib, funcs[i].name);
|
|
if (!*funcs[i].funcptr)
|
|
break;
|
|
}
|
|
if (funcs[i].name)
|
|
{
|
|
Con_DPrintf("Unable to find symbol \"%s\" in \"%s\"\n", funcs[i].name, name);
|
|
Sys_CloseLibrary((dllhandle_t*)lib);
|
|
lib = NULL;
|
|
}
|
|
}
|
|
|
|
return (dllhandle_t*)lib;
|
|
}
|
|
void *Sys_GetAddressForName(dllhandle_t *module, const char *exportname)
|
|
{
|
|
if (!module)
|
|
return NULL;
|
|
return dlsym(module, exportname);
|
|
}
|
|
|
|
void Sys_ServerActivity(void)
|
|
{
|
|
}
|
|
|
|
qboolean Sys_RandomBytes(qbyte *string, int len)
|
|
{
|
|
qboolean res = false;
|
|
int fd = open("/dev/urandom", 0);
|
|
if (fd != -1)
|
|
{
|
|
res = (read(fd, string, len) == len);
|
|
close(fd);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
#ifdef WEBCLIENT
|
|
#include "fs.h"
|
|
static qboolean Sys_DoInstall(void)
|
|
{
|
|
char fname[MAX_QPATH];
|
|
float pct = 0;
|
|
qboolean applied = false;
|
|
#ifdef __unix__
|
|
qboolean showprogress = isatty(STDOUT_FILENO);
|
|
#else
|
|
qboolean showprogress = false;
|
|
#endif
|
|
|
|
#if 1
|
|
FS_CreateBasedir(NULL);
|
|
#else
|
|
char basedir[MAX_OSPATH];
|
|
if (!FS_NativePath("", FS_ROOT, basedir, sizeof(basedir)))
|
|
return true;
|
|
FS_CreateBasedir(basedir);
|
|
#endif
|
|
|
|
*fname = 0;
|
|
for(;;)
|
|
{
|
|
while(FS_DownloadingPackage())
|
|
{
|
|
const char *cur = "";
|
|
float newpct = 50;
|
|
HTTP_CL_Think(&cur, &newpct);
|
|
|
|
if (*cur && Q_strncmp(fname, cur, sizeof(fname)-1))
|
|
{
|
|
Q_strncpyz(fname, cur, sizeof(fname));
|
|
Con_Printf("Downloading: %s\n", fname);
|
|
}
|
|
if (showprogress && (int)(pct*10) != (int)(newpct*10))
|
|
{
|
|
pct = newpct;
|
|
Sys_Printf("%5.1f%%\r", pct);
|
|
}
|
|
|
|
Sys_Sleep(10/1000.0);
|
|
COM_MainThreadWork();
|
|
}
|
|
|
|
if (!applied)
|
|
{
|
|
if (!PM_MarkUpdates())
|
|
break; //no changes to apply
|
|
PM_ApplyChanges();
|
|
applied = true; //don't keep applying.
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (showprogress)
|
|
Sys_Printf(" \r");
|
|
return true;
|
|
}
|
|
qboolean Sys_RunInstaller(void)
|
|
{
|
|
if (COM_CheckParm("-install"))
|
|
{ //install THEN run
|
|
Sys_DoInstall();
|
|
return false;
|
|
}
|
|
if (COM_CheckParm("-doinstall"))
|
|
{
|
|
//install only, then quit
|
|
return Sys_DoInstall();
|
|
}
|
|
if (!com_installer)
|
|
return false;
|
|
return Sys_DoInstall();
|
|
}
|
|
#endif
|