]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sys_shared.c
Fix getting exe name on FreeBSD, which hasn't used /proc for quite a while now
[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 #include "quakedef.h"
8 #include "taskqueue.h"
9 #include "thread.h"
10
11 #define SUPPORTDLL
12
13 #ifdef WIN32
14 # include <windows.h>
15 # include <mmsystem.h> // timeGetTime
16 # include <time.h> // localtime
17 #ifdef _MSC_VER
18 #pragma comment(lib, "winmm.lib")
19 #endif
20 #else
21 # ifdef __FreeBSD__
22 #  include <sys/sysctl.h>
23 # endif
24 # include <unistd.h>
25 # include <fcntl.h>
26 # include <sys/time.h>
27 # include <time.h>
28 # ifdef SUPPORTDLL
29 #  include <dlfcn.h>
30 # endif
31 #endif
32
33 static char sys_timestring[128];
34 char *Sys_TimeString(const char *timeformat)
35 {
36         time_t mytime = time(NULL);
37 #if _MSC_VER >= 1400
38         struct tm mytm;
39         localtime_s(&mytm, &mytime);
40         strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
41 #else
42         strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
43 #endif
44         return sys_timestring;
45 }
46
47
48 extern qboolean host_shuttingdown;
49 void Sys_Quit (int returnvalue)
50 {
51         // Unlock mutexes because the quit command may jump directly here, causing a deadlock
52         if (cmd_client.text_lock)
53                 Cbuf_Unlock(&cmd_client);
54         if (cmd_server.text_lock)
55                 Cbuf_Unlock(&cmd_server);
56         SV_UnlockThreadMutex();
57         TaskQueue_Frame(true);
58
59         if (COM_CheckParm("-profilegameonly"))
60                 Sys_AllowProfiling(false);
61         host_shuttingdown = true;
62         Host_Shutdown();
63         exit(returnvalue);
64 }
65
66 #ifdef __cplusplus
67 extern "C"
68 #endif
69 void Sys_AllowProfiling(qboolean enable)
70 {
71 #ifdef __ANDROID__
72 #ifdef USE_PROFILER
73         extern void monstartup(const char *libname);
74         extern void moncleanup(void);
75         if (enable)
76                 monstartup("libmain.so");
77         else
78                 moncleanup();
79 #endif
80 #elif defined(__linux__) || defined(__FreeBSD__)
81         extern int moncontrol(int);
82         moncontrol(enable);
83 #endif
84 }
85
86
87 /*
88 ===============================================================================
89
90 DLL MANAGEMENT
91
92 ===============================================================================
93 */
94
95 static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next)
96 {
97         const dllfunction_t *func;
98         if(dllhandle)
99         {
100                 for (func = fcts; func && func->name != NULL; func++)
101                         if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
102                         {
103                                 if(complain)
104                                 {
105                                         Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
106                                         if(has_next)
107                                                 Con_DPrintf("\nContinuing with");
108                                 }
109                                 goto notfound;
110                         }
111                 return true;
112
113         notfound:
114                 for (func = fcts; func && func->name != NULL; func++)
115                         *func->funcvariable = NULL;
116         }
117         return false;
118 }
119
120 qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
121 {
122 #ifdef SUPPORTDLL
123         const dllfunction_t *func;
124         dllhandle_t dllhandle = 0;
125         unsigned int i;
126
127         if (handle == NULL)
128                 return false;
129
130 #ifndef WIN32
131 #ifdef PREFER_PRELOAD
132         dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
133         if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false))
134         {
135                 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
136                 *handle = dllhandle;
137                 return true;
138         }
139         else
140                 Sys_UnloadLibrary(&dllhandle);
141 notfound:
142 #endif
143 #endif
144
145         // Initializations
146         for (func = fcts; func && func->name != NULL; func++)
147                 *func->funcvariable = NULL;
148
149         // Try every possible name
150         Con_DPrintf ("Trying to load library...");
151         for (i = 0; dllnames[i] != NULL; i++)
152         {
153                 Con_DPrintf (" \"%s\"", dllnames[i]);
154 #ifdef WIN32
155 # ifndef DONT_USE_SETDLLDIRECTORY
156 #  ifdef _WIN64
157                 SetDllDirectory("bin64");
158 #  else
159                 SetDllDirectory("bin32");
160 #  endif
161 # endif
162                 dllhandle = LoadLibrary (dllnames[i]);
163                 // no need to unset this - we want ALL dlls to be loaded from there, anyway
164 #else
165                 dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL);
166 #endif
167                 if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(com_argv[0], '/'))))
168                         break;
169                 else
170                         Sys_UnloadLibrary (&dllhandle);
171         }
172
173         // see if the names can be loaded relative to the executable path
174         // (this is for Mac OSX which does not check next to the executable)
175         if (!dllhandle && strrchr(com_argv[0], '/'))
176         {
177                 char path[MAX_OSPATH];
178                 strlcpy(path, com_argv[0], sizeof(path));
179                 strrchr(path, '/')[1] = 0;
180                 for (i = 0; dllnames[i] != NULL; i++)
181                 {
182                         char temp[MAX_OSPATH];
183                         strlcpy(temp, path, sizeof(temp));
184                         strlcat(temp, dllnames[i], sizeof(temp));
185                         Con_DPrintf (" \"%s\"", temp);
186 #ifdef WIN32
187                         dllhandle = LoadLibrary (temp);
188 #else
189                         dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL);
190 #endif
191                         if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL))
192                                 break;
193                         else
194                                 Sys_UnloadLibrary (&dllhandle);
195                 }
196         }
197
198         // No DLL found
199         if (! dllhandle)
200         {
201                 Con_DPrintf(" - failed.\n");
202                 return false;
203         }
204
205         Con_DPrintf(" - loaded.\n");
206
207         *handle = dllhandle;
208         return true;
209 #else
210         return false;
211 #endif
212 }
213
214 void Sys_UnloadLibrary (dllhandle_t* handle)
215 {
216 #ifdef SUPPORTDLL
217         if (handle == NULL || *handle == NULL)
218                 return;
219
220 #ifdef WIN32
221         FreeLibrary (*handle);
222 #else
223         dlclose (*handle);
224 #endif
225
226         *handle = NULL;
227 #endif
228 }
229
230 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
231 {
232 #ifdef SUPPORTDLL
233 #ifdef WIN32
234         return (void *)GetProcAddress (handle, name);
235 #else
236         return (void *)dlsym (handle, name);
237 #endif
238 #else
239         return NULL;
240 #endif
241 }
242
243 #ifdef WIN32
244 # define HAVE_TIMEGETTIME 1
245 # define HAVE_QUERYPERFORMANCECOUNTER 1
246 # define HAVE_Sleep 1
247 #endif
248
249 #ifndef WIN32
250 #if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
251 # define HAVE_CLOCKGETTIME 1
252 #endif
253 // FIXME improve this check, manpage hints to DST_NONE
254 # define HAVE_GETTIMEOFDAY 1
255 #endif
256
257 #ifndef WIN32
258 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
259 // (so much for POSIX...)
260 # ifdef FD_SET
261 #  define HAVE_SELECT 1
262 # endif
263 #endif
264
265 #ifndef WIN32
266 // FIXME improve this check
267 # define HAVE_USLEEP 1
268 #endif
269
270 // this one is referenced elsewhere
271 cvar_t sys_usenoclockbutbenchmark = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "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."};
272
273 // these are not
274 static cvar_t sys_debugsleep = {CVAR_CLIENT | CVAR_SERVER, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
275 static cvar_t sys_usesdlgetticks = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"};
276 static cvar_t sys_usesdldelay = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"};
277 #if HAVE_QUERYPERFORMANCECOUNTER
278 static cvar_t sys_usequeryperformancecounter = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "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)"};
279 #endif
280 #if HAVE_CLOCKGETTIME
281 static cvar_t sys_useclockgettime = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "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)"};
282 #endif
283
284 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
285
286 void Sys_Init_Commands (void)
287 {
288         Cvar_RegisterVariable(&sys_debugsleep);
289         Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
290 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
291         if(sys_supportsdlgetticks)
292         {
293                 Cvar_RegisterVariable(&sys_usesdlgetticks);
294                 Cvar_RegisterVariable(&sys_usesdldelay);
295         }
296 #endif
297 #if HAVE_QUERYPERFORMANCECOUNTER
298         Cvar_RegisterVariable(&sys_usequeryperformancecounter);
299 #endif
300 #if HAVE_CLOCKGETTIME
301         Cvar_RegisterVariable(&sys_useclockgettime);
302 #endif
303 }
304
305 double Sys_DirtyTime(void)
306 {
307         // first all the OPTIONAL timers
308
309         // benchmark timer (fake clock)
310         if(sys_usenoclockbutbenchmark.integer)
311         {
312                 double old_benchmark_time = benchmark_time;
313                 benchmark_time += 1;
314                 if(benchmark_time == old_benchmark_time)
315                         Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
316                 return benchmark_time * 0.000001;
317         }
318 #if HAVE_QUERYPERFORMANCECOUNTER
319         if (sys_usequeryperformancecounter.integer)
320         {
321                 // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
322                 // QueryPerformanceCounter
323                 // platform:
324                 // Windows 95/98/ME/NT/2000/XP
325                 // features:
326                 // very accurate (CPU cycles)
327                 // known issues:
328                 // does not necessarily match realtime too well (tends to get faster and faster in win98)
329                 // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
330                 double timescale;
331                 LARGE_INTEGER PerformanceFreq;
332                 LARGE_INTEGER PerformanceCount;
333
334                 if (QueryPerformanceFrequency (&PerformanceFreq))
335                 {
336                         QueryPerformanceCounter (&PerformanceCount);
337         
338                         #ifdef __BORLANDC__
339                         timescale = 1.0 / ((double) PerformanceFreq.u.LowPart + (double) PerformanceFreq.u.HighPart * 65536.0 * 65536.0);
340                         return ((double) PerformanceCount.u.LowPart + (double) PerformanceCount.u.HighPart * 65536.0 * 65536.0) * timescale;
341                         #else
342                         timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
343                         return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
344                         #endif
345                 }
346                 else
347                 {
348                         Con_Printf("No hardware timer available\n");
349                         // fall back to other clock sources
350                         Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
351                 }
352         }
353 #endif
354
355 #if HAVE_CLOCKGETTIME
356         if (sys_useclockgettime.integer)
357         {
358                 struct timespec ts;
359 #  ifdef CLOCK_MONOTONIC
360                 // linux
361                 clock_gettime(CLOCK_MONOTONIC, &ts);
362 #  else
363                 // sunos
364                 clock_gettime(CLOCK_HIGHRES, &ts);
365 #  endif
366                 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
367         }
368 #endif
369
370         // now all the FALLBACK timers
371         if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
372                 return (double) Sys_SDL_GetTicks() / 1000.0;
373 #if HAVE_GETTIMEOFDAY
374         {
375                 struct timeval tp;
376                 gettimeofday(&tp, NULL);
377                 return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
378         }
379 #elif HAVE_TIMEGETTIME
380         {
381                 static int firsttimegettime = true;
382                 // timeGetTime
383                 // platform:
384                 // Windows 95/98/ME/NT/2000/XP
385                 // features:
386                 // reasonable accuracy (millisecond)
387                 // issues:
388                 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
389
390                 // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
391                 if (firsttimegettime)
392                 {
393                         timeBeginPeriod(1);
394                         firsttimegettime = false;
395                 }
396
397                 return (double) timeGetTime() / 1000.0;
398         }
399 #else
400         // fallback for using the SDL timer if no other timer is available
401         // this calls Sys_Error() if not linking against SDL
402         return (double) Sys_SDL_GetTicks() / 1000.0;
403 #endif
404 }
405
406 void Sys_Sleep(int microseconds)
407 {
408         double t = 0;
409         if(sys_usenoclockbutbenchmark.integer)
410         {
411                 if(microseconds)
412                 {
413                         double old_benchmark_time = benchmark_time;
414                         benchmark_time += microseconds;
415                         if(benchmark_time == old_benchmark_time)
416                                 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
417                 }
418                 return;
419         }
420         if(sys_debugsleep.integer)
421         {
422                 t = Sys_DirtyTime();
423         }
424         if(sys_supportsdlgetticks && sys_usesdldelay.integer)
425         {
426                 Sys_SDL_Delay(microseconds / 1000);
427         }
428 #if HAVE_SELECT
429         else
430         {
431                 struct timeval tv;
432                 tv.tv_sec = microseconds / 1000000;
433                 tv.tv_usec = microseconds % 1000000;
434                 select(0, NULL, NULL, NULL, &tv);
435         }
436 #elif HAVE_USLEEP
437         else
438         {
439                 usleep(microseconds);
440         }
441 #elif HAVE_Sleep
442         else
443         {
444                 Sleep(microseconds / 1000);
445         }
446 #else
447         else
448         {
449                 Sys_SDL_Delay(microseconds / 1000);
450         }
451 #endif
452         if(sys_debugsleep.integer)
453         {
454                 t = Sys_DirtyTime() - t;
455                 Sys_PrintfToTerminal("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000));
456         }
457 }
458
459 void Sys_PrintfToTerminal(const char *fmt, ...)
460 {
461         va_list argptr;
462         char msg[MAX_INPUTLINE];
463
464         va_start(argptr,fmt);
465         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
466         va_end(argptr);
467
468         Sys_PrintToTerminal(msg);
469 }
470
471 #ifndef WIN32
472 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
473 {
474         const char *p = PATH;
475         const char *q;
476         if(p && name)
477         {
478                 while((q = strchr(p, ':')))
479                 {
480                         dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
481                         if(FS_SysFileExists(buf))
482                                 return buf;
483                         p = q + 1;
484                 }
485                 if(!q) // none found - try the last item
486                 {
487                         dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
488                         if(FS_SysFileExists(buf))
489                                 return buf;
490                 }
491         }
492         return name;
493 }
494 #endif
495
496 static const char *Sys_FindExecutableName(void)
497 {
498 #if defined(WIN32)
499         return com_argv[0];
500 #else
501         static char exenamebuf[MAX_OSPATH+1];
502         ssize_t n = -1;
503 #if defined(__FreeBSD__)
504         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
505         size_t exenamebuflen = sizeof(exenamebuf)-1;
506         if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
507         {
508                 n = exenamebuflen;
509         }
510 #elif defined(__linux__)
511         n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
512 #endif
513         if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
514         {
515                 exenamebuf[n] = 0;
516                 return exenamebuf;
517         }
518         if(strchr(com_argv[0], '/'))
519                 return com_argv[0]; // possibly a relative path
520         else
521                 return Sys_FindInPATH(com_argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
522 #endif
523 }
524
525 void Sys_ProvideSelfFD(void)
526 {
527         if(com_selffd != -1)
528                 return;
529         com_selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
530 }
531
532 // for x86 cpus only...  (x64 has SSE2_PRESENT)
533 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
534 // code from SDL, shortened as we can expect CPUID to work
535 static int CPUID_Features(void)
536 {
537         int features = 0;
538 # if defined(__GNUC__) && defined(__i386__)
539         __asm__ (
540 "        movl    %%ebx,%%edi\n"
541 "        xorl    %%eax,%%eax                                           \n"
542 "        incl    %%eax                                                 \n"
543 "        cpuid                       # Get family/model/stepping/features\n"
544 "        movl    %%edx,%0                                              \n"
545 "        movl    %%edi,%%ebx\n"
546         : "=m" (features)
547         :
548         : "%eax", "%ecx", "%edx", "%edi"
549         );
550 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
551         __asm {
552         xor     eax, eax
553         inc     eax
554         cpuid                       ; Get family/model/stepping/features
555         mov     features, edx
556         }
557 # else
558 #  error SSE_POSSIBLE set but no CPUID implementation
559 # endif
560         return features;
561 }
562 #endif
563
564 #ifdef SSE_POSSIBLE
565 qboolean Sys_HaveSSE(void)
566 {
567         // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
568         if(COM_CheckParm("-nosse"))
569                 return false;
570 #ifdef SSE_PRESENT
571         return true;
572 #else
573         // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
574         if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2"))
575                 return true;
576         if(CPUID_Features() & (1 << 25))
577                 return true;
578         return false;
579 #endif
580 }
581
582 qboolean Sys_HaveSSE2(void)
583 {
584         // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
585         if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2"))
586                 return false;
587 #ifdef SSE2_PRESENT
588         return true;
589 #else
590         // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
591         if(COM_CheckParm("-forcesse2"))
592                 return true;
593         if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
594                 return true;
595         return false;
596 #endif
597 }
598 #endif
599
600 /// called to set process priority for dedicated servers
601 #if defined(__linux__)
602 #include <sys/resource.h>
603 #include <errno.h>
604 static int nicelevel;
605 static qboolean nicepossible;
606 static qboolean isnice;
607 void Sys_InitProcessNice (void)
608 {
609         struct rlimit lim;
610         nicepossible = false;
611         if(COM_CheckParm("-nonice"))
612                 return;
613         errno = 0;
614         nicelevel = getpriority(PRIO_PROCESS, 0);
615         if(errno)
616         {
617                 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
618                 return;
619         }
620         if(getrlimit(RLIMIT_NICE, &lim))
621         {
622                 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
623                 return;
624         }
625         if(lim.rlim_cur != RLIM_INFINITY && nicelevel < (int) (20 - lim.rlim_cur))
626         {
627                 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
628                 return;
629         }
630         nicepossible = true;
631         isnice = false;
632 }
633 void Sys_MakeProcessNice (void)
634 {
635         if(!nicepossible)
636                 return;
637         if(isnice)
638                 return;
639         Con_DPrintf("Process is becoming 'nice'...\n");
640         if(setpriority(PRIO_PROCESS, 0, 19))
641                 Con_Errorf("Failed to raise nice level to %d\n", 19);
642         isnice = true;
643 }
644 void Sys_MakeProcessMean (void)
645 {
646         if(!nicepossible)
647                 return;
648         if(!isnice)
649                 return;
650         Con_DPrintf("Process is becoming 'mean'...\n");
651         if(setpriority(PRIO_PROCESS, 0, nicelevel))
652                 Con_Errorf("Failed to lower nice level to %d\n", nicelevel);
653         isnice = false;
654 }
655 #else
656 void Sys_InitProcessNice (void)
657 {
658 }
659 void Sys_MakeProcessNice (void)
660 {
661 }
662 void Sys_MakeProcessMean (void)
663 {
664 }
665 #endif