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