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 # define HAVE_CLOCK_NANOSLEEP 1
288 # define HAVE_SELECT 1
291 // these are referenced elsewhere
292 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."};
293 cvar_t sys_libdir = {CF_READONLY | CF_SHARED, "sys_libdir", "", "Default engine library directory"};
296 static cvar_t sys_debugsleep = {CF_SHARED, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
297 static cvar_t sys_usesdlgetticks = {CF_SHARED, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (low precision, for debugging)"};
298 static cvar_t sys_usesdldelay = {CF_SHARED, "sys_usesdldelay", "0", "use SDL_Delay() (low precision, for debugging)"};
299 #if HAVE_QUERYPERFORMANCECOUNTER
300 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)"};
303 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)"};
305 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"};
308 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
314 Returns the position (1 to argc-1) in the program's argument list
315 where the given parameter apears, or 0 if not present
318 int Sys_CheckParm (const char *parm)
322 for (i=1 ; i<sys.argc ; i++)
325 continue; // NEXTSTEP sometimes clears appkit vars.
326 if (!strcmp (parm,sys.argv[i]))
333 static void Sys_UpdateOutFD_c(cvar_t *var)
335 switch (sys_stdout.integer)
337 case 0: sys.outfd = -1; break;
339 case 1: sys.outfd = fileno(stdout); break;
340 case 2: sys.outfd = fileno(stderr); break;
344 void Sys_Init_Commands (void)
346 Cvar_RegisterVariable(&sys_debugsleep);
347 Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
348 Cvar_RegisterVariable(&sys_libdir);
349 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME
350 if(sys_supportsdlgetticks)
352 Cvar_RegisterVariable(&sys_usesdlgetticks);
353 Cvar_RegisterVariable(&sys_usesdldelay);
356 #if HAVE_QUERYPERFORMANCECOUNTER
357 Cvar_RegisterVariable(&sys_usequeryperformancecounter);
360 Cvar_RegisterVariable(&sys_stdout);
361 Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
363 Cvar_RegisterVariable(&sys_stdout_blocks);
368 static LARGE_INTEGER PerformanceFreq;
369 /// Windows default timer resolution is only 15.625ms,
370 /// this affects (at least) timeGetTime() and all forms of sleeping.
371 static void Sys_SetTimerResolution(void)
373 NTSTATUS(NTAPI *qNtQueryTimerResolution)(OUT PULONG MinRes, OUT PULONG MaxRes, OUT PULONG CurrentRes);
374 NTSTATUS(NTAPI *qNtSetTimerResolution)(IN ULONG DesiredRes, IN BOOLEAN SetRes, OUT PULONG ActualRes);
375 const char* ntdll_names [] =
380 dllfunction_t ntdll_funcs[] =
382 {"NtQueryTimerResolution", (void **) &qNtQueryTimerResolution},
383 {"NtSetTimerResolution", (void **) &qNtSetTimerResolution},
387 unsigned long WorstRes, BestRes, CurrentRes;
389 timeBeginPeriod(1); // 1ms, documented
391 // the best Windows can manage (typically 0.5ms)
392 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
393 if (Sys_LoadDependency(ntdll_names, &ntdll, ntdll_funcs))
395 qNtQueryTimerResolution(&WorstRes, &BestRes, &CurrentRes); // no pointers may be NULL
396 if (CurrentRes > BestRes)
397 qNtSetTimerResolution(BestRes, true, &CurrentRes);
399 Sys_FreeLibrary(&ntdll);
402 // Microsoft says the freq is fixed at boot and consistent across all processors
403 // and that it need only be queried once and cached.
404 QueryPerformanceFrequency (&PerformanceFreq);
408 double Sys_DirtyTime(void)
410 // first all the OPTIONAL timers
412 // benchmark timer (fake clock)
413 if(sys_usenoclockbutbenchmark.integer)
415 double old_benchmark_time = benchmark_time;
417 if(benchmark_time == old_benchmark_time)
418 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
419 return benchmark_time * 0.000001;
422 if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
423 return (double) Sys_SDL_GetTicks() / 1000.0;
425 #if HAVE_QUERYPERFORMANCECOUNTER
426 if (sys_usequeryperformancecounter.integer)
428 // QueryPerformanceCounter
430 // Windows 95/98/ME/NT/2000/XP
432 // + very accurate (constant-rate TSCs on modern systems)
434 // - does not necessarily match realtime too well (tends to get faster and faster in win98)
435 // - wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
436 // - higher access latency on Vista
437 // Microsoft says on Win 7 or later, latency and overhead are very low, synchronisation is excellent.
438 LARGE_INTEGER PerformanceCount;
440 if (PerformanceFreq.QuadPart)
442 QueryPerformanceCounter (&PerformanceCount);
443 return (double)PerformanceCount.QuadPart * (1.0 / (double)PerformanceFreq.QuadPart);
447 Con_Printf("No hardware timer available\n");
448 // fall back to other clock sources
449 Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
454 #if HAVE_CLOCKGETTIME
457 # ifdef CLOCK_MONOTONIC_RAW
458 // Linux-specific, SDL_GetPerformanceCounter() uses it
459 clock_gettime(CLOCK_MONOTONIC, &ts);
460 # elif defined(CLOCK_MONOTONIC)
462 clock_gettime(CLOCK_MONOTONIC, &ts);
465 clock_gettime(CLOCK_HIGHRES, &ts);
467 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
471 // now all the FALLBACK timers
476 // Windows 95/98/ME/NT/2000/XP
478 // reasonable accuracy (millisecond)
480 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
481 // requires Sys_SetTimerResolution()
482 return (double) timeGetTime() / 1000.0;
485 // fallback for using the SDL timer if no other timer is available
486 // this calls Sys_Error() if not linking against SDL
487 return (double) Sys_SDL_GetTicks() / 1000.0;
491 double Sys_Sleep(double time)
494 uint32_t msec, usec, nsec;
496 if (time < 1.0/1000000.0 || host.restless)
497 return 0; // not sleeping this frame
499 time = 0.999999; // ensure passed values are in range
501 usec = time * 1000000;
502 nsec = time * 1000000000;
504 if(sys_usenoclockbutbenchmark.integer)
506 double old_benchmark_time = benchmark_time;
507 benchmark_time += usec;
508 if(benchmark_time == old_benchmark_time)
509 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
513 if(sys_debugsleep.integer)
514 Con_Printf("sys_debugsleep: requesting %u ", usec);
515 dt = Sys_DirtyTime();
517 // less important on newer libcurl so no need to disturb dedicated servers
518 if (cls.state != ca_dedicated && Curl_Select(msec))
520 // a transfer is ready or we finished sleeping
522 else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
525 else if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
533 List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
535 if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
537 if (lastfd < s->inetsocket)
538 lastfd = s->inetsocket;
539 #if defined(WIN32) && !defined(_MSC_VER)
540 FD_SET((int)s->inetsocket, &fdreadset);
542 FD_SET((unsigned int)s->inetsocket, &fdreadset);
548 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
549 // (so much for POSIX...), not with an empty fd_set either.
550 select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
553 #if HAVE_CLOCK_NANOSLEEP
559 clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
561 #elif HAVE_WIN32_USLEEP // Windows XP/2003 minimum
565 LARGE_INTEGER sleeptime;
567 // takes 100ns units, negative indicates relative time
568 sleeptime.QuadPart = -((int64_t)nsec / 100);
569 timer = CreateWaitableTimer(NULL, true, NULL);
570 SetWaitableTimer(timer, &sleeptime, 0, NULL, NULL, 0);
571 WaitForSingleObject(timer, INFINITE);
582 dt = Sys_DirtyTime() - dt;
583 if(sys_debugsleep.integer)
584 Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - usec);
585 return (dt < 0 || dt >= 1800) ? 0 : dt;
590 ===============================================================================
594 ===============================================================================
597 // NOTE: use only POSIX async-signal-safe library functions here (see: man signal-safety)
598 void Sys_Print(const char *text, size_t textlen)
601 if (developer.integer > 0)
603 __android_log_write(ANDROID_LOG_DEBUG, sys.argv[0], text);
609 // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0).
610 // this is because both go to /dev/tty by default!
612 int origflags = fcntl(sys.outfd, F_GETFL, 0);
613 if (sys_stdout_blocks.integer)
614 fcntl(sys.outfd, F_SETFL, origflags & ~O_NONBLOCK);
620 fs_offset_t written = (fs_offset_t)write(sys.outfd, text, textlen);
622 break; // sorry, I cannot do anything about this error - without an output
626 if (sys_stdout_blocks.integer)
627 fcntl(sys.outfd, F_SETFL, origflags);
630 //fprintf(stdout, "%s", text);
634 void Sys_Printf(const char *fmt, ...)
637 char msg[MAX_INPUTLINE];
640 va_start(argptr,fmt);
641 msglen = dpvsnprintf(msg, sizeof(msg), fmt, argptr);
645 Sys_Print(msg, msglen);
648 /// Reads a line from POSIX stdin or the Windows console
649 char *Sys_ConsoleInput(void)
651 static char text[MAX_INPUTLINE];
653 static unsigned int len = 0;
678 if (len < sizeof (text) - 1)
687 struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
690 FD_SET(fileno(stdin), &fdset);
691 if (select(1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(fileno(stdin), &fdset))
692 return fgets(text, sizeof(text), stdin);
699 ===============================================================================
703 ===============================================================================
706 void Sys_Error (const char *error, ...)
709 char string[MAX_INPUTLINE];
712 // Disable Sys_HandleSignal() but not Sys_HandleCrash()
713 host.state = host_shutdown;
715 // set output to blocking stderr
716 sys.outfd = fileno(stderr);
718 fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
721 va_start (argptr,error);
722 dpvsnprintf (string, sizeof (string), error, argptr);
725 Con_Printf(CON_ERROR "Engine Aborted: %s\n^9%s\n", string, engineversion);
727 dp_strlcat(string, "\n\n", sizeof(string));
728 dp_strlcat(string, engineversion, sizeof(string));
730 // Most shutdown funcs can't be called here as they could error while we error.
732 // DP8 TODO: send a disconnect message indicating we aborted, see Host_Error() and Sys_HandleCrash()
734 if (cls.demorecording)
735 CL_Stop_f(cmd_local);
738 sv.active = false; // make SV_DropClient() skip the QC stuff to avoid recursive errors
739 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
740 if (host_client->active)
741 SV_DropClient(false, "Server aborted!"); // closes demo file
743 // don't want a dead window left blocking the OS UI or the abort dialog
747 host.state = host_failed; // make Sys_HandleSignal() call _Exit()
748 Sys_SDL_Dialog("Engine Aborted", string);
755 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
757 const char *p = PATH;
761 while((q = strchr(p, ':')))
763 dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
764 if(FS_SysFileExists(buf))
768 if(!q) // none found - try the last item
770 dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
771 if(FS_SysFileExists(buf))
779 static const char *Sys_FindExecutableName(void)
784 static char exenamebuf[MAX_OSPATH+1];
786 #if defined(__FreeBSD__)
787 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
788 size_t exenamebuflen = sizeof(exenamebuf)-1;
789 if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
793 #elif defined(__linux__)
794 n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
796 if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
801 if(strchr(sys.argv[0], '/'))
802 return sys.argv[0]; // possibly a relative path
804 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
808 void Sys_ProvideSelfFD(void)
812 sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
815 // for x86 cpus only... (x64 has SSE2_PRESENT)
816 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
817 // code from SDL, shortened as we can expect CPUID to work
818 static int CPUID_Features(void)
821 # if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) && defined(__i386__)
823 " movl %%ebx,%%edi\n"
824 " xorl %%eax,%%eax \n"
826 " cpuid # Get family/model/stepping/features\n"
828 " movl %%edi,%%ebx\n"
831 : "%eax", "%ecx", "%edx", "%edi"
833 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
837 cpuid ; Get family/model/stepping/features
841 # error SSE_POSSIBLE set but no CPUID implementation
848 qbool Sys_HaveSSE(void)
850 // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
851 if(Sys_CheckParm("-nosse"))
856 // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
857 if(Sys_CheckParm("-forcesse") || Sys_CheckParm("-forcesse2"))
859 if(CPUID_Features() & (1 << 25))
865 qbool Sys_HaveSSE2(void)
867 // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
868 if(Sys_CheckParm("-nosse") || Sys_CheckParm("-nosse2"))
873 // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
874 if(Sys_CheckParm("-forcesse2"))
876 if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
883 /// called to set process priority for dedicated servers
884 #if defined(__linux__)
885 #include <sys/resource.h>
888 void Sys_InitProcessNice (void)
891 sys.nicepossible = false;
892 if(Sys_CheckParm("-nonice"))
895 sys.nicelevel = getpriority(PRIO_PROCESS, 0);
898 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
901 if(getrlimit(RLIMIT_NICE, &lim))
903 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
906 if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
908 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
911 sys.nicepossible = true;
914 void Sys_MakeProcessNice (void)
916 if(!sys.nicepossible)
920 Con_DPrintf("Process is becoming 'nice'...\n");
921 if(setpriority(PRIO_PROCESS, 0, 19))
922 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
925 void Sys_MakeProcessMean (void)
927 if(!sys.nicepossible)
931 Con_DPrintf("Process is becoming 'mean'...\n");
932 if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
933 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
937 void Sys_InitProcessNice (void)
940 void Sys_MakeProcessNice (void)
943 void Sys_MakeProcessMean (void)
949 static const char *Sys_SigDesc(int sig)
953 // Windows only supports the C99 signals
954 case SIGINT: return "Interrupt";
955 case SIGILL: return "Illegal instruction";
956 case SIGABRT: return "Aborted";
957 case SIGFPE: return "Floating point exception";
958 case SIGSEGV: return "Segmentation fault";
959 case SIGTERM: return "Termination";
961 // POSIX has several others worth catching
962 case SIGHUP: return "Hangup";
963 case SIGQUIT: return "Quit";
964 case SIGBUS: return "Bus error (bad memory access)";
965 case SIGPIPE: return "Broken pipe";
967 default: return "Yo dawg, we bugged out while bugging out";
971 /** Halt and try not to catch fire.
972 * Writing to any file could corrupt it,
973 * any uneccessary code could crash while we crash.
974 * Try to use only POSIX async-signal-safe library functions here (see: man signal-safety).
976 static void Sys_HandleCrash(int sig)
978 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
979 // Before doing anything else grab the stack frame addresses
980 #include <execinfo.h>
981 void *stackframes[32];
982 int framecount = backtrace(stackframes, 32);
985 char dialogtext[3072];
988 // Break any loop and disable Sys_HandleSignal()
989 if (host.state == host_failing || host.state == host_failed)
991 host.state = host_failing;
993 sigdesc = Sys_SigDesc(sig);
995 // set output to blocking stderr and print header, backtrace, version
996 sys.outfd = fileno(stderr); // not async-signal-safe :(
998 fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
999 Sys_Print("\n\n\e[1;37;41m Engine Crash: ", 30);
1000 Sys_Print(sigdesc, strlen(sigdesc));
1001 Sys_Print(" \e[m\n", 8);
1002 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
1003 // the first two addresses will be in this function and in signal() in libc
1004 backtrace_symbols_fd(stackframes + 2, framecount - 2, sys.outfd);
1006 Sys_Print("\e[1m", 4);
1007 Sys_Print(engineversion, strlen(engineversion));
1008 Sys_Print("\e[m\n", 4);
1009 #else // Windows console doesn't support colours
1010 Sys_Print("\n\nEngine Crash: ", 16);
1011 Sys_Print(sigdesc, strlen(sigdesc));
1013 Sys_Print(engineversion, strlen(engineversion));
1017 // DP8 TODO: send a disconnect message indicating we crashed, see Sys_Error() and Host_Error()
1019 // don't want a dead window left blocking the OS UI or the crash dialog
1023 // prepare the dialogtext: signal, backtrace, version
1024 // the dp_st* funcs are POSIX async-signal-safe IF we don't trigger their warnings
1025 dp_strlcpy(dialogtext, sigdesc, sizeof(dialogtext));
1026 dp_strlcat(dialogtext, "\n\n", sizeof(dialogtext));
1027 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
1028 btstrings = backtrace_symbols(stackframes + 2, framecount - 2); // calls malloc :(
1030 for (int i = 0; i < framecount - 2; ++i)
1032 dp_strlcat(dialogtext, btstrings[i], sizeof(dialogtext));
1033 dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1036 dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1037 dp_strlcat(dialogtext, engineversion, sizeof(dialogtext));
1039 host.state = host_failed; // make Sys_HandleSignal() call _Exit()
1040 Sys_SDL_Dialog("Engine Crash", dialogtext);
1042 fflush(stderr); // not async-signal-safe :(
1044 // Continue execution with default signal handling.
1045 // A real crash will be re-triggered so the platform can handle it,
1046 // a fake crash (kill -SEGV) will cause a graceful shutdown.
1047 signal(sig, SIG_DFL);
1050 static void Sys_HandleSignal(int sig)
1052 const char *sigdesc;
1054 // Break any loop, eg if each Sys_Print triggers a SIGPIPE
1055 if (host.state == host_shutdown || host.state == host_failing)
1058 sigdesc = Sys_SigDesc(sig);
1059 Sys_Print("\nReceived ", 10);
1060 Sys_Print(sigdesc, strlen(sigdesc));
1061 Sys_Print(" signal, exiting...\n", 20);
1062 if (host.state == host_failed)
1064 // user is trying to kill the process while the SDL dialog is open
1065 fflush(stderr); // not async-signal-safe :(
1068 host.state = host_shutdown;
1071 /// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything
1072 static void Sys_InitSignals(void)
1074 // Windows only supports the C99 signals
1075 signal(SIGINT, Sys_HandleSignal);
1076 signal(SIGILL, Sys_HandleCrash);
1077 signal(SIGABRT, Sys_HandleCrash);
1078 signal(SIGFPE, Sys_HandleCrash);
1079 signal(SIGSEGV, Sys_HandleCrash);
1080 signal(SIGTERM, Sys_HandleSignal);
1082 // POSIX has several others worth catching
1083 signal(SIGHUP, Sys_HandleSignal);
1084 signal(SIGQUIT, Sys_HandleSignal);
1085 signal(SIGBUS, Sys_HandleCrash);
1086 signal(SIGPIPE, Sys_HandleSignal);
1090 int main (int argc, char **argv)
1093 sys.argv = (const char **)argv;
1095 // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
1096 // COMMANDLINEOPTION: -noterminal disables console output on stdout
1097 if(Sys_CheckParm("-noterminal") || Sys_CheckParm("-nostdout"))
1098 sys_stdout.string = "0";
1099 // COMMANDLINEOPTION: -stderr moves console output to stderr
1100 else if(Sys_CheckParm("-stderr"))
1101 sys_stdout.string = "2";
1102 // too early for Cvar_SetQuick
1103 sys_stdout.value = sys_stdout.integer = atoi(sys_stdout.string);
1104 Sys_UpdateOutFD_c(&sys_stdout);
1106 fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, 0) | O_NONBLOCK);
1107 // stdout/stderr will be set to blocking in Sys_Print() if so configured, or during a fatal error.
1108 fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) | O_NONBLOCK);
1109 fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) | O_NONBLOCK);
1113 Sys_ProvideSelfFD(); // may call Con_Printf() so must be after sys.outfd is set
1116 Sys_AllowProfiling(true);
1122 Sys_SetTimerResolution();
1128 Sys_AllowProfiling(false);
1132 fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) & ~O_NONBLOCK);
1133 fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) & ~O_NONBLOCK);