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