llappviewerlinux.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. /**
  2. * @file llappviewerlinux.cpp
  3. * @brief The LLAppViewerLinux class definitions
  4. *
  5. * $LicenseInfo:firstyear=2007&license=viewergpl$
  6. *
  7. * Copyright (c) 2007-2009, Linden Research, Inc.
  8. * Copyright (c) 2013-2022, Henri Beauchamp (better generateSerialNumber(),
  9. * new ELFIO crashlogger with 64 bits support, glib-dbus implementation,
  10. * Lua via DBus, Vulkan detection, etc).
  11. *
  12. * Second Life Viewer Source Code
  13. * The source code in this file ("Source Code") is provided by Linden Lab
  14. * to you under the terms of the GNU General Public License, version 2.0
  15. * ("GPL"), unless you have obtained a separate licensing agreement
  16. * ("Other License"), formally executed by you and Linden Lab. Terms of
  17. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  18. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  19. *
  20. * There are special exceptions to the terms and conditions of the GPL as
  21. * it is applied to this Source Code. View the full text of the exception
  22. * in the file doc/FLOSS-exception.txt in this software distribution, or
  23. * online at
  24. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  25. *
  26. * By copying, modifying or distributing this software, you acknowledge
  27. * that you have read and understood your obligations described above,
  28. * and agree to abide by those obligations.
  29. *
  30. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  31. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  32. * COMPLETENESS OR PERFORMANCE.
  33. * $/LicenseInfo$
  34. */
  35. #include "llviewerprecompiledheaders.h"
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <sys/types.h>
  39. #include <unistd.h>
  40. #include <execinfo.h> // Backtrace with glibc
  41. #include <exception>
  42. #include <fstream>
  43. #include <sstream>
  44. #include "json.hpp" // For Vulkan API version extraction
  45. #include "glib.h"
  46. #include "glib-object.h"
  47. #include "gio/gio.h"
  48. #ifdef __GNUC__
  49. # include <cxxabi.h> // For symbol demangling
  50. #endif
  51. #include "elfio/elfio.hpp" // For better backtraces
  52. #include "llappviewerlinux.h"
  53. #include "llapp.h"
  54. #include "llcommandlineparser.h"
  55. #include "lldir.h"
  56. #include "lldiriterator.h"
  57. #include "llfindlocale.h"
  58. #include "llmd5.h"
  59. #include "llwindowsdl.h"
  60. #include "llgridmanager.h"
  61. #include "hbviewerautomation.h"
  62. #include "llviewercontrol.h"
  63. // Note: LL_CALL_SLURL_DISPATCHER_IN_CALLBACK is defined in llappviewer.h
  64. #if LL_CALL_SLURL_DISPATCHER_IN_CALLBACK
  65. # include "llurldispatcher.h"
  66. #else
  67. std::string LLAppViewerLinux::sReceivedSLURL = "";
  68. #endif
  69. extern "C" { // Do not mangle the name for the main() function !
  70. int main(int argc, char** argv);
  71. }
  72. using lljson = nlohmann::json;
  73. // Used for glib events pumping
  74. // 5 checks a second *should* be more than enough
  75. constexpr F32 GLIB_EVENTS_THROTTLE = 0.2f;
  76. // Pumping shall not eat more than that...
  77. constexpr F32 GLIB_PUMP_TIMEOUT = 0.01f;
  78. constexpr F32 GLIB_PUMP_RETRY_AFTER = 0.01f;
  79. static LLTimer sPumpTimer;
  80. static int sArgC = 0;
  81. static char** sArgV = NULL;
  82. // Used for demangling function names during stack trace logging. HB
  83. static size_t sDemangleBufferSize = 1024;
  84. static char* sDemangleBuffer = NULL;
  85. static void (*sOldTerminateHandler)() = NULL;
  86. static void exceptionTerminateHandler()
  87. {
  88. // Reinstall default terminate() handler in case we re-terminate.
  89. if (sOldTerminateHandler)
  90. {
  91. std::set_terminate(sOldTerminateHandler);
  92. }
  93. // Treat this like a regular viewer crash, with nice stacktrace etc.
  94. LLAppViewer::handleSyncViewerCrash();
  95. LLAppViewer::handleViewerCrash();
  96. // We have probably been killed-off before now, but...
  97. sOldTerminateHandler(); // Call old terminate() handler
  98. }
  99. int main(int argc, char** argv)
  100. {
  101. sArgC = argc;
  102. sArgV = argv;
  103. // Reserve some space for stack trace function names demangling in case of a
  104. // crash; also, reserve enough such space that we do not risk seeing
  105. // abi::__cxa_demangle() reallocating it. HB
  106. sDemangleBuffer = (char*)malloc(sDemangleBufferSize);
  107. LLAppViewer* viewer_app_ptr = new LLAppViewerLinux();
  108. // Install unexpected exception handler
  109. sOldTerminateHandler = std::set_terminate(exceptionTerminateHandler);
  110. // Install crash handlers
  111. LLApp::setErrorHandler(LLAppViewer::handleViewerCrash);
  112. LLApp::setSyncErrorHandler(LLAppViewer::handleSyncViewerCrash);
  113. LLApp::InitState state = viewer_app_ptr->init();
  114. if (state != LLApp::INIT_OK)
  115. {
  116. if (state != LLApp::INIT_OK_EXIT)
  117. {
  118. llwarns << "Application init failed." << llendl;
  119. return LLAppViewer::EXIT_INIT_FAILED;
  120. }
  121. return LLAppViewer::EXIT_OK; // No error, just exiting immediately.
  122. }
  123. llinfos << "Compiled against glib v" << GLIB_MAJOR_VERSION << "."
  124. << GLIB_MINOR_VERSION << "." << GLIB_MICRO_VERSION
  125. << " - Running against glib v" << glib_major_version << "."
  126. << glib_minor_version << "." << glib_micro_version << llendl;
  127. if (glib_minor_version < GLIB_MINOR_VERSION ||
  128. (glib_minor_version == GLIB_MINOR_VERSION &&
  129. glib_micro_version < GLIB_MICRO_VERSION))
  130. {
  131. llwarns << "System glib version too old, expect problems !" << llendl;
  132. }
  133. // Initialize our pump timer
  134. sPumpTimer.reset();
  135. sPumpTimer.setTimerExpirySec(GLIB_PUMP_TIMEOUT);
  136. // Run the application main loop
  137. if (!LLApp::isQuitting())
  138. {
  139. viewer_app_ptr->mainLoop();
  140. }
  141. // We do not want to do cleanup here if the error handler got called: the
  142. // assumption is that the error handler is responsible for doing app
  143. // cleanup if there was a problem.
  144. if (!LLApp::isError())
  145. {
  146. viewer_app_ptr->cleanup();
  147. }
  148. delete viewer_app_ptr;
  149. viewer_app_ptr = NULL;
  150. // Free the demangle buffer. HB
  151. free(sDemangleBuffer);
  152. return gExitCode;
  153. }
  154. #define MAX_STACK_TRACE_DEPTH 40
  155. // This uses glibc's basic built-in stack-trace functions for a not very
  156. // amazing backtrace.
  157. static LL_INLINE bool do_basic_glibc_backtrace()
  158. {
  159. void* stackarray[MAX_STACK_TRACE_DEPTH];
  160. size_t size;
  161. char** strings;
  162. size_t i;
  163. bool success = false;
  164. size = backtrace(stackarray, MAX_STACK_TRACE_DEPTH);
  165. strings = backtrace_symbols(stackarray, size);
  166. std::string strace_filename = gDirUtil.getFullPath(LL_PATH_LOGS,
  167. "stack_trace.log");
  168. llinfos << "Opening stack trace file " << strace_filename << llendl;
  169. LLFILE* strace_fp = LLFile::open(strace_filename, "w");
  170. if (!strace_fp)
  171. {
  172. llinfos << "Opening stack trace file " << strace_filename
  173. << " failed. Using stderr." << llendl;
  174. strace_fp = stderr;
  175. }
  176. if (size)
  177. {
  178. for (i = 0; i < size; ++i)
  179. {
  180. // The format of the strace_fp is very specific, to allow (kludgy)
  181. // machine-parsing
  182. fprintf(strace_fp, "%-3lu ", (unsigned long)i);
  183. fprintf(strace_fp, "%-32s\t", "unknown");
  184. fprintf(strace_fp, "%p ", stackarray[i]);
  185. fprintf(strace_fp, "%s\n", strings[i]);
  186. }
  187. success = true;
  188. }
  189. if (strace_fp != stderr)
  190. {
  191. LLFile::close(strace_fp);
  192. }
  193. free(strings);
  194. return success;
  195. }
  196. // This uses glibc's basic built-in stack-trace functions together with ELFIO's
  197. // ability to parse the .symtab ELF section for better symbol extraction
  198. // without exporting symbols (which would cause subtle, fatal bugs).
  199. static LL_INLINE bool do_elfio_glibc_backtrace()
  200. {
  201. std::string app_filename = gDirUtil.getExecutablePathAndName();
  202. std::string strace_filename = gDirUtil.getFullPath(LL_PATH_LOGS,
  203. "stack_trace.log");
  204. llinfos << "Opening stack trace file " << strace_filename << llendl;
  205. LLFILE* strace_fp = LLFile::open(strace_filename, "w");
  206. if (!strace_fp)
  207. {
  208. llinfos << "Opening stack trace file " << strace_filename
  209. << " failed. Using stderr." << llendl;
  210. strace_fp = stderr;
  211. }
  212. // Get backtrace address list and basic symbol info
  213. void* stackarray[MAX_STACK_TRACE_DEPTH];
  214. size_t btsize = backtrace(stackarray, MAX_STACK_TRACE_DEPTH);
  215. char** strings = backtrace_symbols(stackarray, btsize);
  216. // Create an ELF reader for our app binary
  217. ELFIO::elfio reader;
  218. ELFIO::Elf_Half sections_count = 0;
  219. if (reader.load(app_filename.c_str()))
  220. {
  221. sections_count = reader.sections.size();
  222. }
  223. if (!sections_count)
  224. {
  225. // Failed to open our binary and read its symbol table somehow
  226. llinfos << "Could not initialize ELF symbol reading - doing basic backtrace."
  227. << llendl;
  228. if (strace_fp != stderr)
  229. {
  230. LLFile::close(strace_fp);
  231. }
  232. // Note that we may be leaking some of the above ELFIO objects now,
  233. // but it is expected that we will be dead soon and we want to tread
  234. // delicately until we get *some* kind of useful backtrace.
  235. return do_basic_glibc_backtrace();
  236. }
  237. // Iterate over trace and symtab, looking for plausible symbols
  238. std::string name;
  239. ELFIO::Elf64_Addr value = 0;
  240. ELFIO::Elf_Xword ssize = 0;
  241. ELFIO::Elf_Half section = 0;
  242. unsigned char bind = 0;
  243. unsigned char type = 0;
  244. unsigned char other = 0;
  245. int demangle_result = 0;
  246. for (size_t btpos = 0; btpos < btsize; ++btpos)
  247. {
  248. uintptr_t addr = uintptr_t(stackarray[btpos]);
  249. #if 1 // See 'Update' below.
  250. // backtrace() got it all wrong with clang-compiled binaries, and
  251. // returns an absolute address instead of an offset to the start of the
  252. // program binary (which we need to compare with ELFIO's symbols
  253. // addresses). Thankfully, we can get the latter from the strings array
  254. // returned by backtrace_symbols() instead... HB
  255. // Update (2023-10-19): also seen happening for some stack traces on
  256. // systems running a different glibc than the one the gcc-compiled
  257. // viewer was built against.. So, let's always use this workaround. HB
  258. name = strings[btpos];
  259. size_t idx = name.find("(+0x");
  260. if (idx != std::string::npos)
  261. {
  262. name.erase(0, idx + 2);
  263. idx = name.find(")");
  264. if (idx != std::string::npos)
  265. {
  266. name.erase(idx);
  267. unsigned long a = 0;
  268. if (!name.empty() && sscanf(name.c_str(), "%lx", &a) == 1)
  269. {
  270. addr = (uintptr_t)a;
  271. }
  272. }
  273. }
  274. #endif
  275. // The format of the strace_fp is very specific, to allow (kludgy)
  276. // machine-parsing
  277. fprintf(strace_fp, "%-3ld ", (long)btpos);
  278. bool found = false;
  279. for (int i = 0; i < sections_count && !found; ++i)
  280. {
  281. ELFIO::section* psec = reader.sections[i];
  282. if (!psec || psec->get_type() != ELFIO::SHT_SYMTAB)
  283. {
  284. continue;
  285. }
  286. const ELFIO::symbol_section_accessor symbols(reader, psec);
  287. for (unsigned int j = 0, count = symbols.get_symbols_num();
  288. j < count; ++j)
  289. {
  290. symbols.get_symbol(j, name, value, ssize, bind, type, section,
  291. other);
  292. // Check if trace address is within this symbol range
  293. if (addr >= uintptr_t(value) &&
  294. addr < uintptr_t(value + ssize))
  295. {
  296. // Symbol is inside viewer code
  297. fprintf(strace_fp, "com.secondlife.indra.viewer\t%p ",
  298. (void*)addr);
  299. char* demangled = abi::__cxa_demangle(name.c_str(),
  300. sDemangleBuffer,
  301. &sDemangleBufferSize,
  302. &demangle_result);
  303. if (demangled)
  304. {
  305. fprintf(strace_fp, "%s", demangled);
  306. }
  307. else // Failed demangle; print it raw
  308. {
  309. fprintf(strace_fp, "%s", name.c_str());
  310. }
  311. // Print offset from symbol start
  312. fprintf(strace_fp, " + %lu\n", addr - value);
  313. found = true;
  314. break;
  315. }
  316. }
  317. }
  318. if (!found)
  319. {
  320. // Fallback: did not find a suitable symbol in the binary; it is
  321. // probably a symbol in a DSO; use glibc's idea of what it should
  322. // be.
  323. fprintf(strace_fp, "unknown\t%p ", (void*)addr);
  324. fprintf(strace_fp, "%s\n", strings[btpos]);
  325. }
  326. }
  327. if (strace_fp != stderr)
  328. {
  329. LLFile::close(strace_fp);
  330. }
  331. free(strings);
  332. llinfos << "Finished generating stack trace." << llendl;
  333. return true;
  334. }
  335. void LLAppViewerLinux::handleSyncCrashTrace()
  336. {
  337. // Free our reserved memory space before dumping the stack trace (it should
  338. // already be freed at this point, but it does not hurt calling this
  339. // function twice).
  340. LLMemory::cleanupClass();
  341. // This backtrace writes into stack_trace.log
  342. do_elfio_glibc_backtrace();
  343. }
  344. bool LLAppViewerLinux::beingDebugged()
  345. {
  346. static enum { unknown, no, yes } debugged = unknown;
  347. if (debugged == unknown)
  348. {
  349. debugged = no;
  350. // Note that the debugger is the parent process of the viewer. HB
  351. LLFILE* fp = LLFile::open(llformat("/proc/%d/cmdline", getppid()),
  352. "r");
  353. if (fp)
  354. {
  355. char buf[256];
  356. if (fgets(buf, sizeof(buf) - 1, fp))
  357. {
  358. std::string cmdline(buf);
  359. if (cmdline.find("gdb") != std::string::npos ||
  360. cmdline.find("edb") != std::string::npos ||
  361. cmdline.find("lldb") != std::string::npos)
  362. {
  363. debugged = yes;
  364. }
  365. }
  366. LLFile::close(fp);
  367. }
  368. }
  369. bool debug = LLError::Log::sIsBeingDebugged = debugged == yes;
  370. return debug;
  371. }
  372. void LLAppViewerLinux::initLogging()
  373. {
  374. // Remove the last stack trace, if any
  375. std::string old_stack_file = gDirUtil.getFullPath(LL_PATH_LOGS,
  376. "stack_trace.log");
  377. LLFile::remove(old_stack_file);
  378. LLAppViewer::initLogging();
  379. }
  380. bool LLAppViewerLinux::initParseCommandLine(LLCommandLineParser& clp)
  381. {
  382. if (!clp.parseCommandLine(sArgC, sArgV))
  383. {
  384. return false;
  385. }
  386. // Find the system language.
  387. FL_Locale* locale = NULL;
  388. FL_Success success = FL_FindLocale(&locale, FL_MESSAGES);
  389. if (success >= FL_CONFIDENT && locale->lang)
  390. {
  391. llinfos << "Language " << ll_safe_string(locale->lang) << llendl;
  392. llinfos << "Location " << ll_safe_string(locale->country) << llendl;
  393. llinfos << "Variant " << ll_safe_string(locale->variant) << llendl;
  394. LLControlVariable* c = gSavedSettings.getControl("SystemLanguage");
  395. if (c)
  396. {
  397. c->setValue(std::string(locale->lang), false);
  398. }
  399. }
  400. FL_FreeLocale(&locale);
  401. return true;
  402. }
  403. // Takes the longest scsi-*, ata-* or nvme-* entry in /dev/disk/by-id and
  404. // hashes it into a MD5 sum (such entries correspond to physical disks and
  405. // contain the drive serial number).
  406. // This is a much better algorithm than LL's, since the latter takes only the
  407. // longest id in /dev/disk/by-uuid/ which depends on the *currently* mounted
  408. // disks, the resulting derived serial number not being unique for a given
  409. // Linux system (e.g. if the user mounts an USB stick, the serial number may
  410. // change). HB
  411. std::string LLAppViewerLinux::generateSerialNumber()
  412. {
  413. std::string best;
  414. const std::string iddir = "/dev/disk/by-id/";
  415. if (LLFile::isdir(iddir))
  416. {
  417. std::string first, link_name;
  418. bool best_is_nvme = false;
  419. LLDirIterator iter(iddir);
  420. while (iter.next(link_name))
  421. {
  422. LLStringUtil::toLower(link_name);
  423. if (first.empty())
  424. {
  425. // Remember the first available entry in case nothing better
  426. // can be found later...
  427. first = link_name;
  428. }
  429. if (link_name.rfind("-part") != std::string::npos)
  430. {
  431. // Skip partition IDs: we keep only the drives.
  432. continue;
  433. }
  434. bool is_nvme = link_name.find("nvme-", 0, 5) == 0;
  435. if (!is_nvme && link_name.find("ata-") != 0 &&
  436. link_name.find("scsi-") != 0)
  437. {
  438. // Skip anything not connected to an ATA, SCSI or NVME port
  439. // (since we do not want to take removable devices into
  440. // account).
  441. continue;
  442. }
  443. // If the best found Id so far corresponds to an NVME disk and this
  444. // new found Id pertains to an ATA or SCSI disk, then use it
  445. // instead. This is for compatibility reasons with former viewer
  446. // versions that did not use NVME disk serials; the latter are now
  447. // taken into account, but only when no ATA and no SCSI drive exist
  448. // in the system.
  449. if (best_is_nvme && !is_nvme)
  450. {
  451. best = link_name;
  452. best_is_nvme = false;
  453. continue;
  454. }
  455. // If nothing was found so far, unconditionally adopt this Id as
  456. // the best one.
  457. if (best.empty())
  458. {
  459. best = link_name;
  460. best_is_nvme = is_nvme;
  461. continue;
  462. }
  463. // If we already have a best Id corresponding to an ATA or SCSI
  464. // disk and this new found Id is an NVME disk, then keep our
  465. // current best Id.
  466. if (is_nvme && !best_is_nvme)
  467. {
  468. continue;
  469. }
  470. // If the best Id so far is longer than the new found one, keep the
  471. // former.
  472. if (link_name.length() < best.length())
  473. {
  474. continue;
  475. }
  476. // If the new found Id comes after the best one in alphabetical
  477. // order, then adopt it as the best one.
  478. if (link_name > best)
  479. {
  480. best = link_name;
  481. best_is_nvme = is_nvme;
  482. }
  483. }
  484. if (best.empty())
  485. {
  486. best = first;
  487. }
  488. LL_DEBUGS("AppInit") << "Using disk Id: " << best << LL_ENDL;
  489. }
  490. // Fallback to machine-id, which is "less unique" since it is a per-Linux
  491. // installation Id and the same PC could run several...
  492. const char* id_file = "/etc/machine-id";
  493. if (best.empty() && LLFile::isfile(id_file))
  494. {
  495. std::ifstream ifs(id_file);
  496. if (ifs.good())
  497. {
  498. std::stringstream buffer;
  499. buffer << ifs.rdbuf();
  500. best = buffer.str();
  501. llinfos << "Could not find any disk Id: using /etc/machine-id."
  502. << llendl;
  503. }
  504. }
  505. if (best.empty()) // This should never happen in any modern Linux system...
  506. {
  507. llwarns << "Could not find any machine Id: using a random Id."
  508. << llendl;
  509. // Totally random and regenerated at each viewer session...
  510. LLUUID id;
  511. id.generate();
  512. best = id.asString();
  513. }
  514. // We do not return the disk Id itself, but a hash of it
  515. LLMD5 md5(reinterpret_cast<const unsigned char*>(best.c_str()));
  516. char serial_md5[MD5HEX_STR_SIZE];
  517. md5.hex_digest(serial_md5);
  518. return std::string(serial_md5);
  519. }
  520. // This code was called from llappviewer.cpp via a virtual LLWindow method
  521. // (processMiscNativeEvents()) which was only actually implemented under Linux
  522. // via LLWindowSDL::processMiscNativeEvents(), so there was strictly no point
  523. // in using such a virtual method to cover all OSes !
  524. // Moving this code here also got the nice effect to remove the dependency on
  525. // glib for the llwindow library; *all* glib-related code is now held in this
  526. // file only ! HB
  527. //static
  528. void LLAppViewerLinux::pumpGlib()
  529. {
  530. if (sPumpTimer.hasExpired())
  531. {
  532. // Pump until we have nothing left to do or passed GLIB_PUMP_TIMEOUT
  533. // of a second pumping.
  534. sPumpTimer.reset();
  535. sPumpTimer.setTimerExpirySec(GLIB_PUMP_TIMEOUT);
  536. while (g_main_context_pending(NULL))
  537. {
  538. g_main_context_iteration(NULL, FALSE);
  539. if (sPumpTimer.hasExpired())
  540. {
  541. llwarns_sparse << "Reached GLIB_PUMP_TIMEOUT: something is spamming us !"
  542. << llendl;
  543. // Continue pumping in a subsequent (but close) frame...
  544. sPumpTimer.reset();
  545. sPumpTimer.setTimerExpirySec(GLIB_PUMP_RETRY_AFTER);
  546. return;
  547. }
  548. }
  549. // Throttle to 1/GLIB_EVENTS_THROTTLE per second the number of loops,
  550. // as long as we could process all pending events in this loop.
  551. sPumpTimer.reset();
  552. sPumpTimer.setTimerExpirySec(GLIB_EVENTS_THROTTLE);
  553. }
  554. }
  555. ///////////////////////////////////////////////////////////////////////////////
  556. // Vulkan detection used by llviewerstats.cpp
  557. ///////////////////////////////////////////////////////////////////////////////
  558. //virtual
  559. bool LLAppViewerLinux::probeVulkan(std::string& version)
  560. {
  561. static std::string vk_api_version;
  562. static S32 has_vulkan = -1; // -1 = not yet probed
  563. if (has_vulkan == -1)
  564. {
  565. has_vulkan = 0; // Default to no Vulkan support
  566. // Probe for Vulkan capability (Henri Beauchamp 05/2020)
  567. // Check for presense of a Vulkan ICD-loader manifest file for a
  568. // Vulkan-capable GPU. Gives a good approximation of Vulkan capability
  569. // within current user systems from this.
  570. std::string fname;
  571. if (getenv("VK_ICD_FILENAMES"))
  572. {
  573. fname.assign(getenv("VK_ICD_FILENAMES"));
  574. size_t pos = fname.find(';');
  575. if (pos > 1)
  576. {
  577. // Only check for the first file if several are listed.
  578. // *TODO: also check for any other listed files (unlikely) ?
  579. fname = fname.substr(0, pos);
  580. }
  581. if (LLFile::isfile(fname))
  582. {
  583. llinfos << "Found user-specified Vulkan ICD-loader manifest: "
  584. << fname << llendl;
  585. has_vulkan = 1;
  586. }
  587. }
  588. if (has_vulkan == 0 &&
  589. (gGLManager.mIsNVIDIA || gGLManager.mIsAMD || gGLManager.mIsIntel))
  590. {
  591. std::vector<std::string> paths;
  592. paths.emplace_back("/etc/vulkan/icd.d/");
  593. paths.emplace_back("/usr/share/vulkan/icd.d/");
  594. paths.emplace_back("/usr/local/etc/vulkan/icd.d/");
  595. paths.emplace_back("/usr/local/share/vulkan/icd.d/");
  596. if (getenv("HOME"))
  597. {
  598. fname.assign(getenv("HOME"));
  599. paths.emplace_back(fname + "/.local/share/vulkan/icd.d/");
  600. }
  601. std::string icd_file;
  602. if (gGLManager.mIsNVIDIA)
  603. {
  604. icd_file = "nvidia_icd.json";
  605. }
  606. else if (gGLManager.mIsAMD)
  607. {
  608. icd_file = "radeon_icd.x86_64.json";
  609. }
  610. else // Intel
  611. {
  612. icd_file = "intel_icd.x86_64.json";
  613. }
  614. for (S32 i = 0, count = paths.size(); i < count; ++i)
  615. {
  616. fname = paths[i] + icd_file;
  617. if (LLFile::isfile(fname))
  618. {
  619. llinfos << "Found matching Vulkan ICD-loader manifest: "
  620. << fname << llendl;
  621. has_vulkan = 1;
  622. break;
  623. }
  624. }
  625. }
  626. // Get the Vulkan API version (Henri Beauchamp 01/2022)
  627. if (has_vulkan == 1)
  628. {
  629. std::ifstream file;
  630. file.open(fname.c_str(), std::ios::in);
  631. if (file.is_open())
  632. {
  633. // 'false' = no throw, 'true' = ignore comments.
  634. lljson root = lljson::parse(file, NULL, false, true);
  635. if (root.is_discarded())
  636. {
  637. llwarns << "Cannot read Vulkan manifest file: " << fname
  638. << llendl;
  639. has_vulkan = 0;
  640. }
  641. else
  642. {
  643. auto icd_it = root.find("ICD");
  644. if (icd_it != root.end())
  645. {
  646. lljson icd = *icd_it;
  647. auto ver_it = icd.find("api_version");
  648. if (ver_it != icd.end())
  649. {
  650. vk_api_version = to_string(ver_it.value());
  651. llinfos << "Vulkan API version is: "
  652. << vk_api_version << llendl;
  653. }
  654. }
  655. else
  656. {
  657. llwarns << "Malformed Vulkan manifest file: " << fname
  658. << llendl;
  659. has_vulkan = 0;
  660. }
  661. }
  662. file.close();
  663. }
  664. else
  665. {
  666. llwarns << "Could not open Vulkan manifest file: " << fname
  667. << llendl;
  668. has_vulkan = 0;
  669. }
  670. }
  671. }
  672. version = vk_api_version;
  673. return has_vulkan == 1;
  674. }
  675. ///////////////////////////////////////////////////////////////////////////////
  676. // DBus support for SLURLs passing between viewer instances, and Lua via DBus
  677. ///////////////////////////////////////////////////////////////////////////////
  678. #define VIEWERAPI_SERVICE "com.secondlife.ViewerAppAPIService"
  679. #define VIEWERAPI_PATH "/com/secondlife/ViewerAppAPI"
  680. #define VIEWERAPI_INTERFACE "com.secondlife.ViewerAppAPI"
  681. #define VIEWERAPI_GOSURL_METHOD "GoSLURL"
  682. #define VIEWERAPI_LUA_METHOD "LuaExec"
  683. static GDBusNodeInfo* sIntrospectionData = NULL;
  684. static guint sServerBusId = 0;
  685. //virtual
  686. bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url)
  687. {
  688. bool success = false;
  689. GError* error = NULL;
  690. GDBusConnection* bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
  691. if (bus)
  692. {
  693. if (error)
  694. {
  695. g_error_free(error);
  696. error = NULL;
  697. }
  698. llinfos << "Calling out another instance to send SLURL: " << url
  699. << llendl;
  700. GDBusProxy* proxy =
  701. g_dbus_proxy_new_sync(bus, G_DBUS_PROXY_FLAGS_NONE, NULL,
  702. VIEWERAPI_SERVICE, VIEWERAPI_PATH,
  703. VIEWERAPI_INTERFACE, NULL, &error);
  704. if (proxy)
  705. {
  706. if (error)
  707. {
  708. g_error_free(error);
  709. error = NULL;
  710. }
  711. GVariant* var = g_dbus_proxy_call_sync(proxy,
  712. VIEWERAPI_GOSURL_METHOD,
  713. g_variant_new("(s)",
  714. url.c_str()),
  715. G_DBUS_CALL_FLAGS_NONE,
  716. -1, NULL, &error);
  717. #if 0 // "Recent" (post v0.92) dbus-glib versions got a server-side bug
  718. // causing a timeout while the message was successfully passed...
  719. // Since we must deal with viewers still using dbus-glib and acting
  720. // as the server, we cannot avoid this limitation.
  721. if (var)
  722. {
  723. llinfos << "Call-out to other instance succeeded." << llendl;
  724. gboolean* rtn;
  725. g_variant_get(var, "(b)", &rtn);
  726. if (rtn)
  727. {
  728. success = (bool)*rtn;
  729. g_free(rtn);
  730. llinfos << "Returned boolean: "
  731. << (success ? "TRUE" : "FALSE") << llendl;
  732. }
  733. }
  734. else
  735. {
  736. llinfos << "Call-out to other instance failed." << llendl;
  737. }
  738. #else // Just consider it to always be a success.
  739. success = true;
  740. #endif
  741. if (var)
  742. {
  743. g_variant_unref(var);
  744. }
  745. }
  746. else
  747. {
  748. llinfos << "Call-out to other instance failed." << llendl;
  749. }
  750. g_object_unref(G_OBJECT(proxy));
  751. }
  752. else
  753. {
  754. llwarns << "Could not connect to session bus." << llendl;
  755. }
  756. if (error)
  757. {
  758. llinfos << "Completion message: " << error->message << llendl;
  759. g_error_free(error);
  760. }
  761. return success;
  762. }
  763. static void handle_method_call(GDBusConnection* connection,
  764. const gchar* sender,
  765. const gchar* object_path,
  766. const gchar* interface_name,
  767. const gchar* method_name,
  768. GVariant* parameters,
  769. GDBusMethodInvocation* invocation,
  770. gpointer user_data)
  771. {
  772. if (g_strcmp0(method_name, VIEWERAPI_GOSURL_METHOD) == 0)
  773. {
  774. const gchar* slurl;
  775. g_variant_get(parameters, "(&s)", &slurl);
  776. llinfos << "Was asked to go to slurl: " << slurl << llendl;
  777. #if LL_CALL_SLURL_DISPATCHER_IN_CALLBACK
  778. std::string url = slurl;
  779. LLMediaCtrl* web = NULL;
  780. LLURLDispatcher::dispatch(url, "clicked", web, false);
  781. #else
  782. LLAppViewerLinux::setReceivedSLURL(slurl);
  783. #endif
  784. // Always return a success; if the running viewer instance does not
  785. // know how to dispatch the passed SLURL, the sending instance won't
  786. // know either, so it is pointless to let it try and auto-login in a
  787. // place bearing an invalid SLURL, especially since it would disconnect
  788. // the running instance (auto-login reusing the last logged in avatar
  789. // credentials).
  790. gboolean ret = TRUE;
  791. g_dbus_method_invocation_return_value(invocation,
  792. g_variant_new("(b)", &ret));
  793. }
  794. else if (g_strcmp0(method_name, VIEWERAPI_LUA_METHOD) == 0)
  795. {
  796. const gchar* cmdline;
  797. g_variant_get(parameters, "(&s)", &cmdline);
  798. if (gSavedSettings.getBool("LuaAcceptDbusCommands"))
  799. {
  800. llinfos << "Was asked to go execute Lua command line: " << cmdline
  801. << llendl;
  802. // Note: we pass fake object Id and name to apply the same
  803. // restrictions as for Lua commands sent to the viewer via a
  804. // scripted object.
  805. if (HBViewerAutomation::sLuaDBusFakeObjectId.isNull())
  806. {
  807. HBViewerAutomation::sLuaDBusFakeObjectId.generate();
  808. }
  809. std::string ret =
  810. HBViewerAutomation::eval(std::string(cmdline), true,
  811. HBViewerAutomation::sLuaDBusFakeObjectId,
  812. "Lua D-Bus");
  813. llinfos << "Result: " << ret << llendl;
  814. g_dbus_method_invocation_return_value(invocation,
  815. g_variant_new("(s)",
  816. ret.c_str()));
  817. }
  818. else
  819. {
  820. llwarns << "Rejected D-Bus Lua command: " << cmdline << llendl;
  821. g_dbus_method_invocation_return_value(invocation,
  822. g_variant_new("(s)",
  823. "forbidden"));
  824. }
  825. }
  826. else
  827. {
  828. llwarns_once << "Rejected unknown method: " << method_name << llendl;
  829. g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
  830. G_DBUS_ERROR_UNKNOWN_METHOD,
  831. "Unknown method name: %s",
  832. method_name);
  833. }
  834. }
  835. static GVariant* handle_get_property(GDBusConnection* connection,
  836. const gchar* sender,
  837. const gchar* object_path,
  838. const gchar* interface_name,
  839. const gchar* property_name,
  840. GError** error, gpointer user_data)
  841. {
  842. g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
  843. "Getting property %s is not supported", property_name);
  844. return NULL;
  845. }
  846. static gboolean handle_set_property(GDBusConnection* connection,
  847. const gchar* sender,
  848. const gchar* object_path,
  849. const gchar* interface_name,
  850. const gchar* property_name,
  851. GVariant* value,
  852. GError** error, gpointer user_data)
  853. {
  854. g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
  855. "Setting property %s is not supported", property_name);
  856. return false;
  857. }
  858. static const GDBusInterfaceVTable interface_vtable =
  859. {
  860. handle_method_call,
  861. handle_get_property,
  862. handle_set_property
  863. };
  864. static void on_bus_acquired(GDBusConnection* connection, const gchar* name,
  865. gpointer user_data)
  866. {
  867. llinfos << "Acquired the bus: " << name << llendl;
  868. GError* error = NULL;
  869. guint id =
  870. g_dbus_connection_register_object(connection, VIEWERAPI_PATH,
  871. sIntrospectionData->interfaces[0],
  872. &interface_vtable, NULL, NULL,
  873. &error);
  874. if (id <= 0)
  875. {
  876. llwarns << "Unable to register object: "
  877. << (error ? error->message : "unknown reason") << llendl;
  878. if (sServerBusId)
  879. {
  880. llinfos << "Unowning the bus." << llendl;
  881. g_bus_unown_name(sServerBusId);
  882. sServerBusId = 0;
  883. }
  884. }
  885. if (error)
  886. {
  887. g_error_free(error);
  888. }
  889. }
  890. // Connect to the default DBUS, register our service/API.
  891. //virtual
  892. bool LLAppViewerLinux::initAppMessagesHandler()
  893. {
  894. if (!sIntrospectionData)
  895. {
  896. const gchar introspection_xml[] =
  897. "<node name='" VIEWERAPI_PATH "'>"
  898. " <interface name='" VIEWERAPI_INTERFACE "'>"
  899. " <method name='" VIEWERAPI_GOSURL_METHOD "'>"
  900. " <arg type='s' name='slurl' direction='in'/>"
  901. " <arg type='b' name='success_ret' direction='out'/>"
  902. " </method>"
  903. " <method name='" VIEWERAPI_LUA_METHOD "'>"
  904. " <arg type='s' name='cmdline' direction='in'/>"
  905. " <arg type='s' name='result' direction='out'/>"
  906. " </method>"
  907. " </interface>"
  908. "</node>";
  909. sIntrospectionData = g_dbus_node_info_new_for_xml(introspection_xml,
  910. NULL);
  911. if (!sIntrospectionData)
  912. {
  913. llwarns << "Failed to create instrospection data. Aborted."
  914. << llendl;
  915. return false;
  916. }
  917. }
  918. if (sServerBusId <= 0)
  919. {
  920. sServerBusId = g_bus_own_name(G_BUS_TYPE_SESSION, VIEWERAPI_SERVICE,
  921. G_BUS_NAME_OWNER_FLAGS_NONE,
  922. on_bus_acquired,
  923. NULL, NULL, NULL, NULL);
  924. if (sServerBusId <= 0)
  925. {
  926. llinfos << "Failed to acquire the bus: " << VIEWERAPI_SERVICE
  927. << llendl;
  928. return false;
  929. }
  930. }
  931. return true;
  932. }