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.

626 lines
16KB

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