// Copyright 2021 Jean Pierre Cimalando // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 // #include "ysfx_utils.hpp" #include "base64/Base64.hpp" #include #include #include #include #include #include #if !defined(_WIN32) # include # include # include # include # include #else # include # include #endif namespace ysfx { #if !defined(_WIN32) static_assert(sizeof(off_t) == 8, "64-bit large file support is not enabled"); #endif FILE *fopen_utf8(const char *path, const char *mode) { #if defined(_WIN32) return _wfopen(widen(path).c_str(), widen(mode).c_str()); #else return fopen(path, mode); #endif } int64_t fseek_lfs(FILE *stream, int64_t off, int whence) { #if defined(_WIN32) return _fseeki64(stream, off, whence); #else return fseeko(stream, off, whence); #endif } int64_t ftell_lfs(FILE *stream) { #if defined(_WIN32) return _ftelli64(stream); #else return ftello(stream); #endif } //------------------------------------------------------------------------------ namespace { struct scoped_c_locale { scoped_c_locale(int lc, const char *name); ~scoped_c_locale(); c_locale_t m_loc{}; scoped_c_locale(const scoped_c_locale &) = delete; scoped_c_locale &operator=(const scoped_c_locale &) = delete; }; scoped_c_locale::scoped_c_locale(int lc, const char *name) { #if defined(_WIN32) m_loc = _create_locale(lc, name); #else switch (lc) { case LC_ALL: m_loc = newlocale(LC_ALL_MASK, name, c_locale_t{}); break; case LC_CTYPE: m_loc = newlocale(LC_CTYPE_MASK, name, c_locale_t{}); break; case LC_COLLATE: m_loc = newlocale(LC_COLLATE_MASK, name, c_locale_t{}); break; case LC_MONETARY: m_loc = newlocale(LC_MONETARY_MASK, name, c_locale_t{}); break; case LC_NUMERIC: m_loc = newlocale(LC_NUMERIC_MASK, name, c_locale_t{}); break; case LC_TIME: m_loc = newlocale(LC_TIME_MASK, name, c_locale_t{}); break; case LC_MESSAGES: m_loc = newlocale(LC_MESSAGES_MASK, name, c_locale_t{}); break; default: errno = EINVAL; break; } #endif if (!m_loc) throw std::system_error(errno, std::generic_category()); } scoped_c_locale::~scoped_c_locale() { if (!m_loc) return; #if !defined(_WIN32) freelocale(m_loc); #else _free_locale(m_loc); #endif } #if !defined(_WIN32) struct scoped_posix_uselocale { explicit scoped_posix_uselocale(c_locale_t loc); ~scoped_posix_uselocale(); c_locale_t m_loc{}; c_locale_t m_old{}; scoped_posix_uselocale(const scoped_posix_uselocale &) = delete; scoped_posix_uselocale &operator=(const scoped_posix_uselocale &) = delete; }; scoped_posix_uselocale::scoped_posix_uselocale(c_locale_t loc) { if (loc) { m_loc = loc; m_old = uselocale(loc); } } scoped_posix_uselocale::~scoped_posix_uselocale() { if (m_loc) uselocale(m_old); } #endif } // namespace //------------------------------------------------------------------------------ c_locale_t c_numeric_locale() { static scoped_c_locale loc(LC_NUMERIC, "C"); return loc.m_loc; } //------------------------------------------------------------------------------ double c_atof(const char *text, c_locale_t loc) { #if defined(_WIN32) return _atof_l(text, loc); #else scoped_posix_uselocale use(loc); return atof(text); #endif } double c_strtod(const char *text, char **endp, c_locale_t loc) { #if defined(_WIN32) return _strtod_l(text, endp, loc); #else scoped_posix_uselocale use(loc); return strtod(text, endp); #endif } double dot_atof(const char *text) { return c_atof(text, c_numeric_locale()); } double dot_strtod(const char *text, char **endp) { return c_strtod(text, endp, c_numeric_locale()); } bool ascii_isspace(char c) { switch (c) { case ' ': case '\f': case '\n': case '\r': case '\t': case '\v': return true; default: return false; } } bool ascii_isalpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } char ascii_tolower(char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c; } char ascii_toupper(char c) { return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; } int ascii_casecmp(const char *a, const char *b) { for (char ca, cb; (ca = *a++) | (cb = *b++); ) { ca = ascii_tolower(ca); cb = ascii_tolower(cb); if (ca < cb) return -1; if (ca > cb) return +1; } return 0; } uint32_t latin1_toupper(uint32_t c) { if (c >= 'a' && c <= 'z') return c - 'a' + 'A'; if ((c >= 0xe0 && c <= 0xf6) || (c >= 0xf8 && c <= 0xfe)) return c - 0x20; return c; } uint32_t latin1_tolower(uint32_t c) { if (c >= 'A' && c <= 'Z') return c - 'A' + 'a'; if ((c >= 0xc0 && c <= 0xd6) || (c >= 0xd8 && c <= 0xde)) return c + 0x20; return c; } char *strdup_using_new(const char *src) { size_t len = strlen(src); char *dst = new char[len + 1]; memcpy(dst, src, len + 1); return dst; } string_list split_strings_noempty(const char *input, bool(*pred)(char)) { string_list list; if (input) { std::string acc; acc.reserve(256); for (char c; (c = *input++) != '\0'; ) { if (!pred(c)) acc.push_back(c); else { if (!acc.empty()) { list.push_back(acc); acc.clear(); } } } if (!acc.empty()) list.push_back(acc); } return list; } std::string trim(const char *input, bool(*pred)(char)) { const char *start = input; while (*start != '\0' && pred(*start)) ++start; const char *end = start + strlen(start); while (end > start && pred(*(end - 1))) --end; return std::string(start, end); } //------------------------------------------------------------------------------ void pack_u32le(uint32_t value, uint8_t data[4]) { data[0] = value & 0xff; data[1] = (value >> 8) & 0xff; data[2] = (value >> 16) & 0xff; data[3] = value >> 24; } void pack_f32le(float value, uint8_t data[4]) { uint32_t u; memcpy(&u, &value, 4); pack_u32le(u, data); } uint32_t unpack_u32le(const uint8_t data[4]) { return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } float unpack_f32le(const uint8_t data[4]) { float value; uint32_t u = unpack_u32le(data); memcpy(&value, &u, 4); return value; } //------------------------------------------------------------------------------ std::vector decode_base64(const char *text, size_t len) { return d_getChunkFromBase64String(text, len); } std::string encode_base64(const uint8_t *data, size_t len) { return d_getBase64StringFromChunk(data, len); } //------------------------------------------------------------------------------ bool get_file_uid(const char *path, file_uid &uid) { #ifdef _WIN32 HANDLE handle = CreateFileW(widen(path).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (handle == INVALID_HANDLE_VALUE) return false; bool success = get_handle_file_uid((void *)handle, uid); CloseHandle(handle); return success; #else int fd = open(path, O_RDONLY); if (fd == -1) return false; bool success = get_descriptor_file_uid(fd, uid); close(fd); return success; #endif } bool get_stream_file_uid(FILE *stream, file_uid &uid) { #if !defined(_WIN32) int fd = fileno(stream); if (fd == -1) return false; #else int fd = _fileno(stream); if (fd == -1) return false; #endif return get_descriptor_file_uid(fd, uid); } bool get_descriptor_file_uid(int fd, file_uid &uid) { #if !defined(_WIN32) struct stat st; if (fstat(fd, &st) != 0) return false; uid.first = (uint64_t)st.st_dev; uid.second = (uint64_t)st.st_ino; return true; #else HANDLE handle = (HANDLE)_get_osfhandle(fd); if (handle == INVALID_HANDLE_VALUE) return false; return get_handle_file_uid((void *)handle, uid); #endif } #if defined(_WIN32) bool get_handle_file_uid(void *handle, file_uid &uid) { BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle((HANDLE)handle, &info)) return false; uid.first = info.dwVolumeSerialNumber; uid.second = (uint64_t)info.nFileIndexLow | ((uint64_t)info.nFileIndexHigh << 32); return true; } #endif //------------------------------------------------------------------------------ bool is_path_separator(char ch) { #if !defined(_WIN32) return ch == '/'; #else return ch == '/' || ch == '\\'; #endif } split_path_t split_path(const char *path) { split_path_t sp; #if !defined(_WIN32) size_t npos = ~(size_t)0; size_t pos = npos; for (size_t i = 0; path[i] != '\0'; ++i) { if (is_path_separator(path[i])) pos = i; } if (pos == npos) sp.file.assign(path); else { sp.dir.assign(path, pos + 1); sp.file.assign(path + pos + 1); } #else std::wstring wpath = widen(path); std::unique_ptr drive{new wchar_t[wpath.size() + 1]{}}; std::unique_ptr dir{new wchar_t[wpath.size() + 1]{}}; std::unique_ptr fname{new wchar_t[wpath.size() + 1]{}}; std::unique_ptr ext{new wchar_t[wpath.size() + 1]{}}; _wsplitpath(wpath.c_str(), drive.get(), dir.get(), fname.get(), ext.get()); sp.drive = narrow(drive.get()); if (!drive[0] || dir[0] == L'/' || dir[0] == L'\\') sp.dir = narrow(dir.get()); else sp.dir = narrow(L'\\' + std::wstring(dir.get())); sp.file = narrow(std::wstring(fname.get()) + std::wstring(ext.get())); #endif return sp; } std::string path_file_name(const char *path) { return split_path(path).file; } std::string path_directory(const char *path) { split_path_t sp = split_path(path); return sp.dir.empty() ? std::string("./") : (sp.drive + sp.dir); } std::string path_ensure_final_separator(const char *path) { std::string result(path); if (!result.empty() && !is_path_separator(result.back())) result.push_back('/'); return result; } bool path_has_suffix(const char *path, const char *suffix) { if (*suffix == '.') ++suffix; size_t plen = strlen(path); size_t slen = strlen(suffix); if (plen < slen + 2) return false; return path[plen - slen - 1] == '.' && ascii_casecmp(suffix, &path[plen - slen]) == 0; } bool path_is_relative(const char *path) { #if !defined(_WIN32) return !is_path_separator(path[0]); #else return !is_path_separator(split_path(path).dir.c_str()[0]); #endif } //------------------------------------------------------------------------------ #if !defined(_WIN32) bool exists(const char *path) { return access(path, F_OK) == 0; } string_list list_directory(const char *path) { string_list list; DIR *dir = opendir(path); if (!dir) return list; auto dir_cleanup = defer([dir]() { closedir(dir); }); list.reserve(256); std::string pathbuf; pathbuf.reserve(1024); while (dirent *ent = readdir(dir)) { const char *name = ent->d_name; if (!strcmp(name, ".") || !strcmp(name, "..")) continue; pathbuf.assign(name); if (ent->d_type == DT_DIR) pathbuf.push_back('/'); list.push_back(pathbuf); } std::sort(list.begin(), list.end()); return list; } // void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data); // NOTE: implemented in separate file `ysfx_utils_fts.cpp` #else bool exists(const char *path) { return _waccess(widen(path).c_str(), 0) == 0; } string_list list_directory(const char *path) { string_list list; std::wstring pattern = widen(path) + L"\\*"; WIN32_FIND_DATAW fd; HANDLE handle = FindFirstFileW(pattern.c_str(), &fd); if (handle == INVALID_HANDLE_VALUE) return list; auto handle_cleanup = defer([handle]() { FindClose(handle); }); list.reserve(256); do { const wchar_t *name = fd.cFileName; if (!wcscmp(name, L".") || !wcscmp(name, L"..")) continue; std::string entry = narrow(name); if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) entry.push_back('/'); list.push_back(std::move(entry)); } while (FindNextFileW(handle, &fd)); std::sort(list.begin(), list.end()); return list; } void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data) { std::deque dirs; dirs.push_back(widen(path_ensure_final_separator(rootpath))); std::wstring pathbuf; pathbuf.reserve(1024); std::vector entries; entries.reserve(256); while (!dirs.empty()) { std::wstring dir = std::move(dirs.front()); dirs.pop_front(); if (!visit(narrow(dir), data)) return; pathbuf.assign(dir); pathbuf.append(L"\\*"); WIN32_FIND_DATAW fd; HANDLE handle = FindFirstFileW(pathbuf.c_str(), &fd); if (handle == INVALID_HANDLE_VALUE) continue; auto handle_cleanup = defer([handle]() { FindClose(handle); }); entries.clear(); do { if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue; if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { const wchar_t *name = fd.cFileName; if (!wcscmp(name, L".") || !wcscmp(name, L"..")) continue; pathbuf.assign(dir); pathbuf.append(name); pathbuf.push_back(L'\\'); entries.push_back(pathbuf); } } while (FindNextFileW(handle, &fd)); std::sort(entries.begin(), entries.end()); for (size_t n = entries.size(); n-- > 0; ) dirs.push_front(std::move(entries[n])); } } #endif int case_resolve(const char *root_, const char *fragment, std::string &result) { if (fragment[0] == '\0') return 0; std::string root = path_ensure_final_separator(root_); std::string pathbuf; pathbuf.reserve(1024); pathbuf.assign(root); pathbuf.append(fragment); if (exists(pathbuf.c_str())) { result = std::move(pathbuf); return 1; } struct Item { std::string root; string_list components; }; std::deque worklist; { Item item; item.root = root; item.components = split_strings_noempty(fragment, &is_path_separator); if (item.components.empty()) return 0; for (size_t i = 0; i + 1 < item.components.size(); ++i) item.components[i].push_back('/'); if (is_path_separator(fragment[strlen(fragment) - 1])) item.components.back().push_back('/'); worklist.push_back(std::move(item)); } while (!worklist.empty()) { Item item = std::move(worklist.front()); worklist.pop_front(); for (const std::string &entry : list_directory(item.root.c_str())) { if (ascii_casecmp(entry.c_str(), item.components[0].c_str()) != 0) continue; if (item.components.size() == 1) { pathbuf.assign(item.root); pathbuf.append(entry); if (exists(pathbuf.c_str())) { result = std::move(pathbuf); return 2; } } else { assert(item.components.size() > 1); Item newitem; newitem.root = item.root + entry; newitem.components.assign(item.components.begin() + 1, item.components.end()); worklist.push_front(std::move(newitem)); } } } return 0; } //------------------------------------------------------------------------------ #if defined(_WIN32) std::wstring widen(const std::string &u8str) { return widen(u8str.data(), u8str.size()); } std::wstring widen(const char *u8data, size_t u8len) { if (u8len == ~(size_t)0) u8len = strlen(u8data); std::wstring wstr; int wch = MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, nullptr, 0); if (wch != 0) { wstr.resize((size_t)wch); MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, &wstr[0], wch); } return wstr; } std::string narrow(const std::wstring &wstr) { return narrow(wstr.data(), wstr.size()); } std::string narrow(const wchar_t *wdata, size_t wlen) { if (wlen == ~(size_t)0) wlen = wcslen(wdata); std::string u8str; int u8ch = WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, nullptr, 0, nullptr, nullptr); if (u8ch != 0) { u8str.resize((size_t)u8ch); WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, &u8str[0], u8ch, nullptr, nullptr); } return u8str; } #endif } // namespace ysfx //------------------------------------------------------------------------------ // WDL helpers // our replacement `atof` for WDL, which is unaffected by current locale extern "C" double ysfx_wdl_atof(const char *text) { return ysfx::dot_atof(text); }