7 #define WIN32_LEAN_AND_MEAN
19 #include <system_error>
22 #include "alnumeric.h"
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();
43 auto fpath
= path
.lexically_normal();
44 if(!fs::exists(fpath
))
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())
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());
76 const PathNamePair
&GetProcBinary()
80 #if !defined(ALSOFT_UWP)
82 auto fullpath
= std::wstring(pathlen
, L
'\0');
83 DWORD len
{GetModuleFileNameW(nullptr, fullpath
.data(), pathlen
)};
84 while(len
== fullpath
.size())
89 /* pathlen overflow (more than 4 billion characters??) */
93 fullpath
.resize(pathlen
);
94 len
= GetModuleFileNameW(nullptr, fullpath
.data(), pathlen
);
98 ERR("Failed to get process name: error %lu\n", GetLastError());
99 return PathNamePair
{};
102 fullpath
.resize(len
);
104 const WCHAR
*exePath
{__wargv
[0]};
107 ERR("Failed to get process name: __wargv[0] == nullptr\n");
108 return PathNamePair
{};
110 std::wstring fullpath
{exePath
};
112 std::replace(fullpath
.begin(), fullpath
.end(), L
'/', L
'\\');
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));
121 res
.fname
= wstr_to_utf8(fullpath
);
123 TRACE("Got binary: %s, %s\n", res
.path
.c_str(), res
.fname
.c_str());
126 static const PathNamePair procbin
{get_procbin()};
132 #if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX)
133 struct CoTaskMemDeleter
{
134 void operator()(void *mem
) const { CoTaskMemFree(mem
); }
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
);
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
)
169 DirectorySearch(std::filesystem::path
{buffer
.get()}/path
, ext
, &results
);
178 #if !defined(ALSOFT_UWP)
181 if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL
))
182 ERR("Failed to set priority level for thread\n");
193 #include <sys/sysctl.h>
196 #include <FindDirectory.h>
198 #ifdef HAVE_PROC_PIDPATH
201 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
206 #include <sys/resource.h>
208 #include "dbus_wrap.h"
210 #ifndef RLIMIT_RTTIME
211 #define RLIMIT_RTTIME 15
215 const PathNamePair
&GetProcBinary()
217 auto get_procbin
= []
219 std::string pathname
;
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());
228 auto procpath
= std::vector
<char>(pathlen
+1, '\0');
229 sysctl(mib
.data(), mib
.size(), procpath
.data(), &pathlen
, nullptr, 0);
230 pathname
= procpath
.data();
233 #ifdef HAVE_PROC_PIDPATH
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());
242 pathname
= procpath
.data();
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();
256 const std::array SelfLinkNames
{
259 "/proc/curproc/exe"sv
,
260 "/proc/curproc/file"sv
,
263 for(const std::string_view name
: SelfLinkNames
)
266 if(!std::filesystem::exists(name
))
268 if(auto path
= std::filesystem::read_symlink(name
); !path
.empty())
270 pathname
= path
.u8string();
274 catch(std::exception
& e
) {
275 WARN("Exception getting symlink %.*s: %s\n", al::sizei(name
), name
.data(),
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);
289 res
.fname
= pathname
;
291 TRACE("Got binary: \"%s\", \"%s\"\n", res
.path
.c_str(), res
.fname
.c_str());
294 static const PathNamePair procbin
{get_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
);
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/")};
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
)};
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
);
350 bool SetRTPriorityPthread(int prio
[[maybe_unused
]])
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
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
, ¶m
);
368 err
= pthread_setschedparam(pthread_self(), SCHED_RR
, ¶m
);
369 if(err
== 0) return true;
371 WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err
).c_str(),
376 bool SetRTPriorityRTKit(int prio
[[maybe_unused
]])
381 WARN("D-Bus not available\n");
385 dbus::ConnectionPtr conn
{dbus_bus_get(DBUS_BUS_SYSTEM
, &error
.get())};
388 WARN("D-Bus connection failed with %s: %s\n", error
->name
, error
->message
);
392 /* Don't stupidly exit if the connection dies while doing this. */
393 dbus_connection_set_exit_on_disconnect(conn
.get(), false);
396 int err
{rtkit_get_min_nice_level(conn
.get(), &nicemin
)};
400 ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err
).c_str(), err
);
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)
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)
433 err
= limit_rttime(conn
.get());
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. */
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;
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.
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;
464 WARN("Failed to set high priority: %s (%d)\n",
465 std::generic_category().message(err
).c_str(), err
);
467 #endif /* __linux__ */
471 WARN("D-Bus not supported\n");
483 if(SetRTPriorityPthread(RTPrioLevel
))
485 if(SetRTPriorityRTKit(RTPrioLevel
))