#include #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 #include #include #endif #if defined ARCH_WIN #include #include #include #include #endif #include #include #include #include /* In C++17, this will be `std::filesystem` Important: When using `fs::path`, always convert strings to UTF-8 using fs::path p = fs::u8path(s); In fact, it's best to work only with strings to avoid forgetting to decode a string as UTF-8. The need to do this is a fatal flaw of `fs::path`, but at least `std::filesystem` has some helpful operations. */ namespace fs = ghc::filesystem; namespace rack { namespace system { std::string join(const std::string& path1, const std::string& path2) { return (fs::u8path(path1) / fs::u8path(path2)).generic_u8string(); } std::list getEntries(const std::string& dirPath, int depth) { try { std::list entries; for (auto& entry : fs::directory_iterator(fs::u8path(dirPath))) { std::string subEntry = entry.path().generic_u8string(); entries.push_back(subEntry); // Recurse if depth > 0 (limited recursion) or depth < 0 (infinite recursion). if (depth != 0) { if (fs::is_directory(entry.path())) { std::list subEntries = getEntries(subEntry, depth - 1); entries.splice(entries.end(), subEntries); } } } return entries; } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } bool doesExist(const std::string& path) { try { return fs::exists(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } bool isFile(const std::string& path) { try { return fs::is_regular_file(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } bool isDirectory(const std::string& path) { try { return fs::is_directory(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } uint64_t getFileSize(const std::string& path) { try { return fs::file_size(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } void rename(const std::string& srcPath, const std::string& destPath) { try { fs::rename(fs::u8path(srcPath), fs::u8path(destPath)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } void copy(const std::string& srcPath, const std::string& destPath) { try { fs::copy(fs::u8path(srcPath), fs::u8path(destPath), fs::copy_options::recursive); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } bool createDirectory(const std::string& path) { try { return fs::create_directory(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } bool createDirectories(const std::string& path) { try { return fs::create_directories(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } bool remove(const std::string& path) { try { return fs::remove(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } int removeRecursively(const std::string& path) { try { return fs::remove_all(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getWorkingDirectory() { try { return fs::current_path().generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } void setWorkingDirectory(const std::string& path) { try { fs::current_path(fs::u8path(path)); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getTempDir() { try { return fs::temp_directory_path().generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getAbsolute(const std::string& path) { try { return fs::absolute(fs::u8path(path)).generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getCanonical(const std::string& path) { try { return fs::canonical(fs::u8path(path)).generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getDirectory(const std::string& path) { try { return fs::u8path(path).parent_path().generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getFilename(const std::string& path) { try { return fs::u8path(path).filename().generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getStem(const std::string& path) { try { return fs::u8path(path).stem().generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } std::string getExtension(const std::string& path) { try { return fs::u8path(path).extension().generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } } /** Returns `p` in relative path form, relative to `base` Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path. */ static std::string getRelativePath(std::string path, std::string base) { try { path = fs::absolute(fs::u8path(path)).generic_u8string(); base = fs::absolute(fs::u8path(base)).generic_u8string(); } catch (fs::filesystem_error& e) { throw Exception(e.what()); } if (path.size() < base.size()) throw Exception("getRelativePath() error: path is shorter than base"); if (!std::equal(base.begin(), base.end(), path.begin())) throw Exception("getRelativePath() error: path does not begin with base"); // If path == base, this correctly returns "." return "." + std::string(path.begin() + base.size(), path.end()); } void archiveFolder(const std::string& archivePath, const std::string& folderPath, int compressionLevel) { // Based on minitar.c create() in libarchive examples int r; // Open archive for writing struct archive* a = archive_write_new(); DEFER({archive_write_free(a);}); archive_write_set_format_ustar(a); archive_write_add_filter_zstd(a); assert(0 <= compressionLevel && compressionLevel <= 19); r = archive_write_set_filter_option(a, NULL, "compression-level", std::to_string(compressionLevel).c_str()); if (r < ARCHIVE_OK) throw Exception(string::f("Archiver could not set filter option: %s", archive_error_string(a))); #if defined ARCH_WIN r = archive_write_open_filename_w(a, string::U8toU16(archivePath).c_str()); #else r = archive_write_open_filename(a, archivePath.c_str()); #endif if (r < ARCHIVE_OK) throw Exception(string::f("Archiver could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a))); DEFER({archive_write_close(a);}); // Open folder for reading struct archive* disk = archive_read_disk_new(); DEFER({archive_read_free(disk);}); #if defined ARCH_WIN r = archive_read_disk_open_w(disk, string::U8toU16(folderPath).c_str()); #else r = archive_read_disk_open(disk, folderPath.c_str()); #endif if (r < ARCHIVE_OK) throw Exception(string::f("Archiver could not open folder %s for reading: %s", folderPath.c_str(), archive_error_string(a))); DEFER({archive_read_close(a);}); // Iterate folder for (;;) { struct archive_entry* entry = archive_entry_new(); DEFER({archive_entry_free(entry);}); r = archive_read_next_header2(disk, entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) throw Exception(string::f("Archiver could not get next entry from archive: %s", archive_error_string(disk))); // Recurse dirs archive_read_disk_descend(disk); // Convert absolute path to relative path std::string entryPath; #if defined ARCH_WIN entryPath = string::U16toU8(archive_entry_pathname_w(entry)); #else entryPath = archive_entry_pathname(entry); #endif entryPath = getRelativePath(entryPath, folderPath); #if defined ARCH_WIN // FIXME This doesn't seem to set UTF-8 paths on Windows. archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str()); #else archive_entry_set_pathname(entry, entryPath.c_str()); #endif // Write file to archive r = archive_write_header(a, entry); if (r < ARCHIVE_OK) throw Exception(string::f("Archiver could not write entry to archive: %s", archive_error_string(a))); // Manually copy data #if defined ARCH_WIN std::string entrySourcePath = string::U16toU8(archive_entry_sourcepath_w(entry)); #else std::string entrySourcePath = archive_entry_sourcepath(entry); #endif FILE* f = std::fopen(entrySourcePath.c_str(), "rb"); DEFER({std::fclose(f);}); char buf[1 << 16]; ssize_t len; while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) { archive_write_data(a, buf, len); } } } void unarchiveToFolder(const std::string& archivePath, const std::string& folderPath) { // Based on minitar.c extract() in libarchive examples int r; // Open archive for reading struct archive* a = archive_read_new(); DEFER({archive_read_free(a);}); archive_read_support_filter_zstd(a); // archive_read_support_filter_all(a); archive_read_support_format_tar(a); // archive_read_support_format_all(a); #if defined ARCH_WIN r = archive_read_open_filename_w(a, string::U8toU16(archivePath).c_str(), 1 << 16); #else r = archive_read_open_filename(a, archivePath.c_str(), 1 << 14); #endif if (r < ARCHIVE_OK) throw Exception(string::f("Unarchiver could not open archive %s: %s", archivePath.c_str(), archive_error_string(a))); DEFER({archive_read_close(a);}); // Open folder for writing struct archive* disk = archive_write_disk_new(); DEFER({archive_write_free(disk);}); int flags = ARCHIVE_EXTRACT_TIME; archive_write_disk_set_options(disk, flags); DEFER({archive_write_close(disk);}); // Iterate archive for (;;) { // Get next entry struct archive_entry* entry; r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) throw Exception(string::f("Unarchiver could not read entry from archive: %s", archive_error_string(a))); // Convert relative pathname to absolute based on folderPath std::string entryPath = archive_entry_pathname(entry); if (!fs::u8path(entryPath).is_relative()) throw Exception(string::f("Unarchiver does not support absolute tar paths: %s", entryPath.c_str())); entryPath = (fs::u8path(folderPath) / fs::u8path(entryPath)).generic_u8string(); #if defined ARCH_WIN archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str()); #else archive_entry_set_pathname(entry, entryPath.c_str()); #endif // Write entry to disk r = archive_write_header(disk, entry); if (r < ARCHIVE_OK) throw Exception(string::f("Unarchiver could not write file to folder: %s", archive_error_string(disk))); // Copy data to file for (;;) { const void* buf; size_t size; int64_t offset; // Read data from archive r = archive_read_data_block(a, &buf, &size, &offset); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) throw Exception(string::f("Unarchiver could not read data from archive", archive_error_string(a))); // Write data to file r = archive_write_data_block(disk, buf, size, offset); if (r < ARCHIVE_OK) throw Exception(string::f("Unarchiver could not write data to file", archive_error_string(disk))); } // Close file r = archive_write_finish_entry(disk); if (r < ARCHIVE_OK) throw Exception(string::f("Unarchiver could not close file", archive_error_string(disk))); } } 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; } #if defined ARCH_WIN static int64_t startCounter = 0; static double counterTime = 0.0; #endif #if defined ARCH_LIN || defined ARCH_MAC static int64_t startTime = 0; #endif static void initRuntime() { #if defined ARCH_WIN assert(startCounter == 0); LARGE_INTEGER counter; QueryPerformanceCounter(&counter); startCounter = counter.QuadPart; LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); counterTime = 1.0 / frequency.QuadPart; #endif #if defined ARCH_LIN assert(startTime == 0); struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; #endif #if defined ARCH_MAC assert(startTime == 0); clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); startTime = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec; #endif } double getRuntime() { #if defined ARCH_WIN LARGE_INTEGER counter; QueryPerformanceCounter(&counter); return (counter.QuadPart - startCounter) * counterTime; #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 - startTime) / 1e9; #endif #if defined ARCH_MAC clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); int64_t time = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec; return (time - startTime) / 1e9; #endif } double getUnixTime() { auto duration = std::chrono::system_clock::now().time_since_epoch(); return std::chrono::duration(duration).count(); } 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 } 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 ShellExecuteW(NULL, L"open", string::U8toU16(url).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 ShellExecuteW(NULL, L"explore", string::U8toU16(path).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::wstring pathW = string::U8toU16(path); shExInfo.lpFile = pathW.c_str(); shExInfo.nShow = SW_SHOW; if (ShellExecuteExW(&shExInfo)) { // Do nothing } #else // Not implemented on Linux or Mac assert(0); #endif } void init() { initRuntime(); } } // namespace system } // namespace rack