Audio plugin host https://kx.studio/carla
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.

736 lines
18KB

  1. // Copyright 2021 Jean Pierre Cimalando
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. //
  15. // SPDX-License-Identifier: Apache-2.0
  16. //
  17. #include "ysfx_utils.hpp"
  18. #include "base64/Base64.hpp"
  19. #include <system_error>
  20. #include <algorithm>
  21. #include <deque>
  22. #include <clocale>
  23. #include <cstring>
  24. #include <cassert>
  25. #if !defined(_WIN32)
  26. # include <sys/stat.h>
  27. # include <sys/types.h>
  28. # include <unistd.h>
  29. # include <dirent.h>
  30. # include <fcntl.h>
  31. #else
  32. # include <windows.h>
  33. # include <io.h>
  34. #endif
  35. namespace ysfx {
  36. #if !defined(_WIN32)
  37. static_assert(sizeof(off_t) == 8, "64-bit large file support is not enabled");
  38. #endif
  39. FILE *fopen_utf8(const char *path, const char *mode)
  40. {
  41. #if defined(_WIN32)
  42. return _wfopen(widen(path).c_str(), widen(mode).c_str());
  43. #else
  44. return fopen(path, mode);
  45. #endif
  46. }
  47. int64_t fseek_lfs(FILE *stream, int64_t off, int whence)
  48. {
  49. #if defined(_WIN32)
  50. return _fseeki64(stream, off, whence);
  51. #else
  52. return fseeko(stream, off, whence);
  53. #endif
  54. }
  55. int64_t ftell_lfs(FILE *stream)
  56. {
  57. #if defined(_WIN32)
  58. return _ftelli64(stream);
  59. #else
  60. return ftello(stream);
  61. #endif
  62. }
  63. //------------------------------------------------------------------------------
  64. namespace {
  65. struct scoped_c_locale
  66. {
  67. scoped_c_locale(int lc, const char *name);
  68. ~scoped_c_locale();
  69. c_locale_t m_loc{};
  70. scoped_c_locale(const scoped_c_locale &) = delete;
  71. scoped_c_locale &operator=(const scoped_c_locale &) = delete;
  72. };
  73. scoped_c_locale::scoped_c_locale(int lc, const char *name)
  74. {
  75. #if defined(_WIN32)
  76. m_loc = _create_locale(lc, name);
  77. #else
  78. switch (lc) {
  79. case LC_ALL:
  80. m_loc = newlocale(LC_ALL_MASK, name, c_locale_t{});
  81. break;
  82. case LC_CTYPE:
  83. m_loc = newlocale(LC_CTYPE_MASK, name, c_locale_t{});
  84. break;
  85. case LC_COLLATE:
  86. m_loc = newlocale(LC_COLLATE_MASK, name, c_locale_t{});
  87. break;
  88. case LC_MONETARY:
  89. m_loc = newlocale(LC_MONETARY_MASK, name, c_locale_t{});
  90. break;
  91. case LC_NUMERIC:
  92. m_loc = newlocale(LC_NUMERIC_MASK, name, c_locale_t{});
  93. break;
  94. case LC_TIME:
  95. m_loc = newlocale(LC_TIME_MASK, name, c_locale_t{});
  96. break;
  97. case LC_MESSAGES:
  98. m_loc = newlocale(LC_MESSAGES_MASK, name, c_locale_t{});
  99. break;
  100. default:
  101. errno = EINVAL;
  102. break;
  103. }
  104. #endif
  105. if (!m_loc)
  106. throw std::system_error(errno, std::generic_category());
  107. }
  108. scoped_c_locale::~scoped_c_locale()
  109. {
  110. if (!m_loc) return;
  111. #if !defined(_WIN32)
  112. freelocale(m_loc);
  113. #else
  114. _free_locale(m_loc);
  115. #endif
  116. }
  117. #if !defined(_WIN32)
  118. struct scoped_posix_uselocale {
  119. explicit scoped_posix_uselocale(c_locale_t loc);
  120. ~scoped_posix_uselocale();
  121. c_locale_t m_loc{};
  122. c_locale_t m_old{};
  123. scoped_posix_uselocale(const scoped_posix_uselocale &) = delete;
  124. scoped_posix_uselocale &operator=(const scoped_posix_uselocale &) = delete;
  125. };
  126. scoped_posix_uselocale::scoped_posix_uselocale(c_locale_t loc)
  127. {
  128. if (loc)
  129. {
  130. m_loc = loc;
  131. m_old = uselocale(loc);
  132. }
  133. }
  134. scoped_posix_uselocale::~scoped_posix_uselocale()
  135. {
  136. if (m_loc)
  137. uselocale(m_old);
  138. }
  139. #endif
  140. } // namespace
  141. //------------------------------------------------------------------------------
  142. c_locale_t c_numeric_locale()
  143. {
  144. static scoped_c_locale loc(LC_NUMERIC, "C");
  145. return loc.m_loc;
  146. }
  147. //------------------------------------------------------------------------------
  148. double c_atof(const char *text, c_locale_t loc)
  149. {
  150. #if defined(_WIN32)
  151. return _atof_l(text, loc);
  152. #else
  153. scoped_posix_uselocale use(loc);
  154. return atof(text);
  155. #endif
  156. }
  157. double c_strtod(const char *text, char **endp, c_locale_t loc)
  158. {
  159. #if defined(_WIN32)
  160. return _strtod_l(text, endp, loc);
  161. #else
  162. scoped_posix_uselocale use(loc);
  163. return strtod(text, endp);
  164. #endif
  165. }
  166. double dot_atof(const char *text)
  167. {
  168. return c_atof(text, c_numeric_locale());
  169. }
  170. double dot_strtod(const char *text, char **endp)
  171. {
  172. return c_strtod(text, endp, c_numeric_locale());
  173. }
  174. bool ascii_isspace(char c)
  175. {
  176. switch (c) {
  177. case ' ': case '\f': case '\n': case '\r': case '\t': case '\v':
  178. return true;
  179. default:
  180. return false;
  181. }
  182. }
  183. bool ascii_isalpha(char c)
  184. {
  185. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
  186. }
  187. char ascii_tolower(char c)
  188. {
  189. return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c;
  190. }
  191. char ascii_toupper(char c)
  192. {
  193. return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c;
  194. }
  195. int ascii_casecmp(const char *a, const char *b)
  196. {
  197. for (char ca, cb; (ca = *a++) | (cb = *b++); ) {
  198. ca = ascii_tolower(ca);
  199. cb = ascii_tolower(cb);
  200. if (ca < cb) return -1;
  201. if (ca > cb) return +1;
  202. }
  203. return 0;
  204. }
  205. uint32_t latin1_toupper(uint32_t c)
  206. {
  207. if (c >= 'a' && c <= 'z')
  208. return c - 'a' + 'A';
  209. if ((c >= 0xe0 && c <= 0xf6) || (c >= 0xf8 && c <= 0xfe))
  210. return c - 0x20;
  211. return c;
  212. }
  213. uint32_t latin1_tolower(uint32_t c)
  214. {
  215. if (c >= 'A' && c <= 'Z')
  216. return c - 'A' + 'a';
  217. if ((c >= 0xc0 && c <= 0xd6) || (c >= 0xd8 && c <= 0xde))
  218. return c + 0x20;
  219. return c;
  220. }
  221. char *strdup_using_new(const char *src)
  222. {
  223. size_t len = strlen(src);
  224. char *dst = new char[len + 1];
  225. memcpy(dst, src, len + 1);
  226. return dst;
  227. }
  228. string_list split_strings_noempty(const char *input, bool(*pred)(char))
  229. {
  230. string_list list;
  231. if (input) {
  232. std::string acc;
  233. acc.reserve(256);
  234. for (char c; (c = *input++) != '\0'; ) {
  235. if (!pred(c))
  236. acc.push_back(c);
  237. else {
  238. if (!acc.empty()) {
  239. list.push_back(acc);
  240. acc.clear();
  241. }
  242. }
  243. }
  244. if (!acc.empty())
  245. list.push_back(acc);
  246. }
  247. return list;
  248. }
  249. std::string trim(const char *input, bool(*pred)(char))
  250. {
  251. const char *start = input;
  252. while (*start != '\0' && pred(*start))
  253. ++start;
  254. const char *end = start + strlen(start);
  255. while (end > start && pred(*(end - 1)))
  256. --end;
  257. return std::string(start, end);
  258. }
  259. //------------------------------------------------------------------------------
  260. void pack_u32le(uint32_t value, uint8_t data[4])
  261. {
  262. data[0] = value & 0xff;
  263. data[1] = (value >> 8) & 0xff;
  264. data[2] = (value >> 16) & 0xff;
  265. data[3] = value >> 24;
  266. }
  267. void pack_f32le(float value, uint8_t data[4])
  268. {
  269. uint32_t u;
  270. memcpy(&u, &value, 4);
  271. pack_u32le(u, data);
  272. }
  273. uint32_t unpack_u32le(const uint8_t data[4])
  274. {
  275. return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
  276. }
  277. float unpack_f32le(const uint8_t data[4])
  278. {
  279. float value;
  280. uint32_t u = unpack_u32le(data);
  281. memcpy(&value, &u, 4);
  282. return value;
  283. }
  284. //------------------------------------------------------------------------------
  285. std::vector<uint8_t> decode_base64(const char *text, size_t len)
  286. {
  287. return d_getChunkFromBase64String(text, len);
  288. }
  289. std::string encode_base64(const uint8_t *data, size_t len)
  290. {
  291. return d_getBase64StringFromChunk(data, len);
  292. }
  293. //------------------------------------------------------------------------------
  294. bool get_file_uid(const char *path, file_uid &uid)
  295. {
  296. #ifdef _WIN32
  297. HANDLE handle = CreateFileW(widen(path).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
  298. if (handle == INVALID_HANDLE_VALUE)
  299. return false;
  300. bool success = get_handle_file_uid((void *)handle, uid);
  301. CloseHandle(handle);
  302. return success;
  303. #else
  304. int fd = open(path, O_RDONLY);
  305. if (fd == -1)
  306. return false;
  307. bool success = get_descriptor_file_uid(fd, uid);
  308. close(fd);
  309. return success;
  310. #endif
  311. }
  312. bool get_stream_file_uid(FILE *stream, file_uid &uid)
  313. {
  314. #if !defined(_WIN32)
  315. int fd = fileno(stream);
  316. if (fd == -1)
  317. return false;
  318. #else
  319. int fd = _fileno(stream);
  320. if (fd == -1)
  321. return false;
  322. #endif
  323. return get_descriptor_file_uid(fd, uid);
  324. }
  325. bool get_descriptor_file_uid(int fd, file_uid &uid)
  326. {
  327. #if !defined(_WIN32)
  328. struct stat st;
  329. if (fstat(fd, &st) != 0)
  330. return false;
  331. uid.first = (uint64_t)st.st_dev;
  332. uid.second = (uint64_t)st.st_ino;
  333. return true;
  334. #else
  335. HANDLE handle = (HANDLE)_get_osfhandle(fd);
  336. if (handle == INVALID_HANDLE_VALUE)
  337. return false;
  338. return get_handle_file_uid((void *)handle, uid);
  339. #endif
  340. }
  341. #if defined(_WIN32)
  342. bool get_handle_file_uid(void *handle, file_uid &uid)
  343. {
  344. BY_HANDLE_FILE_INFORMATION info;
  345. if (!GetFileInformationByHandle((HANDLE)handle, &info))
  346. return false;
  347. uid.first = info.dwVolumeSerialNumber;
  348. uid.second = (uint64_t)info.nFileIndexLow | ((uint64_t)info.nFileIndexHigh << 32);
  349. return true;
  350. }
  351. #endif
  352. //------------------------------------------------------------------------------
  353. bool is_path_separator(char ch)
  354. {
  355. #if !defined(_WIN32)
  356. return ch == '/';
  357. #else
  358. return ch == '/' || ch == '\\';
  359. #endif
  360. }
  361. split_path_t split_path(const char *path)
  362. {
  363. split_path_t sp;
  364. #if !defined(_WIN32)
  365. size_t npos = ~(size_t)0;
  366. size_t pos = npos;
  367. for (size_t i = 0; path[i] != '\0'; ++i) {
  368. if (is_path_separator(path[i]))
  369. pos = i;
  370. }
  371. if (pos == npos)
  372. sp.file.assign(path);
  373. else {
  374. sp.dir.assign(path, pos + 1);
  375. sp.file.assign(path + pos + 1);
  376. }
  377. #else
  378. std::wstring wpath = widen(path);
  379. std::unique_ptr<wchar_t[]> drive{new wchar_t[wpath.size() + 1]{}};
  380. std::unique_ptr<wchar_t[]> dir{new wchar_t[wpath.size() + 1]{}};
  381. std::unique_ptr<wchar_t[]> fname{new wchar_t[wpath.size() + 1]{}};
  382. std::unique_ptr<wchar_t[]> ext{new wchar_t[wpath.size() + 1]{}};
  383. _wsplitpath(wpath.c_str(), drive.get(), dir.get(), fname.get(), ext.get());
  384. sp.drive = narrow(drive.get());
  385. if (!drive[0] || dir[0] == L'/' || dir[0] == L'\\')
  386. sp.dir = narrow(dir.get());
  387. else
  388. sp.dir = narrow(L'\\' + std::wstring(dir.get()));
  389. sp.file = narrow(std::wstring(fname.get()) + std::wstring(ext.get()));
  390. #endif
  391. return sp;
  392. }
  393. std::string path_file_name(const char *path)
  394. {
  395. return split_path(path).file;
  396. }
  397. std::string path_directory(const char *path)
  398. {
  399. split_path_t sp = split_path(path);
  400. return sp.dir.empty() ? std::string("./") : (sp.drive + sp.dir);
  401. }
  402. std::string path_ensure_final_separator(const char *path)
  403. {
  404. std::string result(path);
  405. if (!result.empty() && !is_path_separator(result.back()))
  406. result.push_back('/');
  407. return result;
  408. }
  409. bool path_has_suffix(const char *path, const char *suffix)
  410. {
  411. if (*suffix == '.')
  412. ++suffix;
  413. size_t plen = strlen(path);
  414. size_t slen = strlen(suffix);
  415. if (plen < slen + 2)
  416. return false;
  417. return path[plen - slen - 1] == '.' &&
  418. ascii_casecmp(suffix, &path[plen - slen]) == 0;
  419. }
  420. bool path_is_relative(const char *path)
  421. {
  422. #if !defined(_WIN32)
  423. return !is_path_separator(path[0]);
  424. #else
  425. return !is_path_separator(split_path(path).dir.c_str()[0]);
  426. #endif
  427. }
  428. //------------------------------------------------------------------------------
  429. #if !defined(_WIN32)
  430. bool exists(const char *path)
  431. {
  432. return access(path, F_OK) == 0;
  433. }
  434. string_list list_directory(const char *path)
  435. {
  436. string_list list;
  437. DIR *dir = opendir(path);
  438. if (!dir)
  439. return list;
  440. auto dir_cleanup = defer([dir]() { closedir(dir); });
  441. list.reserve(256);
  442. std::string pathbuf;
  443. pathbuf.reserve(1024);
  444. while (dirent *ent = readdir(dir)) {
  445. const char *name = ent->d_name;
  446. if (!strcmp(name, ".") || !strcmp(name, ".."))
  447. continue;
  448. pathbuf.assign(name);
  449. if (ent->d_type == DT_DIR)
  450. pathbuf.push_back('/');
  451. list.push_back(pathbuf);
  452. }
  453. std::sort(list.begin(), list.end());
  454. return list;
  455. }
  456. // void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data);
  457. // NOTE: implemented in separate file `ysfx_utils_fts.cpp`
  458. #else
  459. bool exists(const char *path)
  460. {
  461. return _waccess(widen(path).c_str(), 0) == 0;
  462. }
  463. string_list list_directory(const char *path)
  464. {
  465. string_list list;
  466. std::wstring pattern = widen(path) + L"\\*";
  467. WIN32_FIND_DATAW fd;
  468. HANDLE handle = FindFirstFileW(pattern.c_str(), &fd);
  469. if (handle == INVALID_HANDLE_VALUE)
  470. return list;
  471. auto handle_cleanup = defer([handle]() { FindClose(handle); });
  472. list.reserve(256);
  473. do {
  474. const wchar_t *name = fd.cFileName;
  475. if (!wcscmp(name, L".") || !wcscmp(name, L".."))
  476. continue;
  477. std::string entry = narrow(name);
  478. if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
  479. entry.push_back('/');
  480. list.push_back(std::move(entry));
  481. } while (FindNextFileW(handle, &fd));
  482. std::sort(list.begin(), list.end());
  483. return list;
  484. }
  485. void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data)
  486. {
  487. std::deque<std::wstring> dirs;
  488. dirs.push_back(widen(path_ensure_final_separator(rootpath)));
  489. std::wstring pathbuf;
  490. pathbuf.reserve(1024);
  491. std::vector<std::wstring> entries;
  492. entries.reserve(256);
  493. while (!dirs.empty()) {
  494. std::wstring dir = std::move(dirs.front());
  495. dirs.pop_front();
  496. if (!visit(narrow(dir), data))
  497. return;
  498. pathbuf.assign(dir);
  499. pathbuf.append(L"\\*");
  500. WIN32_FIND_DATAW fd;
  501. HANDLE handle = FindFirstFileW(pathbuf.c_str(), &fd);
  502. if (handle == INVALID_HANDLE_VALUE)
  503. continue;
  504. auto handle_cleanup = defer([handle]() { FindClose(handle); });
  505. entries.clear();
  506. do {
  507. if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
  508. continue;
  509. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  510. const wchar_t *name = fd.cFileName;
  511. if (!wcscmp(name, L".") || !wcscmp(name, L".."))
  512. continue;
  513. pathbuf.assign(dir);
  514. pathbuf.append(name);
  515. pathbuf.push_back(L'\\');
  516. entries.push_back(pathbuf);
  517. }
  518. } while (FindNextFileW(handle, &fd));
  519. std::sort(entries.begin(), entries.end());
  520. for (size_t n = entries.size(); n-- > 0; )
  521. dirs.push_front(std::move(entries[n]));
  522. }
  523. }
  524. #endif
  525. int case_resolve(const char *root_, const char *fragment, std::string &result)
  526. {
  527. if (fragment[0] == '\0')
  528. return 0;
  529. std::string root = path_ensure_final_separator(root_);
  530. std::string pathbuf;
  531. pathbuf.reserve(1024);
  532. pathbuf.assign(root);
  533. pathbuf.append(fragment);
  534. if (exists(pathbuf.c_str())) {
  535. result = std::move(pathbuf);
  536. return 1;
  537. }
  538. struct Item {
  539. std::string root;
  540. string_list components;
  541. };
  542. std::deque<Item> worklist;
  543. {
  544. Item item;
  545. item.root = root;
  546. item.components = split_strings_noempty(fragment, &is_path_separator);
  547. if (item.components.empty())
  548. return 0;
  549. for (size_t i = 0; i + 1 < item.components.size(); ++i)
  550. item.components[i].push_back('/');
  551. if (is_path_separator(fragment[strlen(fragment) - 1]))
  552. item.components.back().push_back('/');
  553. worklist.push_back(std::move(item));
  554. }
  555. while (!worklist.empty()) {
  556. Item item = std::move(worklist.front());
  557. worklist.pop_front();
  558. for (const std::string &entry : list_directory(item.root.c_str())) {
  559. if (ascii_casecmp(entry.c_str(), item.components[0].c_str()) != 0)
  560. continue;
  561. if (item.components.size() == 1) {
  562. pathbuf.assign(item.root);
  563. pathbuf.append(entry);
  564. if (exists(pathbuf.c_str())) {
  565. result = std::move(pathbuf);
  566. return 2;
  567. }
  568. }
  569. else {
  570. assert(item.components.size() > 1);
  571. Item newitem;
  572. newitem.root = item.root + entry;
  573. newitem.components.assign(item.components.begin() + 1, item.components.end());
  574. worklist.push_front(std::move(newitem));
  575. }
  576. }
  577. }
  578. return 0;
  579. }
  580. //------------------------------------------------------------------------------
  581. #if defined(_WIN32)
  582. std::wstring widen(const std::string &u8str)
  583. {
  584. return widen(u8str.data(), u8str.size());
  585. }
  586. std::wstring widen(const char *u8data, size_t u8len)
  587. {
  588. if (u8len == ~(size_t)0)
  589. u8len = strlen(u8data);
  590. std::wstring wstr;
  591. int wch = MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, nullptr, 0);
  592. if (wch != 0) {
  593. wstr.resize((size_t)wch);
  594. MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, &wstr[0], wch);
  595. }
  596. return wstr;
  597. }
  598. std::string narrow(const std::wstring &wstr)
  599. {
  600. return narrow(wstr.data(), wstr.size());
  601. }
  602. std::string narrow(const wchar_t *wdata, size_t wlen)
  603. {
  604. if (wlen == ~(size_t)0)
  605. wlen = wcslen(wdata);
  606. std::string u8str;
  607. int u8ch = WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, nullptr, 0, nullptr, nullptr);
  608. if (u8ch != 0) {
  609. u8str.resize((size_t)u8ch);
  610. WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, &u8str[0], u8ch, nullptr, nullptr);
  611. }
  612. return u8str;
  613. }
  614. #endif
  615. } // namespace ysfx
  616. //------------------------------------------------------------------------------
  617. // WDL helpers
  618. // our replacement `atof` for WDL, which is unaffected by current locale
  619. extern "C" double ysfx_wdl_atof(const char *text)
  620. {
  621. return ysfx::dot_atof(text);
  622. }