]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - sys_shared.c
sys: make stdout/stderr configurable during runtime
[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 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)"};
333 #ifndef WIN32
334 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"};
335 #endif
336
337 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
338
339 /*
340 ================
341 Sys_CheckParm
342
343 Returns the position (1 to argc-1) in the program's argument list
344 where the given parameter apears, or 0 if not present
345 ================
346 */
347 int Sys_CheckParm (const char *parm)
348 {
349         int i;
350
351         for (i=1 ; i<sys.argc ; i++)
352         {
353                 if (!sys.argv[i])
354                         continue;               // NEXTSTEP sometimes clears appkit vars.
355                 if (!strcmp (parm,sys.argv[i]))
356                         return i;
357         }
358
359         return 0;
360 }
361
362 static void Sys_UpdateOutFD_c(cvar_t *var)
363 {
364         switch (sys_stdout.integer)
365         {
366                 case 0: sys.outfd = -1; break;
367                 default:
368                 case 1: sys.outfd = fileno(stdout); break;
369                 case 2: sys.outfd = fileno(stderr); break;
370         }
371 }
372
373 void Sys_Init_Commands (void)
374 {
375         Cvar_RegisterVariable(&sys_debugsleep);
376         Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
377         Cvar_RegisterVariable(&sys_libdir);
378 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
379         if(sys_supportsdlgetticks)
380         {
381                 Cvar_RegisterVariable(&sys_usesdlgetticks);
382                 Cvar_RegisterVariable(&sys_usesdldelay);
383         }
384 #endif
385 #if HAVE_QUERYPERFORMANCECOUNTER
386         Cvar_RegisterVariable(&sys_usequeryperformancecounter);
387 #endif
388 #if HAVE_CLOCKGETTIME
389         Cvar_RegisterVariable(&sys_useclockgettime);
390 #endif
391         Cvar_RegisterVariable(&sys_stdout);
392         Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
393 #ifndef WIN32
394         Cvar_RegisterVariable(&sys_stdout_blocks);
395 #endif
396 }
397
398 double Sys_DirtyTime(void)
399 {
400         // first all the OPTIONAL timers
401
402         // benchmark timer (fake clock)
403         if(sys_usenoclockbutbenchmark.integer)
404         {
405                 double old_benchmark_time = benchmark_time;
406                 benchmark_time += 1;
407                 if(benchmark_time == old_benchmark_time)
408                         Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
409                 return benchmark_time * 0.000001;
410         }
411 #if HAVE_QUERYPERFORMANCECOUNTER
412         if (sys_usequeryperformancecounter.integer)
413         {
414                 // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
415                 // QueryPerformanceCounter
416                 // platform:
417                 // Windows 95/98/ME/NT/2000/XP
418                 // features:
419                 // very accurate (CPU cycles)
420                 // known issues:
421                 // does not necessarily match realtime too well (tends to get faster and faster in win98)
422                 // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
423                 double timescale;
424                 LARGE_INTEGER PerformanceFreq;
425                 LARGE_INTEGER PerformanceCount;
426
427                 if (QueryPerformanceFrequency (&PerformanceFreq))
428                 {
429                         QueryPerformanceCounter (&PerformanceCount);
430         
431                         timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
432                         return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
433                 }
434                 else
435                 {
436                         Con_Printf("No hardware timer available\n");
437                         // fall back to other clock sources
438                         Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
439                 }
440         }
441 #endif
442
443 #if HAVE_CLOCKGETTIME
444         if (sys_useclockgettime.integer)
445         {
446                 struct timespec ts;
447 #  ifdef CLOCK_MONOTONIC
448                 // linux
449                 clock_gettime(CLOCK_MONOTONIC, &ts);
450 #  else
451                 // sunos
452                 clock_gettime(CLOCK_HIGHRES, &ts);
453 #  endif
454                 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
455         }
456 #endif
457
458         // now all the FALLBACK timers
459         if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
460                 return (double) Sys_SDL_GetTicks() / 1000.0;
461 #if HAVE_GETTIMEOFDAY
462         {
463                 struct timeval tp;
464                 gettimeofday(&tp, NULL);
465                 return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
466         }
467 #elif HAVE_TIMEGETTIME
468         {
469                 static int firsttimegettime = true;
470                 // timeGetTime
471                 // platform:
472                 // Windows 95/98/ME/NT/2000/XP
473                 // features:
474                 // reasonable accuracy (millisecond)
475                 // issues:
476                 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
477
478                 // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
479                 if (firsttimegettime)
480                 {
481                         timeBeginPeriod(1);
482                         firsttimegettime = false;
483                 }
484
485                 return (double) timeGetTime() / 1000.0;
486         }
487 #else
488         // fallback for using the SDL timer if no other timer is available
489         // this calls Sys_Error() if not linking against SDL
490         return (double) Sys_SDL_GetTicks() / 1000.0;
491 #endif
492 }
493
494 extern cvar_t host_maxwait;
495 double Sys_Sleep(double time)
496 {
497         double dt;
498         uint32_t microseconds;
499
500         // convert to microseconds
501         time *= 1000000.0;
502
503         if(host_maxwait.value <= 0)
504                 time = min(time, 1000000.0);
505         else
506                 time = min(time, host_maxwait.value * 1000.0);
507
508         if (time < 1 || host.restless)
509                 return 0; // not sleeping this frame
510
511         microseconds = time; // post-validation to prevent overflow
512
513         if(sys_usenoclockbutbenchmark.integer)
514         {
515                 double old_benchmark_time = benchmark_time;
516                 benchmark_time += microseconds;
517                 if(benchmark_time == old_benchmark_time)
518                         Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
519                 return 0;
520         }
521
522         if(sys_debugsleep.integer)
523                 Con_Printf("sys_debugsleep: requesting %u ", microseconds);
524         dt = Sys_DirtyTime();
525
526         // less important on newer libcurl so no need to disturb dedicated servers
527         if (cls.state != ca_dedicated && Curl_Select(microseconds))
528         {
529                 // a transfer is ready or we finished sleeping
530         }
531         else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
532                 Sys_SDL_Delay(microseconds / 1000);
533 #if HAVE_SELECT
534         else
535         {
536                 struct timeval tv;
537                 lhnetsocket_t *s;
538                 fd_set fdreadset;
539                 int lastfd = -1;
540
541                 FD_ZERO(&fdreadset);
542                 if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
543                 {
544                         List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
545                         {
546                                 if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
547                                 {
548                                         if (lastfd < s->inetsocket)
549                                                 lastfd = s->inetsocket;
550         #if defined(WIN32) && !defined(_MSC_VER)
551                                         FD_SET((int)s->inetsocket, &fdreadset);
552         #else
553                                         FD_SET((unsigned int)s->inetsocket, &fdreadset);
554         #endif
555                                 }
556                         }
557                 }
558                 tv.tv_sec = microseconds / 1000000;
559                 tv.tv_usec = microseconds % 1000000;
560                 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
561                 // (so much for POSIX...)
562                 // bones_was_here: but a zeroed fd_set seems to be tolerated (tested on Win 7)
563                 select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
564         }
565 #elif HAVE_USLEEP
566         else
567                 usleep(microseconds);
568 #elif HAVE_Sleep
569         else
570                 Sleep(microseconds / 1000);
571 #else
572         else
573                 Sys_SDL_Delay(microseconds / 1000);
574 #endif
575
576         dt = Sys_DirtyTime() - dt;
577         if(sys_debugsleep.integer)
578                 Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - microseconds);
579         return (dt < 0 || dt >= 1800) ? 0 : dt;
580 }
581
582
583 /*
584 ===============================================================================
585
586 STDIO
587
588 ===============================================================================
589 */
590
591 void Sys_Print(const char *text)
592 {
593 #ifdef __ANDROID__
594         if (developer.integer > 0)
595         {
596                 __android_log_write(ANDROID_LOG_DEBUG, sys.argv[0], text);
597         }
598 #else
599         if(sys.outfd < 0)
600                 return;
601   #ifndef WIN32
602         // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0).
603         // this is because both go to /dev/tty by default!
604         {
605                 int origflags = fcntl(sys.outfd, F_GETFL, 0);
606                 if (sys_stdout_blocks.integer)
607                         fcntl(sys.outfd, F_SETFL, origflags & ~O_NONBLOCK);
608   #else
609     #define write _write
610   #endif
611                 while(*text)
612                 {
613                         fs_offset_t written = (fs_offset_t)write(sys.outfd, text, (int)strlen(text));
614                         if(written <= 0)
615                                 break; // sorry, I cannot do anything about this error - without an output
616                         text += written;
617                 }
618   #ifndef WIN32
619                 if (sys_stdout_blocks.integer)
620                         fcntl(sys.outfd, F_SETFL, origflags);
621         }
622   #endif
623         //fprintf(stdout, "%s", text);
624 #endif
625 }
626
627 /// for the console to report failures inside Con_Printf()
628 void Sys_Printf(const char *fmt, ...)
629 {
630         va_list argptr;
631         char msg[MAX_INPUTLINE];
632
633         va_start(argptr,fmt);
634         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
635         va_end(argptr);
636
637         Sys_Print(msg);
638 }
639
640 /// Reads a line from POSIX stdin or the Windows console
641 char *Sys_ConsoleInput(void)
642 {
643         static char text[MAX_INPUTLINE];
644 #ifdef WIN32
645         static unsigned int len = 0;
646         int c;
647
648         // read a line out
649         while (_kbhit ())
650         {
651                 c = _getch ();
652                 if (c == '\r')
653                 {
654                         text[len] = '\0';
655                         _putch ('\n');
656                         len = 0;
657                         return text;
658                 }
659                 if (c == '\b')
660                 {
661                         if (len)
662                         {
663                                 _putch (c);
664                                 _putch (' ');
665                                 _putch (c);
666                                 len--;
667                         }
668                         continue;
669                 }
670                 if (len < sizeof (text) - 1)
671                 {
672                         _putch (c);
673                         text[len] = c;
674                         len++;
675                 }
676         }
677 #else
678         fd_set fdset;
679         struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
680
681         FD_ZERO(&fdset);
682         FD_SET(fileno(stdin), &fdset);
683         if (select(1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(fileno(stdin), &fdset))
684                 return fgets(text, sizeof(text), stdin);
685 #endif
686         return NULL;
687 }
688
689
690 /*
691 ===============================================================================
692
693 Startup and Shutdown
694
695 ===============================================================================
696 */
697
698 void Sys_Error (const char *error, ...)
699 {
700         va_list argptr;
701         char string[MAX_INPUTLINE];
702
703 // change stdin to non blocking
704 #ifndef WIN32
705         fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~O_NONBLOCK);
706 #endif
707
708         va_start (argptr,error);
709         dpvsnprintf (string, sizeof (string), error, argptr);
710         va_end (argptr);
711
712         Con_Printf(CON_ERROR "Engine Error: %s\n", string);
713
714         // don't want a dead window left blocking the OS UI or the crash dialog
715         Host_Shutdown();
716
717         Sys_SDL_Dialog("Engine Error", string);
718
719         exit (1);
720 }
721
722 #ifndef WIN32
723 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
724 {
725         const char *p = PATH;
726         const char *q;
727         if(p && name)
728         {
729                 while((q = strchr(p, ':')))
730                 {
731                         dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
732                         if(FS_SysFileExists(buf))
733                                 return buf;
734                         p = q + 1;
735                 }
736                 if(!q) // none found - try the last item
737                 {
738                         dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
739                         if(FS_SysFileExists(buf))
740                                 return buf;
741                 }
742         }
743         return name;
744 }
745 #endif
746
747 static const char *Sys_FindExecutableName(void)
748 {
749 #if defined(WIN32)
750         return sys.argv[0];
751 #else
752         static char exenamebuf[MAX_OSPATH+1];
753         ssize_t n = -1;
754 #if defined(__FreeBSD__)
755         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
756         size_t exenamebuflen = sizeof(exenamebuf)-1;
757         if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
758         {
759                 n = exenamebuflen;
760         }
761 #elif defined(__linux__)
762         n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
763 #endif
764         if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
765         {
766                 exenamebuf[n] = 0;
767                 return exenamebuf;
768         }
769         if(strchr(sys.argv[0], '/'))
770                 return sys.argv[0]; // possibly a relative path
771         else
772                 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
773 #endif
774 }
775
776 void Sys_ProvideSelfFD(void)
777 {
778         if(sys.selffd != -1)
779                 return;
780         sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
781 }
782
783 // for x86 cpus only...  (x64 has SSE2_PRESENT)
784 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
785 // code from SDL, shortened as we can expect CPUID to work
786 static int CPUID_Features(void)
787 {
788         int features = 0;
789 # if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) && defined(__i386__)
790         __asm__ (
791 "        movl    %%ebx,%%edi\n"
792 "        xorl    %%eax,%%eax                                           \n"
793 "        incl    %%eax                                                 \n"
794 "        cpuid                       # Get family/model/stepping/features\n"
795 "        movl    %%edx,%0                                              \n"
796 "        movl    %%edi,%%ebx\n"
797         : "=m" (features)
798         :
799         : "%eax", "%ecx", "%edx", "%edi"
800         );
801 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
802         __asm {
803         xor     eax, eax
804         inc     eax
805         cpuid                       ; Get family/model/stepping/features
806         mov     features, edx
807         }
808 # else
809 #  error SSE_POSSIBLE set but no CPUID implementation
810 # endif
811         return features;
812 }
813 #endif
814
815 #ifdef SSE_POSSIBLE
816 qbool Sys_HaveSSE(void)
817 {
818         // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
819         if(Sys_CheckParm("-nosse"))
820                 return false;
821 #ifdef SSE_PRESENT
822         return true;
823 #else
824         // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
825         if(Sys_CheckParm("-forcesse") || Sys_CheckParm("-forcesse2"))
826                 return true;
827         if(CPUID_Features() & (1 << 25))
828                 return true;
829         return false;
830 #endif
831 }
832
833 qbool Sys_HaveSSE2(void)
834 {
835         // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
836         if(Sys_CheckParm("-nosse") || Sys_CheckParm("-nosse2"))
837                 return false;
838 #ifdef SSE2_PRESENT
839         return true;
840 #else
841         // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
842         if(Sys_CheckParm("-forcesse2"))
843                 return true;
844         if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
845                 return true;
846         return false;
847 #endif
848 }
849 #endif
850
851 /// called to set process priority for dedicated servers
852 #if defined(__linux__)
853 #include <sys/resource.h>
854 #include <errno.h>
855
856 void Sys_InitProcessNice (void)
857 {
858         struct rlimit lim;
859         sys.nicepossible = false;
860         if(Sys_CheckParm("-nonice"))
861                 return;
862         errno = 0;
863         sys.nicelevel = getpriority(PRIO_PROCESS, 0);
864         if(errno)
865         {
866                 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
867                 return;
868         }
869         if(getrlimit(RLIMIT_NICE, &lim))
870         {
871                 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
872                 return;
873         }
874         if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
875         {
876                 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
877                 return;
878         }
879         sys.nicepossible = true;
880         sys.isnice = false;
881 }
882 void Sys_MakeProcessNice (void)
883 {
884         if(!sys.nicepossible)
885                 return;
886         if(sys.isnice)
887                 return;
888         Con_DPrintf("Process is becoming 'nice'...\n");
889         if(setpriority(PRIO_PROCESS, 0, 19))
890                 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
891         sys.isnice = true;
892 }
893 void Sys_MakeProcessMean (void)
894 {
895         if(!sys.nicepossible)
896                 return;
897         if(!sys.isnice)
898                 return;
899         Con_DPrintf("Process is becoming 'mean'...\n");
900         if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
901                 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
902         sys.isnice = false;
903 }
904 #else
905 void Sys_InitProcessNice (void)
906 {
907 }
908 void Sys_MakeProcessNice (void)
909 {
910 }
911 void Sys_MakeProcessMean (void)
912 {
913 }
914 #endif
915
916 /** Halt and try not to catch fire.
917  * Writing to any file could corrupt it,
918  * any uneccessary code could crash while we crash.
919  * No malloc() (libgcc should be loaded already) or Con_Printf() allowed here.
920  */
921 static void Sys_HandleCrash(int sig)
922 {
923 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
924         // Before doing anything else grab the stack frame addresses
925         #include <execinfo.h>
926         void *stackframes[32];
927         int framecount = backtrace(stackframes, 32);
928 #endif
929
930         // Windows doesn't have strsignal()
931         const char *sigdesc;
932         switch (sig)
933         {
934 #ifndef WIN32 // or SIGBUS
935                 case SIGBUS:  sigdesc = "Bus error"; break;
936 #endif
937                 case SIGILL:  sigdesc = "Illegal instruction"; break;
938                 case SIGABRT: sigdesc = "Aborted"; break;
939                 case SIGFPE:  sigdesc = "Floating point exception"; break;
940                 case SIGSEGV: sigdesc = "Segmentation fault"; break;
941                 default:      sigdesc = "Yo dawg, we hit a bug while hitting a bug";
942         }
943
944         fprintf(stderr, "\n\n\e[1;37;41m    Engine Crash: %s (%d)    \e[m\n", sigdesc, sig);
945 #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
946         // the first two addresses will be in this function and in signal() in libc
947         backtrace_symbols_fd(stackframes + 2, framecount - 2, fileno(stderr));
948 #endif
949         fprintf(stderr, "\e[1m%s\e[m\n", engineversion);
950
951         // DP8 TODO: send a disconnect message indicating we crashed, see CL_DisconnectEx()
952
953         // don't want a dead window left blocking the OS UI or the crash dialog
954         VID_Shutdown();
955         S_StopAllSounds();
956
957         Sys_SDL_Dialog("Engine Crash", sigdesc);
958
959         exit (sig);
960 }
961
962 static void Sys_HandleSignal(int sig)
963 {
964 #ifdef WIN32
965         // Windows users will likely never see this so no point replicating strsignal()
966         Con_Printf("\nReceived signal %d, exiting...\n", sig);
967 #else
968         Con_Printf("\nReceived %s signal (%d), exiting...\n", strsignal(sig), sig);
969 #endif
970         host.state = host_shutdown;
971 }
972
973 /// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything
974 static void Sys_InitSignals(void)
975 {
976 // Windows docs say its signal() only accepts these ones
977         signal(SIGABRT, Sys_HandleCrash);
978         signal(SIGFPE,  Sys_HandleCrash);
979         signal(SIGILL,  Sys_HandleCrash);
980         signal(SIGINT,  Sys_HandleSignal);
981         signal(SIGSEGV, Sys_HandleCrash);
982         signal(SIGTERM, Sys_HandleSignal);
983 #ifndef WIN32
984         signal(SIGHUP,  Sys_HandleSignal);
985         signal(SIGQUIT, Sys_HandleSignal);
986         signal(SIGBUS,  Sys_HandleCrash);
987         signal(SIGPIPE, Sys_HandleSignal);
988 #endif
989 }
990
991 int main (int argc, char **argv)
992 {
993         sys.argc = argc;
994         sys.argv = (const char **)argv;
995
996         // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
997         // COMMANDLINEOPTION: -noterminal disables console output on stdout
998         if(Sys_CheckParm("-noterminal") || Sys_CheckParm("-nostdout"))
999                 sys_stdout.string = "0";
1000         // COMMANDLINEOPTION: -stderr moves console output to stderr
1001         else if(Sys_CheckParm("-stderr"))
1002                 sys_stdout.string = "2";
1003         // too early for Cvar_SetQuick
1004         sys_stdout.value = sys_stdout.integer = atoi(sys_stdout.string);
1005         Sys_UpdateOutFD_c(&sys_stdout);
1006 #ifndef WIN32
1007         fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, 0) | O_NONBLOCK);
1008         // stdout/stderr will be set to blocking in Sys_Print() if so configured, or during a fatal error.
1009         fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) | O_NONBLOCK);
1010         fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) | O_NONBLOCK);
1011 #endif
1012
1013         sys.selffd = -1;
1014         Sys_ProvideSelfFD(); // may call Con_Printf() so must be after sys.outfd is set
1015
1016 #ifdef __ANDROID__
1017         Sys_AllowProfiling(true);
1018 #endif
1019
1020         Sys_InitSignals();
1021
1022         Host_Main();
1023
1024         Sys_Quit(0);
1025
1026         return 0;
1027 }