]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sys_shared.c
Update and micro-optimise memory allocation
[xonotic/darkplaces.git] / sys_shared.c
1 #ifdef WIN32
2 # ifndef DONT_USE_SETDLLDIRECTORY
3 #  define _WIN32_WINNT 0x0502
4 # endif
5 #endif
6
7 #define SUPPORTDLL
8
9 #ifdef WIN32
10 # include <windows.h>
11 # include <mmsystem.h> // timeGetTime
12 # include <time.h> // localtime
13 # include <conio.h> // _kbhit, _getch, _putch
14 # include <io.h> // write; Include this BEFORE darkplaces.h because it uses strncpy which trips DP_STATIC_ASSERT
15 #ifdef _MSC_VER
16 #pragma comment(lib, "winmm.lib")
17 #endif
18 #else
19 # ifdef __FreeBSD__
20 #  include <sys/sysctl.h>
21 # endif
22 # ifdef __ANDROID__
23 #  include <android/log.h>
24 # endif
25 # include <unistd.h>
26 # include <fcntl.h>
27 # include <sys/time.h>
28 # include <time.h>
29 # ifdef SUPPORTDLL
30 #  include <dlfcn.h>
31 # endif
32 #endif
33
34 #include <signal.h>
35
36 #include "quakedef.h"
37 #include "taskqueue.h"
38 #include "thread.h"
39 #include "libcurl.h"
40
41 #ifdef WIN32
42         // Microsoft's compiler complains about portable code
43         #pragma warning(disable : 4996)
44 #endif
45
46 sys_t sys;
47
48 static char sys_timestring[128];
49 char *Sys_TimeString(const char *timeformat)
50 {
51         time_t mytime = time(NULL);
52 #if _MSC_VER >= 1400
53         struct tm mytm;
54         localtime_s(&mytm, &mytime);
55         strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
56 #else
57         strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
58 #endif
59         return sys_timestring;
60 }
61
62
63 #ifdef __cplusplus
64 extern "C"
65 #endif
66 void Sys_AllowProfiling(qbool enable)
67 {
68 #ifdef __ANDROID__
69 #ifdef USE_PROFILER
70         extern void monstartup(const char *libname);
71         extern void moncleanup(void);
72         if (enable)
73                 monstartup("libmain.so");
74         else
75                 moncleanup();
76 #endif
77 #elif (defined(__linux__) && (defined(__GLIBC__) || defined(__GNU_LIBRARY__))) || defined(__FreeBSD__)
78         extern int moncontrol(int);
79         moncontrol(enable);
80 #endif
81 }
82
83
84 /*
85 ===============================================================================
86
87 DLL MANAGEMENT
88
89 ===============================================================================
90 */
91
92 static qbool Sys_LoadDependencyFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qbool complain, qbool has_next)
93 {
94         const dllfunction_t *func;
95         if(dllhandle)
96         {
97                 for (func = fcts; func && func->name != NULL; func++)
98                         if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
99                         {
100                                 if(complain)
101                                 {
102                                         Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
103                                         if(has_next)
104                                                 Con_DPrintf("\nContinuing with");
105                                 }
106                                 goto notfound;
107                         }
108                 return true;
109
110         notfound:
111                 for (func = fcts; func && func->name != NULL; func++)
112                         *func->funcvariable = NULL;
113         }
114         return false;
115 }
116
117 qbool Sys_LoadSelf(dllhandle_t *handle)
118 {
119         dllhandle_t dllhandle = 0;
120
121         if (handle == NULL)
122                 return false;
123 #ifdef WIN32
124         dllhandle = LoadLibrary (NULL);
125 #else
126         dllhandle = dlopen (NULL, RTLD_NOW | RTLD_GLOBAL);
127 #endif
128         *handle = dllhandle;
129         return true;
130 }
131
132 qbool Sys_LoadDependency (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
133 {
134 #ifdef SUPPORTDLL
135         const dllfunction_t *func;
136         dllhandle_t dllhandle = 0;
137         unsigned int i;
138
139         if (handle == NULL)
140                 return false;
141
142 #ifndef WIN32
143 #ifdef PREFER_PRELOAD
144         dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
145         if(Sys_LoadDependencyFunctions(dllhandle, fcts, false, false))
146         {
147                 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
148                 *handle = dllhandle;
149                 return true;
150         }
151         else
152                 Sys_FreeLibrary(&dllhandle);
153 notfound:
154 #endif
155 #endif
156
157         // Initializations
158         for (func = fcts; func && func->name != NULL; func++)
159                 *func->funcvariable = NULL;
160
161         // Try every possible name
162         Con_DPrintf ("Trying to load library...");
163         for (i = 0; dllnames[i] != NULL; i++)
164         {
165                 Con_DPrintf (" \"%s\"", dllnames[i]);
166 #ifdef WIN32
167 # ifndef DONT_USE_SETDLLDIRECTORY
168 #  ifdef _WIN64
169                 SetDllDirectory("bin64");
170 #  else
171                 SetDllDirectory("bin32");
172 #  endif
173 # endif
174 #endif
175                 if(Sys_LoadLibrary(dllnames[i], &dllhandle))
176                 {
177                         if (Sys_LoadDependencyFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
178                                 break;
179                         else
180                                 Sys_FreeLibrary (&dllhandle);
181                 }
182         }
183
184         // see if the names can be loaded relative to the executable path
185         // (this is for Mac OSX which does not check next to the executable)
186         if (!dllhandle && strrchr(sys.argv[0], '/'))
187         {
188                 char path[MAX_OSPATH];
189                 dp_strlcpy(path, sys.argv[0], sizeof(path));
190                 strrchr(path, '/')[1] = 0;
191                 for (i = 0; dllnames[i] != NULL; i++)
192                 {
193                         char temp[MAX_OSPATH];
194                         dp_strlcpy(temp, path, sizeof(temp));
195                         dp_strlcat(temp, dllnames[i], sizeof(temp));
196                         Con_DPrintf (" \"%s\"", temp);
197
198                         if(Sys_LoadLibrary(temp, &dllhandle))
199                         {
200                                 if (Sys_LoadDependencyFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
201                                         break;
202                                 else
203                                         Sys_FreeLibrary (&dllhandle);
204                         }
205                 }
206         }
207
208         // No DLL found
209         if (! dllhandle)
210         {
211                 Con_DPrintf(" - failed.\n");
212                 return false;
213         }
214
215         Con_DPrintf(" - loaded.\n");
216         Con_Printf("Loaded library \"%s\"\n", dllnames[i]);
217
218         *handle = dllhandle;
219         return true;
220 #else
221         return false;
222 #endif
223 }
224
225 qbool Sys_LoadLibrary(const char *name, dllhandle_t *handle)
226 {
227         dllhandle_t dllhandle = 0;
228
229         if(handle == NULL)
230                 return false;
231
232 #ifdef SUPPORTDLL
233 # ifdef WIN32
234         dllhandle = LoadLibrary (name);
235 # else
236         dllhandle = dlopen (name, RTLD_LAZY | RTLD_GLOBAL);
237 # endif
238 #endif
239         if(!dllhandle)
240                 return false;
241
242         *handle = dllhandle;
243         return true;
244 }
245
246 void Sys_FreeLibrary (dllhandle_t* handle)
247 {
248 #ifdef SUPPORTDLL
249         if (handle == NULL || *handle == NULL)
250                 return;
251
252 #ifdef WIN32
253         FreeLibrary (*handle);
254 #else
255         dlclose (*handle);
256 #endif
257
258         *handle = NULL;
259 #endif
260 }
261
262 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
263 {
264 #ifdef SUPPORTDLL
265 #ifdef WIN32
266         return (void *)GetProcAddress (handle, name);
267 #else
268         return (void *)dlsym (handle, name);
269 #endif
270 #else
271         return NULL;
272 #endif
273 }
274
275 #ifdef WIN32
276 # define HAVE_TIMEGETTIME 1
277 # define HAVE_QUERYPERFORMANCECOUNTER 1
278 # define HAVE_Sleep 1
279 #endif
280
281 #ifndef WIN32
282 #if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
283 # define HAVE_CLOCKGETTIME 1
284 #endif
285 // FIXME improve this check, manpage hints to DST_NONE
286 # define HAVE_GETTIMEOFDAY 1
287 #endif
288
289 #ifdef FD_SET
290 # define HAVE_SELECT 1
291 #endif
292
293 #ifndef WIN32
294 // FIXME improve this check
295 # define HAVE_USLEEP 1
296 #endif
297
298 // these are referenced elsewhere
299 cvar_t sys_usenoclockbutbenchmark = {CF_SHARED, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."};
300 cvar_t sys_libdir = {CF_READONLY | CF_SHARED, "sys_libdir", "", "Default engine library directory"};
301
302 // these are not
303 static cvar_t sys_debugsleep = {CF_SHARED, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
304 static cvar_t sys_usesdlgetticks = {CF_SHARED, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"};
305 static cvar_t sys_usesdldelay = {CF_SHARED, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"};
306 #if HAVE_QUERYPERFORMANCECOUNTER
307 static cvar_t sys_usequeryperformancecounter = {CF_SHARED | CF_ARCHIVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"};
308 #endif
309 #if HAVE_CLOCKGETTIME
310 static cvar_t sys_useclockgettime = {CF_SHARED | CF_ARCHIVE, "sys_useclockgettime", "1", "use POSIX clock_gettime function (not adjusted by NTP on some older Linux kernels) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"};
311 #endif
312
313 static cvar_t sys_stdout = {CF_SHARED, "sys_stdout", "1", "0: nothing is written to stdout (-nostdout cmdline option sets this), 1: normal messages are written to stdout, 2: normal messages are written to stderr (-stderr cmdline option sets this)"};
314 #ifndef WIN32
315 static cvar_t sys_stdout_blocks = {CF_SHARED, "sys_stdout_blocks", "0", "1: writes to stdout and stderr streams will block (causing a stutter or complete halt) if the buffer is full, ensuring no messages are lost at a price"};
316 #endif
317
318 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
319
320 /*
321 ================
322 Sys_CheckParm
323
324 Returns the position (1 to argc-1) in the program's argument list
325 where the given parameter apears, or 0 if not present
326 ================
327 */
328 int Sys_CheckParm (const char *parm)
329 {
330         int i;
331
332         for (i=1 ; i<sys.argc ; i++)
333         {
334                 if (!sys.argv[i])
335                         continue;               // NEXTSTEP sometimes clears appkit vars.
336                 if (!strcmp (parm,sys.argv[i]))
337                         return i;
338         }
339
340         return 0;
341 }
342
343 static void Sys_UpdateOutFD_c(cvar_t *var)
344 {
345         switch (sys_stdout.integer)
346         {
347                 case 0: sys.outfd = -1; break;
348                 default:
349                 case 1: sys.outfd = fileno(stdout); break;
350                 case 2: sys.outfd = fileno(stderr); break;
351         }
352 }
353
354 void Sys_Init_Commands (void)
355 {
356         Cvar_RegisterVariable(&sys_debugsleep);
357         Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
358         Cvar_RegisterVariable(&sys_libdir);
359 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
360         if(sys_supportsdlgetticks)
361         {
362                 Cvar_RegisterVariable(&sys_usesdlgetticks);
363                 Cvar_RegisterVariable(&sys_usesdldelay);
364         }
365 #endif
366 #if HAVE_QUERYPERFORMANCECOUNTER
367         Cvar_RegisterVariable(&sys_usequeryperformancecounter);
368 #endif
369 #if HAVE_CLOCKGETTIME
370         Cvar_RegisterVariable(&sys_useclockgettime);
371 #endif
372         Cvar_RegisterVariable(&sys_stdout);
373         Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
374 #ifndef WIN32
375         Cvar_RegisterVariable(&sys_stdout_blocks);
376 #endif
377 }
378
379 double Sys_DirtyTime(void)
380 {
381         // first all the OPTIONAL timers
382
383         // benchmark timer (fake clock)
384         if(sys_usenoclockbutbenchmark.integer)
385         {
386                 double old_benchmark_time = benchmark_time;
387                 benchmark_time += 1;
388                 if(benchmark_time == old_benchmark_time)
389                         Sys_Abort("sys_usenoclockbutbenchmark cannot run any longer, sorry");
390                 return benchmark_time * 0.000001;
391         }
392 #if HAVE_QUERYPERFORMANCECOUNTER
393         if (sys_usequeryperformancecounter.integer)
394         {
395                 // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
396                 // QueryPerformanceCounter
397                 // platform:
398                 // Windows 95/98/ME/NT/2000/XP
399                 // features:
400                 // very accurate (CPU cycles)
401                 // known issues:
402                 // does not necessarily match realtime too well (tends to get faster and faster in win98)
403                 // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
404                 double timescale;
405                 LARGE_INTEGER PerformanceFreq;
406                 LARGE_INTEGER PerformanceCount;
407
408                 if (QueryPerformanceFrequency (&PerformanceFreq))
409                 {
410                         QueryPerformanceCounter (&PerformanceCount);
411         
412                         timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
413                         return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
414                 }
415                 else
416                 {
417                         Con_Printf("No hardware timer available\n");
418                         // fall back to other clock sources
419                         Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
420                 }
421         }
422 #endif
423
424 #if HAVE_CLOCKGETTIME
425         if (sys_useclockgettime.integer)
426         {
427                 struct timespec ts;
428 #  ifdef CLOCK_MONOTONIC
429                 // linux
430                 clock_gettime(CLOCK_MONOTONIC, &ts);
431 #  else
432                 // sunos
433                 clock_gettime(CLOCK_HIGHRES, &ts);
434 #  endif
435                 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
436         }
437 #endif
438
439         // now all the FALLBACK timers
440         if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
441                 return (double) Sys_SDL_GetTicks() / 1000.0;
442 #if HAVE_GETTIMEOFDAY
443         {
444                 struct timeval tp;
445                 gettimeofday(&tp, NULL);
446                 return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
447         }
448 #elif HAVE_TIMEGETTIME
449         {
450                 static int firsttimegettime = true;
451                 // timeGetTime
452                 // platform:
453                 // Windows 95/98/ME/NT/2000/XP
454                 // features:
455                 // reasonable accuracy (millisecond)
456                 // issues:
457                 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
458
459                 // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
460                 if (firsttimegettime)
461                 {
462                         timeBeginPeriod(1);
463                         firsttimegettime = false;
464                 }
465
466                 return (double) timeGetTime() / 1000.0;
467         }
468 #else
469         // fallback for using the SDL timer if no other timer is available
470         // this calls Sys_Abort() if not linking against SDL
471         return (double) Sys_SDL_GetTicks() / 1000.0;
472 #endif
473 }
474
475 extern cvar_t host_maxwait;
476 double Sys_Sleep(double time)
477 {
478         double dt;
479         uint32_t microseconds;
480
481         // convert to microseconds
482         time *= 1000000.0;
483
484         if(host_maxwait.value <= 0)
485                 time = min(time, 1000000.0);
486         else
487                 time = min(time, host_maxwait.value * 1000.0);
488
489         if (time < 1 || host.restless)
490                 return 0; // not sleeping this frame
491
492         microseconds = time; // post-validation to prevent overflow
493
494         if(sys_usenoclockbutbenchmark.integer)
495         {
496                 double old_benchmark_time = benchmark_time;
497                 benchmark_time += microseconds;
498                 if(benchmark_time == old_benchmark_time)
499                         Sys_Abort("sys_usenoclockbutbenchmark cannot run any longer, sorry");
500                 return 0;
501         }
502
503         if(sys_debugsleep.integer)
504                 Con_Printf("sys_debugsleep: requesting %u ", microseconds);
505         dt = Sys_DirtyTime();
506
507         // less important on newer libcurl so no need to disturb dedicated servers
508         if (cls.state != ca_dedicated && Curl_Select(microseconds))
509         {
510                 // a transfer is ready or we finished sleeping
511         }
512         else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
513                 Sys_SDL_Delay(microseconds / 1000);
514 #if HAVE_SELECT
515         else
516         {
517                 struct timeval tv;
518                 lhnetsocket_t *s;
519                 fd_set fdreadset;
520                 int lastfd = -1;
521
522                 FD_ZERO(&fdreadset);
523                 if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
524                 {
525                         List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
526                         {
527                                 if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
528                                 {
529                                         if (lastfd < s->inetsocket)
530                                                 lastfd = s->inetsocket;
531         #if defined(WIN32) && !defined(_MSC_VER)
532                                         FD_SET((int)s->inetsocket, &fdreadset);
533         #else
534                                         FD_SET((unsigned int)s->inetsocket, &fdreadset);
535         #endif
536                                 }
537                         }
538                 }
539                 tv.tv_sec = microseconds / 1000000;
540                 tv.tv_usec = microseconds % 1000000;
541                 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
542                 // (so much for POSIX...)
543                 // bones_was_here: but a zeroed fd_set seems to be tolerated (tested on Win 7)
544                 select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
545         }
546 #elif HAVE_USLEEP
547         else
548                 usleep(microseconds);
549 #elif HAVE_Sleep
550         else
551                 Sleep(microseconds / 1000);
552 #else
553         else
554                 Sys_SDL_Delay(microseconds / 1000);
555 #endif
556
557         dt = Sys_DirtyTime() - dt;
558         if(sys_debugsleep.integer)
559                 Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - microseconds);
560         return (dt < 0 || dt >= 1800) ? 0 : dt;
561 }
562
563
564 /*
565 ===============================================================================
566
567 STDIO
568
569 ===============================================================================
570 */
571
572 // NOTE: use only POSIX async-signal-safe library functions here (see: man signal-safety)
573 void Sys_Print(const char *text, size_t textlen)
574 {
575 #ifdef __ANDROID__
576         if (developer.integer > 0)
577         {
578                 __android_log_write(ANDROID_LOG_DEBUG, sys.argv[0], text);
579         }
580 #else
581         if(sys.outfd < 0)
582                 return;
583   #ifndef WIN32
584         // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0).
585         // this is because both go to /dev/tty by default!
586         {
587                 int origflags = fcntl(sys.outfd, F_GETFL, 0);
588                 if (sys_stdout_blocks.integer)
589                         fcntl(sys.outfd, F_SETFL, origflags & ~O_NONBLOCK);
590   #else
591     #define write _write
592   #endif
593                 while(*text)
594                 {
595                         fs_offset_t written = (fs_offset_t)write(sys.outfd, text, textlen);
596                         if(written <= 0)
597                                 break; // sorry, I cannot do anything about this error - without an output
598                         text += written;
599                 }
600   #ifndef WIN32
601                 if (sys_stdout_blocks.integer)
602                         fcntl(sys.outfd, F_SETFL, origflags);
603         }
604   #endif
605         //fprintf(stdout, "%s", text);
606 #endif
607 }
608
609 void Sys_Printf(const char *fmt, ...)
610 {
611         va_list argptr;
612         char msg[MAX_INPUTLINE];
613         int msglen;
614
615         va_start(argptr,fmt);
616         msglen = dpvsnprintf(msg, sizeof(msg), fmt, argptr);
617         va_end(argptr);
618
619         if (msglen >= 0)
620                 Sys_Print(msg, msglen);
621 }
622
623 /// Reads a line from POSIX stdin or the Windows console
624 char *Sys_ConsoleInput(void)
625 {
626         static char text[MAX_INPUTLINE];
627 #ifdef WIN32
628         static unsigned int len = 0;
629         int c;
630
631         // read a line out
632         while (_kbhit ())
633         {
634                 c = _getch ();
635                 if (c == '\r')
636                 {
637                         text[len] = '\0';
638                         _putch ('\n');
639                         len = 0;
640                         return text;
641                 }
642                 if (c == '\b')
643                 {
644                         if (len)
645                         {
646                                 _putch (c);
647                                 _putch (' ');
648                                 _putch (c);
649                                 len--;
650                         }
651                         continue;
652                 }
653                 if (len < sizeof (text) - 1)
654                 {
655                         _putch (c);
656                         text[len] = c;
657                         len++;
658                 }
659         }
660 #else
661         fd_set fdset;
662         struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
663
664         FD_ZERO(&fdset);
665         FD_SET(fileno(stdin), &fdset);
666         if (select(1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(fileno(stdin), &fdset))
667                 return fgets(text, sizeof(text), stdin);
668 #endif
669         return NULL;
670 }
671
672
673 /*
674 ===============================================================================
675
676 Startup and Shutdown
677
678 ===============================================================================
679 */
680
681 void Sys_Abort (const char *error, ...)
682 {
683         va_list argptr;
684         char string[MAX_INPUTLINE];
685         int i;
686
687         // set output to blocking stderr
688         sys.outfd = fileno(stderr);
689 #ifndef WIN32
690         fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
691 #endif
692
693         va_start (argptr,error);
694         dpvsnprintf (string, sizeof (string), error, argptr);
695         va_end (argptr);
696
697         Con_Printf(CON_ERROR "Engine Abort: %s\n^9%s\n", string, engineversion);
698
699         dp_strlcat(string, "\n\n", sizeof(string));
700         dp_strlcat(string, engineversion, sizeof(string));
701
702         // Most shutdown funcs can't be called here as they could error while we error.
703
704         // DP8 TODO: send a disconnect message indicating we aborted, see Host_Error() and Sys_HandleCrash()
705
706         if (cls.demorecording)
707                 CL_Stop_f(cmd_local);
708         if (sv.active)
709         {
710                 sv.active = false; // make SV_DropClient() skip the QC stuff to avoid recursive errors
711                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
712                         if (host_client->active)
713                                 SV_DropClient(false, "Server abort!"); // closes demo file
714         }
715         // don't want a dead window left blocking the OS UI or the abort dialog
716         VID_Shutdown();
717         S_StopAllSounds();
718
719         host.state = host_failed; // make Sys_HandleSignal() call exit()
720         Sys_SDL_Dialog("Engine Abort", string);
721
722         fflush(stderr);
723
724         exit (1);
725 }
726
727 #ifndef WIN32
728 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
729 {
730         const char *p = PATH;
731         const char *q;
732         if(p && name)
733         {
734                 while((q = strchr(p, ':')))
735                 {
736                         dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
737                         if(FS_SysFileExists(buf))
738                                 return buf;
739                         p = q + 1;
740                 }
741                 if(!q) // none found - try the last item
742                 {
743                         dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
744                         if(FS_SysFileExists(buf))
745                                 return buf;
746                 }
747         }
748         return name;
749 }
750 #endif
751
752 static const char *Sys_FindExecutableName(void)
753 {
754 #if defined(WIN32)
755         return sys.argv[0];
756 #else
757         static char exenamebuf[MAX_OSPATH+1];
758         ssize_t n = -1;
759 #if defined(__FreeBSD__)
760         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
761         size_t exenamebuflen = sizeof(exenamebuf)-1;
762         if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
763         {
764                 n = exenamebuflen;
765         }
766 #elif defined(__linux__)
767         n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
768 #endif
769         if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
770         {
771                 exenamebuf[n] = 0;
772                 return exenamebuf;
773         }
774         if(strchr(sys.argv[0], '/'))
775                 return sys.argv[0]; // possibly a relative path
776         else
777                 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
778 #endif
779 }
780
781 void Sys_ProvideSelfFD(void)
782 {
783         if(sys.selffd != -1)
784                 return;
785         sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
786 }
787
788 // for x86 cpus only...  (x64 has SSE2_PRESENT)
789 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
790 // code from SDL, shortened as we can expect CPUID to work
791 static int CPUID_Features(void)
792 {
793         int features = 0;
794 # if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) && defined(__i386__)
795         __asm__ (
796 "        movl    %%ebx,%%edi\n"
797 "        xorl    %%eax,%%eax                                           \n"
798 "        incl    %%eax                                                 \n"
799 "        cpuid                       # Get family/model/stepping/features\n"
800 "        movl    %%edx,%0                                              \n"
801 "        movl    %%edi,%%ebx\n"
802         : "=m" (features)
803         :
804         : "%eax", "%ecx", "%edx", "%edi"
805         );
806 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
807         __asm {
808         xor     eax, eax
809         inc     eax
810         cpuid                       ; Get family/model/stepping/features
811         mov     features, edx
812         }
813 # else
814 #  error SSE_POSSIBLE set but no CPUID implementation
815 # endif
816         return features;
817 }
818 #endif
819
820 #ifdef SSE_POSSIBLE
821 qbool Sys_HaveSSE(void)
822 {
823         // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
824         if(Sys_CheckParm("-nosse"))
825                 return false;
826 #ifdef SSE_PRESENT
827         return true;
828 #else
829         // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
830         if(Sys_CheckParm("-forcesse") || Sys_CheckParm("-forcesse2"))
831                 return true;
832         if(CPUID_Features() & (1 << 25))
833                 return true;
834         return false;
835 #endif
836 }
837
838 qbool Sys_HaveSSE2(void)
839 {
840         // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
841         if(Sys_CheckParm("-nosse") || Sys_CheckParm("-nosse2"))
842                 return false;
843 #ifdef SSE2_PRESENT
844         return true;
845 #else
846         // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
847         if(Sys_CheckParm("-forcesse2"))
848                 return true;
849         if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
850                 return true;
851         return false;
852 #endif
853 }
854 #endif
855
856 /// called to set process priority for dedicated servers
857 #if defined(__linux__)
858 #include <sys/resource.h>
859 #include <errno.h>
860
861 void Sys_InitProcessNice (void)
862 {
863         struct rlimit lim;
864         sys.nicepossible = false;
865         if(Sys_CheckParm("-nonice"))
866                 return;
867         errno = 0;
868         sys.nicelevel = getpriority(PRIO_PROCESS, 0);
869         if(errno)
870         {
871                 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
872                 return;
873         }
874         if(getrlimit(RLIMIT_NICE, &lim))
875         {
876                 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
877                 return;
878         }
879         if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
880         {
881                 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
882                 return;
883         }
884         sys.nicepossible = true;
885         sys.isnice = false;
886 }
887 void Sys_MakeProcessNice (void)
888 {
889         if(!sys.nicepossible)
890                 return;
891         if(sys.isnice)
892                 return;
893         Con_DPrintf("Process is becoming 'nice'...\n");
894         if(setpriority(PRIO_PROCESS, 0, 19))
895                 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
896         sys.isnice = true;
897 }
898 void Sys_MakeProcessMean (void)
899 {
900         if(!sys.nicepossible)
901                 return;
902         if(!sys.isnice)
903                 return;
904         Con_DPrintf("Process is becoming 'mean'...\n");
905         if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
906                 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
907         sys.isnice = false;
908 }
909 #else
910 void Sys_InitProcessNice (void)
911 {
912 }
913 void Sys_MakeProcessNice (void)
914 {
915 }
916 void Sys_MakeProcessMean (void)
917 {
918 }
919 #endif
920
921
922 static const char *Sys_SigDesc(int sig)
923 {
924         switch (sig)
925         {
926                 // Windows only supports the C99 signals
927                 case SIGINT:  return "Interrupt";
928                 case SIGILL:  return "Illegal instruction";
929                 case SIGABRT: return "Aborted";
930                 case SIGFPE:  return "Floating point exception";
931                 case SIGSEGV: return "Segmentation fault";
932                 case SIGTERM: return "Termination";
933 #ifndef WIN32
934                 // POSIX has several others worth catching
935                 case SIGHUP:  return "Hangup";
936                 case SIGQUIT: return "Quit";
937                 case SIGBUS:  return "Bus error (bad memory access)";
938                 case SIGPIPE: return "Broken pipe";
939 #endif
940                 default:      return "Yo dawg, we bugged out while bugging out";
941         }
942 }
943
944 /** Halt and try not to catch fire.
945  * Writing to any file could corrupt it,
946  * any uneccessary code could crash while we crash.
947  * Try to use only POSIX async-signal-safe library functions here (see: man signal-safety).
948  */
949 static void Sys_HandleCrash(int sig)
950 {
951 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
952         // Before doing anything else grab the stack frame addresses
953         #include <execinfo.h>
954         void *stackframes[32];
955         int framecount = backtrace(stackframes, 32);
956         char **btstrings;
957 #endif
958         char dialogtext[3072];
959         const char *sigdesc = Sys_SigDesc(sig);
960
961         // set output to blocking stderr and print header, backtrace, version
962         sys.outfd = fileno(stderr); // not async-signal-safe :(
963 #ifndef WIN32
964         fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
965         Sys_Print("\n\n\e[1;37;41m    Engine Crash: ", 30);
966         Sys_Print(sigdesc, strlen(sigdesc));
967         Sys_Print("    \e[m\n", 8);
968   #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
969         // the first two addresses will be in this function and in signal() in libc
970         backtrace_symbols_fd(stackframes + 2, framecount - 2, sys.outfd);
971   #endif
972         Sys_Print("\e[1m", 4);
973         Sys_Print(engineversion, strlen(engineversion));
974         Sys_Print("\e[m\n", 4);
975 #else // Windows console doesn't support colours
976         Sys_Print("\n\nEngine Crash: ", 16);
977         Sys_Print(sigdesc, strlen(sigdesc));
978         Sys_Print("\n", 1);
979         Sys_Print(engineversion, strlen(engineversion));
980         Sys_Print("\n", 1);
981 #endif
982
983         // DP8 TODO: send a disconnect message indicating we crashed, see Sys_Abort() and Host_Error()
984
985         // don't want a dead window left blocking the OS UI or the crash dialog
986         VID_Shutdown();
987         S_StopAllSounds();
988
989         // prepare the dialogtext: signal, backtrace, version
990         // the dp_st* funcs are POSIX async-signal-safe IF we don't trigger their warnings
991         dp_strlcpy(dialogtext, sigdesc, sizeof(dialogtext));
992         dp_strlcat(dialogtext, "\n\n", sizeof(dialogtext));
993 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
994         btstrings = backtrace_symbols(stackframes + 2, framecount - 2); // calls malloc :(
995         if (btstrings)
996                 for (int i = 0; i < framecount - 2; ++i)
997                 {
998                         dp_strlcat(dialogtext, btstrings[i], sizeof(dialogtext));
999                         dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1000                 }
1001 #endif
1002         dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1003         dp_strlcat(dialogtext, engineversion, sizeof(dialogtext));
1004
1005         host.state = host_failed; // make Sys_HandleSignal() call _Exit()
1006         Sys_SDL_Dialog("Engine Crash", dialogtext);
1007
1008         fflush(stderr); // not async-signal-safe :(
1009         _Exit(sig);
1010 }
1011
1012 static void Sys_HandleSignal(int sig)
1013 {
1014         const char *sigdesc = Sys_SigDesc(sig);
1015         Sys_Print("\nReceived ", 10);
1016         Sys_Print(sigdesc, strlen(sigdesc));
1017         Sys_Print(" signal, exiting...\n", 20);
1018         if (host.state == host_failed)
1019         {
1020                 // user is trying to kill the process while the dialog is open
1021                 fflush(stderr); // not async-signal-safe :(
1022                 _Exit(sig);
1023         }
1024         host.state = host_shutdown;
1025 }
1026
1027 /// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything
1028 static void Sys_InitSignals(void)
1029 {
1030         // Windows only supports the C99 signals
1031         signal(SIGINT,  Sys_HandleSignal);
1032         signal(SIGILL,  Sys_HandleCrash);
1033         signal(SIGABRT, Sys_HandleCrash);
1034         signal(SIGFPE,  Sys_HandleCrash);
1035         signal(SIGSEGV, Sys_HandleCrash);
1036         signal(SIGTERM, Sys_HandleSignal);
1037 #ifndef WIN32
1038         // POSIX has several others worth catching
1039         signal(SIGHUP,  Sys_HandleSignal);
1040         signal(SIGQUIT, Sys_HandleSignal);
1041         signal(SIGBUS,  Sys_HandleCrash);
1042         signal(SIGPIPE, Sys_HandleSignal);
1043 #endif
1044 }
1045
1046 int main (int argc, char **argv)
1047 {
1048         sys.argc = argc;
1049         sys.argv = (const char **)argv;
1050
1051         // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
1052         // COMMANDLINEOPTION: -noterminal disables console output on stdout
1053         if(Sys_CheckParm("-noterminal") || Sys_CheckParm("-nostdout"))
1054                 sys_stdout.string = "0";
1055         // COMMANDLINEOPTION: -stderr moves console output to stderr
1056         else if(Sys_CheckParm("-stderr"))
1057                 sys_stdout.string = "2";
1058         // too early for Cvar_SetQuick
1059         sys_stdout.value = sys_stdout.integer = atoi(sys_stdout.string);
1060         Sys_UpdateOutFD_c(&sys_stdout);
1061 #ifndef WIN32
1062         fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, 0) | O_NONBLOCK);
1063         // stdout/stderr will be set to blocking in Sys_Print() if so configured, or during a fatal error.
1064         fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) | O_NONBLOCK);
1065         fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) | O_NONBLOCK);
1066 #endif
1067
1068         sys.selffd = -1;
1069         Sys_ProvideSelfFD(); // may call Con_Printf() so must be after sys.outfd is set
1070
1071 #ifdef __ANDROID__
1072         Sys_AllowProfiling(true);
1073 #endif
1074
1075         Sys_InitSignals();
1076
1077         Host_Main();
1078
1079 #ifdef __ANDROID__
1080         Sys_AllowProfiling(false);
1081 #endif
1082
1083 #ifndef WIN32
1084         fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) & ~O_NONBLOCK);
1085         fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) & ~O_NONBLOCK);
1086 #endif
1087         fflush(stdout);
1088         fflush(stderr);
1089
1090         return 0;
1091 }