#include #include #include #include #include #include // for __cxxabiv1::__cxa_demangle #if defined ARCH_LIN || defined ARCH_MAC #include #include #include // for backtrace and backtrace_symbols #include // for execl #include #endif #if defined ARCH_MAC #include #include #endif #if defined ARCH_WIN #include #include #include #include #endif #define ZIP_STATIC #include #include #include namespace rack { namespace system { std::list getEntries(const std::string& path) { std::list filenames; DIR* dir = opendir(path.c_str()); if (dir) { struct dirent* d; while ((d = readdir(dir))) { std::string filename = d->d_name; if (filename == "." || filename == "..") continue; filenames.push_back(path + "/" + filename); } closedir(dir); } filenames.sort(); return filenames; } std::list getEntriesRecursive(const std::string &path, int depth) { std::list entries = getEntries(path); if (depth > 0) { // Don't iterate using iterators because the list will be growing. size_t limit = entries.size(); auto it = entries.begin(); for (size_t i = 0; i < limit; i++) { const std::string &entry = *it++; if (isDirectory(entry)) { std::list subEntries = getEntriesRecursive(entry, depth - 1); // Append subEntries to entries entries.splice(entries.end(), subEntries); } } } return entries; } bool isFile(const std::string& path) { struct stat statbuf; if (stat(path.c_str(), &statbuf)) return false; return S_ISREG(statbuf.st_mode); } bool isDirectory(const std::string& path) { struct stat statbuf; if (stat(path.c_str(), &statbuf)) return false; return S_ISDIR(statbuf.st_mode); } void moveFile(const std::string& srcPath, const std::string& destPath) { std::remove(destPath.c_str()); // Whether this overwrites existing files is implementation-defined. // i.e. Mingw64 fails to overwrite. // This is why we remove the file above. std::rename(srcPath.c_str(), destPath.c_str()); } void copyFile(const std::string& srcPath, const std::string& destPath) { // Open source FILE* source = fopen(srcPath.c_str(), "rb"); if (!source) return; DEFER({ fclose(source); }); // Open destination FILE* dest = fopen(destPath.c_str(), "wb"); if (!dest) return; DEFER({ fclose(dest); }); // Copy buffer const int bufferSize = (1 << 15); char buffer[bufferSize]; while (1) { size_t size = fread(buffer, 1, bufferSize, source); if (size == 0) break; size = fwrite(buffer, 1, size, dest); if (size == 0) break; } } void createDirectory(const std::string& path) { #if defined ARCH_WIN std::u16string pathU16 = string::UTF8toUTF16(path); _wmkdir((wchar_t*) pathU16.c_str()); #else mkdir(path.c_str(), 0755); #endif } void createDirectories(const std::string& path) { for (size_t i = 1; i < path.size(); i++) { char c = path[i]; if (c == '/' || c == '\\') createDirectory(path.substr(0, i)); } createDirectory(path); } void removeDirectory(const std::string& path) { #if defined ARCH_WIN std::u16string pathU16 = string::UTF8toUTF16(path); _wrmdir((wchar_t*) pathU16.c_str()); #else rmdir(path.c_str()); #endif } void removeDirectories(const std::string& path) { removeDirectory(path); for (size_t i = path.size() - 1; i >= 1; i--) { char c = path[i]; if (c == '/' || c == '\\') removeDirectory(path.substr(0, i)); } } std::string getWorkingDirectory() { #if defined ARCH_WIN char16_t buf[4096] = u""; GetCurrentDirectory(sizeof(buf), (wchar_t*) buf); return string::UTF16toUTF8(buf); #else char buf[4096] = ""; getcwd(buf, sizeof(buf)); return buf; #endif } void setWorkingDirectory(const std::string& path) { #if defined ARCH_WIN std::u16string pathU16 = string::UTF8toUTF16(path); SetCurrentDirectory((wchar_t*) pathU16.c_str()); #else chdir(path.c_str()); #endif } int getLogicalCoreCount() { return std::thread::hardware_concurrency(); } void setThreadName(const std::string& name) { #if defined ARCH_LIN pthread_setname_np(pthread_self(), name.c_str()); #elif defined ARCH_WIN // Unsupported on Windows #endif } std::string getStackTrace() { int stackLen = 128; void* stack[stackLen]; std::string s; #if defined ARCH_LIN || defined ARCH_MAC stackLen = backtrace(stack, stackLen); char** strings = backtrace_symbols(stack, stackLen); // Skip the first line because it's this function. for (int i = 1; i < stackLen; i++) { s += string::f("%d: ", stackLen - i - 1); std::string line = strings[i]; #if 0 // Parse line std::regex r(R"((.*)\((.*)\+(.*)\) (.*))"); std::smatch match; if (std::regex_search(line, match, r)) { s += match[1].str(); s += "("; std::string symbol = match[2].str(); // Demangle symbol char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL); if (symbolD) { symbol = symbolD; free(symbolD); } s += symbol; s += "+"; s += match[3].str(); s += ")"; } #else s += line; #endif s += "\n"; } free(strings); #elif defined ARCH_WIN HANDLE process = GetCurrentProcess(); SymInitialize(process, NULL, true); stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL); SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); for (int i = 1; i < stackLen; i++) { SymFromAddr(process, (DWORD64) stack[i], 0, symbol); s += string::f("%d: %s 0x%0x\n", stackLen - i - 1, symbol->Name, symbol->Address); } free(symbol); #endif return s; } int64_t getNanoseconds() { #if defined ARCH_WIN LARGE_INTEGER counter; QueryPerformanceCounter(&counter); LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); // TODO Check if this is always an integer factor on all CPUs int64_t nsPerTick = 1000000000LL / frequency.QuadPart; int64_t time = counter.QuadPart * nsPerTick; return time; #endif #if defined ARCH_LIN struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; return time; #endif #if defined ARCH_MAC using clock = std::chrono::high_resolution_clock; using time_point = std::chrono::time_point; time_point now = clock::now(); using duration = std::chrono::duration; duration d = now.time_since_epoch(); return d.count(); #endif } void openBrowser(const std::string& url) { #if defined ARCH_LIN std::string command = "xdg-open \"" + url + "\""; (void) std::system(command.c_str()); #endif #if defined ARCH_MAC std::string command = "open \"" + url + "\""; std::system(command.c_str()); #endif #if defined ARCH_WIN std::u16string urlW = string::UTF8toUTF16(url); ShellExecuteW(NULL, L"open", (wchar_t*) urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); #endif } void openFolder(const std::string& path) { #if defined ARCH_LIN std::string command = "xdg-open \"" + path + "\""; (void) std::system(command.c_str()); #endif #if defined ARCH_MAC std::string command = "open \"" + path + "\""; std::system(command.c_str()); #endif #if defined ARCH_WIN std::u16string pathU16 = string::UTF8toUTF16(path); ShellExecuteW(NULL, L"explore", (wchar_t*) pathU16.c_str(), NULL, NULL, SW_SHOWDEFAULT); #endif } void runProcessDetached(const std::string& path) { #if defined ARCH_WIN SHELLEXECUTEINFOW shExInfo; ZeroMemory(&shExInfo, sizeof(shExInfo)); shExInfo.cbSize = sizeof(shExInfo); shExInfo.lpVerb = L"runas"; std::u16string pathU16 = string::UTF8toUTF16(path); shExInfo.lpFile = (wchar_t*) pathU16.c_str(); shExInfo.nShow = SW_SHOW; if (ShellExecuteExW(&shExInfo)) { // Do nothing } #else // Not implemented on Linux or Mac assert(0); #endif } std::string getOperatingSystemInfo() { #if defined ARCH_LIN || defined ARCH_MAC struct utsname u; uname(&u); return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine); #elif defined ARCH_WIN OSVERSIONINFOW info; ZeroMemory(&info, sizeof(info)); info.dwOSVersionInfoSize = sizeof(info); GetVersionExW(&info); // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers. return string::f("Windows %u.%u", info.dwMajorVersion, info.dwMinorVersion); #endif } int unzipToFolder(const std::string& zipPath, const std::string& dir) { int err; // Open ZIP file zip_t* za = zip_open(zipPath.c_str(), 0, &err); if (!za) { WARN("Could not open ZIP file %s: error %d", zipPath.c_str(), err); return err; } DEFER({ zip_close(za); }); // Iterate ZIP entries for (int i = 0; i < zip_get_num_entries(za, 0); i++) { zip_stat_t zs; err = zip_stat_index(za, i, 0, &zs); if (err) { WARN("zip_stat_index() failed: error %d", err); return err; } std::string path = dir + "/" + zs.name; if (path[path.size() - 1] == '/') { // Create directory system::createDirectory(path); // HACK // Create and delete file to update the directory's mtime. std::string tmpPath = path + "/.tmp"; FILE* tmpFile = fopen(tmpPath.c_str(), "w"); fclose(tmpFile); std::remove(tmpPath.c_str()); } else { // Open ZIP entry zip_file_t* zf = zip_fopen_index(za, i, 0); if (!zf) { WARN("zip_fopen_index() failed"); return -1; } DEFER({ zip_fclose(zf); }); // Create file FILE* outFile = fopen(path.c_str(), "wb"); if (!outFile) { WARN("Could not create file %s", path.c_str()); return -1; } DEFER({ fclose(outFile); }); // Read buffer and copy to file while (true) { char buffer[1 << 15]; int len = zip_fread(zf, buffer, sizeof(buffer)); if (len <= 0) break; fwrite(buffer, 1, len, outFile); } } } return 0; } } // namespace system } // namespace rack