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"
41 // Not sure why MS compiler needs this here and gcc doesn't...
42 // and gcc fails to build darkplaces-dedicated if it's included here.
49 static char sys_timestring[128];
50 char *Sys_TimeString(const char *timeformat)
52 time_t mytime = time(NULL);
55 localtime_s(&mytm, &mytime);
56 strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
58 strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
60 return sys_timestring;
67 void Sys_AllowProfiling(qbool enable)
71 extern void monstartup(const char *libname);
72 extern void moncleanup(void);
74 monstartup("libmain.so");
78 #elif (defined(__linux__) && (defined(__GLIBC__) || defined(__GNU_LIBRARY__))) || defined(__FreeBSD__)
79 extern int moncontrol(int);
86 ===============================================================================
90 ===============================================================================
93 static qbool Sys_LoadDependencyFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qbool complain, qbool has_next)
95 const dllfunction_t *func;
98 for (func = fcts; func && func->name != NULL; func++)
99 if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
103 Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
105 Con_DPrintf("\nContinuing with");
112 for (func = fcts; func && func->name != NULL; func++)
113 *func->funcvariable = NULL;
118 qbool Sys_LoadSelf(dllhandle_t *handle)
120 dllhandle_t dllhandle = 0;
125 dllhandle = LoadLibrary (NULL);
127 dllhandle = dlopen (NULL, RTLD_NOW | RTLD_GLOBAL);
133 qbool Sys_LoadDependency (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
136 const dllfunction_t *func;
137 dllhandle_t dllhandle = 0;
144 #ifdef PREFER_PRELOAD
145 dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
146 if(Sys_LoadDependencyFunctions(dllhandle, fcts, false, false))
148 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
153 Sys_FreeLibrary(&dllhandle);
159 for (func = fcts; func && func->name != NULL; func++)
160 *func->funcvariable = NULL;
162 // Try every possible name
163 Con_DPrintf ("Trying to load library...");
164 for (i = 0; dllnames[i] != NULL; i++)
166 Con_DPrintf (" \"%s\"", dllnames[i]);
168 # ifndef DONT_USE_SETDLLDIRECTORY
170 SetDllDirectory("bin64");
172 SetDllDirectory("bin32");
176 if(Sys_LoadLibrary(dllnames[i], &dllhandle))
178 if (Sys_LoadDependencyFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
181 Sys_FreeLibrary (&dllhandle);
185 // see if the names can be loaded relative to the executable path
186 // (this is for Mac OSX which does not check next to the executable)
187 if (!dllhandle && strrchr(sys.argv[0], '/'))
189 char path[MAX_OSPATH];
190 dp_strlcpy(path, sys.argv[0], sizeof(path));
191 strrchr(path, '/')[1] = 0;
192 for (i = 0; dllnames[i] != NULL; i++)
194 char temp[MAX_OSPATH];
195 dp_strlcpy(temp, path, sizeof(temp));
196 dp_strlcat(temp, dllnames[i], sizeof(temp));
197 Con_DPrintf (" \"%s\"", temp);
199 if(Sys_LoadLibrary(temp, &dllhandle))
201 if (Sys_LoadDependencyFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
204 Sys_FreeLibrary (&dllhandle);
212 Con_DPrintf(" - failed.\n");
216 Con_DPrintf(" - loaded.\n");
217 Con_Printf("Loaded library \"%s\"\n", dllnames[i]);
226 qbool Sys_LoadLibrary(const char *name, dllhandle_t *handle)
228 dllhandle_t dllhandle = 0;
235 dllhandle = LoadLibrary (name);
237 dllhandle = dlopen (name, RTLD_LAZY | RTLD_GLOBAL);
247 void Sys_FreeLibrary (dllhandle_t* handle)
250 if (handle == NULL || *handle == NULL)
254 FreeLibrary (*handle);
263 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
267 return (void *)GetProcAddress (handle, name);
269 return (void *)dlsym (handle, name);
279 # define HAVE_TIMEGETTIME 1
280 # define HAVE_QUERYPERFORMANCECOUNTER 1
281 # define HAVE_WIN32_USLEEP 1
282 # define HAVE_Sleep 1
284 # if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
285 # define HAVE_CLOCKGETTIME 1
287 # if _POSIX_VERSION >= 200112L
288 # define HAVE_CLOCK_NANOSLEEP 1
293 # define HAVE_SELECT 1
296 // these are referenced elsewhere
297 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."};
298 cvar_t sys_libdir = {CF_READONLY | CF_SHARED, "sys_libdir", "", "Default engine library directory"};
301 static cvar_t sys_debugsleep = {CF_SHARED, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
302 static cvar_t sys_usesdlgetticks = {CF_SHARED, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (low precision, for debugging)"};
303 static cvar_t sys_usesdldelay = {CF_SHARED, "sys_usesdldelay", "0", "use SDL_Delay() (low precision, for debugging)"};
304 #if HAVE_QUERYPERFORMANCECOUNTER
305 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)"};
308 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)"};
310 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"};
313 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
319 Returns the position (1 to argc-1) in the program's argument list
320 where the given parameter apears, or 0 if not present
323 int Sys_CheckParm (const char *parm)
327 for (i=1 ; i<sys.argc ; i++)
330 continue; // NEXTSTEP sometimes clears appkit vars.
331 if (!strcmp (parm,sys.argv[i]))
338 static void Sys_UpdateOutFD_c(cvar_t *var)
340 switch (sys_stdout.integer)
342 case 0: sys.outfd = -1; break;
344 case 1: sys.outfd = fileno(stdout); break;
345 case 2: sys.outfd = fileno(stderr); break;
349 void Sys_Init_Commands (void)
351 Cvar_RegisterVariable(&sys_debugsleep);
352 Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
353 Cvar_RegisterVariable(&sys_libdir);
354 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME
355 if(sys_supportsdlgetticks)
357 Cvar_RegisterVariable(&sys_usesdlgetticks);
358 Cvar_RegisterVariable(&sys_usesdldelay);
361 #if HAVE_QUERYPERFORMANCECOUNTER
362 Cvar_RegisterVariable(&sys_usequeryperformancecounter);
365 Cvar_RegisterVariable(&sys_stdout);
366 Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
368 Cvar_RegisterVariable(&sys_stdout_blocks);
373 static LARGE_INTEGER PerformanceFreq;
374 /// Windows default timer resolution is only 15.625ms,
375 /// this affects (at least) timeGetTime() and all forms of sleeping.
376 static void Sys_SetTimerResolution(void)
378 NTSTATUS(NTAPI *qNtQueryTimerResolution)(OUT PULONG MinRes, OUT PULONG MaxRes, OUT PULONG CurrentRes);
379 NTSTATUS(NTAPI *qNtSetTimerResolution)(IN ULONG DesiredRes, IN BOOLEAN SetRes, OUT PULONG ActualRes);
380 const char* ntdll_names [] =
385 dllfunction_t ntdll_funcs[] =
387 {"NtQueryTimerResolution", (void **) &qNtQueryTimerResolution},
388 {"NtSetTimerResolution", (void **) &qNtSetTimerResolution},
392 unsigned long WorstRes, BestRes, CurrentRes;
394 timeBeginPeriod(1); // 1ms, documented
396 // the best Windows can manage (typically 0.5ms)
397 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
398 if (Sys_LoadDependency(ntdll_names, &ntdll, ntdll_funcs))
400 qNtQueryTimerResolution(&WorstRes, &BestRes, &CurrentRes); // no pointers may be NULL
401 if (CurrentRes > BestRes)
402 qNtSetTimerResolution(BestRes, true, &CurrentRes);
404 Sys_FreeLibrary(&ntdll);
407 // Microsoft says the freq is fixed at boot and consistent across all processors
408 // and that it need only be queried once and cached.
409 QueryPerformanceFrequency (&PerformanceFreq);
413 double Sys_DirtyTime(void)
415 // first all the OPTIONAL timers
417 // benchmark timer (fake clock)
418 if(sys_usenoclockbutbenchmark.integer)
420 double old_benchmark_time = benchmark_time;
422 if(benchmark_time == old_benchmark_time)
423 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
424 return benchmark_time * 0.000001;
427 if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
428 return (double) Sys_SDL_GetTicks() / 1000.0;
430 #if HAVE_QUERYPERFORMANCECOUNTER
431 if (sys_usequeryperformancecounter.integer)
433 // QueryPerformanceCounter
435 // Windows 95/98/ME/NT/2000/XP
437 // + very accurate (constant-rate TSCs on modern systems)
439 // - does not necessarily match realtime too well (tends to get faster and faster in win98)
440 // - wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
441 // - higher access latency on Vista
442 // Microsoft says on Win 7 or later, latency and overhead are very low, synchronisation is excellent.
443 LARGE_INTEGER PerformanceCount;
445 if (PerformanceFreq.QuadPart)
447 QueryPerformanceCounter (&PerformanceCount);
448 return (double)PerformanceCount.QuadPart * (1.0 / (double)PerformanceFreq.QuadPart);
452 Con_Printf("No hardware timer available\n");
453 // fall back to other clock sources
454 Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
459 #if HAVE_CLOCKGETTIME
462 # ifdef CLOCK_MONOTONIC_RAW
463 // Linux-specific, SDL_GetPerformanceCounter() uses it
464 clock_gettime(CLOCK_MONOTONIC, &ts);
465 # elif defined(CLOCK_MONOTONIC)
467 clock_gettime(CLOCK_MONOTONIC, &ts);
470 clock_gettime(CLOCK_HIGHRES, &ts);
472 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
476 // now all the FALLBACK timers
481 // Windows 95/98/ME/NT/2000/XP
483 // reasonable accuracy (millisecond)
485 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
486 // requires Sys_SetTimerResolution()
487 return (double) timeGetTime() / 1000.0;
490 // fallback for using the SDL timer if no other timer is available
491 // this calls Sys_Error() if not linking against SDL
492 return (double) Sys_SDL_GetTicks() / 1000.0;
496 double Sys_Sleep(double time)
499 uint32_t msec, usec, nsec;
501 if (time < 1.0/1000000.0 || host.restless)
502 return 0; // not sleeping this frame
504 time = 0.999999; // ensure passed values are in range
506 usec = time * 1000000;
507 nsec = time * 1000000000;
509 if(sys_usenoclockbutbenchmark.integer)
511 double old_benchmark_time = benchmark_time;
512 benchmark_time += usec;
513 if(benchmark_time == old_benchmark_time)
514 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
518 if(sys_debugsleep.integer)
519 Con_Printf("sys_debugsleep: requesting %u ", usec);
520 dt = Sys_DirtyTime();
522 // less important on newer libcurl so no need to disturb dedicated servers
523 if (cls.state != ca_dedicated && Curl_Select(msec))
525 // a transfer is ready or we finished sleeping
527 else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
530 else if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
538 List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
540 if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
542 if (lastfd < s->inetsocket)
543 lastfd = s->inetsocket;
544 #if defined(WIN32) && !defined(_MSC_VER)
545 FD_SET((int)s->inetsocket, &fdreadset);
547 FD_SET((unsigned int)s->inetsocket, &fdreadset);
553 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
554 // (so much for POSIX...), not with an empty fd_set either.
555 select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
558 #if HAVE_CLOCK_NANOSLEEP
564 clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
566 #elif HAVE_WIN32_USLEEP // Windows XP/2003 minimum
570 LARGE_INTEGER sleeptime;
572 // takes 100ns units, negative indicates relative time
573 sleeptime.QuadPart = -((int64_t)nsec / 100);
574 timer = CreateWaitableTimer(NULL, true, NULL);
575 SetWaitableTimer(timer, &sleeptime, 0, NULL, NULL, 0);
576 WaitForSingleObject(timer, INFINITE);
587 dt = Sys_DirtyTime() - dt;
588 if(sys_debugsleep.integer)
589 Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - usec);
590 return (dt < 0 || dt >= 1800) ? 0 : dt;
595 ===============================================================================
599 ===============================================================================
602 // NOTE: use only POSIX async-signal-safe library functions here (see: man signal-safety)
603 void Sys_Print(const char *text, size_t textlen)
606 if (developer.integer > 0)
608 __android_log_write(ANDROID_LOG_DEBUG, sys.argv[0], text);
614 // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0).
615 // this is because both go to /dev/tty by default!
617 int origflags = fcntl(sys.outfd, F_GETFL, 0);
618 if (sys_stdout_blocks.integer)
619 fcntl(sys.outfd, F_SETFL, origflags & ~O_NONBLOCK);
625 fs_offset_t written = (fs_offset_t)write(sys.outfd, text, textlen);
627 break; // sorry, I cannot do anything about this error - without an output
631 if (sys_stdout_blocks.integer)
632 fcntl(sys.outfd, F_SETFL, origflags);
635 //fprintf(stdout, "%s", text);
639 void Sys_Printf(const char *fmt, ...)
642 char msg[MAX_INPUTLINE];
645 va_start(argptr,fmt);
646 msglen = dpvsnprintf(msg, sizeof(msg), fmt, argptr);
650 Sys_Print(msg, msglen);
653 /// Reads a line from POSIX stdin or the Windows console
654 char *Sys_ConsoleInput(void)
656 static char text[MAX_INPUTLINE];
658 static unsigned int len = 0;
683 if (len < sizeof (text) - 1)
692 struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
695 FD_SET(fileno(stdin), &fdset);
696 if (select(1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(fileno(stdin), &fdset))
697 return fgets(text, sizeof(text), stdin);
704 ===============================================================================
708 ===============================================================================
711 void Sys_Error (const char *error, ...)
714 char string[MAX_INPUTLINE];
717 // Disable Sys_HandleSignal() but not Sys_HandleCrash()
718 host.state = host_shutdown;
720 // set output to blocking stderr
721 sys.outfd = fileno(stderr);
723 fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
726 va_start (argptr,error);
727 dpvsnprintf (string, sizeof (string), error, argptr);
730 Con_Printf(CON_ERROR "Engine Aborted: %s\n^9%s\n", string, engineversion);
732 dp_strlcat(string, "\n\n", sizeof(string));
733 dp_strlcat(string, engineversion, sizeof(string));
735 // Most shutdown funcs can't be called here as they could error while we error.
737 // DP8 TODO: send a disconnect message indicating we aborted, see Host_Error() and Sys_HandleCrash()
739 if (cls.demorecording)
740 CL_Stop_f(cmd_local);
743 sv.active = false; // make SV_DropClient() skip the QC stuff to avoid recursive errors
744 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
745 if (host_client->active)
746 SV_DropClient(false, "Server aborted!"); // closes demo file
748 // don't want a dead window left blocking the OS UI or the abort dialog
752 host.state = host_failed; // make Sys_HandleSignal() call _Exit()
753 Sys_SDL_Dialog("Engine Aborted", string);
760 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
762 const char *p = PATH;
766 while((q = strchr(p, ':')))
768 dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
769 if(FS_SysFileExists(buf))
773 if(!q) // none found - try the last item
775 dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
776 if(FS_SysFileExists(buf))
784 static const char *Sys_FindExecutableName(void)
789 static char exenamebuf[MAX_OSPATH+1];
791 #if defined(__FreeBSD__)
792 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
793 size_t exenamebuflen = sizeof(exenamebuf)-1;
794 if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
798 #elif defined(__linux__)
799 n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
801 if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
806 if(strchr(sys.argv[0], '/'))
807 return sys.argv[0]; // possibly a relative path
809 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
813 void Sys_ProvideSelfFD(void)
817 sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
820 // for x86 cpus only... (x64 has SSE2_PRESENT)
821 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
822 // code from SDL, shortened as we can expect CPUID to work
823 static int CPUID_Features(void)
826 # if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) && defined(__i386__)
828 " movl %%ebx,%%edi\n"
829 " xorl %%eax,%%eax \n"
831 " cpuid # Get family/model/stepping/features\n"
833 " movl %%edi,%%ebx\n"
836 : "%eax", "%ecx", "%edx", "%edi"
838 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
842 cpuid ; Get family/model/stepping/features
846 # error SSE_POSSIBLE set but no CPUID implementation
853 qbool Sys_HaveSSE(void)
855 // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
856 if(Sys_CheckParm("-nosse"))
861 // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
862 if(Sys_CheckParm("-forcesse") || Sys_CheckParm("-forcesse2"))
864 if(CPUID_Features() & (1 << 25))
870 qbool Sys_HaveSSE2(void)
872 // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
873 if(Sys_CheckParm("-nosse") || Sys_CheckParm("-nosse2"))
878 // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
879 if(Sys_CheckParm("-forcesse2"))
881 if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
888 /// called to set process priority for dedicated servers
889 #if defined(__linux__)
890 #include <sys/resource.h>
893 void Sys_InitProcessNice (void)
896 sys.nicepossible = false;
897 if(Sys_CheckParm("-nonice"))
900 sys.nicelevel = getpriority(PRIO_PROCESS, 0);
903 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
906 if(getrlimit(RLIMIT_NICE, &lim))
908 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
911 if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
913 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
916 sys.nicepossible = true;
919 void Sys_MakeProcessNice (void)
921 if(!sys.nicepossible)
925 Con_DPrintf("Process is becoming 'nice'...\n");
926 if(setpriority(PRIO_PROCESS, 0, 19))
927 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
930 void Sys_MakeProcessMean (void)
932 if(!sys.nicepossible)
936 Con_DPrintf("Process is becoming 'mean'...\n");
937 if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
938 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
942 void Sys_InitProcessNice (void)
945 void Sys_MakeProcessNice (void)
948 void Sys_MakeProcessMean (void)
954 static const char *Sys_SigDesc(int sig)
958 // Windows only supports the C99 signals
959 case SIGINT: return "Interrupt";
960 case SIGILL: return "Illegal instruction";
961 case SIGABRT: return "Aborted";
962 case SIGFPE: return "Floating point exception";
963 case SIGSEGV: return "Segmentation fault";
964 case SIGTERM: return "Termination";
966 // POSIX has several others worth catching
967 case SIGHUP: return "Hangup";
968 case SIGQUIT: return "Quit";
969 case SIGBUS: return "Bus error (bad memory access)";
970 case SIGPIPE: return "Broken pipe";
972 default: return "Yo dawg, we bugged out while bugging out";
976 /** Halt and try not to catch fire.
977 * Writing to any file could corrupt it,
978 * any uneccessary code could crash while we crash.
979 * Try to use only POSIX async-signal-safe library functions here (see: man signal-safety).
981 static void Sys_HandleCrash(int sig)
983 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
984 // Before doing anything else grab the stack frame addresses
985 #include <execinfo.h>
986 void *stackframes[32];
987 int framecount = backtrace(stackframes, 32);
990 char dialogtext[3072];
993 // Break any loop and disable Sys_HandleSignal()
994 if (host.state == host_failing || host.state == host_failed)
996 host.state = host_failing;
998 sigdesc = Sys_SigDesc(sig);
1000 // set output to blocking stderr and print header, backtrace, version
1001 sys.outfd = fileno(stderr); // not async-signal-safe :(
1003 fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
1004 Sys_Print("\n\n\e[1;37;41m Engine Crash: ", 30);
1005 Sys_Print(sigdesc, strlen(sigdesc));
1006 Sys_Print(" \e[m\n", 8);
1007 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
1008 // the first two addresses will be in this function and in signal() in libc
1009 backtrace_symbols_fd(stackframes + 2, framecount - 2, sys.outfd);
1011 Sys_Print("\e[1m", 4);
1012 Sys_Print(engineversion, strlen(engineversion));
1013 Sys_Print("\e[m\n", 4);
1014 #else // Windows console doesn't support colours
1015 Sys_Print("\n\nEngine Crash: ", 16);
1016 Sys_Print(sigdesc, strlen(sigdesc));
1018 Sys_Print(engineversion, strlen(engineversion));
1022 // DP8 TODO: send a disconnect message indicating we crashed, see Sys_Error() and Host_Error()
1024 // don't want a dead window left blocking the OS UI or the crash dialog
1028 // prepare the dialogtext: signal, backtrace, version
1029 // the dp_st* funcs are POSIX async-signal-safe IF we don't trigger their warnings
1030 dp_strlcpy(dialogtext, sigdesc, sizeof(dialogtext));
1031 dp_strlcat(dialogtext, "\n\n", sizeof(dialogtext));
1032 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
1033 btstrings = backtrace_symbols(stackframes + 2, framecount - 2); // calls malloc :(
1035 for (int i = 0; i < framecount - 2; ++i)
1037 dp_strlcat(dialogtext, btstrings[i], sizeof(dialogtext));
1038 dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1041 dp_strlcat(dialogtext, "\n", sizeof(dialogtext));
1042 dp_strlcat(dialogtext, engineversion, sizeof(dialogtext));
1044 host.state = host_failed; // make Sys_HandleSignal() call _Exit()
1045 Sys_SDL_Dialog("Engine Crash", dialogtext);
1047 fflush(stderr); // not async-signal-safe :(
1049 // Continue execution with default signal handling.
1050 // A real crash will be re-triggered so the platform can handle it,
1051 // a fake crash (kill -SEGV) will cause a graceful shutdown.
1052 signal(sig, SIG_DFL);
1055 static void Sys_HandleSignal(int sig)
1057 const char *sigdesc;
1059 // Break any loop, eg if each Sys_Print triggers a SIGPIPE
1060 if (host.state == host_shutdown || host.state == host_failing)
1063 sigdesc = Sys_SigDesc(sig);
1064 Sys_Print("\nReceived ", 10);
1065 Sys_Print(sigdesc, strlen(sigdesc));
1066 Sys_Print(" signal, exiting...\n", 20);
1067 if (host.state == host_failed)
1069 // user is trying to kill the process while the SDL dialog is open
1070 fflush(stderr); // not async-signal-safe :(
1073 host.state = host_shutdown;
1076 /// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything
1077 static void Sys_InitSignals(void)
1079 // Windows only supports the C99 signals
1080 signal(SIGINT, Sys_HandleSignal);
1081 signal(SIGILL, Sys_HandleCrash);
1082 signal(SIGABRT, Sys_HandleCrash);
1083 signal(SIGFPE, Sys_HandleCrash);
1084 signal(SIGSEGV, Sys_HandleCrash);
1085 signal(SIGTERM, Sys_HandleSignal);
1087 // POSIX has several others worth catching
1088 signal(SIGHUP, Sys_HandleSignal);
1089 signal(SIGQUIT, Sys_HandleSignal);
1090 signal(SIGBUS, Sys_HandleCrash);
1091 signal(SIGPIPE, Sys_HandleSignal);
1095 int main (int argc, char **argv)
1098 sys.argv = (const char **)argv;
1100 // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
1101 // COMMANDLINEOPTION: -noterminal disables console output on stdout
1102 if(Sys_CheckParm("-noterminal") || Sys_CheckParm("-nostdout"))
1103 sys_stdout.string = "0";
1104 // COMMANDLINEOPTION: -stderr moves console output to stderr
1105 else if(Sys_CheckParm("-stderr"))
1106 sys_stdout.string = "2";
1107 // too early for Cvar_SetQuick
1108 sys_stdout.value = sys_stdout.integer = atoi(sys_stdout.string);
1109 Sys_UpdateOutFD_c(&sys_stdout);
1111 fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, 0) | O_NONBLOCK);
1112 // stdout/stderr will be set to blocking in Sys_Print() if so configured, or during a fatal error.
1113 fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) | O_NONBLOCK);
1114 fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) | O_NONBLOCK);
1118 Sys_ProvideSelfFD(); // may call Con_Printf() so must be after sys.outfd is set
1121 Sys_AllowProfiling(true);
1127 Sys_SetTimerResolution();
1133 Sys_AllowProfiling(false);
1137 fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) & ~O_NONBLOCK);
1138 fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) & ~O_NONBLOCK);