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.

892 lines
22KB

  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 __cxxabiv1::__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_ustar(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. int stackLen = 128;
  524. void* stack[stackLen];
  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. // Skip the first line because it's this function.
  530. for (int i = 1; i < stackLen; i++) {
  531. s += string::f("%d: ", stackLen - i - 1);
  532. std::string line = strings[i];
  533. #if 0
  534. // Parse line
  535. std::regex r(R"((.*)\((.*)\+(.*)\) (.*))");
  536. std::smatch match;
  537. if (std::regex_search(line, match, r)) {
  538. s += match[1].str();
  539. s += "(";
  540. std::string symbol = match[2].str();
  541. // Demangle symbol
  542. char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL);
  543. if (symbolD) {
  544. symbol = symbolD;
  545. free(symbolD);
  546. }
  547. s += symbol;
  548. s += "+";
  549. s += match[3].str();
  550. s += ")";
  551. }
  552. #else
  553. s += line;
  554. #endif
  555. s += "\n";
  556. }
  557. free(strings);
  558. #elif defined ARCH_WIN
  559. HANDLE process = GetCurrentProcess();
  560. SymInitialize(process, NULL, true);
  561. stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL);
  562. SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1);
  563. symbol->MaxNameLen = 255;
  564. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  565. for (int i = 1; i < stackLen; i++) {
  566. SymFromAddr(process, (DWORD64) stack[i], 0, symbol);
  567. s += string::f("%d: %s 0x%" PRIx64 "\n", stackLen - i - 1, symbol->Name, symbol->Address);
  568. }
  569. free(symbol);
  570. #endif
  571. return s;
  572. }
  573. #if defined ARCH_WIN
  574. static int64_t startCounter = 0;
  575. static double counterTime = 0.0;
  576. #endif
  577. #if defined ARCH_LIN
  578. static int64_t startTime = 0;
  579. #endif
  580. #if defined ARCH_MAC
  581. static int64_t startCounter = 0;
  582. static double counterTime = 0.0;
  583. #endif
  584. static void initTime() {
  585. #if defined ARCH_WIN
  586. LARGE_INTEGER counter;
  587. QueryPerformanceCounter(&counter);
  588. startCounter = counter.QuadPart;
  589. LARGE_INTEGER frequency;
  590. QueryPerformanceFrequency(&frequency);
  591. counterTime = 1.0 / frequency.QuadPart;
  592. #endif
  593. #if defined ARCH_LIN
  594. struct timespec ts;
  595. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  596. startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  597. #endif
  598. #if defined ARCH_MAC
  599. startCounter = mach_absolute_time();
  600. mach_timebase_info_data_t tb;
  601. mach_timebase_info(&tb);
  602. counterTime = 1e-9 * (double) tb.numer / tb.denom;
  603. #endif
  604. }
  605. double getTime() {
  606. #if defined ARCH_WIN
  607. LARGE_INTEGER counter;
  608. QueryPerformanceCounter(&counter);
  609. return (counter.QuadPart - startCounter) * counterTime;
  610. #endif
  611. #if defined ARCH_LIN
  612. struct timespec ts;
  613. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  614. int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  615. return (time - startTime) / 1e9;
  616. #endif
  617. #if defined ARCH_MAC
  618. int64_t counter = mach_absolute_time();
  619. return (counter - startCounter) * counterTime;
  620. #endif
  621. }
  622. double getUnixTime() {
  623. // This is not guaranteed to return the time since 1970 in C++11. (It only does in C++20).
  624. // However, it does on all platforms I care about.
  625. auto duration = std::chrono::system_clock::now().time_since_epoch();
  626. return std::chrono::duration<double>(duration).count();
  627. }
  628. double getThreadTime() {
  629. #if defined ARCH_LIN
  630. struct timespec ts;
  631. clockid_t cid;
  632. pthread_getcpuclockid(pthread_self(), &cid);
  633. clock_gettime(cid, &ts);
  634. return ts.tv_sec + ts.tv_nsec * 1e-9;
  635. #elif defined ARCH_MAC
  636. mach_port_t thread = mach_thread_self();
  637. mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
  638. thread_basic_info_data_t info;
  639. kern_return_t kr = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t) &info, &count);
  640. if (kr != KERN_SUCCESS || (info.flags & TH_FLAGS_IDLE) != 0)
  641. return 0.0;
  642. return info.user_time.seconds + info.user_time.microseconds * 1e-6;
  643. #elif defined ARCH_WIN
  644. FILETIME creationTime;
  645. FILETIME exitTime;
  646. FILETIME kernelTime;
  647. FILETIME userTime;
  648. if (GetThreadTimes(GetCurrentThread(), &creationTime, &exitTime, &kernelTime, &userTime) == 0)
  649. return 0.0;
  650. return ((uint64_t(userTime.dwHighDateTime) << 32) + userTime.dwLowDateTime) * 1e-7;
  651. #endif
  652. }
  653. std::string getOperatingSystemInfo() {
  654. #if defined ARCH_LIN
  655. struct utsname u;
  656. uname(&u);
  657. return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine);
  658. #elif defined ARCH_MAC
  659. // From https://opensource.apple.com/source/cctools/cctools-973.0.1/libstuff/macosx_deployment_target.c.auto.html
  660. char osversion[32];
  661. int osversion_name[2] = {CTL_KERN, KERN_OSRELEASE};
  662. size_t osversion_len = sizeof(osversion) - 1;
  663. if (sysctl(osversion_name, 2, osversion, &osversion_len, NULL, 0) != 0) return "Mac";
  664. int major = 0;
  665. int minor = 0;
  666. if (sscanf(osversion, "%d.%d", &major, &minor) != 2)
  667. return "Mac";
  668. // Try to match version numbers to retail versions
  669. if (major >= 20) {
  670. major -= 9;
  671. return string::f("Mac %d.%d", major, minor);
  672. }
  673. else {
  674. major -= 4;
  675. return string::f("Mac 10.%d.%d", major, minor);
  676. }
  677. #elif defined ARCH_WIN
  678. OSVERSIONINFOW info;
  679. ZeroMemory(&info, sizeof(info));
  680. info.dwOSVersionInfoSize = sizeof(info);
  681. GetVersionExW(&info);
  682. // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers.
  683. return string::f("Windows %lu.%lu", info.dwMajorVersion, info.dwMinorVersion);
  684. #endif
  685. }
  686. void openBrowser(const std::string& url) {
  687. if (url.empty())
  688. return;
  689. std::string urlL = url;
  690. std::thread t([=] {
  691. #if defined ARCH_LIN
  692. std::string command = "xdg-open \"" + urlL + "\"";
  693. (void) std::system(command.c_str());
  694. #endif
  695. #if defined ARCH_MAC
  696. std::string command = "open \"" + urlL + "\"";
  697. std::system(command.c_str());
  698. #endif
  699. #if defined ARCH_WIN
  700. ShellExecuteW(NULL, L"open", string::UTF8toUTF16(urlL).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  701. #endif
  702. });
  703. t.detach();
  704. }
  705. void openDirectory(const std::string& path) {
  706. if (path.empty())
  707. return;
  708. std::string pathL = path;
  709. std::thread t([=] {
  710. #if defined ARCH_LIN
  711. std::string command = "xdg-open \"" + pathL + "\"";
  712. (void) std::system(command.c_str());
  713. #endif
  714. #if defined ARCH_MAC
  715. std::string command = "open \"" + pathL + "\"";
  716. std::system(command.c_str());
  717. #endif
  718. #if defined ARCH_WIN
  719. ShellExecuteW(NULL, L"explore", string::UTF8toUTF16(pathL).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  720. #endif
  721. });
  722. t.detach();
  723. }
  724. void runProcessDetached(const std::string& path) {
  725. #if defined ARCH_WIN
  726. SHELLEXECUTEINFOW shExInfo;
  727. ZeroMemory(&shExInfo, sizeof(shExInfo));
  728. shExInfo.cbSize = sizeof(shExInfo);
  729. shExInfo.lpVerb = L"runas";
  730. std::wstring pathW = string::UTF8toUTF16(path);
  731. shExInfo.lpFile = pathW.c_str();
  732. shExInfo.nShow = SW_SHOW;
  733. if (ShellExecuteExW(&shExInfo)) {
  734. // Do nothing
  735. }
  736. #else
  737. // Not implemented on Linux or Mac
  738. assert(0);
  739. #endif
  740. }
  741. void init() {
  742. initTime();
  743. }
  744. } // namespace system
  745. } // namespace rack