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.

671 lines
17KB

  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 <sched.h>
  11. #include <execinfo.h> // for backtrace and backtrace_symbols
  12. #include <unistd.h> // for execl
  13. #include <sys/utsname.h>
  14. #endif
  15. #if defined ARCH_MAC
  16. #include <mach/mach_init.h>
  17. #include <mach/thread_act.h>
  18. #include <mach/clock.h>
  19. #include <mach/mach.h>
  20. #endif
  21. #if defined ARCH_WIN
  22. #include <windows.h>
  23. #include <shellapi.h>
  24. #include <processthreadsapi.h>
  25. #include <dbghelp.h>
  26. #endif
  27. #include <archive.h>
  28. #include <archive_entry.h>
  29. #include <system.hpp>
  30. #include <string.hpp>
  31. /*
  32. In C++17, this will be `std::filesystem`
  33. Important: When using `fs::path`, always convert strings to UTF-8 using
  34. fs::path p = fs::u8path(s);
  35. In fact, it's best to work only with strings to avoid forgetting to decode a string as UTF-8.
  36. The need to do this is a fatal flaw of `fs::path`, but at least `std::filesystem` has some helpful operations.
  37. */
  38. namespace fs = ghc::filesystem;
  39. namespace rack {
  40. namespace system {
  41. std::string join(const std::string& path1, const std::string& path2) {
  42. return (fs::u8path(path1) / fs::u8path(path2)).generic_u8string();
  43. }
  44. std::list<std::string> getEntries(const std::string& dirPath, int depth) {
  45. try {
  46. std::list<std::string> entries;
  47. for (auto& entry : fs::directory_iterator(fs::u8path(dirPath))) {
  48. std::string subEntry = entry.path().generic_u8string();
  49. entries.push_back(subEntry);
  50. // Recurse if depth > 0 (limited recursion) or depth < 0 (infinite recursion).
  51. if (depth != 0) {
  52. if (fs::is_directory(entry.path())) {
  53. std::list<std::string> subEntries = getEntries(subEntry, depth - 1);
  54. entries.splice(entries.end(), subEntries);
  55. }
  56. }
  57. }
  58. return entries;
  59. }
  60. catch (fs::filesystem_error& e) {
  61. throw Exception(e.what());
  62. }
  63. }
  64. bool doesExist(const std::string& path) {
  65. try {
  66. return fs::exists(fs::u8path(path));
  67. }
  68. catch (fs::filesystem_error& e) {
  69. throw Exception(e.what());
  70. }
  71. }
  72. bool isFile(const std::string& path) {
  73. try {
  74. return fs::is_regular_file(fs::u8path(path));
  75. }
  76. catch (fs::filesystem_error& e) {
  77. throw Exception(e.what());
  78. }
  79. }
  80. bool isDirectory(const std::string& path) {
  81. try {
  82. return fs::is_directory(fs::u8path(path));
  83. }
  84. catch (fs::filesystem_error& e) {
  85. throw Exception(e.what());
  86. }
  87. }
  88. uint64_t getFileSize(const std::string& path) {
  89. try {
  90. return fs::file_size(fs::u8path(path));
  91. }
  92. catch (fs::filesystem_error& e) {
  93. throw Exception(e.what());
  94. }
  95. }
  96. void rename(const std::string& srcPath, const std::string& destPath) {
  97. try {
  98. fs::rename(fs::u8path(srcPath), fs::u8path(destPath));
  99. }
  100. catch (fs::filesystem_error& e) {
  101. throw Exception(e.what());
  102. }
  103. }
  104. void copy(const std::string& srcPath, const std::string& destPath) {
  105. try {
  106. fs::copy(fs::u8path(srcPath), fs::u8path(destPath), fs::copy_options::recursive);
  107. }
  108. catch (fs::filesystem_error& e) {
  109. throw Exception(e.what());
  110. }
  111. }
  112. bool createDirectory(const std::string& path) {
  113. try {
  114. return fs::create_directory(fs::u8path(path));
  115. }
  116. catch (fs::filesystem_error& e) {
  117. throw Exception(e.what());
  118. }
  119. }
  120. bool createDirectories(const std::string& path) {
  121. try {
  122. return fs::create_directories(fs::u8path(path));
  123. }
  124. catch (fs::filesystem_error& e) {
  125. throw Exception(e.what());
  126. }
  127. }
  128. bool remove(const std::string& path) {
  129. try {
  130. return fs::remove(fs::u8path(path));
  131. }
  132. catch (fs::filesystem_error& e) {
  133. throw Exception(e.what());
  134. }
  135. }
  136. int removeRecursively(const std::string& path) {
  137. try {
  138. return fs::remove_all(fs::u8path(path));
  139. }
  140. catch (fs::filesystem_error& e) {
  141. throw Exception(e.what());
  142. }
  143. }
  144. std::string getWorkingDirectory() {
  145. try {
  146. return fs::current_path().generic_u8string();
  147. }
  148. catch (fs::filesystem_error& e) {
  149. throw Exception(e.what());
  150. }
  151. }
  152. void setWorkingDirectory(const std::string& path) {
  153. try {
  154. fs::current_path(fs::u8path(path));
  155. }
  156. catch (fs::filesystem_error& e) {
  157. throw Exception(e.what());
  158. }
  159. }
  160. std::string getTempDir() {
  161. try {
  162. return fs::temp_directory_path().generic_u8string();
  163. }
  164. catch (fs::filesystem_error& e) {
  165. throw Exception(e.what());
  166. }
  167. }
  168. std::string getAbsolute(const std::string& path) {
  169. try {
  170. return fs::absolute(fs::u8path(path)).generic_u8string();
  171. }
  172. catch (fs::filesystem_error& e) {
  173. throw Exception(e.what());
  174. }
  175. }
  176. std::string getCanonical(const std::string& path) {
  177. try {
  178. return fs::canonical(fs::u8path(path)).generic_u8string();
  179. }
  180. catch (fs::filesystem_error& e) {
  181. throw Exception(e.what());
  182. }
  183. }
  184. std::string getDirectory(const std::string& path) {
  185. try {
  186. return fs::u8path(path).parent_path().generic_u8string();
  187. }
  188. catch (fs::filesystem_error& e) {
  189. throw Exception(e.what());
  190. }
  191. }
  192. std::string getFilename(const std::string& path) {
  193. try {
  194. return fs::u8path(path).filename().generic_u8string();
  195. }
  196. catch (fs::filesystem_error& e) {
  197. throw Exception(e.what());
  198. }
  199. }
  200. std::string getStem(const std::string& path) {
  201. try {
  202. return fs::u8path(path).stem().generic_u8string();
  203. }
  204. catch (fs::filesystem_error& e) {
  205. throw Exception(e.what());
  206. }
  207. }
  208. std::string getExtension(const std::string& path) {
  209. try {
  210. return fs::u8path(path).extension().generic_u8string();
  211. }
  212. catch (fs::filesystem_error& e) {
  213. throw Exception(e.what());
  214. }
  215. }
  216. /** Returns `p` in relative path form, relative to `base`
  217. Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path.
  218. */
  219. static std::string getRelativePath(std::string path, std::string base) {
  220. try {
  221. path = fs::absolute(fs::u8path(path)).generic_u8string();
  222. base = fs::absolute(fs::u8path(base)).generic_u8string();
  223. }
  224. catch (fs::filesystem_error& e) {
  225. throw Exception(e.what());
  226. }
  227. if (path.size() < base.size())
  228. throw Exception("getRelativePath() error: path is shorter than base");
  229. if (!std::equal(base.begin(), base.end(), path.begin()))
  230. throw Exception("getRelativePath() error: path does not begin with base");
  231. // If path == base, this correctly returns "."
  232. return "." + std::string(path.begin() + base.size(), path.end());
  233. }
  234. void archiveFolder(const std::string& archivePath, const std::string& folderPath, int compressionLevel) {
  235. // Based on minitar.c create() in libarchive examples
  236. int r;
  237. // Open archive for writing
  238. struct archive* a = archive_write_new();
  239. DEFER({archive_write_free(a);});
  240. archive_write_set_format_ustar(a);
  241. archive_write_add_filter_zstd(a);
  242. assert(0 <= compressionLevel && compressionLevel <= 19);
  243. r = archive_write_set_filter_option(a, NULL, "compression-level", std::to_string(compressionLevel).c_str());
  244. if (r < ARCHIVE_OK)
  245. throw Exception(string::f("Archiver could not set filter option: %s", archive_error_string(a)));
  246. #if defined ARCH_WIN
  247. r = archive_write_open_filename_w(a, string::U8toU16(archivePath).c_str());
  248. #else
  249. r = archive_write_open_filename(a, archivePath.c_str());
  250. #endif
  251. if (r < ARCHIVE_OK)
  252. throw Exception(string::f("Archiver could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a)));
  253. DEFER({archive_write_close(a);});
  254. // Open folder for reading
  255. struct archive* disk = archive_read_disk_new();
  256. DEFER({archive_read_free(disk);});
  257. #if defined ARCH_WIN
  258. r = archive_read_disk_open_w(disk, string::U8toU16(folderPath).c_str());
  259. #else
  260. r = archive_read_disk_open(disk, folderPath.c_str());
  261. #endif
  262. if (r < ARCHIVE_OK)
  263. throw Exception(string::f("Archiver could not open folder %s for reading: %s", folderPath.c_str(), archive_error_string(a)));
  264. DEFER({archive_read_close(a);});
  265. // Iterate folder
  266. for (;;) {
  267. struct archive_entry* entry = archive_entry_new();
  268. DEFER({archive_entry_free(entry);});
  269. r = archive_read_next_header2(disk, entry);
  270. if (r == ARCHIVE_EOF)
  271. break;
  272. if (r < ARCHIVE_OK)
  273. throw Exception(string::f("Archiver could not get next entry from archive: %s", archive_error_string(disk)));
  274. // Recurse dirs
  275. archive_read_disk_descend(disk);
  276. // Convert absolute path to relative path
  277. std::string entryPath;
  278. #if defined ARCH_WIN
  279. entryPath = string::U16toU8(archive_entry_pathname_w(entry));
  280. #else
  281. entryPath = archive_entry_pathname(entry);
  282. #endif
  283. entryPath = getRelativePath(entryPath, folderPath);
  284. #if defined ARCH_WIN
  285. // FIXME This doesn't seem to set UTF-8 paths on Windows.
  286. archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str());
  287. #else
  288. archive_entry_set_pathname(entry, entryPath.c_str());
  289. #endif
  290. // Write file to archive
  291. r = archive_write_header(a, entry);
  292. if (r < ARCHIVE_OK)
  293. throw Exception(string::f("Archiver could not write entry to archive: %s", archive_error_string(a)));
  294. // Manually copy data
  295. #if defined ARCH_WIN
  296. std::string entrySourcePath = string::U16toU8(archive_entry_sourcepath_w(entry));
  297. #else
  298. std::string entrySourcePath = archive_entry_sourcepath(entry);
  299. #endif
  300. FILE* f = std::fopen(entrySourcePath.c_str(), "rb");
  301. DEFER({std::fclose(f);});
  302. char buf[1 << 16];
  303. ssize_t len;
  304. while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) {
  305. archive_write_data(a, buf, len);
  306. }
  307. }
  308. }
  309. void unarchiveToFolder(const std::string& archivePath, const std::string& folderPath) {
  310. // Based on minitar.c extract() in libarchive examples
  311. int r;
  312. // Open archive for reading
  313. struct archive* a = archive_read_new();
  314. DEFER({archive_read_free(a);});
  315. archive_read_support_filter_zstd(a);
  316. // archive_read_support_filter_all(a);
  317. archive_read_support_format_tar(a);
  318. // archive_read_support_format_all(a);
  319. #if defined ARCH_WIN
  320. r = archive_read_open_filename_w(a, string::U8toU16(archivePath).c_str(), 1 << 16);
  321. #else
  322. r = archive_read_open_filename(a, archivePath.c_str(), 1 << 14);
  323. #endif
  324. if (r < ARCHIVE_OK)
  325. throw Exception(string::f("Unarchiver could not open archive %s: %s", archivePath.c_str(), archive_error_string(a)));
  326. DEFER({archive_read_close(a);});
  327. // Open folder for writing
  328. struct archive* disk = archive_write_disk_new();
  329. DEFER({archive_write_free(disk);});
  330. int flags = ARCHIVE_EXTRACT_TIME;
  331. archive_write_disk_set_options(disk, flags);
  332. DEFER({archive_write_close(disk);});
  333. // Iterate archive
  334. for (;;) {
  335. // Get next entry
  336. struct archive_entry* entry;
  337. r = archive_read_next_header(a, &entry);
  338. if (r == ARCHIVE_EOF)
  339. break;
  340. if (r < ARCHIVE_OK)
  341. throw Exception(string::f("Unarchiver could not read entry from archive: %s", archive_error_string(a)));
  342. // Convert relative pathname to absolute based on folderPath
  343. std::string entryPath = archive_entry_pathname(entry);
  344. if (!fs::u8path(entryPath).is_relative())
  345. throw Exception(string::f("Unarchiver does not support absolute tar paths: %s", entryPath.c_str()));
  346. entryPath = (fs::u8path(folderPath) / fs::u8path(entryPath)).generic_u8string();
  347. #if defined ARCH_WIN
  348. archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str());
  349. #else
  350. archive_entry_set_pathname(entry, entryPath.c_str());
  351. #endif
  352. // Write entry to disk
  353. r = archive_write_header(disk, entry);
  354. if (r < ARCHIVE_OK)
  355. throw Exception(string::f("Unarchiver could not write file to folder: %s", archive_error_string(disk)));
  356. // Copy data to file
  357. for (;;) {
  358. const void* buf;
  359. size_t size;
  360. int64_t offset;
  361. // Read data from archive
  362. r = archive_read_data_block(a, &buf, &size, &offset);
  363. if (r == ARCHIVE_EOF)
  364. break;
  365. if (r < ARCHIVE_OK)
  366. throw Exception(string::f("Unarchiver could not read data from archive", archive_error_string(a)));
  367. // Write data to file
  368. r = archive_write_data_block(disk, buf, size, offset);
  369. if (r < ARCHIVE_OK)
  370. throw Exception(string::f("Unarchiver could not write data to file", archive_error_string(disk)));
  371. }
  372. // Close file
  373. r = archive_write_finish_entry(disk);
  374. if (r < ARCHIVE_OK)
  375. throw Exception(string::f("Unarchiver could not close file", archive_error_string(disk)));
  376. }
  377. }
  378. int getLogicalCoreCount() {
  379. return std::thread::hardware_concurrency();
  380. }
  381. void setThreadName(const std::string& name) {
  382. #if defined ARCH_LIN
  383. pthread_setname_np(pthread_self(), name.c_str());
  384. #elif defined ARCH_WIN
  385. // Unsupported on Windows
  386. #endif
  387. }
  388. std::string getStackTrace() {
  389. int stackLen = 128;
  390. void* stack[stackLen];
  391. std::string s;
  392. #if defined ARCH_LIN || defined ARCH_MAC
  393. stackLen = backtrace(stack, stackLen);
  394. char** strings = backtrace_symbols(stack, stackLen);
  395. // Skip the first line because it's this function.
  396. for (int i = 1; i < stackLen; i++) {
  397. s += string::f("%d: ", stackLen - i - 1);
  398. std::string line = strings[i];
  399. #if 0
  400. // Parse line
  401. std::regex r(R"((.*)\((.*)\+(.*)\) (.*))");
  402. std::smatch match;
  403. if (std::regex_search(line, match, r)) {
  404. s += match[1].str();
  405. s += "(";
  406. std::string symbol = match[2].str();
  407. // Demangle symbol
  408. char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL);
  409. if (symbolD) {
  410. symbol = symbolD;
  411. free(symbolD);
  412. }
  413. s += symbol;
  414. s += "+";
  415. s += match[3].str();
  416. s += ")";
  417. }
  418. #else
  419. s += line;
  420. #endif
  421. s += "\n";
  422. }
  423. free(strings);
  424. #elif defined ARCH_WIN
  425. HANDLE process = GetCurrentProcess();
  426. SymInitialize(process, NULL, true);
  427. stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL);
  428. SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1);
  429. symbol->MaxNameLen = 255;
  430. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  431. for (int i = 1; i < stackLen; i++) {
  432. SymFromAddr(process, (DWORD64) stack[i], 0, symbol);
  433. s += string::f("%d: %s 0x%0x\n", stackLen - i - 1, symbol->Name, symbol->Address);
  434. }
  435. free(symbol);
  436. #endif
  437. return s;
  438. }
  439. #if defined ARCH_WIN
  440. static int64_t startCounter = 0;
  441. static double counterTime = 0.0;
  442. #endif
  443. #if defined ARCH_LIN || defined ARCH_MAC
  444. static int64_t startTime = 0;
  445. #endif
  446. static void initRuntime() {
  447. #if defined ARCH_WIN
  448. assert(startCounter == 0);
  449. LARGE_INTEGER counter;
  450. QueryPerformanceCounter(&counter);
  451. startCounter = counter.QuadPart;
  452. LARGE_INTEGER frequency;
  453. QueryPerformanceFrequency(&frequency);
  454. counterTime = 1.0 / frequency.QuadPart;
  455. #endif
  456. #if defined ARCH_LIN
  457. assert(startTime == 0);
  458. struct timespec ts;
  459. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  460. startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  461. #endif
  462. #if defined ARCH_MAC
  463. assert(startTime == 0);
  464. clock_serv_t cclock;
  465. mach_timespec_t mts;
  466. host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
  467. clock_get_time(cclock, &mts);
  468. mach_port_deallocate(mach_task_self(), cclock);
  469. startTime = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec;
  470. #endif
  471. }
  472. double getRuntime() {
  473. #if defined ARCH_WIN
  474. LARGE_INTEGER counter;
  475. QueryPerformanceCounter(&counter);
  476. return (counter.QuadPart - startCounter) * counterTime;
  477. #endif
  478. #if defined ARCH_LIN
  479. struct timespec ts;
  480. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  481. int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  482. return (time - startTime) / 1e9;
  483. #endif
  484. #if defined ARCH_MAC
  485. clock_serv_t cclock;
  486. mach_timespec_t mts;
  487. host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
  488. clock_get_time(cclock, &mts);
  489. mach_port_deallocate(mach_task_self(), cclock);
  490. int64_t time = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec;
  491. return (time - startTime) / 1e9;
  492. #endif
  493. }
  494. double getUnixTime() {
  495. auto duration = std::chrono::system_clock::now().time_since_epoch();
  496. return std::chrono::duration<double>(duration).count();
  497. }
  498. std::string getOperatingSystemInfo() {
  499. #if defined ARCH_LIN || defined ARCH_MAC
  500. struct utsname u;
  501. uname(&u);
  502. return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine);
  503. #elif defined ARCH_WIN
  504. OSVERSIONINFOW info;
  505. ZeroMemory(&info, sizeof(info));
  506. info.dwOSVersionInfoSize = sizeof(info);
  507. GetVersionExW(&info);
  508. // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers.
  509. return string::f("Windows %u.%u", info.dwMajorVersion, info.dwMinorVersion);
  510. #endif
  511. }
  512. void openBrowser(const std::string& url) {
  513. #if defined ARCH_LIN
  514. std::string command = "xdg-open \"" + url + "\"";
  515. (void) std::system(command.c_str());
  516. #endif
  517. #if defined ARCH_MAC
  518. std::string command = "open \"" + url + "\"";
  519. std::system(command.c_str());
  520. #endif
  521. #if defined ARCH_WIN
  522. ShellExecuteW(NULL, L"open", string::U8toU16(url).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  523. #endif
  524. }
  525. void openFolder(const std::string& path) {
  526. #if defined ARCH_LIN
  527. std::string command = "xdg-open \"" + path + "\"";
  528. (void) std::system(command.c_str());
  529. #endif
  530. #if defined ARCH_MAC
  531. std::string command = "open \"" + path + "\"";
  532. std::system(command.c_str());
  533. #endif
  534. #if defined ARCH_WIN
  535. ShellExecuteW(NULL, L"explore", string::U8toU16(path).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  536. #endif
  537. }
  538. void runProcessDetached(const std::string& path) {
  539. #if defined ARCH_WIN
  540. SHELLEXECUTEINFOW shExInfo;
  541. ZeroMemory(&shExInfo, sizeof(shExInfo));
  542. shExInfo.cbSize = sizeof(shExInfo);
  543. shExInfo.lpVerb = L"runas";
  544. std::wstring pathW = string::U8toU16(path);
  545. shExInfo.lpFile = pathW.c_str();
  546. shExInfo.nShow = SW_SHOW;
  547. if (ShellExecuteExW(&shExInfo)) {
  548. // Do nothing
  549. }
  550. #else
  551. // Not implemented on Linux or Mac
  552. assert(0);
  553. #endif
  554. }
  555. void init() {
  556. initRuntime();
  557. }
  558. } // namespace system
  559. } // namespace rack