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.

433 lines
9.8KB

  1. #include <thread>
  2. #include <regex>
  3. #include <chrono>
  4. #include <dirent.h>
  5. #include <sys/stat.h>
  6. #include <cxxabi.h> // for __cxxabiv1::__cxa_demangle
  7. #if defined ARCH_LIN || defined ARCH_MAC
  8. #include <pthread.h>
  9. #include <sched.h>
  10. #include <execinfo.h> // for backtrace and backtrace_symbols
  11. #include <unistd.h> // for execl
  12. #include <sys/utsname.h>
  13. #endif
  14. #if defined ARCH_MAC
  15. #include <mach/mach_init.h>
  16. #include <mach/thread_act.h>
  17. #endif
  18. #if defined ARCH_WIN
  19. #include <windows.h>
  20. #include <shellapi.h>
  21. #include <processthreadsapi.h>
  22. #include <dbghelp.h>
  23. #endif
  24. #define ZIP_STATIC
  25. #include <zip.h>
  26. #include <system.hpp>
  27. #include <string.hpp>
  28. namespace rack {
  29. namespace system {
  30. std::list<std::string> getEntries(const std::string& path) {
  31. std::list<std::string> filenames;
  32. DIR* dir = opendir(path.c_str());
  33. if (dir) {
  34. struct dirent* d;
  35. while ((d = readdir(dir))) {
  36. std::string filename = d->d_name;
  37. if (filename == "." || filename == "..")
  38. continue;
  39. filenames.push_back(path + "/" + filename);
  40. }
  41. closedir(dir);
  42. }
  43. filenames.sort();
  44. return filenames;
  45. }
  46. std::list<std::string> getEntriesRecursive(const std::string &path, int depth) {
  47. std::list<std::string> entries = getEntries(path);
  48. if (depth > 0) {
  49. // Don't iterate using iterators because the list will be growing.
  50. size_t limit = entries.size();
  51. auto it = entries.begin();
  52. for (size_t i = 0; i < limit; i++) {
  53. const std::string &entry = *it++;
  54. if (isDirectory(entry)) {
  55. std::list<std::string> subEntries = getEntriesRecursive(entry, depth - 1);
  56. // Append subEntries to entries
  57. entries.splice(entries.end(), subEntries);
  58. }
  59. }
  60. }
  61. return entries;
  62. }
  63. bool isFile(const std::string& path) {
  64. struct stat statbuf;
  65. if (stat(path.c_str(), &statbuf))
  66. return false;
  67. return S_ISREG(statbuf.st_mode);
  68. }
  69. bool isDirectory(const std::string& path) {
  70. struct stat statbuf;
  71. if (stat(path.c_str(), &statbuf))
  72. return false;
  73. return S_ISDIR(statbuf.st_mode);
  74. }
  75. void moveFile(const std::string& srcPath, const std::string& destPath) {
  76. std::remove(destPath.c_str());
  77. // Whether this overwrites existing files is implementation-defined.
  78. // i.e. Mingw64 fails to overwrite.
  79. // This is why we remove the file above.
  80. std::rename(srcPath.c_str(), destPath.c_str());
  81. }
  82. void copyFile(const std::string& srcPath, const std::string& destPath) {
  83. // Open source
  84. FILE* source = fopen(srcPath.c_str(), "rb");
  85. if (!source)
  86. return;
  87. DEFER({
  88. fclose(source);
  89. });
  90. // Open destination
  91. FILE* dest = fopen(destPath.c_str(), "wb");
  92. if (!dest)
  93. return;
  94. DEFER({
  95. fclose(dest);
  96. });
  97. // Copy buffer
  98. const int bufferSize = (1 << 15);
  99. char buffer[bufferSize];
  100. while (1) {
  101. size_t size = fread(buffer, 1, bufferSize, source);
  102. if (size == 0)
  103. break;
  104. size = fwrite(buffer, 1, size, dest);
  105. if (size == 0)
  106. break;
  107. }
  108. }
  109. void createDirectory(const std::string& path) {
  110. #if defined ARCH_WIN
  111. std::u16string pathU16 = string::UTF8toUTF16(path);
  112. _wmkdir((wchar_t*) pathU16.c_str());
  113. #else
  114. mkdir(path.c_str(), 0755);
  115. #endif
  116. }
  117. void createDirectories(const std::string& path) {
  118. for (size_t i = 1; i < path.size(); i++) {
  119. char c = path[i];
  120. if (c == '/' || c == '\\')
  121. createDirectory(path.substr(0, i));
  122. }
  123. createDirectory(path);
  124. }
  125. void removeDirectory(const std::string& path) {
  126. #if defined ARCH_WIN
  127. std::u16string pathU16 = string::UTF8toUTF16(path);
  128. _wrmdir((wchar_t*) pathU16.c_str());
  129. #else
  130. rmdir(path.c_str());
  131. #endif
  132. }
  133. void removeDirectories(const std::string& path) {
  134. removeDirectory(path);
  135. for (size_t i = path.size() - 1; i >= 1; i--) {
  136. char c = path[i];
  137. if (c == '/' || c == '\\')
  138. removeDirectory(path.substr(0, i));
  139. }
  140. }
  141. std::string getWorkingDirectory() {
  142. #if defined ARCH_WIN
  143. char16_t buf[4096] = u"";
  144. GetCurrentDirectory(sizeof(buf), (wchar_t*) buf);
  145. return string::UTF16toUTF8(buf);
  146. #else
  147. char buf[4096] = "";
  148. getcwd(buf, sizeof(buf));
  149. return buf;
  150. #endif
  151. }
  152. void setWorkingDirectory(const std::string& path) {
  153. #if defined ARCH_WIN
  154. std::u16string pathU16 = string::UTF8toUTF16(path);
  155. SetCurrentDirectory((wchar_t*) pathU16.c_str());
  156. #else
  157. chdir(path.c_str());
  158. #endif
  159. }
  160. int getLogicalCoreCount() {
  161. return std::thread::hardware_concurrency();
  162. }
  163. void setThreadName(const std::string& name) {
  164. #if defined ARCH_LIN
  165. pthread_setname_np(pthread_self(), name.c_str());
  166. #elif defined ARCH_WIN
  167. // Unsupported on Windows
  168. #endif
  169. }
  170. std::string getStackTrace() {
  171. int stackLen = 128;
  172. void* stack[stackLen];
  173. std::string s;
  174. #if defined ARCH_LIN || defined ARCH_MAC
  175. stackLen = backtrace(stack, stackLen);
  176. char** strings = backtrace_symbols(stack, stackLen);
  177. // Skip the first line because it's this function.
  178. for (int i = 1; i < stackLen; i++) {
  179. s += string::f("%d: ", stackLen - i - 1);
  180. std::string line = strings[i];
  181. #if 0
  182. // Parse line
  183. std::regex r(R"((.*)\((.*)\+(.*)\) (.*))");
  184. std::smatch match;
  185. if (std::regex_search(line, match, r)) {
  186. s += match[1].str();
  187. s += "(";
  188. std::string symbol = match[2].str();
  189. // Demangle symbol
  190. char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL);
  191. if (symbolD) {
  192. symbol = symbolD;
  193. free(symbolD);
  194. }
  195. s += symbol;
  196. s += "+";
  197. s += match[3].str();
  198. s += ")";
  199. }
  200. #else
  201. s += line;
  202. #endif
  203. s += "\n";
  204. }
  205. free(strings);
  206. #elif defined ARCH_WIN
  207. HANDLE process = GetCurrentProcess();
  208. SymInitialize(process, NULL, true);
  209. stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL);
  210. SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1);
  211. symbol->MaxNameLen = 255;
  212. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  213. for (int i = 1; i < stackLen; i++) {
  214. SymFromAddr(process, (DWORD64) stack[i], 0, symbol);
  215. s += string::f("%d: %s 0x%0x\n", stackLen - i - 1, symbol->Name, symbol->Address);
  216. }
  217. free(symbol);
  218. #endif
  219. return s;
  220. }
  221. int64_t getNanoseconds() {
  222. #if defined ARCH_WIN
  223. LARGE_INTEGER counter;
  224. QueryPerformanceCounter(&counter);
  225. LARGE_INTEGER frequency;
  226. QueryPerformanceFrequency(&frequency);
  227. // TODO Check if this is always an integer factor on all CPUs
  228. int64_t nsPerTick = 1000000000LL / frequency.QuadPart;
  229. int64_t time = counter.QuadPart * nsPerTick;
  230. return time;
  231. #endif
  232. #if defined ARCH_LIN
  233. struct timespec ts;
  234. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  235. int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  236. return time;
  237. #endif
  238. #if defined ARCH_MAC
  239. using clock = std::chrono::high_resolution_clock;
  240. using time_point = std::chrono::time_point<clock>;
  241. time_point now = clock::now();
  242. using duration = std::chrono::duration<int64_t, std::nano>;
  243. duration d = now.time_since_epoch();
  244. return d.count();
  245. #endif
  246. }
  247. void openBrowser(const std::string& url) {
  248. #if defined ARCH_LIN
  249. std::string command = "xdg-open \"" + url + "\"";
  250. (void) std::system(command.c_str());
  251. #endif
  252. #if defined ARCH_MAC
  253. std::string command = "open \"" + url + "\"";
  254. std::system(command.c_str());
  255. #endif
  256. #if defined ARCH_WIN
  257. std::u16string urlW = string::UTF8toUTF16(url);
  258. ShellExecuteW(NULL, L"open", (wchar_t*) urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT);
  259. #endif
  260. }
  261. void openFolder(const std::string& path) {
  262. #if defined ARCH_LIN
  263. std::string command = "xdg-open \"" + path + "\"";
  264. (void) std::system(command.c_str());
  265. #endif
  266. #if defined ARCH_MAC
  267. std::string command = "open \"" + path + "\"";
  268. std::system(command.c_str());
  269. #endif
  270. #if defined ARCH_WIN
  271. std::u16string pathU16 = string::UTF8toUTF16(path);
  272. ShellExecuteW(NULL, L"explore", (wchar_t*) pathU16.c_str(), NULL, NULL, SW_SHOWDEFAULT);
  273. #endif
  274. }
  275. void runProcessDetached(const std::string& path) {
  276. #if defined ARCH_WIN
  277. SHELLEXECUTEINFOW shExInfo;
  278. ZeroMemory(&shExInfo, sizeof(shExInfo));
  279. shExInfo.cbSize = sizeof(shExInfo);
  280. shExInfo.lpVerb = L"runas";
  281. std::u16string pathU16 = string::UTF8toUTF16(path);
  282. shExInfo.lpFile = (wchar_t*) pathU16.c_str();
  283. shExInfo.nShow = SW_SHOW;
  284. if (ShellExecuteExW(&shExInfo)) {
  285. // Do nothing
  286. }
  287. #else
  288. // Not implemented on Linux or Mac
  289. assert(0);
  290. #endif
  291. }
  292. std::string getOperatingSystemInfo() {
  293. #if defined ARCH_LIN || defined ARCH_MAC
  294. struct utsname u;
  295. uname(&u);
  296. return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine);
  297. #elif defined ARCH_WIN
  298. OSVERSIONINFOW info;
  299. ZeroMemory(&info, sizeof(info));
  300. info.dwOSVersionInfoSize = sizeof(info);
  301. GetVersionExW(&info);
  302. // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers.
  303. return string::f("Windows %u.%u", info.dwMajorVersion, info.dwMinorVersion);
  304. #endif
  305. }
  306. int unzipToFolder(const std::string& zipPath, const std::string& dir) {
  307. int err;
  308. // Open ZIP file
  309. zip_t* za = zip_open(zipPath.c_str(), 0, &err);
  310. if (!za) {
  311. WARN("Could not open ZIP file %s: error %d", zipPath.c_str(), err);
  312. return err;
  313. }
  314. DEFER({
  315. zip_close(za);
  316. });
  317. // Iterate ZIP entries
  318. for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
  319. zip_stat_t zs;
  320. err = zip_stat_index(za, i, 0, &zs);
  321. if (err) {
  322. WARN("zip_stat_index() failed: error %d", err);
  323. return err;
  324. }
  325. std::string path = dir + "/" + zs.name;
  326. if (path[path.size() - 1] == '/') {
  327. // Create directory
  328. system::createDirectory(path);
  329. // HACK
  330. // Create and delete file to update the directory's mtime.
  331. std::string tmpPath = path + "/.tmp";
  332. FILE* tmpFile = fopen(tmpPath.c_str(), "w");
  333. fclose(tmpFile);
  334. std::remove(tmpPath.c_str());
  335. }
  336. else {
  337. // Open ZIP entry
  338. zip_file_t* zf = zip_fopen_index(za, i, 0);
  339. if (!zf) {
  340. WARN("zip_fopen_index() failed");
  341. return -1;
  342. }
  343. DEFER({
  344. zip_fclose(zf);
  345. });
  346. // Create file
  347. FILE* outFile = fopen(path.c_str(), "wb");
  348. if (!outFile) {
  349. WARN("Could not create file %s", path.c_str());
  350. return -1;
  351. }
  352. DEFER({
  353. fclose(outFile);
  354. });
  355. // Read buffer and copy to file
  356. while (true) {
  357. char buffer[1 << 15];
  358. int len = zip_fread(zf, buffer, sizeof(buffer));
  359. if (len <= 0)
  360. break;
  361. fwrite(buffer, 1, len, outFile);
  362. }
  363. }
  364. }
  365. return 0;
  366. }
  367. } // namespace system
  368. } // namespace rack