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