helpers.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 2011 by authors.
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Library General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Library General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Library General Public
  15. * License along with this library; if not, write to the
  16. * Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. * Or go to http://www.gnu.org/copyleft/lgpl.html
  19. */
  20. #include "config.h"
  21. #include <algorithm>
  22. #include <cerrno>
  23. #include <cstdarg>
  24. #include <cstdlib>
  25. #include <cstdio>
  26. #include <cstring>
  27. #include <mutex>
  28. #include <string>
  29. #include "alcmain.h"
  30. #include "almalloc.h"
  31. #include "alfstream.h"
  32. #include "alspan.h"
  33. #include "alstring.h"
  34. #include "compat.h"
  35. #include "core/logging.h"
  36. #include "strutils.h"
  37. #include "vector.h"
  38. #ifdef _WIN32
  39. #include <shlobj.h>
  40. const PathNamePair &GetProcBinary()
  41. {
  42. static PathNamePair ret;
  43. if(!ret.fname.empty() || !ret.path.empty())
  44. return ret;
  45. auto fullpath = al::vector<WCHAR>(256);
  46. DWORD len;
  47. while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))) == fullpath.size())
  48. fullpath.resize(fullpath.size() << 1);
  49. if(len == 0)
  50. {
  51. ERR("Failed to get process name: error %lu\n", GetLastError());
  52. return ret;
  53. }
  54. fullpath.resize(len);
  55. if(fullpath.back() != 0)
  56. fullpath.push_back(0);
  57. auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\');
  58. sep = std::find(fullpath.rbegin()+1, sep, '/');
  59. if(sep != fullpath.rend())
  60. {
  61. *sep = 0;
  62. ret.fname = wstr_to_utf8(&*sep + 1);
  63. ret.path = wstr_to_utf8(fullpath.data());
  64. }
  65. else
  66. ret.fname = wstr_to_utf8(fullpath.data());
  67. TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str());
  68. return ret;
  69. }
  70. namespace {
  71. void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
  72. {
  73. std::string pathstr{path};
  74. pathstr += "\\*";
  75. pathstr += ext;
  76. TRACE("Searching %s\n", pathstr.c_str());
  77. std::wstring wpath{utf8_to_wstr(pathstr.c_str())};
  78. WIN32_FIND_DATAW fdata;
  79. HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)};
  80. if(hdl == INVALID_HANDLE_VALUE) return;
  81. const auto base = results->size();
  82. do {
  83. results->emplace_back();
  84. std::string &str = results->back();
  85. str = path;
  86. str += '\\';
  87. str += wstr_to_utf8(fdata.cFileName);
  88. } while(FindNextFileW(hdl, &fdata));
  89. FindClose(hdl);
  90. const al::span<std::string> newlist{results->data()+base, results->size()-base};
  91. std::sort(newlist.begin(), newlist.end());
  92. for(const auto &name : newlist)
  93. TRACE(" got %s\n", name.c_str());
  94. }
  95. } // namespace
  96. al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
  97. {
  98. auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); };
  99. static std::mutex search_lock;
  100. std::lock_guard<std::mutex> _{search_lock};
  101. /* If the path is absolute, use it directly. */
  102. al::vector<std::string> results;
  103. if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
  104. {
  105. std::string path{subdir};
  106. std::replace(path.begin(), path.end(), '/', '\\');
  107. DirectorySearch(path.c_str(), ext, &results);
  108. return results;
  109. }
  110. if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\')
  111. {
  112. DirectorySearch(subdir, ext, &results);
  113. return results;
  114. }
  115. std::string path;
  116. /* Search the app-local directory. */
  117. if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
  118. {
  119. path = wstr_to_utf8(localpath->c_str());
  120. if(is_slash(path.back()))
  121. path.pop_back();
  122. }
  123. else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)})
  124. {
  125. path = wstr_to_utf8(cwdbuf);
  126. if(is_slash(path.back()))
  127. path.pop_back();
  128. free(cwdbuf);
  129. }
  130. else
  131. path = ".";
  132. std::replace(path.begin(), path.end(), '/', '\\');
  133. DirectorySearch(path.c_str(), ext, &results);
  134. /* Search the local and global data dirs. */
  135. static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
  136. for(int id : ids)
  137. {
  138. WCHAR buffer[MAX_PATH];
  139. if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE)
  140. continue;
  141. path = wstr_to_utf8(buffer);
  142. if(!is_slash(path.back()))
  143. path += '\\';
  144. path += subdir;
  145. std::replace(path.begin(), path.end(), '/', '\\');
  146. DirectorySearch(path.c_str(), ext, &results);
  147. }
  148. return results;
  149. }
  150. void SetRTPriority(void)
  151. {
  152. if(RTPrioLevel > 0)
  153. {
  154. if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
  155. ERR("Failed to set priority level for thread\n");
  156. }
  157. }
  158. #else
  159. #include <sys/types.h>
  160. #include <unistd.h>
  161. #include <dirent.h>
  162. #ifdef __FreeBSD__
  163. #include <sys/sysctl.h>
  164. #endif
  165. #ifdef __HAIKU__
  166. #include <FindDirectory.h>
  167. #endif
  168. #ifdef HAVE_PROC_PIDPATH
  169. #include <libproc.h>
  170. #endif
  171. #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
  172. #include <pthread.h>
  173. #include <sched.h>
  174. #endif
  175. const PathNamePair &GetProcBinary()
  176. {
  177. static PathNamePair ret;
  178. if(!ret.fname.empty() || !ret.path.empty())
  179. return ret;
  180. al::vector<char> pathname;
  181. #ifdef __FreeBSD__
  182. size_t pathlen;
  183. int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
  184. if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1)
  185. WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno));
  186. else
  187. {
  188. pathname.resize(pathlen + 1);
  189. sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0);
  190. pathname.resize(pathlen);
  191. }
  192. #endif
  193. #ifdef HAVE_PROC_PIDPATH
  194. if(pathname.empty())
  195. {
  196. char procpath[PROC_PIDPATHINFO_MAXSIZE]{};
  197. const pid_t pid{getpid()};
  198. if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1)
  199. ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno));
  200. else
  201. pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
  202. }
  203. #endif
  204. #ifdef __HAIKU__
  205. if(pathname.empty())
  206. {
  207. char procpath[PATH_MAX];
  208. if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath, sizeof(procpath)) == B_OK)
  209. pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
  210. }
  211. #endif
  212. if(pathname.empty())
  213. {
  214. static const char SelfLinkNames[][32]{
  215. "/proc/self/exe",
  216. "/proc/self/file",
  217. "/proc/curproc/exe",
  218. "/proc/curproc/file"
  219. };
  220. pathname.resize(256);
  221. const char *selfname{};
  222. ssize_t len{};
  223. for(const char *name : SelfLinkNames)
  224. {
  225. selfname = name;
  226. len = readlink(selfname, pathname.data(), pathname.size());
  227. if(len >= 0 || errno != ENOENT) break;
  228. }
  229. while(len > 0 && static_cast<size_t>(len) == pathname.size())
  230. {
  231. pathname.resize(pathname.size() << 1);
  232. len = readlink(selfname, pathname.data(), pathname.size());
  233. }
  234. if(len <= 0)
  235. {
  236. WARN("Failed to readlink %s: %s\n", selfname, strerror(errno));
  237. return ret;
  238. }
  239. pathname.resize(static_cast<size_t>(len));
  240. }
  241. while(!pathname.empty() && pathname.back() == 0)
  242. pathname.pop_back();
  243. auto sep = std::find(pathname.crbegin(), pathname.crend(), '/');
  244. if(sep != pathname.crend())
  245. {
  246. ret.path = std::string(pathname.cbegin(), sep.base()-1);
  247. ret.fname = std::string(sep.base(), pathname.cend());
  248. }
  249. else
  250. ret.fname = std::string(pathname.cbegin(), pathname.cend());
  251. TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str());
  252. return ret;
  253. }
  254. namespace {
  255. void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
  256. {
  257. TRACE("Searching %s for *%s\n", path, ext);
  258. DIR *dir{opendir(path)};
  259. if(!dir) return;
  260. const auto base = results->size();
  261. const size_t extlen{strlen(ext)};
  262. while(struct dirent *dirent{readdir(dir)})
  263. {
  264. if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
  265. continue;
  266. const size_t len{strlen(dirent->d_name)};
  267. if(len <= extlen) continue;
  268. if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0)
  269. continue;
  270. results->emplace_back();
  271. std::string &str = results->back();
  272. str = path;
  273. if(str.back() != '/')
  274. str.push_back('/');
  275. str += dirent->d_name;
  276. }
  277. closedir(dir);
  278. const al::span<std::string> newlist{results->data()+base, results->size()-base};
  279. std::sort(newlist.begin(), newlist.end());
  280. for(const auto &name : newlist)
  281. TRACE(" got %s\n", name.c_str());
  282. }
  283. } // namespace
  284. al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
  285. {
  286. static std::mutex search_lock;
  287. std::lock_guard<std::mutex> _{search_lock};
  288. al::vector<std::string> results;
  289. if(subdir[0] == '/')
  290. {
  291. DirectorySearch(subdir, ext, &results);
  292. return results;
  293. }
  294. /* Search the app-local directory. */
  295. if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
  296. DirectorySearch(localpath->c_str(), ext, &results);
  297. else
  298. {
  299. al::vector<char> cwdbuf(256);
  300. while(!getcwd(cwdbuf.data(), cwdbuf.size()))
  301. {
  302. if(errno != ERANGE)
  303. {
  304. cwdbuf.clear();
  305. break;
  306. }
  307. cwdbuf.resize(cwdbuf.size() << 1);
  308. }
  309. if(cwdbuf.empty())
  310. DirectorySearch(".", ext, &results);
  311. else
  312. {
  313. DirectorySearch(cwdbuf.data(), ext, &results);
  314. cwdbuf.clear();
  315. }
  316. }
  317. // Search local data dir
  318. if(auto datapath = al::getenv("XDG_DATA_HOME"))
  319. {
  320. std::string &path = *datapath;
  321. if(path.back() != '/')
  322. path += '/';
  323. path += subdir;
  324. DirectorySearch(path.c_str(), ext, &results);
  325. }
  326. else if(auto homepath = al::getenv("HOME"))
  327. {
  328. std::string &path = *homepath;
  329. if(path.back() == '/')
  330. path.pop_back();
  331. path += "/.local/share/";
  332. path += subdir;
  333. DirectorySearch(path.c_str(), ext, &results);
  334. }
  335. // Search global data dirs
  336. std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
  337. size_t curpos{0u};
  338. while(curpos < datadirs.size())
  339. {
  340. size_t nextpos{datadirs.find(':', curpos)};
  341. std::string path{(nextpos != std::string::npos) ?
  342. datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)};
  343. curpos = nextpos;
  344. if(path.empty()) continue;
  345. if(path.back() != '/')
  346. path += '/';
  347. path += subdir;
  348. DirectorySearch(path.c_str(), ext, &results);
  349. }
  350. return results;
  351. }
  352. void SetRTPriority()
  353. {
  354. #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
  355. if(RTPrioLevel > 0)
  356. {
  357. struct sched_param param{};
  358. /* Use the minimum real-time priority possible for now (on Linux this
  359. * should be 1 for SCHED_RR).
  360. */
  361. param.sched_priority = sched_get_priority_min(SCHED_RR);
  362. int err;
  363. #ifdef SCHED_RESET_ON_FORK
  364. err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &param);
  365. if(err == EINVAL)
  366. #endif
  367. err = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
  368. if(err != 0)
  369. ERR("Failed to set real-time priority for thread: %s (%d)\n", std::strerror(err), err);
  370. }
  371. #else
  372. /* Real-time priority not available */
  373. if(RTPrioLevel > 0)
  374. ERR("Cannot set priority level for thread\n");
  375. #endif
  376. }
  377. #endif