1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "../../SDL_internal.h"
22
23#ifdef __LINUX__
24
25#include "SDL_error.h"
26#include "SDL_stdinc.h"
27#include "SDL_thread.h"
28
29#if !SDL_THREADS_DISABLED
30#include <sys/time.h>
31#include <sys/resource.h>
32#include <pthread.h>
33#include "SDL_system.h"
34
35/* RLIMIT_RTTIME requires kernel >= 2.6.25 and is in glibc >= 2.14 */
36#ifndef RLIMIT_RTTIME
37#define RLIMIT_RTTIME 15
38#endif
39/* SCHED_RESET_ON_FORK is in kernel >= 2.6.32. */
40#ifndef SCHED_RESET_ON_FORK
41#define SCHED_RESET_ON_FORK 0x40000000
42#endif
43
44#include "SDL_dbus.h"
45
46#if SDL_USE_LIBDBUS
47#include <sched.h>
48
49/* d-bus queries to org.freedesktop.RealtimeKit1. */
50#define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
51#define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1"
52#define RTKIT_DBUS_INTERFACE "org.freedesktop.RealtimeKit1"
53
54static pthread_once_t rtkit_initialize_once = PTHREAD_ONCE_INIT;
55static Sint32 rtkit_min_nice_level = -20;
56static Sint32 rtkit_max_realtime_priority = 99;
57static Sint64 rtkit_max_rttime_usec = 200000;
58
59static void
60rtkit_initialize()
61{
62 SDL_DBusContext *dbus = SDL_DBus_GetContext();
63
64 /* Try getting minimum nice level: this is often greater than PRIO_MIN (-20). */
65 if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MinNiceLevel",
66 DBUS_TYPE_INT32, &rtkit_min_nice_level)) {
67 rtkit_min_nice_level = -20;
68 }
69
70 /* Try getting maximum realtime priority: this can be less than the POSIX default (99). */
71 if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MaxRealtimePriority",
72 DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) {
73 rtkit_max_realtime_priority = 99;
74 }
75
76 /* Try getting maximum rttime allowed by rtkit: exceeding this value will result in SIGKILL */
77 if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "RTTimeUSecMax",
78 DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) {
79 rtkit_max_rttime_usec = 200000;
80 }
81}
82
83static SDL_bool
84rtkit_initialize_realtime_thread()
85{
86 // Following is an excerpt from rtkit README that outlines the requirements
87 // a thread must meet before making rtkit requests:
88 //
89 // * Only clients with RLIMIT_RTTIME set will get RT scheduling
90 //
91 // * RT scheduling will only be handed out to processes with
92 // SCHED_RESET_ON_FORK set to guarantee that the scheduling
93 // settings cannot 'leak' to child processes, thus making sure
94 // that 'RT fork bombs' cannot be used to bypass RLIMIT_RTTIME
95 // and take the system down.
96 //
97 // * Limits are enforced on all user controllable resources, only
98 // a maximum number of users, processes, threads can request RT
99 // scheduling at the same time.
100 //
101 // * Only a limited number of threads may be made RT in a
102 // specific time frame.
103 //
104 // * Client authorization is verified with PolicyKit
105
106 int err;
107 struct rlimit rlimit;
108 int nLimit = RLIMIT_RTTIME;
109 pid_t nPid = 0; //self
110 int nSchedPolicy = sched_getscheduler(nPid) | SCHED_RESET_ON_FORK;
111 struct sched_param schedParam = {};
112
113 // Requirement #1: Set RLIMIT_RTTIME
114 err = getrlimit(nLimit, &rlimit);
115 if (err)
116 {
117 return SDL_FALSE;
118 }
119
120 // Current rtkit allows a max of 200ms right now
121 rlimit.rlim_max = rtkit_max_rttime_usec;
122 rlimit.rlim_cur = rlimit.rlim_max / 2;
123 err = setrlimit(nLimit, &rlimit);
124 if (err)
125 {
126 return SDL_FALSE;
127 }
128
129 // Requirement #2: Add SCHED_RESET_ON_FORK to the scheduler policy
130 err = sched_getparam(nPid, &schedParam);
131 if (err)
132 {
133 return SDL_FALSE;
134 }
135
136 err = sched_setscheduler(nPid, nSchedPolicy, &schedParam);
137 if (err)
138 {
139 return SDL_FALSE;
140 }
141
142 return SDL_TRUE;
143}
144
145static SDL_bool
146rtkit_setpriority_nice(pid_t thread, int nice_level)
147{
148 Uint64 ui64 = (Uint64)thread;
149 Sint32 si32 = (Sint32)nice_level;
150 SDL_DBusContext *dbus = SDL_DBus_GetContext();
151
152 pthread_once(&rtkit_initialize_once, rtkit_initialize);
153
154 if (si32 < rtkit_min_nice_level)
155 si32 = rtkit_min_nice_level;
156
157 if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
158 RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadHighPriority",
159 DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID,
160 DBUS_TYPE_INVALID)) {
161 return SDL_FALSE;
162 }
163 return SDL_TRUE;
164}
165
166static SDL_bool
167rtkit_setpriority_realtime(pid_t thread, int rt_priority)
168{
169 Uint64 ui64 = (Uint64)thread;
170 Uint32 ui32 = (Uint32)rt_priority;
171 SDL_DBusContext *dbus = SDL_DBus_GetContext();
172
173 pthread_once(&rtkit_initialize_once, rtkit_initialize);
174
175 if (ui32 > rtkit_max_realtime_priority)
176 ui32 = rtkit_max_realtime_priority;
177
178 // We always perform the thread state changes necessary for rtkit.
179 // This wastes some system calls if the state is already set but
180 // typically code sets a thread priority and leaves it so it's
181 // not expected that this wasted effort will be an issue.
182 // We also do not quit if this fails, we let the rtkit request
183 // go through to determine whether it really needs to fail or not.
184 rtkit_initialize_realtime_thread();
185
186 if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
187 RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadRealtime",
188 DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_UINT32, &ui32, DBUS_TYPE_INVALID,
189 DBUS_TYPE_INVALID)) {
190 return SDL_FALSE;
191 }
192 return SDL_TRUE;
193}
194#else
195
196#define rtkit_max_realtime_priority 99
197
198#endif /* dbus */
199#endif /* threads */
200
201/* this is a public symbol, so it has to exist even if threads are disabled. */
202int
203SDL_LinuxSetThreadPriority(Sint64 threadID, int priority)
204{
205#if SDL_THREADS_DISABLED
206 return SDL_Unsupported();
207#else
208 if (setpriority(PRIO_PROCESS, (id_t)threadID, priority) == 0) {
209 return 0;
210 }
211
212#if SDL_USE_LIBDBUS
213 /* Note that this fails you most likely:
214 * Have your process's scheduler incorrectly configured.
215 See the requirements at:
216 http://git.0pointer.net/rtkit.git/tree/README#n16
217 * Encountered dbus/polkit security restrictions. Note
218 that the RealtimeKit1 dbus endpoint is inaccessible
219 over ssh connections for most common distro configs.
220 You might want to check your local config for details:
221 /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
222
223 README and sample code at: http://git.0pointer.net/rtkit.git
224 */
225 if (rtkit_setpriority_nice((pid_t)threadID, priority)) {
226 return 0;
227 }
228#endif
229
230 return SDL_SetError("setpriority() failed");
231#endif
232}
233
234/* this is a public symbol, so it has to exist even if threads are disabled. */
235int
236SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
237{
238#if SDL_THREADS_DISABLED
239 return SDL_Unsupported();
240#else
241 int osPriority;
242
243 if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
244 if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
245 osPriority = 1;
246 } else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
247 osPriority = rtkit_max_realtime_priority * 3 / 4;
248 } else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
249 osPriority = rtkit_max_realtime_priority;
250 } else {
251 osPriority = rtkit_max_realtime_priority / 2;
252 }
253 } else {
254 if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
255 osPriority = 19;
256 } else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
257 osPriority = -10;
258 } else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
259 osPriority = -20;
260 } else {
261 osPriority = 0;
262 }
263
264 if (setpriority(PRIO_PROCESS, (id_t)threadID, osPriority) == 0) {
265 return 0;
266 }
267 }
268
269#if SDL_USE_LIBDBUS
270 /* Note that this fails you most likely:
271 * Have your process's scheduler incorrectly configured.
272 See the requirements at:
273 http://git.0pointer.net/rtkit.git/tree/README#n16
274 * Encountered dbus/polkit security restrictions. Note
275 that the RealtimeKit1 dbus endpoint is inaccessible
276 over ssh connections for most common distro configs.
277 You might want to check your local config for details:
278 /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
279
280 README and sample code at: http://git.0pointer.net/rtkit.git
281 */
282 if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
283 if (rtkit_setpriority_realtime((pid_t)threadID, osPriority)) {
284 return 0;
285 }
286 } else {
287 if (rtkit_setpriority_nice((pid_t)threadID, osPriority)) {
288 return 0;
289 }
290 }
291#endif
292
293 return SDL_SetError("setpriority() failed");
294#endif
295}
296
297#endif /* __LINUX__ */
298
299/* vi: set ts=4 sw=4 expandtab: */
300