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.

921 lines
23KB

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