]> git.tdb.fi Git - ext/openal.git/blob - core/helpers.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / core / helpers.cpp
1
2 #include "config.h"
3
4 #include "helpers.h"
5
6 #include <algorithm>
7 #include <cerrno>
8 #include <cstdarg>
9 #include <cstdlib>
10 #include <cstdio>
11 #include <cstring>
12 #include <mutex>
13 #include <limits>
14 #include <string>
15 #include <tuple>
16
17 #include "almalloc.h"
18 #include "alfstream.h"
19 #include "alnumeric.h"
20 #include "aloptional.h"
21 #include "alspan.h"
22 #include "alstring.h"
23 #include "logging.h"
24 #include "strutils.h"
25 #include "vector.h"
26
27
28 /* Mixing thread piority level */
29 int RTPrioLevel{1};
30
31 /* Allow reducing the process's RTTime limit for RTKit. */
32 bool AllowRTTimeLimit{true};
33
34
35 #ifdef _WIN32
36
37 #include <shlobj.h>
38
39 const PathNamePair &GetProcBinary()
40 {
41     static al::optional<PathNamePair> procbin;
42     if(procbin) return *procbin;
43
44     auto fullpath = al::vector<WCHAR>(256);
45     DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))};
46     while(len == fullpath.size())
47     {
48         fullpath.resize(fullpath.size() << 1);
49         len = GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()));
50     }
51     if(len == 0)
52     {
53         ERR("Failed to get process name: error %lu\n", GetLastError());
54         procbin.emplace();
55         return *procbin;
56     }
57
58     fullpath.resize(len);
59     if(fullpath.back() != 0)
60         fullpath.push_back(0);
61
62     std::replace(fullpath.begin(), fullpath.end(), '/', '\\');
63     auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\');
64     if(sep != fullpath.rend())
65     {
66         *sep = 0;
67         procbin.emplace(wstr_to_utf8(fullpath.data()), wstr_to_utf8(al::to_address(sep.base())));
68     }
69     else
70         procbin.emplace(std::string{}, wstr_to_utf8(fullpath.data()));
71
72     TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str());
73     return *procbin;
74 }
75
76 namespace {
77
78 void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
79 {
80     std::string pathstr{path};
81     pathstr += "\\*";
82     pathstr += ext;
83     TRACE("Searching %s\n", pathstr.c_str());
84
85     std::wstring wpath{utf8_to_wstr(pathstr.c_str())};
86     WIN32_FIND_DATAW fdata;
87     HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)};
88     if(hdl == INVALID_HANDLE_VALUE) return;
89
90     const auto base = results->size();
91
92     do {
93         results->emplace_back();
94         std::string &str = results->back();
95         str = path;
96         str += '\\';
97         str += wstr_to_utf8(fdata.cFileName);
98     } while(FindNextFileW(hdl, &fdata));
99     FindClose(hdl);
100
101     const al::span<std::string> newlist{results->data()+base, results->size()-base};
102     std::sort(newlist.begin(), newlist.end());
103     for(const auto &name : newlist)
104         TRACE(" got %s\n", name.c_str());
105 }
106
107 } // namespace
108
109 al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
110 {
111     auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); };
112
113     static std::mutex search_lock;
114     std::lock_guard<std::mutex> _{search_lock};
115
116     /* If the path is absolute, use it directly. */
117     al::vector<std::string> results;
118     if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
119     {
120         std::string path{subdir};
121         std::replace(path.begin(), path.end(), '/', '\\');
122         DirectorySearch(path.c_str(), ext, &results);
123         return results;
124     }
125     if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\')
126     {
127         DirectorySearch(subdir, ext, &results);
128         return results;
129     }
130
131     std::string path;
132
133     /* Search the app-local directory. */
134     if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
135     {
136         path = wstr_to_utf8(localpath->c_str());
137         if(is_slash(path.back()))
138             path.pop_back();
139     }
140     else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)})
141     {
142         path = wstr_to_utf8(cwdbuf);
143         if(is_slash(path.back()))
144             path.pop_back();
145         free(cwdbuf);
146     }
147     else
148         path = ".";
149     std::replace(path.begin(), path.end(), '/', '\\');
150     DirectorySearch(path.c_str(), ext, &results);
151
152     /* Search the local and global data dirs. */
153     static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
154     for(int id : ids)
155     {
156         WCHAR buffer[MAX_PATH];
157         if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE)
158             continue;
159
160         path = wstr_to_utf8(buffer);
161         if(!is_slash(path.back()))
162             path += '\\';
163         path += subdir;
164         std::replace(path.begin(), path.end(), '/', '\\');
165
166         DirectorySearch(path.c_str(), ext, &results);
167     }
168
169     return results;
170 }
171
172 void SetRTPriority(void)
173 {
174     if(RTPrioLevel > 0)
175     {
176         if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
177             ERR("Failed to set priority level for thread\n");
178     }
179 }
180
181 #else
182
183 #include <sys/types.h>
184 #include <unistd.h>
185 #include <dirent.h>
186 #ifdef __FreeBSD__
187 #include <sys/sysctl.h>
188 #endif
189 #ifdef __HAIKU__
190 #include <FindDirectory.h>
191 #endif
192 #ifdef HAVE_PROC_PIDPATH
193 #include <libproc.h>
194 #endif
195 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
196 #include <pthread.h>
197 #include <sched.h>
198 #endif
199 #ifdef HAVE_RTKIT
200 #include <sys/time.h>
201 #include <sys/resource.h>
202
203 #include "dbus_wrap.h"
204 #include "rtkit.h"
205 #ifndef RLIMIT_RTTIME
206 #define RLIMIT_RTTIME 15
207 #endif
208 #endif
209
210 const PathNamePair &GetProcBinary()
211 {
212     static al::optional<PathNamePair> procbin;
213     if(procbin) return *procbin;
214
215     al::vector<char> pathname;
216 #ifdef __FreeBSD__
217     size_t pathlen;
218     int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
219     if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1)
220         WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno));
221     else
222     {
223         pathname.resize(pathlen + 1);
224         sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0);
225         pathname.resize(pathlen);
226     }
227 #endif
228 #ifdef HAVE_PROC_PIDPATH
229     if(pathname.empty())
230     {
231         char procpath[PROC_PIDPATHINFO_MAXSIZE]{};
232         const pid_t pid{getpid()};
233         if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1)
234             ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno));
235         else
236             pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
237     }
238 #endif
239 #ifdef __HAIKU__
240     if(pathname.empty())
241     {
242         char procpath[PATH_MAX];
243         if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath, sizeof(procpath)) == B_OK)
244             pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
245     }
246 #endif
247 #ifndef __SWITCH__
248     if(pathname.empty())
249     {
250         static const char SelfLinkNames[][32]{
251             "/proc/self/exe",
252             "/proc/self/file",
253             "/proc/curproc/exe",
254             "/proc/curproc/file"
255         };
256
257         pathname.resize(256);
258
259         const char *selfname{};
260         ssize_t len{};
261         for(const char *name : SelfLinkNames)
262         {
263             selfname = name;
264             len = readlink(selfname, pathname.data(), pathname.size());
265             if(len >= 0 || errno != ENOENT) break;
266         }
267
268         while(len > 0 && static_cast<size_t>(len) == pathname.size())
269         {
270             pathname.resize(pathname.size() << 1);
271             len = readlink(selfname, pathname.data(), pathname.size());
272         }
273         if(len <= 0)
274         {
275             WARN("Failed to readlink %s: %s\n", selfname, strerror(errno));
276             len = 0;
277         }
278
279         pathname.resize(static_cast<size_t>(len));
280     }
281 #endif
282     while(!pathname.empty() && pathname.back() == 0)
283         pathname.pop_back();
284
285     auto sep = std::find(pathname.crbegin(), pathname.crend(), '/');
286     if(sep != pathname.crend())
287         procbin.emplace(std::string(pathname.cbegin(), sep.base()-1),
288             std::string(sep.base(), pathname.cend()));
289     else
290         procbin.emplace(std::string{}, std::string(pathname.cbegin(), pathname.cend()));
291
292     TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str());
293     return *procbin;
294 }
295
296 namespace {
297
298 void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
299 {
300     TRACE("Searching %s for *%s\n", path, ext);
301     DIR *dir{opendir(path)};
302     if(!dir) return;
303
304     const auto base = results->size();
305     const size_t extlen{strlen(ext)};
306
307     while(struct dirent *dirent{readdir(dir)})
308     {
309         if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
310             continue;
311
312         const size_t len{strlen(dirent->d_name)};
313         if(len <= extlen) continue;
314         if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0)
315             continue;
316
317         results->emplace_back();
318         std::string &str = results->back();
319         str = path;
320         if(str.back() != '/')
321             str.push_back('/');
322         str += dirent->d_name;
323     }
324     closedir(dir);
325
326     const al::span<std::string> newlist{results->data()+base, results->size()-base};
327     std::sort(newlist.begin(), newlist.end());
328     for(const auto &name : newlist)
329         TRACE(" got %s\n", name.c_str());
330 }
331
332 } // namespace
333
334 al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
335 {
336     static std::mutex search_lock;
337     std::lock_guard<std::mutex> _{search_lock};
338
339     al::vector<std::string> results;
340     if(subdir[0] == '/')
341     {
342         DirectorySearch(subdir, ext, &results);
343         return results;
344     }
345
346     /* Search the app-local directory. */
347     if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
348         DirectorySearch(localpath->c_str(), ext, &results);
349     else
350     {
351         al::vector<char> cwdbuf(256);
352         while(!getcwd(cwdbuf.data(), cwdbuf.size()))
353         {
354             if(errno != ERANGE)
355             {
356                 cwdbuf.clear();
357                 break;
358             }
359             cwdbuf.resize(cwdbuf.size() << 1);
360         }
361         if(cwdbuf.empty())
362             DirectorySearch(".", ext, &results);
363         else
364         {
365             DirectorySearch(cwdbuf.data(), ext, &results);
366             cwdbuf.clear();
367         }
368     }
369
370     // Search local data dir
371     if(auto datapath = al::getenv("XDG_DATA_HOME"))
372     {
373         std::string &path = *datapath;
374         if(path.back() != '/')
375             path += '/';
376         path += subdir;
377         DirectorySearch(path.c_str(), ext, &results);
378     }
379     else if(auto homepath = al::getenv("HOME"))
380     {
381         std::string &path = *homepath;
382         if(path.back() == '/')
383             path.pop_back();
384         path += "/.local/share/";
385         path += subdir;
386         DirectorySearch(path.c_str(), ext, &results);
387     }
388
389     // Search global data dirs
390     std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
391
392     size_t curpos{0u};
393     while(curpos < datadirs.size())
394     {
395         size_t nextpos{datadirs.find(':', curpos)};
396
397         std::string path{(nextpos != std::string::npos) ?
398             datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)};
399         curpos = nextpos;
400
401         if(path.empty()) continue;
402         if(path.back() != '/')
403             path += '/';
404         path += subdir;
405
406         DirectorySearch(path.c_str(), ext, &results);
407     }
408
409 #ifdef ALSOFT_INSTALL_DATADIR
410     // Search the installation data directory
411     {
412         std::string path{ALSOFT_INSTALL_DATADIR};
413         if(!path.empty())
414         {
415             if(path.back() != '/')
416                 path += '/';
417             path += subdir;
418             DirectorySearch(path.c_str(), ext, &results);
419         }
420     }
421 #endif
422
423     return results;
424 }
425
426 namespace {
427
428 bool SetRTPriorityPthread(int prio)
429 {
430     int err{ENOTSUP};
431 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
432     /* Get the min and max priority for SCHED_RR. Limit the max priority to
433      * half, for now, to ensure the thread can't take the highest priority and
434      * go rogue.
435      */
436     int rtmin{sched_get_priority_min(SCHED_RR)};
437     int rtmax{sched_get_priority_max(SCHED_RR)};
438     rtmax = (rtmax-rtmin)/2 + rtmin;
439
440     struct sched_param param{};
441     param.sched_priority = clampi(prio, rtmin, rtmax);
442 #ifdef SCHED_RESET_ON_FORK
443     err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &param);
444     if(err == EINVAL)
445 #endif
446         err = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
447     if(err == 0) return true;
448
449 #else
450
451     std::ignore = prio;
452 #endif
453     WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err);
454     return false;
455 }
456
457 bool SetRTPriorityRTKit(int prio)
458 {
459 #ifdef HAVE_RTKIT
460     if(!HasDBus())
461     {
462         WARN("D-Bus not available\n");
463         return false;
464     }
465     dbus::Error error;
466     dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())};
467     if(!conn)
468     {
469         WARN("D-Bus connection failed with %s: %s\n", error->name, error->message);
470         return false;
471     }
472
473     /* Don't stupidly exit if the connection dies while doing this. */
474     dbus_connection_set_exit_on_disconnect(conn.get(), false);
475
476     int nicemin{};
477     int err{rtkit_get_min_nice_level(conn.get(), &nicemin)};
478     if(err == -ENOENT)
479     {
480         err = std::abs(err);
481         ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err);
482         return false;
483     }
484     int rtmax{rtkit_get_max_realtime_priority(conn.get())};
485     TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin);
486
487     auto limit_rttime = [](DBusConnection *c) -> int
488     {
489         using ulonglong = unsigned long long;
490         long long maxrttime{rtkit_get_rttime_usec_max(c)};
491         if(maxrttime <= 0) return static_cast<int>(std::abs(maxrttime));
492         const ulonglong umaxtime{static_cast<ulonglong>(maxrttime)};
493
494         struct rlimit rlim{};
495         if(getrlimit(RLIMIT_RTTIME, &rlim) != 0)
496             return errno;
497
498         TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime,
499             static_cast<ulonglong>(rlim.rlim_max), static_cast<ulonglong>(rlim.rlim_cur));
500         if(rlim.rlim_max > umaxtime)
501         {
502             rlim.rlim_max = static_cast<rlim_t>(std::min<ulonglong>(umaxtime,
503                 std::numeric_limits<rlim_t>::max()));
504             rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max);
505             if(setrlimit(RLIMIT_RTTIME, &rlim) != 0)
506                 return errno;
507         }
508         return 0;
509     };
510     if(rtmax > 0)
511     {
512         if(AllowRTTimeLimit)
513         {
514             err = limit_rttime(conn.get());
515             if(err != 0)
516                 WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
517                     std::strerror(err), err);
518         }
519
520         /* Limit the maximum real-time priority to half. */
521         rtmax = (rtmax+1)/2;
522         prio = clampi(prio, 1, rtmax);
523
524         TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax);
525         err = rtkit_make_realtime(conn.get(), 0, prio);
526         if(err == 0) return true;
527
528         err = std::abs(err);
529         WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err);
530     }
531     /* Don't try to set the niceness for non-Linux systems. Standard POSIX has
532      * niceness as a per-process attribute, while the intent here is for the
533      * audio processing thread only to get a priority boost. Currently only
534      * Linux is known to have per-thread niceness.
535      */
536 #ifdef __linux__
537     if(nicemin < 0)
538     {
539         TRACE("Making high priority with niceness %d\n", nicemin);
540         err = rtkit_make_high_priority(conn.get(), 0, nicemin);
541         if(err == 0) return true;
542
543         err = std::abs(err);
544         WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err);
545     }
546 #endif /* __linux__ */
547
548 #else
549
550     std::ignore = prio;
551     WARN("D-Bus not supported\n");
552 #endif
553     return false;
554 }
555
556 } // namespace
557
558 void SetRTPriority()
559 {
560     if(RTPrioLevel <= 0)
561         return;
562
563     if(SetRTPriorityPthread(RTPrioLevel))
564         return;
565     if(SetRTPriorityRTKit(RTPrioLevel))
566         return;
567 }
568
569 #endif