porting.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. /*
  17. Random portability stuff
  18. See comments in porting.h
  19. */
  20. #include "porting.h"
  21. #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
  22. #include <sys/types.h>
  23. #include <sys/sysctl.h>
  24. extern char **environ;
  25. #elif defined(_WIN32)
  26. #include <windows.h>
  27. #include <wincrypt.h>
  28. #include <algorithm>
  29. #include <shlwapi.h>
  30. #include <shellapi.h>
  31. #include <mmsystem.h>
  32. #endif
  33. #if !defined(_WIN32)
  34. #include <unistd.h>
  35. #include <sys/utsname.h>
  36. #if !defined(__ANDROID__)
  37. #include <spawn.h>
  38. #endif
  39. #endif
  40. #if defined(__hpux)
  41. #define _PSTAT64
  42. #include <sys/pstat.h>
  43. #endif
  44. #if defined(__ANDROID__)
  45. #include "porting_android.h"
  46. #endif
  47. #if defined(__APPLE__)
  48. // For _NSGetEnviron()
  49. // Related: https://gitlab.haskell.org/ghc/ghc/issues/2458
  50. #include <crt_externs.h>
  51. #endif
  52. #if defined(__HAIKU__)
  53. #include <FindDirectory.h>
  54. #endif
  55. #include "config.h"
  56. #include "debug.h"
  57. #include "filesys.h"
  58. #include "log.h"
  59. #include "util/string.h"
  60. #include <list>
  61. #include <cstdarg>
  62. #include <cstdio>
  63. #if !defined(SERVER) && defined(_WIN32)
  64. // On Windows export some driver-specific variables to encourage Minetest to be
  65. // executed on the discrete GPU in case of systems with two. Portability is fun.
  66. extern "C" {
  67. __declspec(dllexport) DWORD NvOptimusEnablement = 1;
  68. __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
  69. }
  70. #endif
  71. namespace porting
  72. {
  73. /*
  74. Signal handler (grabs Ctrl-C on POSIX systems)
  75. */
  76. bool g_killed = false;
  77. bool *signal_handler_killstatus()
  78. {
  79. return &g_killed;
  80. }
  81. #if !defined(_WIN32) // POSIX
  82. #include <signal.h>
  83. void signal_handler(int sig)
  84. {
  85. if (!g_killed) {
  86. if (sig == SIGINT) {
  87. dstream << "INFO: signal_handler(): "
  88. << "Ctrl-C pressed, shutting down." << std::endl;
  89. } else if (sig == SIGTERM) {
  90. dstream << "INFO: signal_handler(): "
  91. << "got SIGTERM, shutting down." << std::endl;
  92. }
  93. // Comment out for less clutter when testing scripts
  94. /*dstream << "INFO: sigint_handler(): "
  95. << "Printing debug stacks" << std::endl;
  96. debug_stacks_print();*/
  97. g_killed = true;
  98. } else {
  99. (void)signal(sig, SIG_DFL);
  100. }
  101. }
  102. void signal_handler_init(void)
  103. {
  104. (void)signal(SIGINT, signal_handler);
  105. (void)signal(SIGTERM, signal_handler);
  106. }
  107. #else // _WIN32
  108. #include <signal.h>
  109. BOOL WINAPI event_handler(DWORD sig)
  110. {
  111. switch (sig) {
  112. case CTRL_C_EVENT:
  113. case CTRL_CLOSE_EVENT:
  114. case CTRL_LOGOFF_EVENT:
  115. case CTRL_SHUTDOWN_EVENT:
  116. if (!g_killed) {
  117. dstream << "INFO: event_handler(): "
  118. << "Ctrl+C, Close Event, Logoff Event or Shutdown Event,"
  119. " shutting down." << std::endl;
  120. g_killed = true;
  121. } else {
  122. (void)signal(SIGINT, SIG_DFL);
  123. }
  124. break;
  125. case CTRL_BREAK_EVENT:
  126. break;
  127. }
  128. return TRUE;
  129. }
  130. void signal_handler_init(void)
  131. {
  132. SetConsoleCtrlHandler((PHANDLER_ROUTINE)event_handler, TRUE);
  133. }
  134. #endif
  135. /*
  136. Path mangler
  137. */
  138. // Default to RUN_IN_PLACE style relative paths
  139. std::string path_share = "..";
  140. std::string path_user = "..";
  141. std::string path_locale = path_share + DIR_DELIM + "locale";
  142. std::string path_cache = path_user + DIR_DELIM + "cache";
  143. std::string getDataPath(const char *subpath)
  144. {
  145. return path_share + DIR_DELIM + subpath;
  146. }
  147. void pathRemoveFile(char *path, char delim)
  148. {
  149. // Remove filename and path delimiter
  150. int i;
  151. for(i = strlen(path)-1; i>=0; i--)
  152. {
  153. if(path[i] == delim)
  154. break;
  155. }
  156. path[i] = 0;
  157. }
  158. bool detectMSVCBuildDir(const std::string &path)
  159. {
  160. const char *ends[] = {
  161. "bin\\Release",
  162. "bin\\MinSizeRel",
  163. "bin\\RelWithDebInfo",
  164. "bin\\Debug",
  165. "bin\\Build",
  166. NULL
  167. };
  168. return (!removeStringEnd(path, ends).empty());
  169. }
  170. std::string get_sysinfo()
  171. {
  172. #ifdef _WIN32
  173. std::ostringstream oss;
  174. LPSTR filePath = new char[MAX_PATH];
  175. UINT blockSize;
  176. VS_FIXEDFILEINFO *fixedFileInfo;
  177. GetSystemDirectoryA(filePath, MAX_PATH);
  178. PathAppendA(filePath, "kernel32.dll");
  179. DWORD dwVersionSize = GetFileVersionInfoSizeA(filePath, NULL);
  180. LPBYTE lpVersionInfo = new BYTE[dwVersionSize];
  181. GetFileVersionInfoA(filePath, 0, dwVersionSize, lpVersionInfo);
  182. VerQueryValueA(lpVersionInfo, "\\", (LPVOID *)&fixedFileInfo, &blockSize);
  183. oss << "Windows/"
  184. << HIWORD(fixedFileInfo->dwProductVersionMS) << '.' // Major
  185. << LOWORD(fixedFileInfo->dwProductVersionMS) << '.' // Minor
  186. << HIWORD(fixedFileInfo->dwProductVersionLS) << ' '; // Build
  187. #ifdef _WIN64
  188. oss << "x86_64";
  189. #else
  190. BOOL is64 = FALSE;
  191. if (IsWow64Process(GetCurrentProcess(), &is64) && is64)
  192. oss << "x86_64"; // 32-bit app on 64-bit OS
  193. else
  194. oss << "x86";
  195. #endif
  196. delete[] lpVersionInfo;
  197. delete[] filePath;
  198. return oss.str();
  199. #else
  200. struct utsname osinfo;
  201. uname(&osinfo);
  202. return std::string(osinfo.sysname) + "/"
  203. + osinfo.release + " " + osinfo.machine;
  204. #endif
  205. }
  206. bool getCurrentWorkingDir(char *buf, size_t len)
  207. {
  208. #ifdef _WIN32
  209. DWORD ret = GetCurrentDirectory(len, buf);
  210. return (ret != 0) && (ret <= len);
  211. #else
  212. return getcwd(buf, len);
  213. #endif
  214. }
  215. bool getExecPathFromProcfs(char *buf, size_t buflen)
  216. {
  217. #ifndef _WIN32
  218. buflen--;
  219. ssize_t len;
  220. if ((len = readlink("/proc/self/exe", buf, buflen)) == -1 &&
  221. (len = readlink("/proc/curproc/file", buf, buflen)) == -1 &&
  222. (len = readlink("/proc/curproc/exe", buf, buflen)) == -1)
  223. return false;
  224. buf[len] = '\0';
  225. return true;
  226. #else
  227. return false;
  228. #endif
  229. }
  230. //// Windows
  231. #if defined(_WIN32)
  232. bool getCurrentExecPath(char *buf, size_t len)
  233. {
  234. DWORD written = GetModuleFileNameA(NULL, buf, len);
  235. if (written == 0 || written == len)
  236. return false;
  237. return true;
  238. }
  239. //// Linux
  240. #elif defined(__linux__)
  241. bool getCurrentExecPath(char *buf, size_t len)
  242. {
  243. if (!getExecPathFromProcfs(buf, len))
  244. return false;
  245. return true;
  246. }
  247. //// Mac OS X, Darwin
  248. #elif defined(__APPLE__)
  249. bool getCurrentExecPath(char *buf, size_t len)
  250. {
  251. uint32_t lenb = (uint32_t)len;
  252. if (_NSGetExecutablePath(buf, &lenb) == -1)
  253. return false;
  254. return true;
  255. }
  256. //// FreeBSD, NetBSD, DragonFlyBSD
  257. #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
  258. bool getCurrentExecPath(char *buf, size_t len)
  259. {
  260. // Try getting path from procfs first, since valgrind
  261. // doesn't work with the latter
  262. if (getExecPathFromProcfs(buf, len))
  263. return true;
  264. int mib[4];
  265. mib[0] = CTL_KERN;
  266. mib[1] = KERN_PROC;
  267. mib[2] = KERN_PROC_PATHNAME;
  268. mib[3] = -1;
  269. if (sysctl(mib, 4, buf, &len, NULL, 0) == -1)
  270. return false;
  271. return true;
  272. }
  273. #elif defined(__HAIKU__)
  274. bool getCurrentExecPath(char *buf, size_t len)
  275. {
  276. return find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, buf, len) == B_OK;
  277. }
  278. //// Solaris
  279. #elif defined(__sun) || defined(sun)
  280. bool getCurrentExecPath(char *buf, size_t len)
  281. {
  282. const char *exec = getexecname();
  283. if (exec == NULL)
  284. return false;
  285. if (strlcpy(buf, exec, len) >= len)
  286. return false;
  287. return true;
  288. }
  289. // HP-UX
  290. #elif defined(__hpux)
  291. bool getCurrentExecPath(char *buf, size_t len)
  292. {
  293. struct pst_status psts;
  294. if (pstat_getproc(&psts, sizeof(psts), 0, getpid()) == -1)
  295. return false;
  296. if (pstat_getpathname(buf, len, &psts.pst_fid_text) == -1)
  297. return false;
  298. return true;
  299. }
  300. #else
  301. bool getCurrentExecPath(char *buf, size_t len)
  302. {
  303. return false;
  304. }
  305. #endif
  306. //// Non-Windows
  307. #if !defined(_WIN32)
  308. const char *getHomeOrFail()
  309. {
  310. const char *home = getenv("HOME");
  311. // In rare cases the HOME environment variable may be unset
  312. FATAL_ERROR_IF(!home,
  313. "Required environment variable HOME is not set");
  314. return home;
  315. }
  316. #endif
  317. //// Windows
  318. #if defined(_WIN32)
  319. bool setSystemPaths()
  320. {
  321. char buf[BUFSIZ];
  322. // Find path of executable and set path_share relative to it
  323. FATAL_ERROR_IF(!getCurrentExecPath(buf, sizeof(buf)),
  324. "Failed to get current executable path");
  325. pathRemoveFile(buf, '\\');
  326. std::string exepath(buf);
  327. // Use ".\bin\.."
  328. path_share = exepath + "\\..";
  329. if (detectMSVCBuildDir(exepath)) {
  330. // The msvc build dir schould normaly not be present if properly installed,
  331. // but its usefull for debugging.
  332. path_share += DIR_DELIM "..";
  333. }
  334. // Use "C:\Users\<user>\AppData\Roaming\<PROJECT_NAME_C>"
  335. DWORD len = GetEnvironmentVariable("APPDATA", buf, sizeof(buf));
  336. FATAL_ERROR_IF(len == 0 || len > sizeof(buf), "Failed to get APPDATA");
  337. path_user = std::string(buf) + DIR_DELIM + PROJECT_NAME_C;
  338. return true;
  339. }
  340. //// Linux
  341. #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
  342. bool setSystemPaths()
  343. {
  344. char buf[BUFSIZ];
  345. if (!getCurrentExecPath(buf, sizeof(buf))) {
  346. #ifdef __ANDROID__
  347. errorstream << "Unable to read bindir "<< std::endl;
  348. #else
  349. FATAL_ERROR("Unable to read bindir");
  350. #endif
  351. return false;
  352. }
  353. pathRemoveFile(buf, '/');
  354. std::string bindir(buf);
  355. // Find share directory from these.
  356. // It is identified by containing the subdirectory "builtin".
  357. std::list<std::string> trylist;
  358. std::string static_sharedir = STATIC_SHAREDIR;
  359. if (!static_sharedir.empty() && static_sharedir != ".")
  360. trylist.push_back(static_sharedir);
  361. trylist.push_back(bindir + DIR_DELIM ".." DIR_DELIM "share"
  362. DIR_DELIM + PROJECT_NAME);
  363. trylist.push_back(bindir + DIR_DELIM "..");
  364. #ifdef __ANDROID__
  365. trylist.push_back(path_user);
  366. #endif
  367. for (std::list<std::string>::const_iterator
  368. i = trylist.begin(); i != trylist.end(); ++i) {
  369. const std::string &trypath = *i;
  370. if (!fs::PathExists(trypath) ||
  371. !fs::PathExists(trypath + DIR_DELIM + "builtin")) {
  372. warningstream << "system-wide share not found at \""
  373. << trypath << "\""<< std::endl;
  374. continue;
  375. }
  376. // Warn if was not the first alternative
  377. if (i != trylist.begin()) {
  378. warningstream << "system-wide share found at \""
  379. << trypath << "\"" << std::endl;
  380. }
  381. path_share = trypath;
  382. break;
  383. }
  384. #ifndef __ANDROID__
  385. path_user = std::string(getHomeOrFail()) + DIR_DELIM "."
  386. + PROJECT_NAME;
  387. #endif
  388. return true;
  389. }
  390. //// Mac OS X
  391. #elif defined(__APPLE__)
  392. bool setSystemPaths()
  393. {
  394. CFBundleRef main_bundle = CFBundleGetMainBundle();
  395. CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(main_bundle);
  396. char path[PATH_MAX];
  397. if (CFURLGetFileSystemRepresentation(resources_url,
  398. TRUE, (UInt8 *)path, PATH_MAX)) {
  399. path_share = std::string(path);
  400. } else {
  401. warningstream << "Could not determine bundle resource path" << std::endl;
  402. }
  403. CFRelease(resources_url);
  404. path_user = std::string(getHomeOrFail())
  405. + "/Library/Application Support/"
  406. + PROJECT_NAME;
  407. return true;
  408. }
  409. #else
  410. bool setSystemPaths()
  411. {
  412. path_share = STATIC_SHAREDIR;
  413. path_user = std::string(getHomeOrFail()) + DIR_DELIM "."
  414. + lowercase(PROJECT_NAME);
  415. return true;
  416. }
  417. #endif
  418. void migrateCachePath()
  419. {
  420. const std::string local_cache_path = path_user + DIR_DELIM + "cache";
  421. // Delete tmp folder if it exists (it only ever contained
  422. // a temporary ogg file, which is no longer used).
  423. if (fs::PathExists(local_cache_path + DIR_DELIM + "tmp"))
  424. fs::RecursiveDelete(local_cache_path + DIR_DELIM + "tmp");
  425. // Bail if migration impossible
  426. if (path_cache == local_cache_path || !fs::PathExists(local_cache_path)
  427. || fs::PathExists(path_cache)) {
  428. return;
  429. }
  430. if (!fs::Rename(local_cache_path, path_cache)) {
  431. errorstream << "Failed to migrate local cache path "
  432. "to system path!" << std::endl;
  433. }
  434. }
  435. void initializePaths()
  436. {
  437. #if RUN_IN_PLACE
  438. char buf[BUFSIZ];
  439. infostream << "Using relative paths (RUN_IN_PLACE)" << std::endl;
  440. bool success =
  441. getCurrentExecPath(buf, sizeof(buf)) ||
  442. getExecPathFromProcfs(buf, sizeof(buf));
  443. if (success) {
  444. pathRemoveFile(buf, DIR_DELIM_CHAR);
  445. std::string execpath(buf);
  446. path_share = execpath + DIR_DELIM "..";
  447. path_user = execpath + DIR_DELIM "..";
  448. if (detectMSVCBuildDir(execpath)) {
  449. path_share += DIR_DELIM "..";
  450. path_user += DIR_DELIM "..";
  451. }
  452. } else {
  453. errorstream << "Failed to get paths by executable location, "
  454. "trying cwd" << std::endl;
  455. if (!getCurrentWorkingDir(buf, sizeof(buf)))
  456. FATAL_ERROR("Ran out of methods to get paths");
  457. size_t cwdlen = strlen(buf);
  458. if (cwdlen >= 1 && buf[cwdlen - 1] == DIR_DELIM_CHAR) {
  459. cwdlen--;
  460. buf[cwdlen] = '\0';
  461. }
  462. if (cwdlen >= 4 && !strcmp(buf + cwdlen - 4, DIR_DELIM "bin"))
  463. pathRemoveFile(buf, DIR_DELIM_CHAR);
  464. std::string execpath(buf);
  465. path_share = execpath;
  466. path_user = execpath;
  467. }
  468. path_cache = path_user + DIR_DELIM + "cache";
  469. #else
  470. infostream << "Using system-wide paths (NOT RUN_IN_PLACE)" << std::endl;
  471. if (!setSystemPaths())
  472. errorstream << "Failed to get one or more system-wide path" << std::endl;
  473. # ifdef _WIN32
  474. path_cache = path_user + DIR_DELIM + "cache";
  475. # else
  476. // Initialize path_cache
  477. // First try $XDG_CACHE_HOME/PROJECT_NAME
  478. const char *cache_dir = getenv("XDG_CACHE_HOME");
  479. const char *home_dir = getenv("HOME");
  480. if (cache_dir && cache_dir[0] != '\0') {
  481. path_cache = std::string(cache_dir) + DIR_DELIM + PROJECT_NAME;
  482. } else if (home_dir) {
  483. // Then try $HOME/.cache/PROJECT_NAME
  484. path_cache = std::string(home_dir) + DIR_DELIM + ".cache"
  485. + DIR_DELIM + PROJECT_NAME;
  486. } else {
  487. // If neither works, use $PATH_USER/cache
  488. path_cache = path_user + DIR_DELIM + "cache";
  489. }
  490. // Migrate cache folder to new location if possible
  491. migrateCachePath();
  492. # endif // _WIN32
  493. #endif // RUN_IN_PLACE
  494. infostream << "Detected share path: " << path_share << std::endl;
  495. infostream << "Detected user path: " << path_user << std::endl;
  496. infostream << "Detected cache path: " << path_cache << std::endl;
  497. #if USE_GETTEXT
  498. bool found_localedir = false;
  499. # ifdef STATIC_LOCALEDIR
  500. /* STATIC_LOCALEDIR may be a generalized path such as /usr/share/locale that
  501. * doesn't necessarily contain our locale files, so check data path first. */
  502. path_locale = getDataPath("locale");
  503. if (fs::PathExists(path_locale)) {
  504. found_localedir = true;
  505. infostream << "Using in-place locale directory " << path_locale
  506. << " even though a static one was provided." << std::endl;
  507. } else if (STATIC_LOCALEDIR[0] && fs::PathExists(STATIC_LOCALEDIR)) {
  508. found_localedir = true;
  509. path_locale = STATIC_LOCALEDIR;
  510. infostream << "Using static locale directory " << STATIC_LOCALEDIR
  511. << std::endl;
  512. }
  513. # else
  514. path_locale = getDataPath("locale");
  515. if (fs::PathExists(path_locale)) {
  516. found_localedir = true;
  517. }
  518. # endif
  519. if (!found_localedir) {
  520. warningstream << "Couldn't find a locale directory!" << std::endl;
  521. }
  522. #endif // USE_GETTEXT
  523. }
  524. ////
  525. //// OS-specific Secure Random
  526. ////
  527. #ifdef WIN32
  528. bool secure_rand_fill_buf(void *buf, size_t len)
  529. {
  530. HCRYPTPROV wctx;
  531. if (!CryptAcquireContext(&wctx, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
  532. return false;
  533. CryptGenRandom(wctx, len, (BYTE *)buf);
  534. CryptReleaseContext(wctx, 0);
  535. return true;
  536. }
  537. #else
  538. bool secure_rand_fill_buf(void *buf, size_t len)
  539. {
  540. // N.B. This function checks *only* for /dev/urandom, because on most
  541. // common OSes it is non-blocking, whereas /dev/random is blocking, and it
  542. // is exceptionally uncommon for there to be a situation where /dev/random
  543. // exists but /dev/urandom does not. This guesswork is necessary since
  544. // random devices are not covered by any POSIX standard...
  545. FILE *fp = fopen("/dev/urandom", "rb");
  546. if (!fp)
  547. return false;
  548. bool success = fread(buf, len, 1, fp) == 1;
  549. fclose(fp);
  550. return success;
  551. }
  552. #endif
  553. void attachOrCreateConsole()
  554. {
  555. #ifdef _WIN32
  556. static bool consoleAllocated = false;
  557. const bool redirected = (_fileno(stdout) == -2 || _fileno(stdout) == -1); // If output is redirected to e.g a file
  558. if (!consoleAllocated && redirected && (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())) {
  559. freopen("CONOUT$", "w", stdout);
  560. freopen("CONOUT$", "w", stderr);
  561. consoleAllocated = true;
  562. }
  563. #endif
  564. }
  565. int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)
  566. {
  567. // https://msdn.microsoft.com/en-us/library/bt7tawza.aspx
  568. // Many of the MSVC / Windows printf-style functions do not support positional
  569. // arguments (eg. "%1$s"). We just forward the call to vsnprintf for sane
  570. // platforms, but defer to _vsprintf_p on MSVC / Windows.
  571. // https://github.com/FFmpeg/FFmpeg/blob/5ae9fa13f5ac640bec113120d540f70971aa635d/compat/msvcrt/snprintf.c#L46
  572. // _vsprintf_p has to be shimmed with _vscprintf_p on -1 (for an example see
  573. // above FFmpeg link).
  574. va_list args;
  575. va_start(args, fmt);
  576. #ifndef _MSC_VER
  577. int c = vsnprintf(buf, buf_size, fmt, args);
  578. #else // _MSC_VER
  579. int c = _vsprintf_p(buf, buf_size, fmt, args);
  580. if (c == -1)
  581. c = _vscprintf_p(fmt, args);
  582. #endif // _MSC_VER
  583. va_end(args);
  584. return c;
  585. }
  586. static bool open_uri(const std::string &uri)
  587. {
  588. if (uri.find_first_of("\r\n") != std::string::npos) {
  589. errorstream << "Unable to open URI as it is invalid, contains new line: " << uri << std::endl;
  590. return false;
  591. }
  592. #if defined(_WIN32)
  593. return (intptr_t)ShellExecuteA(NULL, NULL, uri.c_str(), NULL, NULL, SW_SHOWNORMAL) > 32;
  594. #elif defined(__ANDROID__)
  595. openURIAndroid(uri);
  596. return true;
  597. #elif defined(__APPLE__)
  598. const char *argv[] = {"open", uri.c_str(), NULL};
  599. return posix_spawnp(NULL, "open", NULL, NULL, (char**)argv,
  600. (*_NSGetEnviron())) == 0;
  601. #else
  602. const char *argv[] = {"xdg-open", uri.c_str(), NULL};
  603. return posix_spawnp(NULL, "xdg-open", NULL, NULL, (char**)argv, environ) == 0;
  604. #endif
  605. }
  606. bool open_url(const std::string &url)
  607. {
  608. if (url.substr(0, 7) != "http://" && url.substr(0, 8) != "https://") {
  609. errorstream << "Unable to open browser as URL is missing schema: " << url << std::endl;
  610. return false;
  611. }
  612. return open_uri(url);
  613. }
  614. bool open_directory(const std::string &path)
  615. {
  616. if (!fs::IsDir(path)) {
  617. errorstream << "Unable to open directory as it does not exist: " << path << std::endl;
  618. return false;
  619. }
  620. return open_uri(path);
  621. }
  622. // Load performance counter frequency only once at startup
  623. #ifdef _WIN32
  624. inline double get_perf_freq()
  625. {
  626. // Also use this opportunity to enable high-res timers
  627. timeBeginPeriod(1);
  628. LARGE_INTEGER freq;
  629. QueryPerformanceFrequency(&freq);
  630. return freq.QuadPart;
  631. }
  632. double perf_freq = get_perf_freq();
  633. #endif
  634. } //namespace porting