|
- #include <thread>
- #include <regex>
- #include <chrono>
- #include <ghc/filesystem.hpp>
-
- #include <dirent.h>
- #include <sys/stat.h>
- #include <cxxabi.h> // for __cxxabiv1::__cxa_demangle
-
- #if defined ARCH_LIN || defined ARCH_MAC
- #include <pthread.h>
- #include <time.h> // for clock_gettime
- #include <sched.h>
- #include <execinfo.h> // for backtrace and backtrace_symbols
- #include <unistd.h> // for execl
- #include <sys/utsname.h>
- #endif
-
- #if defined ARCH_MAC
- #include <mach/mach_init.h>
- #include <mach/thread_act.h>
- #include <mach/clock.h>
- #include <mach/mach.h>
- #endif
-
- #if defined ARCH_WIN
- #include <windows.h>
- #include <shellapi.h>
- #include <processthreadsapi.h>
- #include <dbghelp.h>
- #endif
-
- #include <archive.h>
- #include <archive_entry.h>
-
- #include <system.hpp>
- #include <string.hpp>
-
-
- /*
- 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<std::string> getEntries(const std::string& dirPath, int depth) {
- try {
- std::list<std::string> 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<std::string> subEntries = getEntries(subEntry, depth - 1);
- entries.splice(entries.end(), subEntries);
- }
- }
- }
- return entries;
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- bool exists(const std::string& path) {
- try {
- return fs::exists(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- bool isFile(const std::string& path) {
- try {
- return fs::is_regular_file(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- bool isDir(const std::string& path) {
- try {
- return fs::is_directory(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- uint64_t getFileSize(const std::string& path) {
- try {
- return fs::file_size(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", 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("%s", 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("%s", e.what());
- }
- }
-
-
- bool createDir(const std::string& path) {
- try {
- return fs::create_directory(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- bool createDirs(const std::string& path) {
- try {
- return fs::create_directories(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- bool remove(const std::string& path) {
- try {
- return fs::remove(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- int removeRecursively(const std::string& path) {
- try {
- return fs::remove_all(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- std::string getWorkingDir() {
- try {
- return fs::current_path().generic_u8string();
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- void setWorkingDir(const std::string& path) {
- try {
- fs::current_path(fs::u8path(path));
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- std::string getTempDir() {
- try {
- return fs::temp_directory_path().generic_u8string();
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", 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("%s", 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("%s", e.what());
- }
- }
-
-
- std::string getDir(const std::string& path) {
- try {
- return fs::u8path(path).parent_path().generic_u8string();
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- std::string getFilename(const std::string& path) {
- try {
- return fs::u8path(path).filename().generic_u8string();
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- std::string getStem(const std::string& path) {
- try {
- return fs::u8path(path).stem().generic_u8string();
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", e.what());
- }
- }
-
-
- std::string getExtension(const std::string& path) {
- try {
- return fs::u8path(path).extension().generic_u8string();
- }
- catch (fs::filesystem_error& e) {
- throw Exception("%s", 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("%s", 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());
- }
-
-
- static la_ssize_t archiveWriteVectorCallback(struct archive* a, void* client_data, const void* buffer, size_t length) {
- assert(client_data);
- std::vector<uint8_t>& data = *((std::vector<uint8_t>*) client_data);
- uint8_t* buf = (uint8_t*) buffer;
- data.insert(data.end(), buf, buf + length);
- return length;
- }
-
-
- static void archiveDir(const std::string& archivePath, std::vector<uint8_t>* archiveData, const std::string& dirPath, 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);});
- // For some reason libarchive adds 10k of padding to archive_write_open() (but not archive_write_open_filename()) unless this is set to 0.
- archive_write_set_bytes_per_block(a, 0);
- 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("Archiver could not set filter option: %s", archive_error_string(a));
-
- if (archiveData) {
- // Open vector
- archive_write_open(a, (void*) archiveData, NULL, archiveWriteVectorCallback, NULL);
- }
- else {
- // Open file
- #if defined ARCH_WIN
- r = archive_write_open_filename_w(a, string::UTF8toUTF16(archivePath).c_str());
- #else
- r = archive_write_open_filename(a, archivePath.c_str());
- #endif
- if (r < ARCHIVE_OK)
- throw Exception("Archiver could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a));
- }
- DEFER({archive_write_close(a);});
-
- // Open dir 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::UTF8toUTF16(dirPath).c_str());
- #else
- r = archive_read_disk_open(disk, dirPath.c_str());
- #endif
- if (r < ARCHIVE_OK)
- throw Exception("Archiver could not open dir %s for reading: %s", dirPath.c_str(), archive_error_string(a));
- DEFER({archive_read_close(a);});
-
- // Iterate dir
- 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("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::UTF16toUTF8(archive_entry_pathname_w(entry));
- #else
- entryPath = archive_entry_pathname(entry);
- #endif
- entryPath = getRelativePath(entryPath, dirPath);
- #if defined ARCH_WIN
- // FIXME This doesn't seem to set UTF-8 paths on Windows.
- archive_entry_copy_pathname_w(entry, string::UTF8toUTF16(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("Archiver could not write entry to archive: %s", archive_error_string(a));
-
- // Manually copy data
- #if defined ARCH_WIN
- std::string entrySourcePath = string::UTF16toUTF8(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 archiveDir(const std::string& archivePath, const std::string& dirPath, int compressionLevel) {
- archiveDir(archivePath, NULL, dirPath, compressionLevel);
- }
-
- std::vector<uint8_t> archiveDir(const std::string& dirPath, int compressionLevel) {
- std::vector<uint8_t> archiveData;
- archiveDir("", &archiveData, dirPath, compressionLevel);
- return archiveData;
- }
-
-
- struct ArchiveReadVectorData {
- const std::vector<uint8_t>* data = NULL;
- size_t pos = 0;
- };
-
- static la_ssize_t archiveReadVectorCallback(struct archive *a, void* client_data, const void** buffer) {
- assert(client_data);
- ArchiveReadVectorData* arvd = (ArchiveReadVectorData*) client_data;
- assert(arvd->data);
- const std::vector<uint8_t>& data = *arvd->data;
- *buffer = &data[arvd->pos];
- // Read up to some block size of bytes
- size_t len = std::min(data.size() - arvd->pos, size_t(1 << 16));
- arvd->pos += len;
- return len;
- }
-
- static void unarchiveToDir(const std::string& archivePath, const std::vector<uint8_t>* archiveData, const std::string& dirPath) {
- // 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);
-
- ArchiveReadVectorData arvd;
- if (archiveData) {
- // Open vector
- arvd.data = archiveData;
- archive_read_open(a, &arvd, NULL, archiveReadVectorCallback, NULL);
- }
- else {
- // Open file
- #if defined ARCH_WIN
- r = archive_read_open_filename_w(a, string::UTF8toUTF16(archivePath).c_str(), 1 << 16);
- #else
- r = archive_read_open_filename(a, archivePath.c_str(), 1 << 16);
- #endif
- if (r < ARCHIVE_OK)
- throw Exception("Unarchiver could not open archive %s: %s", archivePath.c_str(), archive_error_string(a));
- }
- DEFER({archive_read_close(a);});
-
- // Open dir 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("Unarchiver could not read entry from archive: %s", archive_error_string(a));
-
- // Convert relative pathname to absolute based on dirPath
- std::string entryPath = archive_entry_pathname(entry);
- if (!fs::u8path(entryPath).is_relative())
- throw Exception("Unarchiver does not support absolute tar paths: %s", entryPath.c_str());
- entryPath = (fs::u8path(dirPath) / fs::u8path(entryPath)).generic_u8string();
- #if defined ARCH_WIN
- archive_entry_copy_pathname_w(entry, string::UTF8toUTF16(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("Unarchiver could not write file to dir: %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("Unarchiver could not read data from archive: %s", archive_error_string(a));
-
- // Write data to file
- r = archive_write_data_block(disk, buf, size, offset);
- if (r < ARCHIVE_OK)
- throw Exception("Unarchiver could not write data to file: %s", archive_error_string(disk));
- }
-
- // Close file
- r = archive_write_finish_entry(disk);
- if (r < ARCHIVE_OK)
- throw Exception("Unarchiver could not close file: %s", archive_error_string(disk));
- }
- }
-
- void unarchiveToDir(const std::string& archivePath, const std::string& dirPath) {
- unarchiveToDir(archivePath, NULL, dirPath);
- }
-
- void unarchiveToDir(const std::vector<uint8_t>& archiveData, const std::string& dirPath) {
- unarchiveToDir("", &archiveData, dirPath);
- }
-
-
- 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%" PRIx64 "\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 initTime() {
- #if defined ARCH_WIN
- LARGE_INTEGER counter;
- QueryPerformanceCounter(&counter);
- startCounter = counter.QuadPart;
-
- LARGE_INTEGER frequency;
- QueryPerformanceFrequency(&frequency);
- counterTime = 1.0 / frequency.QuadPart;
- #endif
- #if defined ARCH_LIN
- struct timespec ts;
- clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
- startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
- #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);
- startTime = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec;
- #endif
- }
-
-
- double getTime() {
- #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() {
- // This is not guaranteed to return the time since 1970 in C++11. (It only does in C++20).
- // However, it does on all platforms I care about.
- auto duration = std::chrono::system_clock::now().time_since_epoch();
- return std::chrono::duration<double>(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 %lu.%lu", 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::UTF8toUTF16(url).c_str(), NULL, NULL, SW_SHOWDEFAULT);
- #endif
- }
-
-
- void openDir(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::UTF8toUTF16(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::UTF8toUTF16(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() {
- initTime();
- }
-
-
- } // namespace system
- } // namespace rack
|