You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

960 lines
24KB

  1. #include <thread>
  2. #include <regex>
  3. #include <chrono>
  4. #include <ghc/filesystem.hpp>
  5. #include <dirent.h>
  6. #include <sys/stat.h>
  7. #include <cxxabi.h> // for abi::__cxa_demangle
  8. #if defined ARCH_LIN || defined ARCH_MAC
  9. #include <pthread.h>
  10. #include <time.h> // for clock_gettime
  11. #include <sched.h>
  12. #include <execinfo.h> // for backtrace and backtrace_symbols
  13. #include <unistd.h> // for execl
  14. #include <sys/utsname.h>
  15. #endif
  16. #if defined ARCH_MAC
  17. #include <mach/mach_init.h>
  18. #include <mach/thread_act.h>
  19. #include <mach/mach_time.h>
  20. #include <sys/sysctl.h>
  21. #endif
  22. #if defined ARCH_WIN
  23. #include <windows.h>
  24. #include <shellapi.h>
  25. #include <processthreadsapi.h>
  26. #include <dbghelp.h>
  27. #endif
  28. #include <archive.h>
  29. #include <archive_entry.h>
  30. #if defined ARCH_MAC
  31. #include <locale.h>
  32. #endif
  33. #include <system.hpp>
  34. #include <string.hpp>
  35. /*
  36. In C++17, this will be `std::filesystem`
  37. Important: When using `fs::path`, always convert strings to/from UTF-8 using
  38. fs::path p = fs::u8path(s);
  39. std::string s = p.generic_u8string();
  40. */
  41. namespace fs = ghc::filesystem;
  42. namespace rack {
  43. namespace system {
  44. std::string join(const std::string& path1, const std::string& path2) {
  45. return (fs::u8path(path1) / fs::u8path(path2)).generic_u8string();
  46. }
  47. static void appendEntries(std::vector<std::string>& entries, const fs::path& dir, int depth) {
  48. try {
  49. for (const auto& entry : fs::directory_iterator(dir)) {
  50. entries.push_back(entry.path().generic_u8string());
  51. // Recurse if depth > 0 (limited recursion) or depth < 0 (infinite recursion).
  52. if (depth != 0) {
  53. if (entry.is_directory()) {
  54. appendEntries(entries, entry.path(), depth - 1);
  55. }
  56. }
  57. }
  58. }
  59. catch (fs::filesystem_error& e) {}
  60. }
  61. std::vector<std::string> getEntries(const std::string& dirPath, int depth) {
  62. std::vector<std::string> entries;
  63. appendEntries(entries, fs::u8path(dirPath), depth);
  64. return entries;
  65. }
  66. bool exists(const std::string& path) {
  67. try {
  68. return fs::exists(fs::u8path(path));
  69. }
  70. catch (fs::filesystem_error& e) {
  71. return false;
  72. }
  73. }
  74. bool isFile(const std::string& path) {
  75. try {
  76. return fs::is_regular_file(fs::u8path(path));
  77. }
  78. catch (fs::filesystem_error& e) {
  79. return false;
  80. }
  81. }
  82. bool isDirectory(const std::string& path) {
  83. try {
  84. return fs::is_directory(fs::u8path(path));
  85. }
  86. catch (fs::filesystem_error& e) {
  87. return false;
  88. }
  89. }
  90. uint64_t getFileSize(const std::string& path) {
  91. try {
  92. return fs::file_size(fs::u8path(path));
  93. }
  94. catch (fs::filesystem_error& e) {
  95. return 0;
  96. }
  97. }
  98. bool rename(const std::string& srcPath, const std::string& destPath) {
  99. try {
  100. fs::rename(fs::u8path(srcPath), fs::u8path(destPath));
  101. return true;
  102. }
  103. catch (fs::filesystem_error& e) {
  104. return false;
  105. }
  106. }
  107. bool copy(const std::string& srcPath, const std::string& destPath) {
  108. try {
  109. fs::copy(fs::u8path(srcPath), fs::u8path(destPath), fs::copy_options::recursive | fs::copy_options::overwrite_existing);
  110. return true;
  111. }
  112. catch (fs::filesystem_error& e) {
  113. return false;
  114. }
  115. }
  116. bool createDirectory(const std::string& path) {
  117. try {
  118. return fs::create_directory(fs::u8path(path));
  119. }
  120. catch (fs::filesystem_error& e) {
  121. return false;
  122. }
  123. }
  124. bool createDirectories(const std::string& path) {
  125. try {
  126. return fs::create_directories(fs::u8path(path));
  127. }
  128. catch (fs::filesystem_error& e) {
  129. return false;
  130. }
  131. }
  132. bool createSymbolicLink(const std::string& target, const std::string& link) {
  133. try {
  134. fs::create_symlink(fs::u8path(target), fs::u8path(link));
  135. return true;
  136. }
  137. catch (fs::filesystem_error& e) {
  138. return false;
  139. }
  140. }
  141. bool remove(const std::string& path) {
  142. try {
  143. return fs::remove(fs::u8path(path));
  144. }
  145. catch (fs::filesystem_error& e) {
  146. return false;
  147. }
  148. }
  149. int removeRecursively(const std::string& pathStr) {
  150. fs::path path = fs::u8path(pathStr);
  151. try {
  152. // Make all entries writable before attempting to remove
  153. for (auto& entry : fs::recursive_directory_iterator(path)) {
  154. fs::permissions(entry.path(), fs::perms::owner_write, fs::perm_options::add);
  155. }
  156. return fs::remove_all(path);
  157. }
  158. catch (fs::filesystem_error& e) {
  159. return 0;
  160. }
  161. }
  162. std::string getWorkingDirectory() {
  163. try {
  164. return fs::current_path().generic_u8string();
  165. }
  166. catch (fs::filesystem_error& e) {
  167. return "";
  168. }
  169. }
  170. void setWorkingDirectory(const std::string& path) {
  171. try {
  172. fs::current_path(fs::u8path(path));
  173. }
  174. catch (fs::filesystem_error& e) {
  175. // Do nothing
  176. }
  177. }
  178. std::string getTempDirectory() {
  179. try {
  180. return fs::temp_directory_path().generic_u8string();
  181. }
  182. catch (fs::filesystem_error& e) {
  183. return "";
  184. }
  185. }
  186. std::string getAbsolute(const std::string& path) {
  187. try {
  188. return fs::absolute(fs::u8path(path)).generic_u8string();
  189. }
  190. catch (fs::filesystem_error& e) {
  191. return "";
  192. }
  193. }
  194. std::string getCanonical(const std::string& path) {
  195. try {
  196. return fs::canonical(fs::u8path(path)).generic_u8string();
  197. }
  198. catch (fs::filesystem_error& e) {
  199. return "";
  200. }
  201. }
  202. std::string getDirectory(const std::string& path) {
  203. try {
  204. return fs::u8path(path).parent_path().generic_u8string();
  205. }
  206. catch (fs::filesystem_error& e) {
  207. return "";
  208. }
  209. }
  210. std::string getFilename(const std::string& path) {
  211. try {
  212. return fs::u8path(path).filename().generic_u8string();
  213. }
  214. catch (fs::filesystem_error& e) {
  215. return "";
  216. }
  217. }
  218. std::string getStem(const std::string& path) {
  219. try {
  220. return fs::u8path(path).stem().generic_u8string();
  221. }
  222. catch (fs::filesystem_error& e) {
  223. return "";
  224. }
  225. }
  226. std::string getExtension(const std::string& path) {
  227. try {
  228. return fs::u8path(path).extension().generic_u8string();
  229. }
  230. catch (fs::filesystem_error& e) {
  231. return "";
  232. }
  233. }
  234. std::vector<uint8_t> readFile(const std::string& path) {
  235. std::vector<uint8_t> data;
  236. FILE* f = std::fopen(path.c_str(), "rb");
  237. if (!f)
  238. throw Exception("Cannot read file %s", path.c_str());
  239. DEFER({
  240. std::fclose(f);
  241. });
  242. // Get file size so we can make a single allocation
  243. std::fseek(f, 0, SEEK_END);
  244. size_t len = std::ftell(f);
  245. std::fseek(f, 0, SEEK_SET);
  246. data.resize(len);
  247. std::fread(data.data(), 1, len, f);
  248. return data;
  249. }
  250. uint8_t* readFile(const std::string& path, size_t* size) {
  251. FILE* f = std::fopen(path.c_str(), "rb");
  252. if (!f)
  253. throw Exception("Cannot read file %s", path.c_str());
  254. DEFER({
  255. std::fclose(f);
  256. });
  257. // Get file size so we can make a single allocation
  258. std::fseek(f, 0, SEEK_END);
  259. size_t len = std::ftell(f);
  260. std::fseek(f, 0, SEEK_SET);
  261. uint8_t* data = (uint8_t*) std::malloc(len);
  262. std::fread(data, 1, len, f);
  263. if (size)
  264. *size = len;
  265. return data;
  266. }
  267. void writeFile(const std::string& path, const std::vector<uint8_t>& data) {
  268. FILE* f = std::fopen(path.c_str(), "wb");
  269. if (!f)
  270. throw Exception("Cannot create file %s", path.c_str());
  271. DEFER({
  272. std::fclose(f);
  273. });
  274. std::fwrite(data.data(), 1, data.size(), f);
  275. }
  276. /** Returns `p` in relative path form, relative to `base`
  277. Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path.
  278. */
  279. static std::string getRelativePath(std::string path, std::string base) {
  280. try {
  281. path = fs::absolute(fs::u8path(path)).generic_u8string();
  282. base = fs::absolute(fs::u8path(base)).generic_u8string();
  283. }
  284. catch (fs::filesystem_error& e) {
  285. throw Exception("%s", e.what());
  286. }
  287. if (path.size() < base.size())
  288. throw Exception("getRelativePath() error: path is shorter than base");
  289. if (!std::equal(base.begin(), base.end(), path.begin()))
  290. throw Exception("getRelativePath() error: path does not begin with base");
  291. // If path == base, this correctly returns "."
  292. return "." + std::string(path.begin() + base.size(), path.end());
  293. }
  294. static la_ssize_t archiveWriteVectorCallback(struct archive* a, void* client_data, const void* buffer, size_t length) {
  295. assert(client_data);
  296. std::vector<uint8_t>& data = *((std::vector<uint8_t>*) client_data);
  297. uint8_t* buf = (uint8_t*) buffer;
  298. data.insert(data.end(), buf, buf + length);
  299. return length;
  300. }
  301. static void archiveDirectory(const std::string& archivePath, std::vector<uint8_t>* archiveData, const std::string& dirPath, int compressionLevel) {
  302. // Based on minitar.c create() in libarchive examples
  303. int r;
  304. // Open archive for writing
  305. struct archive* a = archive_write_new();
  306. DEFER({archive_write_free(a);});
  307. // For some reason libarchive adds 10k of padding to archive_write_open() (but not archive_write_open_filename()) unless this is set to 0.
  308. archive_write_set_bytes_per_block(a, 0);
  309. archive_write_set_format_pax_restricted(a);
  310. archive_write_add_filter_zstd(a);
  311. if (!(0 <= compressionLevel && compressionLevel <= 19))
  312. throw Exception("Invalid Zstandard compression level");
  313. r = archive_write_set_filter_option(a, NULL, "compression-level", std::to_string(compressionLevel).c_str());
  314. if (r < ARCHIVE_OK)
  315. throw Exception("Archiver could not set filter option: %s", archive_error_string(a));
  316. if (archiveData) {
  317. // Open vector
  318. archive_write_open(a, (void*) archiveData, NULL, archiveWriteVectorCallback, NULL);
  319. }
  320. else {
  321. // Open file
  322. #if defined ARCH_WIN
  323. r = archive_write_open_filename_w(a, string::UTF8toUTF16(archivePath).c_str());
  324. #else
  325. r = archive_write_open_filename(a, archivePath.c_str());
  326. #endif
  327. if (r < ARCHIVE_OK)
  328. throw Exception("Archiver could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a));
  329. }
  330. DEFER({archive_write_close(a);});
  331. // Open dir for reading
  332. struct archive* disk = archive_read_disk_new();
  333. DEFER({archive_read_free(disk);});
  334. #if defined ARCH_WIN
  335. r = archive_read_disk_open_w(disk, string::UTF8toUTF16(dirPath).c_str());
  336. #else
  337. r = archive_read_disk_open(disk, dirPath.c_str());
  338. #endif
  339. if (r < ARCHIVE_OK)
  340. throw Exception("Archiver could not open dir %s for reading: %s", dirPath.c_str(), archive_error_string(a));
  341. DEFER({archive_read_close(a);});
  342. // Iterate dir
  343. for (;;) {
  344. struct archive_entry* entry = archive_entry_new();
  345. DEFER({archive_entry_free(entry);});
  346. r = archive_read_next_header2(disk, entry);
  347. if (r == ARCHIVE_EOF)
  348. break;
  349. if (r < ARCHIVE_OK)
  350. throw Exception("Archiver could not get next entry from archive: %s", archive_error_string(disk));
  351. // Recurse dirs
  352. archive_read_disk_descend(disk);
  353. // Convert absolute path to relative path
  354. std::string entryPath;
  355. #if defined ARCH_WIN
  356. entryPath = string::UTF16toUTF8(archive_entry_pathname_w(entry));
  357. #else
  358. entryPath = archive_entry_pathname(entry);
  359. #endif
  360. entryPath = getRelativePath(entryPath, dirPath);
  361. #if defined ARCH_WIN
  362. // FIXME This doesn't seem to set UTF-8 paths on Windows.
  363. archive_entry_copy_pathname_w(entry, string::UTF8toUTF16(entryPath).c_str());
  364. #else
  365. archive_entry_set_pathname(entry, entryPath.c_str());
  366. #endif
  367. // Set uid and gid to 0 because we don't need to store these.
  368. archive_entry_set_uid(entry, 0);
  369. archive_entry_set_uname(entry, NULL);
  370. archive_entry_set_gid(entry, 0);
  371. archive_entry_set_gname(entry, NULL);
  372. // Write file to archive
  373. r = archive_write_header(a, entry);
  374. if (r < ARCHIVE_OK)
  375. throw Exception("Archiver could not write entry to archive: %s", archive_error_string(a));
  376. // Manually copy data
  377. #if defined ARCH_WIN
  378. std::string entrySourcePath = string::UTF16toUTF8(archive_entry_sourcepath_w(entry));
  379. #else
  380. std::string entrySourcePath = archive_entry_sourcepath(entry);
  381. #endif
  382. FILE* f = std::fopen(entrySourcePath.c_str(), "rb");
  383. DEFER({std::fclose(f);});
  384. char buf[1 << 16];
  385. ssize_t len;
  386. while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) {
  387. archive_write_data(a, buf, len);
  388. }
  389. }
  390. }
  391. void archiveDirectory(const std::string& archivePath, const std::string& dirPath, int compressionLevel) {
  392. archiveDirectory(archivePath, NULL, dirPath, compressionLevel);
  393. }
  394. std::vector<uint8_t> archiveDirectory(const std::string& dirPath, int compressionLevel) {
  395. std::vector<uint8_t> archiveData;
  396. archiveDirectory("", &archiveData, dirPath, compressionLevel);
  397. return archiveData;
  398. }
  399. struct ArchiveReadVectorData {
  400. const std::vector<uint8_t>* data = NULL;
  401. size_t pos = 0;
  402. };
  403. static la_ssize_t archiveReadVectorCallback(struct archive *a, void* client_data, const void** buffer) {
  404. assert(client_data);
  405. ArchiveReadVectorData* arvd = (ArchiveReadVectorData*) client_data;
  406. assert(arvd->data);
  407. const std::vector<uint8_t>& data = *arvd->data;
  408. *buffer = &data[arvd->pos];
  409. // Read up to some block size of bytes
  410. size_t len = std::min(data.size() - arvd->pos, size_t(1 << 16));
  411. arvd->pos += len;
  412. return len;
  413. }
  414. static void unarchiveToDirectory(const std::string& archivePath, const std::vector<uint8_t>* archiveData, const std::string& dirPathStr) {
  415. #if defined ARCH_MAC
  416. // libarchive depends on locale so set thread locale
  417. // If locale is not found, returns NULL which resets thread to global locale
  418. locale_t loc = newlocale(LC_CTYPE_MASK, "en_US.UTF-8", NULL);
  419. locale_t oldLoc = uselocale(loc);
  420. freelocale(loc);
  421. DEFER({
  422. uselocale(oldLoc);
  423. });
  424. #endif
  425. fs::path dirPath = fs::u8path(dirPathStr);
  426. // Based on minitar.c extract() in libarchive examples
  427. int r;
  428. // Open archive for reading
  429. struct archive* a = archive_read_new();
  430. if (!a)
  431. throw Exception("Unarchiver could not be created");
  432. DEFER({archive_read_free(a);});
  433. archive_read_support_filter_zstd(a);
  434. // archive_read_support_filter_all(a);
  435. archive_read_support_format_tar(a);
  436. // archive_read_support_format_all(a);
  437. ArchiveReadVectorData arvd;
  438. if (archiveData) {
  439. // Open vector
  440. arvd.data = archiveData;
  441. archive_read_open(a, &arvd, NULL, archiveReadVectorCallback, NULL);
  442. }
  443. else {
  444. // Open file
  445. const size_t blockSize = 1 << 16;
  446. #if defined ARCH_WIN
  447. r = archive_read_open_filename_w(a, string::UTF8toUTF16(archivePath).c_str(), blockSize);
  448. #else
  449. r = archive_read_open_filename(a, archivePath.c_str(), blockSize);
  450. #endif
  451. if (r < ARCHIVE_OK)
  452. throw Exception("Unarchiver could not open archive %s: %s", archivePath.c_str(), archive_error_string(a));
  453. }
  454. DEFER({archive_read_close(a);});
  455. // Open dir for writing
  456. struct archive* disk = archive_write_disk_new();
  457. DEFER({archive_write_free(disk);});
  458. // Don't restore timestamps
  459. int flags = 0;
  460. // Delete existing files instead of truncating and rewriting
  461. flags |= ARCHIVE_EXTRACT_UNLINK;
  462. archive_write_disk_set_options(disk, flags);
  463. DEFER({archive_write_close(disk);});
  464. // Iterate archive
  465. for (;;) {
  466. // Get next entry
  467. struct archive_entry* entry;
  468. r = archive_read_next_header(a, &entry);
  469. if (r == ARCHIVE_EOF)
  470. break;
  471. if (r < ARCHIVE_OK)
  472. throw Exception("Unarchiver could not read entry from archive: %s", archive_error_string(a));
  473. // Convert relative pathname to absolute based on dirPath
  474. fs::path entryPath = fs::u8path(archive_entry_pathname(entry));
  475. // DEBUG("entryPath: %s", entryPath.generic_u8string().c_str());
  476. if (!entryPath.is_relative())
  477. throw Exception("Unarchiver does not support absolute tar paths: %s", entryPath.u8string().c_str());
  478. entryPath = dirPath / entryPath;
  479. #if defined ARCH_WIN
  480. archive_entry_copy_pathname_w(entry, string::UTF8toUTF16(entryPath.u8string()).c_str());
  481. #else
  482. archive_entry_set_pathname(entry, entryPath.u8string().c_str());
  483. #endif
  484. mode_t mode = archive_entry_mode(entry);
  485. mode_t filetype = archive_entry_filetype(entry);
  486. int64_t size = archive_entry_size(entry);
  487. // Force minimum modes
  488. if (filetype == AE_IFREG) {
  489. mode |= 0644;
  490. }
  491. else if (filetype == AE_IFDIR) {
  492. mode |= 0755;
  493. }
  494. archive_entry_set_mode(entry, mode);
  495. // Delete zero-byte files
  496. if (filetype == AE_IFREG && size == 0) {
  497. remove(entryPath.generic_u8string());
  498. continue;
  499. }
  500. // Write entry to disk
  501. r = archive_write_header(disk, entry);
  502. if (r < ARCHIVE_OK)
  503. throw Exception("Unarchiver could not write file to dir: %s", archive_error_string(disk));
  504. // Copy data to file
  505. for (;;) {
  506. const void* buf;
  507. size_t size;
  508. int64_t offset;
  509. // Read data from archive
  510. r = archive_read_data_block(a, &buf, &size, &offset);
  511. if (r == ARCHIVE_EOF)
  512. break;
  513. if (r < ARCHIVE_OK)
  514. throw Exception("Unarchiver could not read data from archive: %s", archive_error_string(a));
  515. // Write data to file
  516. r = archive_write_data_block(disk, buf, size, offset);
  517. if (r < ARCHIVE_OK)
  518. throw Exception("Unarchiver could not write data to file: %s", archive_error_string(disk));
  519. }
  520. // Close file
  521. r = archive_write_finish_entry(disk);
  522. if (r < ARCHIVE_OK)
  523. throw Exception("Unarchiver could not close file: %s", archive_error_string(disk));
  524. }
  525. }
  526. void unarchiveToDirectory(const std::string& archivePath, const std::string& dirPath) {
  527. unarchiveToDirectory(archivePath, NULL, dirPath);
  528. }
  529. void unarchiveToDirectory(const std::vector<uint8_t>& archiveData, const std::string& dirPath) {
  530. unarchiveToDirectory("", &archiveData, dirPath);
  531. }
  532. int getLogicalCoreCount() {
  533. return std::thread::hardware_concurrency();
  534. }
  535. void setThreadName(const std::string& name) {
  536. #if defined ARCH_LIN
  537. pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
  538. #elif defined ARCH_MAC
  539. // Not supported (yet) on Mac
  540. #elif defined ARCH_WIN
  541. // Not supported on Windows
  542. #endif
  543. }
  544. std::string getStackTrace() {
  545. void* stack[128];
  546. int stackLen = LENGTHOF(stack);
  547. std::string s;
  548. #if defined ARCH_LIN || defined ARCH_MAC
  549. stackLen = backtrace(stack, stackLen);
  550. char** strings = backtrace_symbols(stack, stackLen);
  551. if (!strings)
  552. return "";
  553. // Skip the first line because it's this function.
  554. for (int i = 1; i < stackLen; i++) {
  555. s += string::f("%d: ", stackLen - i - 1);
  556. std::string line = strings[i];
  557. #if ARCH_LIN
  558. // Parse line, e.g.
  559. // ./main(__mangled_symbol+0x100) [0x12345678]
  560. std::regex r(R"((.+?)\((.*?)\+(.+?)\) (.+?))");
  561. std::smatch match;
  562. if (std::regex_match(line, match, r)) {
  563. s += match[1].str();
  564. s += "(";
  565. std::string symbol = match[2].str();
  566. // Demangle symbol
  567. char* symbolD = abi::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL);
  568. if (symbolD) {
  569. symbol = symbolD;
  570. free(symbolD);
  571. }
  572. s += symbol;
  573. s += "+";
  574. s += match[3].str();
  575. s += ")";
  576. }
  577. else {
  578. // If regex fails, just use the raw line
  579. s += line;
  580. }
  581. #elif ARCH_MAC
  582. // Parse line, e.g.
  583. // 1 Rack 0x0000000002ddc3eb _mangled_symbol + 27
  584. std::regex r(R"((\d+)\s+(.+?)\s+(.+?) (.*?) \+ (.+?))");
  585. std::smatch match;
  586. if (std::regex_match(line, match, r)) {
  587. s += match[2].str();
  588. s += "(";
  589. std::string symbol = match[4].str();
  590. // Demangle symbol
  591. char* symbolD = abi::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL);
  592. if (symbolD) {
  593. symbol = symbolD;
  594. free(symbolD);
  595. }
  596. s += symbol;
  597. s += "+";
  598. s += match[5].str();
  599. s += ")";
  600. }
  601. else {
  602. // If regex fails, just use the raw line
  603. s += line;
  604. }
  605. #endif
  606. s += "\n";
  607. }
  608. free(strings);
  609. #elif defined ARCH_WIN
  610. HANDLE process = GetCurrentProcess();
  611. SymInitialize(process, NULL, true);
  612. stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL);
  613. SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1);
  614. symbol->MaxNameLen = 255;
  615. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  616. for (int i = 1; i < stackLen; i++) {
  617. SymFromAddr(process, (DWORD64) stack[i], 0, symbol);
  618. s += string::f("%d: %s 0x%" PRIx64 "\n", stackLen - i - 1, symbol->Name, symbol->Address);
  619. }
  620. free(symbol);
  621. #endif
  622. return s;
  623. }
  624. #if defined ARCH_WIN
  625. static int64_t startCounter = 0;
  626. static double counterTime = 0.0;
  627. #endif
  628. #if defined ARCH_LIN
  629. static int64_t startTime = 0;
  630. #endif
  631. #if defined ARCH_MAC
  632. static int64_t startCounter = 0;
  633. static double counterTime = 0.0;
  634. #endif
  635. static void initTime() {
  636. #if defined ARCH_WIN
  637. LARGE_INTEGER counter;
  638. QueryPerformanceCounter(&counter);
  639. startCounter = counter.QuadPart;
  640. LARGE_INTEGER frequency;
  641. QueryPerformanceFrequency(&frequency);
  642. counterTime = 1.0 / frequency.QuadPart;
  643. #endif
  644. #if defined ARCH_LIN
  645. struct timespec ts;
  646. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  647. startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  648. #endif
  649. #if defined ARCH_MAC
  650. startCounter = mach_absolute_time();
  651. mach_timebase_info_data_t tb;
  652. mach_timebase_info(&tb);
  653. counterTime = 1e-9 * (double) tb.numer / tb.denom;
  654. #endif
  655. }
  656. double getTime() {
  657. #if defined ARCH_WIN
  658. LARGE_INTEGER counter;
  659. QueryPerformanceCounter(&counter);
  660. return (counter.QuadPart - startCounter) * counterTime;
  661. #elif defined ARCH_LIN
  662. struct timespec ts;
  663. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  664. int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  665. return (time - startTime) / 1e9;
  666. #elif defined ARCH_MAC
  667. int64_t counter = mach_absolute_time();
  668. return (counter - startCounter) * counterTime;
  669. #else
  670. return 0.0;
  671. #endif
  672. }
  673. double getUnixTime() {
  674. // This is not guaranteed to return the time since 1970 in C++11. (It only does in C++20).
  675. // However, it does on all platforms I care about.
  676. auto duration = std::chrono::system_clock::now().time_since_epoch();
  677. return std::chrono::duration<double>(duration).count();
  678. }
  679. double getThreadTime() {
  680. #if defined ARCH_LIN
  681. struct timespec ts;
  682. clockid_t cid;
  683. pthread_getcpuclockid(pthread_self(), &cid);
  684. clock_gettime(cid, &ts);
  685. return ts.tv_sec + ts.tv_nsec * 1e-9;
  686. #elif defined ARCH_MAC
  687. mach_port_t thread = mach_thread_self();
  688. mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
  689. thread_basic_info_data_t info;
  690. kern_return_t kr = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t) &info, &count);
  691. if (kr != KERN_SUCCESS || (info.flags & TH_FLAGS_IDLE) != 0)
  692. return 0.0;
  693. return info.user_time.seconds + info.user_time.microseconds * 1e-6;
  694. #elif defined ARCH_WIN
  695. FILETIME creationTime;
  696. FILETIME exitTime;
  697. FILETIME kernelTime;
  698. FILETIME userTime;
  699. if (GetThreadTimes(GetCurrentThread(), &creationTime, &exitTime, &kernelTime, &userTime) == 0)
  700. return 0.0;
  701. return ((uint64_t(userTime.dwHighDateTime) << 32) + userTime.dwLowDateTime) * 1e-7;
  702. #else
  703. return 0.0;
  704. #endif
  705. }
  706. void sleep(double time) {
  707. std::this_thread::sleep_for(std::chrono::duration<double>(time));
  708. }
  709. std::string getOperatingSystemInfo() {
  710. #if defined ARCH_LIN
  711. struct utsname u;
  712. uname(&u);
  713. return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine);
  714. #elif defined ARCH_MAC
  715. // From https://opensource.apple.com/source/cctools/cctools-973.0.1/libstuff/macosx_deployment_target.c.auto.html
  716. char osversion[32];
  717. int osversion_name[2] = {CTL_KERN, KERN_OSRELEASE};
  718. size_t osversion_len = sizeof(osversion) - 1;
  719. if (sysctl(osversion_name, 2, osversion, &osversion_len, NULL, 0) != 0)
  720. return "Mac";
  721. int major = 0;
  722. int minor = 0;
  723. if (sscanf(osversion, "%d.%d", &major, &minor) != 2)
  724. return "Mac";
  725. // Try to match version numbers to retail versions
  726. if (major >= 20) {
  727. if (major >= 22) {
  728. minor -= 1;
  729. }
  730. major -= 9;
  731. return string::f("Mac %d.%d", major, minor);
  732. }
  733. else {
  734. major -= 4;
  735. return string::f("Mac 10.%d.%d", major, minor);
  736. }
  737. #elif defined ARCH_WIN
  738. OSVERSIONINFOW info;
  739. ZeroMemory(&info, sizeof(info));
  740. info.dwOSVersionInfoSize = sizeof(info);
  741. GetVersionExW(&info);
  742. // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers.
  743. return string::f("Windows %lu.%lu", info.dwMajorVersion, info.dwMinorVersion);
  744. #endif
  745. }
  746. void openBrowser(const std::string& url) {
  747. if (url.empty())
  748. return;
  749. std::string urlL = url;
  750. std::thread t([=] {
  751. #if defined ARCH_LIN
  752. std::string command = "xdg-open \"" + urlL + "\"";
  753. (void) std::system(command.c_str());
  754. #endif
  755. #if defined ARCH_MAC
  756. std::string command = "open \"" + urlL + "\"";
  757. std::system(command.c_str());
  758. #endif
  759. #if defined ARCH_WIN
  760. ShellExecuteW(NULL, L"open", string::UTF8toUTF16(urlL).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  761. #endif
  762. });
  763. t.detach();
  764. }
  765. void openDirectory(const std::string& path) {
  766. if (path.empty())
  767. return;
  768. std::string pathL = path;
  769. std::thread t([=] {
  770. #if defined ARCH_LIN
  771. std::string command = "xdg-open \"" + pathL + "\"";
  772. (void) std::system(command.c_str());
  773. #endif
  774. #if defined ARCH_MAC
  775. std::string command = "open \"" + pathL + "\"";
  776. std::system(command.c_str());
  777. #endif
  778. #if defined ARCH_WIN
  779. ShellExecuteW(NULL, L"explore", string::UTF8toUTF16(pathL).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  780. #endif
  781. });
  782. t.detach();
  783. }
  784. void runProcessDetached(const std::string& path) {
  785. #if defined ARCH_WIN
  786. SHELLEXECUTEINFOW shExInfo;
  787. ZeroMemory(&shExInfo, sizeof(shExInfo));
  788. shExInfo.cbSize = sizeof(shExInfo);
  789. shExInfo.lpVerb = L"runas";
  790. std::wstring pathW = string::UTF8toUTF16(path);
  791. shExInfo.lpFile = pathW.c_str();
  792. shExInfo.nShow = SW_SHOW;
  793. if (ShellExecuteExW(&shExInfo)) {
  794. // Do nothing
  795. }
  796. #else
  797. // Not implemented on Linux or Mac
  798. assert(0);
  799. #endif
  800. }
  801. void init() {
  802. initTime();
  803. }
  804. } // namespace system
  805. } // namespace rack