2 # ifndef DONT_USE_SETDLLDIRECTORY
3 # define _WIN32_WINNT 0x0502
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
16 #pragma comment(lib, "winmm.lib")
20 # include <sys/sysctl.h>
23 # include <android/log.h>
27 # include <sys/time.h>
37 #include "taskqueue.h"
44 static char sys_timestring[128];
45 char *Sys_TimeString(const char *timeformat)
47 time_t mytime = time(NULL);
50 localtime_s(&mytm, &mytime);
51 strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
53 strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
55 return sys_timestring;
62 void Sys_AllowProfiling(qbool enable)
66 extern void monstartup(const char *libname);
67 extern void moncleanup(void);
69 monstartup("libmain.so");
73 #elif (defined(__linux__) && (defined(__GLIBC__) || defined(__GNU_LIBRARY__))) || defined(__FreeBSD__)
74 extern int moncontrol(int);
81 ===============================================================================
85 ===============================================================================
88 static qbool Sys_LoadDependencyFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qbool complain, qbool has_next)
90 const dllfunction_t *func;
93 for (func = fcts; func && func->name != NULL; func++)
94 if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
98 Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
100 Con_DPrintf("\nContinuing with");
107 for (func = fcts; func && func->name != NULL; func++)
108 *func->funcvariable = NULL;
113 qbool Sys_LoadSelf(dllhandle_t *handle)
115 dllhandle_t dllhandle = 0;
120 dllhandle = LoadLibrary (NULL);
122 dllhandle = dlopen (NULL, RTLD_NOW | RTLD_GLOBAL);
128 qbool Sys_LoadDependency (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
131 const dllfunction_t *func;
132 dllhandle_t dllhandle = 0;
139 #ifdef PREFER_PRELOAD
140 dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
141 if(Sys_LoadDependencyFunctions(dllhandle, fcts, false, false))
143 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
148 Sys_FreeLibrary(&dllhandle);
154 for (func = fcts; func && func->name != NULL; func++)
155 *func->funcvariable = NULL;
157 // Try every possible name
158 Con_DPrintf ("Trying to load library...");
159 for (i = 0; dllnames[i] != NULL; i++)
161 Con_DPrintf (" \"%s\"", dllnames[i]);
163 # ifndef DONT_USE_SETDLLDIRECTORY
165 SetDllDirectory("bin64");
167 SetDllDirectory("bin32");
171 if(Sys_LoadLibrary(dllnames[i], &dllhandle))
173 if (Sys_LoadDependencyFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
176 Sys_FreeLibrary (&dllhandle);
180 // see if the names can be loaded relative to the executable path
181 // (this is for Mac OSX which does not check next to the executable)
182 if (!dllhandle && strrchr(sys.argv[0], '/'))
184 char path[MAX_OSPATH];
185 dp_strlcpy(path, sys.argv[0], sizeof(path));
186 strrchr(path, '/')[1] = 0;
187 for (i = 0; dllnames[i] != NULL; i++)
189 char temp[MAX_OSPATH];
190 dp_strlcpy(temp, path, sizeof(temp));
191 dp_strlcat(temp, dllnames[i], sizeof(temp));
192 Con_DPrintf (" \"%s\"", temp);
194 if(Sys_LoadLibrary(temp, &dllhandle))
196 if (Sys_LoadDependencyFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
199 Sys_FreeLibrary (&dllhandle);
207 Con_DPrintf(" - failed.\n");
211 Con_DPrintf(" - loaded.\n");
212 Con_Printf("Loaded library \"%s\"\n", dllnames[i]);
221 qbool Sys_LoadLibrary(const char *name, dllhandle_t *handle)
223 dllhandle_t dllhandle = 0;
230 dllhandle = LoadLibrary (name);
232 dllhandle = dlopen (name, RTLD_LAZY | RTLD_GLOBAL);
242 void Sys_FreeLibrary (dllhandle_t* handle)
245 if (handle == NULL || *handle == NULL)
249 FreeLibrary (*handle);
258 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
262 return (void *)GetProcAddress (handle, name);
264 return (void *)dlsym (handle, name);
274 # define HAVE_TIMEGETTIME 1
275 # define HAVE_QUERYPERFORMANCECOUNTER 1
276 # define HAVE_WIN32_USLEEP 1
277 # define HAVE_Sleep 1
279 # if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
280 # define HAVE_CLOCKGETTIME 1
282 # if _POSIX_VERSION >= 200112L
283 // MacOS advertises POSIX support but doesn't implement clock_nanosleep().
284 // POSIX deprecated and removed usleep() so select() seems like a safer choice.
286 # define HAVE_SELECT_POSIX 1
288 # define HAVE_CLOCK_NANOSLEEP 1
294 # define HAVE_SELECT 1
297 // these are referenced elsewhere
298 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."};
299 cvar_t sys_libdir = {CF_READONLY | CF_SHARED, "sys_libdir", "", "Default engine library directory"};
302 static cvar_t sys_debugsleep = {CF_SHARED, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
303 static cvar_t sys_usesdlgetticks = {CF_SHARED, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (low precision, for debugging)"};
304 static cvar_t sys_usesdldelay = {CF_SHARED, "sys_usesdldelay", "0", "use SDL_Delay() (low precision, for debugging)"};
305 #if HAVE_QUERYPERFORMANCECOUNTER
306 static cvar_t sys_usequeryperformancecounter = {CF_SHARED | CF_ARCHIVE, "sys_usequeryperformancecounter", "1", "use windows QueryPerformanceCounter timer (which has issues on systems lacking constant-rate TSCs synchronised across all cores, such as ancient PCs or VMs) for timing rather than timeGetTime function (which is low precision and had issues on some old motherboards)"};
309 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)"};
311 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"};
314 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
320 Returns the position (1 to argc-1) in the program's argument list
321 where the given parameter apears, or 0 if not present
324 int Sys_CheckParm (const char *parm)
328 for (i=1 ; i<sys.argc ; i++)
331 continue; // NEXTSTEP sometimes clears appkit vars.
332 if (!strcmp (parm,sys.argv[i]))
339 static void Sys_UpdateOutFD_c(cvar_t *var)
341 switch (sys_stdout.integer)
343 case 0: sys.outfd = -1; break;
345 case 1: sys.outfd = fileno(stdout); break;
346 case 2: sys.outfd = fileno(stderr); break;
350 void Sys_Init_Commands (void)
352 Cvar_RegisterVariable(&sys_debugsleep);
353 Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
354 Cvar_RegisterVariable(&sys_libdir);
355 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME
356 if(sys_supportsdlgetticks)
358 Cvar_RegisterVariable(&sys_usesdlgetticks);
359 Cvar_RegisterVariable(&sys_usesdldelay);
362 #if HAVE_QUERYPERFORMANCECOUNTER
363 Cvar_RegisterVariable(&sys_usequeryperformancecounter);
366 Cvar_RegisterVariable(&sys_stdout);
367 Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
369 Cvar_RegisterVariable(&sys_stdout_blocks);
374 static LARGE_INTEGER PerformanceFreq;
375 /// Windows default timer resolution is only 15.625ms,
376 /// this affects (at least) timeGetTime() and all forms of sleeping.
377 static void Sys_SetTimerResolution(void)
379 NTSTATUS(NTAPI *qNtQueryTimerResolution)(OUT PULONG MinRes, OUT PULONG MaxRes, OUT PULONG CurrentRes);
380 NTSTATUS(NTAPI *qNtSetTimerResolution)(IN ULONG DesiredRes, IN BOOLEAN SetRes, OUT PULONG ActualRes);
381 const char* ntdll_names [] =
386 dllfunction_t ntdll_funcs[] =
388 {"NtQueryTimerResolution", (void **) &qNtQueryTimerResolution},
389 {"NtSetTimerResolution", (void **) &qNtSetTimerResolution},
393 unsigned long WorstRes, BestRes, CurrentRes;
395 timeBeginPeriod(1); // 1ms, documented
397 // the best Windows can manage (typically 0.5ms)
398 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
399 if (Sys_LoadDependency(ntdll_names, &ntdll, ntdll_funcs))
401 qNtQueryTimerResolution(&WorstRes, &BestRes, &CurrentRes); // no pointers may be NULL
402 if (CurrentRes > BestRes)
403 qNtSetTimerResolution(BestRes, true, &CurrentRes);
405 Sys_FreeLibrary(&ntdll);
408 // Microsoft says the freq is fixed at boot and consistent across all processors
409 // and that it need only be queried once and cached.
410 QueryPerformanceFrequency (&PerformanceFreq);
414 double Sys_DirtyTime(void)
416 // first all the OPTIONAL timers
418 // benchmark timer (fake clock)
419 if(sys_usenoclockbutbenchmark.integer)
421 double old_benchmark_time = benchmark_time;
423 if(benchmark_time == old_benchmark_time)
424 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
425 return benchmark_time * 0.000001;
428 if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
429 return (double) Sys_SDL_GetTicks() / 1000.0;
431 #if HAVE_QUERYPERFORMANCECOUNTER
432 if (sys_usequeryperformancecounter.integer)
434 // QueryPerformanceCounter
436 // Windows 95/98/ME/NT/2000/XP
438 // + very accurate (constant-rate TSCs on modern systems)
440 // - does not necessarily match realtime too well (tends to get faster and faster in win98)
441 // - wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
442 // - higher access latency on Vista
443 // Microsoft says on Win 7 or later, latency and overhead are very low, synchronisation is excellent.
444 LARGE_INTEGER PerformanceCount;
446 if (PerformanceFreq.QuadPart)
448 QueryPerformanceCounter (&PerformanceCount);
449 return (double)PerformanceCount.QuadPart * (1.0 / (double)PerformanceFreq.QuadPart);
453 Con_Printf("No hardware timer available\n");
454 // fall back to other clock sources
455 Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
460 #if HAVE_CLOCKGETTIME
463 # ifdef CLOCK_MONOTONIC_RAW
464 // Linux-specific, SDL_GetPerformanceCounter() uses it
465 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
466 # elif defined(CLOCK_MONOTONIC)
468 clock_gettime(CLOCK_MONOTONIC, &ts);
471 clock_gettime(CLOCK_HIGHRES, &ts);
473 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
477 // now all the FALLBACK timers
482 // Windows 95/98/ME/NT/2000/XP
484 // reasonable accuracy (millisecond)
486 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
487 // requires Sys_SetTimerResolution()
488 return (double) timeGetTime() / 1000.0;
491 // fallback for using the SDL timer if no other timer is available
492 // this calls Sys_Error() if not linking against SDL
493 return (double) Sys_SDL_GetTicks() / 1000.0;
497 double Sys_Sleep(double time)
500 uint32_t msec, usec, nsec;
502 if (time < 1.0/1000000.0 || host.restless)
503 return 0; // not sleeping this frame
505 time = 0.999999; // simpler, also ensures values are in range for all platform APIs
507 usec = time * 1000000;
508 nsec = time * 1000000000;
510 if(sys_usenoclockbutbenchmark.integer)
512 double old_benchmark_time = benchmark_time;
513 benchmark_time += usec;
514 if(benchmark_time == old_benchmark_time)
515 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
519 if(sys_debugsleep.integer)
520 Con_Printf("sys_debugsleep: requesting %u ", usec);
521 dt = Sys_DirtyTime();
523 // less important on newer libcurl so no need to disturb dedicated servers
524 if (cls.state != ca_dedicated && Curl_Select(msec))
526 // a transfer is ready or we finished sleeping
528 else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
531 else if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
539 List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
541 if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
543 if (lastfd < s->inetsocket)
544 lastfd = s->inetsocket;
545 #if defined(WIN32) && !defined(_MSC_VER)
546 FD_SET((int)s->inetsocket, &fdreadset);
548 FD_SET((unsigned int)s->inetsocket, &fdreadset);
554 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
555 // (so much for POSIX...), not with an empty fd_set either.
556 select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
559 #if HAVE_CLOCK_NANOSLEEP
565 clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
567 #elif HAVE_SELECT_POSIX
573 select(0, NULL, NULL, NULL, &tv);
575 #elif HAVE_WIN32_USLEEP // Windows XP/2003 minimum
579 LARGE_INTEGER sleeptime;
581 // takes 100ns units, negative indicates relative time
582 sleeptime.QuadPart = -((int64_t)nsec / 100);
583 timer = CreateWaitableTimer(NULL, true, NULL);
584 SetWaitableTimer(timer, &sleeptime, 0, NULL, NULL, 0);
585 WaitForSingleObject(timer, INFINITE);
596 dt = Sys_DirtyTime() - dt;
597 if(sys_debugsleep.integer)
598 Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - usec);
599 return (dt < 0 || dt >= 1800) ? 0 : dt;
604 ===============================================================================
608 ===============================================================================
611 // NOTE: use only POSIX async-signal-safe library functions here (see: man signal-safety)
612 void Sys_Print(const char *text, size_t textlen)
615 if (developer.integer > 0)
617 __android_log_write(ANDROID_LOG_DEBUG, sys.argv[0], text);
623 // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0).
624 // this is because both go to /dev/tty by default!
626 int origflags = fcntl(sys.outfd, F_GETFL, 0);
627 if (sys_stdout_blocks.integer)
628 fcntl(sys.outfd, F_SETFL, origflags & ~O_NONBLOCK);
634 fs_offset_t written = (fs_offset_t)write(sys.outfd, text, textlen);
636 break; // sorry, I cannot do anything about this error - without an output
640 if (sys_stdout_blocks.integer)
641 fcntl(sys.outfd, F_SETFL, origflags);
644 //fprintf(stdout, "%s", text);
648 void Sys_Printf(const char *fmt, ...)
651 char msg[MAX_INPUTLINE];
654 va_start(argptr,fmt);
655 msglen = dpvsnprintf(msg, sizeof(msg), fmt, argptr);
659 Sys_Print(msg, msglen);
662 /// Reads a line from POSIX stdin or the Windows console
663 char *Sys_ConsoleInput(void)
665 static char text[MAX_INPUTLINE];
667 static unsigned int len = 0;
692 if (len < sizeof (text) - 1)
701 struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
704 FD_SET(fileno(stdin), &fdset);
705 if (select(1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(fileno(stdin), &fdset))
706 return fgets(text, sizeof(text), stdin);
713 ===============================================================================
717 ===============================================================================
720 void Sys_Error (const char *error, ...)
723 char string[MAX_INPUTLINE];
726 // Disable Sys_HandleSignal() but not Sys_HandleCrash()
727 host.state = host_shutdown;
729 // set output to blocking stderr
730 sys.outfd = fileno(stderr);
732 fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
735 va_start (argptr,error);
736 dpvsnprintf (string, sizeof (string), error, argptr);
739 Con_Printf(CON_ERROR "Engine Aborted: %s\n^9%s\n", string, engineversion);
741 dp_strlcat(string, "\n\n", sizeof(string));
742 dp_strlcat(string, engineversion, sizeof(string));
744 // Most shutdown funcs can't be called here as they could error while we error.
746 // DP8 TODO: send a disconnect message indicating we aborted, see Host_Error() and Sys_HandleCrash()
748 if (cls.demorecording)
749 CL_Stop_f(cmd_local);
752 sv.active = false; // make SV_DropClient() skip the QC stuff to avoid recursive errors
753 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
754 if (host_client->active)
755 SV_DropClient(false, "Server aborted!"); // closes demo file
757 // don't want a dead window left blocking the OS UI or the abort dialog
761 host.state = host_failed; // make Sys_HandleSignal() call _Exit()
762 Sys_SDL_Dialog("Engine Aborted", string);
769 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
771 const char *p = PATH;
775 while((q = strchr(p, ':')))
777 dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
778 if(FS_SysFileExists(buf))
782 if(!q) // none found - try the last item
784 dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
785 if(FS_SysFileExists(buf))
793 static const char *Sys_FindExecutableName(void)
798 static char exenamebuf[MAX_OSPATH+1];
800 #if defined(__FreeBSD__)
801 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
802 size_t exenamebuflen = sizeof(exenamebuf)-1;
803 if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
807 #elif defined(__linux__)
808 n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
810 if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
815 if(strchr(sys.argv[0], '/'))
816 return sys.argv[0]; // possibly a relative path
818 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
822 void Sys_ProvideSelfFD(void)
826 sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
829 // for x86 cpus only... (x64 has SSE2_PRESENT)
830 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
831 // code from SDL, shortened as we can expect CPUID to work
832 static int CPUID_Features(void)
835 # if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) && defined(__i386__)
837 " movl %%ebx,%%edi\n"
838 " xorl %%eax,%%eax \n"
840 " cpuid # Get family/model/stepping/features\n"
842 " movl %%edi,%%ebx\n"
845 : "%eax", "%ecx", "%edx", "%edi"
847 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
851 cpuid ; Get family/model/stepping/features
855 # error SSE_POSSIBLE set but no CPUID implementation
862 qbool Sys_HaveSSE(void)
864 // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
865 if(Sys_CheckParm("-nosse"))
870 // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
871 if(Sys_CheckParm("-forcesse") || Sys_CheckParm("-forcesse2"))
873 if(CPUID_Features() & (1 << 25))
879 qbool Sys_HaveSSE2(void)
881 // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
882 if(Sys_CheckParm("-nosse") || Sys_CheckParm("-nosse2"))
887 // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
888 if(Sys_CheckParm("-forcesse2"))
890 if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
897 /// called to set process priority for dedicated servers
898 #if defined(__linux__)
899 #include <sys/resource.h>
902 void Sys_InitProcessNice (void)
905 sys.nicepossible = false;
906 if(Sys_CheckParm("-nonice"))
909 sys.nicelevel = getpriority(PRIO_PROCESS, 0);
912 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
915 if(getrlimit(RLIMIT_NICE, &lim))
917 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
920 if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
922 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
925 sys.nicepossible = true;
928 void Sys_MakeProcessNice (void)
930 if(!sys.nicepossible)
934 Con_DPrintf("Process is becoming 'nice'...\n");
935 if(setpriority(PRIO_PROCESS, 0, 19))
936 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
939 void Sys_MakeProcessMean (void)
941 if(!sys.nicepossible)
945 Con_DPrintf("Process is becoming 'mean'...\n");
946 if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
947 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
951 void Sys_InitProcessNice (void)
954 void Sys_MakeProcessNice (void)
957 void Sys_MakeProcessMean (void)
963 static const char *Sys_SigDesc(int sig)
967 // Windows only supports the C99 signals
968 case SIGINT: return "Interrupt";
969 case SIGILL: return "Illegal instruction";
970 case SIGABRT: return "Aborted";
971 case SIGFPE: return "Floating point exception";
972 case SIGSEGV: return "Segmentation fault";
973 case SIGTERM: return "Termination";
975 // POSIX has several others worth catching
976 case SIGHUP: return "Hangup";
977 case SIGQUIT: return "Quit";
978 case SIGBUS: return "Bus error (bad memory access)";
979 case SIGPIPE: return "Broken pipe";
981 default: return "Yo dawg, we bugged out while bugging out";
985 /** Halt and try not to catch fire.
986 * Writing to any file could corrupt it,
987 * any uneccessary code could crash while we crash.
988 * Try to use only POSIX async-signal-safe library functions here (see: man signal-safety).
990 static void Sys_HandleCrash(int sig)
992 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
993 // Before doing anything else grab the stack frame addresses
994 #include <execinfo.h>
995 void *stackframes[32];
996 int framecount = backtrace(stackframes, 32);
999 char dialogtext[3072];
1000 const char *sigdesc;
1002 // Break any loop and disable Sys_HandleSignal()
1003 if (host.state == host_failing || host.state == host_failed)
1005 host.state = host_failing;
1007 sigdesc = Sys_SigDesc(sig);
1009 // set output to blocking stderr and print header, backtrace, version
1010 sys.outfd = fileno(stderr); // not async-signal-safe :(
1012 fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
1013 Sys_Print("\n\n\e[1;37;41m Engine Crash: ", 30);
1014 Sys_Print(sigdesc, strlen(sigdesc));
1015 Sys_Print(" \e[m\n", 8);
1016 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
1017 // the first two addresses will be in this function and in signal() in libc
1018 backtrace_symbols_fd(stackframes + 2, framecount - 2, sys.outfd);
1020 Sys_Print("\e[1m", 4);
1021 Sys_Print(engineversion, strlen(engineversion));
1022 Sys_Print("\e[m\n", 4);
1023 #else // Windows console doesn't support colours
1024 Sys_Print("\n\nEngine Crash: ", 16);
1025 Sys_Print(sigdesc, strlen(sigdesc));
1027 Sys_Print(engineversion, strlen(engineversion));
1031 // DP8 TODO: send a disconnect message indicating we crashed, see Sys_Error() and Host_Error()
1033 // don't want a dead window left blocking the OS UI or the crash dialog
1037 // prepare the dialogtext: signal, backtrace, version
1038 // the dp_st* funcs are POSIX async-signal-safe IF we don't trigger their warnings
1039 dp_strlcpy(dialogtext, sigdesc, sizeof(dialogtext));
1040 dp_strlcat(dialogtext, "\n\n", sizeof(dialogtext));
1041 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
1042 btstrings = backtrace_symbols(stackframes + 2, framecount - 2); // calls malloc :(
1044 for (int i = 0; i < framecount - 2; ++i)
1046 dp_strlcat(dialogtext, btstrings[i], sizeof(dialogtext));
1047 dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1050 dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1051 dp_strlcat(dialogtext, engineversion, sizeof(dialogtext));
1053 host.state = host_failed; // make Sys_HandleSignal() call _Exit()
1054 Sys_SDL_Dialog("Engine Crash", dialogtext);
1056 fflush(stderr); // not async-signal-safe :(
1058 // Continue execution with default signal handling.
1059 // A real crash will be re-triggered so the platform can handle it,
1060 // a fake crash (kill -SEGV) will cause a graceful shutdown.
1061 signal(sig, SIG_DFL);
1064 static void Sys_HandleSignal(int sig)
1066 const char *sigdesc;
1068 // Break any loop, eg if each Sys_Print triggers a SIGPIPE
1069 if (host.state == host_shutdown || host.state == host_failing)
1072 sigdesc = Sys_SigDesc(sig);
1073 Sys_Print("\nReceived ", 10);
1074 Sys_Print(sigdesc, strlen(sigdesc));
1075 Sys_Print(" signal, exiting...\n", 20);
1076 if (host.state == host_failed)
1078 // user is trying to kill the process while the SDL dialog is open
1079 fflush(stderr); // not async-signal-safe :(
1082 host.state = host_shutdown;
1085 /// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything
1086 static void Sys_InitSignals(void)
1088 // Windows only supports the C99 signals
1089 signal(SIGINT, Sys_HandleSignal);
1090 signal(SIGILL, Sys_HandleCrash);
1091 signal(SIGABRT, Sys_HandleCrash);
1092 signal(SIGFPE, Sys_HandleCrash);
1093 signal(SIGSEGV, Sys_HandleCrash);
1094 signal(SIGTERM, Sys_HandleSignal);
1096 // POSIX has several others worth catching
1097 signal(SIGHUP, Sys_HandleSignal);
1098 signal(SIGQUIT, Sys_HandleSignal);
1099 signal(SIGBUS, Sys_HandleCrash);
1100 signal(SIGPIPE, Sys_HandleSignal);
1104 /** main() but renamed so we can wrap it in sys_sdl.c and sys_null.c
1105 * to avoid needing to include SDL.h in this file (would make the dedicated server require SDL).
1106 * SDL builds need SDL.h in the file where main() is defined because SDL renames and wraps main().
1108 int Sys_Main(int argc, char *argv[])
1111 sys.argv = (const char **)argv;
1113 // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
1114 // COMMANDLINEOPTION: -noterminal disables console output on stdout
1115 if(Sys_CheckParm("-noterminal") || Sys_CheckParm("-nostdout"))
1116 sys_stdout.string = "0";
1117 // COMMANDLINEOPTION: -stderr moves console output to stderr
1118 else if(Sys_CheckParm("-stderr"))
1119 sys_stdout.string = "2";
1120 // too early for Cvar_SetQuick
1121 sys_stdout.value = sys_stdout.integer = atoi(sys_stdout.string);
1122 Sys_UpdateOutFD_c(&sys_stdout);
1124 fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, 0) | O_NONBLOCK);
1125 // stdout/stderr will be set to blocking in Sys_Print() if so configured, or during a fatal error.
1126 fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) | O_NONBLOCK);
1127 fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) | O_NONBLOCK);
1131 Sys_ProvideSelfFD(); // may call Con_Printf() so must be after sys.outfd is set
1134 Sys_AllowProfiling(true);
1140 Sys_SetTimerResolution();
1146 Sys_AllowProfiling(false);
1150 fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) & ~O_NONBLOCK);
1151 fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) & ~O_NONBLOCK);