Separate declaring a lambda from calling it
[openal-soft.git] / core / helpers.cpp
blob5e973bf288733611bc118b6e7967c557468a060a
2 #include "config.h"
4 #include "helpers.h"
6 #if defined(_WIN32)
7 #define WIN32_LEAN_AND_MEAN
8 #include <windows.h>
9 #endif
11 #include <algorithm>
12 #include <cstdlib>
13 #include <cstring>
14 #include <filesystem>
15 #include <limits>
16 #include <mutex>
17 #include <optional>
18 #include <string>
19 #include <system_error>
21 #include "almalloc.h"
22 #include "alnumeric.h"
23 #include "alspan.h"
24 #include "alstring.h"
25 #include "logging.h"
26 #include "strutils.h"
29 namespace {
31 using namespace std::string_view_literals;
33 std::mutex gSearchLock;
35 void DirectorySearch(const std::filesystem::path &path, const std::string_view ext,
36 std::vector<std::string> *const results)
38 namespace fs = std::filesystem;
40 const auto base = results->size();
42 try {
43 auto fpath = path.lexically_normal();
44 if(!fs::exists(fpath))
45 return;
47 TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data());
48 for(auto&& dirent : fs::directory_iterator{fpath})
50 auto&& entrypath = dirent.path();
51 if(!entrypath.has_extension())
52 continue;
54 if(fs::status(entrypath).type() == fs::file_type::regular
55 && al::case_compare(entrypath.extension().u8string(), ext) == 0)
56 results->emplace_back(entrypath.u8string());
59 catch(std::exception& e) {
60 ERR("Exception enumerating files: %s\n", e.what());
63 const auto newlist = al::span{*results}.subspan(base);
64 std::sort(newlist.begin(), newlist.end());
65 for(const auto &name : newlist)
66 TRACE(" got %s\n", name.c_str());
69 } // namespace
71 #ifdef _WIN32
73 #include <cctype>
74 #include <shlobj.h>
76 const PathNamePair &GetProcBinary()
78 auto get_procbin = []
80 #if !defined(ALSOFT_UWP)
81 DWORD pathlen{256};
82 auto fullpath = std::wstring(pathlen, L'\0');
83 DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)};
84 while(len == fullpath.size())
86 pathlen <<= 1;
87 if(pathlen == 0)
89 /* pathlen overflow (more than 4 billion characters??) */
90 len = 0;
91 break;
93 fullpath.resize(pathlen);
94 len = GetModuleFileNameW(nullptr, fullpath.data(), pathlen);
96 if(len == 0)
98 ERR("Failed to get process name: error %lu\n", GetLastError());
99 return PathNamePair{};
102 fullpath.resize(len);
103 #else
104 const WCHAR *exePath{__wargv[0]};
105 if(!exePath)
107 ERR("Failed to get process name: __wargv[0] == nullptr\n");
108 return PathNamePair{};
110 std::wstring fullpath{exePath};
111 #endif
112 std::replace(fullpath.begin(), fullpath.end(), L'/', L'\\');
114 PathNamePair res{};
115 if(auto seppos = fullpath.rfind(L'\\'); seppos < fullpath.size())
117 res.path = wstr_to_utf8(std::wstring_view{fullpath}.substr(0, seppos));
118 res.fname = wstr_to_utf8(std::wstring_view{fullpath}.substr(seppos+1));
120 else
121 res.fname = wstr_to_utf8(fullpath);
123 TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str());
124 return res;
126 static const PathNamePair procbin{get_procbin()};
127 return procbin;
130 namespace {
132 #if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX)
133 struct CoTaskMemDeleter {
134 void operator()(void *mem) const { CoTaskMemFree(mem); }
136 #endif
138 } // namespace
140 std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir)
142 std::lock_guard<std::mutex> srchlock{gSearchLock};
144 /* If the path is absolute, use it directly. */
145 std::vector<std::string> results;
146 auto path = std::filesystem::u8path(subdir);
147 if(path.is_absolute())
149 DirectorySearch(path, ext, &results);
150 return results;
153 /* Search the app-local directory. */
154 if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
155 DirectorySearch(*localpath, ext, &results);
156 else if(auto curpath = std::filesystem::current_path(); !curpath.empty())
157 DirectorySearch(curpath, ext, &results);
159 #if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX)
160 /* Search the local and global data dirs. */
161 for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData})
163 std::unique_ptr<WCHAR,CoTaskMemDeleter> buffer;
164 const HRESULT hr{SHGetKnownFolderPath(folderid, KF_FLAG_DONT_UNEXPAND, nullptr,
165 al::out_ptr(buffer))};
166 if(FAILED(hr) || !buffer || !*buffer)
167 continue;
169 DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results);
171 #endif
173 return results;
176 void SetRTPriority()
178 #if !defined(ALSOFT_UWP)
179 if(RTPrioLevel > 0)
181 if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
182 ERR("Failed to set priority level for thread\n");
184 #endif
187 #else
189 #include <cerrno>
190 #include <dirent.h>
191 #include <unistd.h>
192 #ifdef __FreeBSD__
193 #include <sys/sysctl.h>
194 #endif
195 #ifdef __HAIKU__
196 #include <FindDirectory.h>
197 #endif
198 #ifdef HAVE_PROC_PIDPATH
199 #include <libproc.h>
200 #endif
201 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
202 #include <pthread.h>
203 #include <sched.h>
204 #endif
205 #ifdef HAVE_RTKIT
206 #include <sys/resource.h>
208 #include "dbus_wrap.h"
209 #include "rtkit.h"
210 #ifndef RLIMIT_RTTIME
211 #define RLIMIT_RTTIME 15
212 #endif
213 #endif
215 const PathNamePair &GetProcBinary()
217 auto get_procbin = []
219 std::string pathname;
220 #ifdef __FreeBSD__
221 size_t pathlen{};
222 std::array<int,4> mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}};
223 if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1)
224 WARN("Failed to sysctl kern.proc.pathname: %s\n",
225 std::generic_category().message(errno).c_str());
226 else
228 auto procpath = std::vector<char>(pathlen+1, '\0');
229 sysctl(mib.data(), mib.size(), procpath.data(), &pathlen, nullptr, 0);
230 pathname = procpath.data();
232 #endif
233 #ifdef HAVE_PROC_PIDPATH
234 if(pathname.empty())
236 std::array<char,PROC_PIDPATHINFO_MAXSIZE> procpath{};
237 const pid_t pid{getpid()};
238 if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1)
239 ERR("proc_pidpath(%d, ...) failed: %s\n", pid,
240 std::generic_category().message(errno).c_str());
241 else
242 pathname = procpath.data();
244 #endif
245 #ifdef __HAIKU__
246 if(pathname.empty())
248 std::array<char,PATH_MAX> procpath{};
249 if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath.data(), procpath.size()) == B_OK)
250 pathname = procpath.data();
252 #endif
253 #ifndef __SWITCH__
254 if(pathname.empty())
256 const std::array SelfLinkNames{
257 "/proc/self/exe"sv,
258 "/proc/self/file"sv,
259 "/proc/curproc/exe"sv,
260 "/proc/curproc/file"sv,
263 for(const std::string_view name : SelfLinkNames)
265 try {
266 if(!std::filesystem::exists(name))
267 continue;
268 if(auto path = std::filesystem::read_symlink(name); !path.empty())
270 pathname = path.u8string();
271 break;
274 catch(std::exception& e) {
275 WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(),
276 e.what());
280 #endif
282 PathNamePair res{};
283 if(auto seppos = pathname.rfind('/'); seppos < pathname.size())
285 res.path = std::string_view{pathname}.substr(0, seppos);
286 res.fname = std::string_view{pathname}.substr(seppos+1);
288 else
289 res.fname = pathname;
291 TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str());
292 return res;
294 static const PathNamePair procbin{get_procbin()};
295 return procbin;
298 std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir)
300 std::lock_guard<std::mutex> srchlock{gSearchLock};
302 std::vector<std::string> results;
303 auto path = std::filesystem::u8path(subdir);
304 if(path.is_absolute())
306 DirectorySearch(path, ext, &results);
307 return results;
310 /* Search the app-local directory. */
311 if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
312 DirectorySearch(*localpath, ext, &results);
313 else if(auto curpath = std::filesystem::current_path(); !curpath.empty())
314 DirectorySearch(curpath, ext, &results);
316 /* Search local data dir */
317 if(auto datapath = al::getenv("XDG_DATA_HOME"))
318 DirectorySearch(std::filesystem::path{*datapath}/path, ext, &results);
319 else if(auto homepath = al::getenv("HOME"))
320 DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results);
322 /* Search global data dirs */
323 std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
325 size_t curpos{0u};
326 while(curpos < datadirs.size())
328 size_t nextpos{datadirs.find(':', curpos)};
330 std::string_view pathname{(nextpos != std::string::npos)
331 ? std::string_view{datadirs}.substr(curpos, nextpos++ - curpos)
332 : std::string_view{datadirs}.substr(curpos)};
333 curpos = nextpos;
335 if(!pathname.empty())
336 DirectorySearch(std::filesystem::path{pathname}/path, ext, &results);
339 #ifdef ALSOFT_INSTALL_DATADIR
340 /* Search the installation data directory */
341 if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty())
342 DirectorySearch(instpath/path, ext, &results);
343 #endif
345 return results;
348 namespace {
350 bool SetRTPriorityPthread(int prio [[maybe_unused]])
352 int err{ENOTSUP};
353 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
354 /* Get the min and max priority for SCHED_RR. Limit the max priority to
355 * half, for now, to ensure the thread can't take the highest priority and
356 * go rogue.
358 int rtmin{sched_get_priority_min(SCHED_RR)};
359 int rtmax{sched_get_priority_max(SCHED_RR)};
360 rtmax = (rtmax-rtmin)/2 + rtmin;
362 struct sched_param param{};
363 param.sched_priority = std::clamp(prio, rtmin, rtmax);
364 #ifdef SCHED_RESET_ON_FORK
365 err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &param);
366 if(err == EINVAL)
367 #endif
368 err = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
369 if(err == 0) return true;
370 #endif
371 WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(),
372 err);
373 return false;
376 bool SetRTPriorityRTKit(int prio [[maybe_unused]])
378 #ifdef HAVE_RTKIT
379 if(!HasDBus())
381 WARN("D-Bus not available\n");
382 return false;
384 dbus::Error error;
385 dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())};
386 if(!conn)
388 WARN("D-Bus connection failed with %s: %s\n", error->name, error->message);
389 return false;
392 /* Don't stupidly exit if the connection dies while doing this. */
393 dbus_connection_set_exit_on_disconnect(conn.get(), false);
395 int nicemin{};
396 int err{rtkit_get_min_nice_level(conn.get(), &nicemin)};
397 if(err == -ENOENT)
399 err = std::abs(err);
400 ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err);
401 return false;
403 int rtmax{rtkit_get_max_realtime_priority(conn.get())};
404 TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin);
406 auto limit_rttime = [](DBusConnection *c) -> int
408 using ulonglong = unsigned long long;
409 long long maxrttime{rtkit_get_rttime_usec_max(c)};
410 if(maxrttime <= 0) return static_cast<int>(std::abs(maxrttime));
411 const ulonglong umaxtime{static_cast<ulonglong>(maxrttime)};
413 struct rlimit rlim{};
414 if(getrlimit(RLIMIT_RTTIME, &rlim) != 0)
415 return errno;
417 TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime,
418 static_cast<ulonglong>(rlim.rlim_max), static_cast<ulonglong>(rlim.rlim_cur));
419 if(rlim.rlim_max > umaxtime)
421 rlim.rlim_max = static_cast<rlim_t>(std::min<ulonglong>(umaxtime,
422 std::numeric_limits<rlim_t>::max()));
423 rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max);
424 if(setrlimit(RLIMIT_RTTIME, &rlim) != 0)
425 return errno;
427 return 0;
429 if(rtmax > 0)
431 if(AllowRTTimeLimit)
433 err = limit_rttime(conn.get());
434 if(err != 0)
435 WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
436 std::generic_category().message(err).c_str(), err);
439 /* Limit the maximum real-time priority to half. */
440 rtmax = (rtmax+1)/2;
441 prio = std::clamp(prio, 1, rtmax);
443 TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax);
444 err = rtkit_make_realtime(conn.get(), 0, prio);
445 if(err == 0) return true;
447 err = std::abs(err);
448 WARN("Failed to set real-time priority: %s (%d)\n",
449 std::generic_category().message(err).c_str(), err);
451 /* Don't try to set the niceness for non-Linux systems. Standard POSIX has
452 * niceness as a per-process attribute, while the intent here is for the
453 * audio processing thread only to get a priority boost. Currently only
454 * Linux is known to have per-thread niceness.
456 #ifdef __linux__
457 if(nicemin < 0)
459 TRACE("Making high priority with niceness %d\n", nicemin);
460 err = rtkit_make_high_priority(conn.get(), 0, nicemin);
461 if(err == 0) return true;
463 err = std::abs(err);
464 WARN("Failed to set high priority: %s (%d)\n",
465 std::generic_category().message(err).c_str(), err);
467 #endif /* __linux__ */
469 #else
471 WARN("D-Bus not supported\n");
472 #endif
473 return false;
476 } // namespace
478 void SetRTPriority()
480 if(RTPrioLevel <= 0)
481 return;
483 if(SetRTPriorityPthread(RTPrioLevel))
484 return;
485 if(SetRTPriorityRTKit(RTPrioLevel))
486 return;
489 #endif