Merge pull request #106 from mdiluz/supervisor-support

Add "supervisor" support
This commit is contained in:
Alex Smith 2019-02-26 18:31:18 +00:00 committed by GitHub
commit 326be7ebbd
10 changed files with 505 additions and 61 deletions

View File

@ -148,6 +148,18 @@ Developers can integrate the request directly into an app. Note that none of the
Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your linker arguments, or add it to an LD\_PRELOAD in a launch script.
### Supervisor support
Developers can also create apps that manage GameMode on the system, for other processes:
```C
#include "gamemode_client.h"
gamemode_request_start_for(gamePID);
gamemode_request_end_for(gamePID);
```
This functionality can also be controlled in the config file in the `supervisor` section.
---
## Contributions

View File

@ -94,6 +94,10 @@ struct GameModeConfig {
long nv_perf_level;
long amd_core_clock_percentage;
long amd_mem_clock_percentage;
long require_supervisor;
char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
} values;
};
@ -171,6 +175,21 @@ static bool get_long_value_hex(const char *value_name, const char *value, long *
return true;
}
/**
* Simple strstr scheck
* Could be expanded for wildcard or regex
*/
static bool config_string_list_contains(const char *needle,
char haystack[CONFIG_LIST_MAX][CONFIG_VALUE_MAX])
{
for (unsigned int i = 0; i < CONFIG_LIST_MAX && haystack[i][0]; i++) {
if (strstr(needle, haystack[i])) {
return true;
}
}
return false;
}
/*
* Get a string value
*/
@ -246,6 +265,15 @@ static int inih_handler(void *user, const char *section, const char *name, const
} else if (strcmp(name, "amd_mem_clock_percentage") == 0) {
valid = get_long_value(name, value, &self->values.amd_mem_clock_percentage);
}
} else if (strcmp(section, "supervisor") == 0) {
/* Supervisor subsection */
if (strcmp(name, "supervisor_whitelist") == 0) {
valid = append_value_to_list(name, value, self->values.supervisor_whitelist);
} else if (strcmp(name, "supervisor_blacklist") == 0) {
valid = append_value_to_list(name, value, self->values.supervisor_blacklist);
} else if (strcmp(name, "require_supervisor") == 0) {
valid = get_long_value(name, value, &self->values.require_supervisor);
}
} else if (strcmp(section, "custom") == 0) {
/* Custom subsection */
if (strcmp(name, "start") == 0) {
@ -417,12 +445,7 @@ bool config_get_client_whitelisted(GameModeConfig *self, const char *client)
* Check if the value is found in our whitelist
* Currently is a simple strstr check, but could be modified for wildcards etc.
*/
found = false;
for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->values.whitelist[i][0]; i++) {
if (strstr(client, self->values.whitelist[i])) {
found = true;
}
}
found = config_string_list_contains(client, self->values.whitelist);
}
/* release the lock */
@ -442,12 +465,7 @@ bool config_get_client_blacklisted(GameModeConfig *self, const char *client)
* Check if the value is found in our whitelist
* Currently is a simple strstr check, but could be modified for wildcards etc.
*/
bool found = false;
for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->values.blacklist[i][0]; i++) {
if (strstr(client, self->values.blacklist[i])) {
found = true;
}
}
bool found = config_string_list_contains(client, self->values.blacklist);
/* release the lock */
pthread_rwlock_unlock(&self->rwlock);
@ -563,3 +581,51 @@ DEFINE_CONFIG_GET(nv_mem_clock_mhz_offset)
DEFINE_CONFIG_GET(nv_perf_level)
DEFINE_CONFIG_GET(amd_core_clock_percentage)
DEFINE_CONFIG_GET(amd_mem_clock_percentage)
/*
char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
*/
DEFINE_CONFIG_GET(require_supervisor)
/*
* Checks if the supervisor is whitelisted
*/
bool config_get_supervisor_whitelisted(GameModeConfig *self, const char *supervisor)
{
/* Take the read lock for the internal data */
pthread_rwlock_rdlock(&self->rwlock);
/* If the whitelist is empty then everything passes */
bool found = true;
if (self->values.supervisor_whitelist[0][0]) {
/*
* Check if the value is found in our whitelist
* Currently is a simple strstr check, but could be modified for wildcards etc.
*/
found = config_string_list_contains(supervisor, self->values.supervisor_whitelist);
}
/* release the lock */
pthread_rwlock_unlock(&self->rwlock);
return found;
}
/*
* Checks if the supervisor is blacklisted
*/
bool config_get_supervisor_blacklisted(GameModeConfig *self, const char *supervisor)
{
/* Take the read lock for the internal data */
pthread_rwlock_rdlock(&self->rwlock);
/*
* Check if the value is found in our whitelist
* Currently is a simple strstr check, but could be modified for wildcards etc.
*/
bool found = config_string_list_contains(supervisor, self->values.supervisor_blacklist);
/* release the lock */
pthread_rwlock_unlock(&self->rwlock);
return found;
}

View File

@ -141,3 +141,10 @@ long config_get_nv_mem_clock_mhz_offset(GameModeConfig *self);
long config_get_nv_perf_level(GameModeConfig *self);
long config_get_amd_core_clock_percentage(GameModeConfig *self);
long config_get_amd_mem_clock_percentage(GameModeConfig *self);
/**
* Functions to get supervisor config permissions
*/
long config_get_require_supervisor(GameModeConfig *self);
bool config_get_supervisor_whitelisted(GameModeConfig *self, const char *supervisor);
bool config_get_supervisor_blacklisted(GameModeConfig *self, const char *supervisor);

View File

@ -75,9 +75,9 @@ static int method_register_game(sd_bus_message *m, void *userdata,
return ret;
}
game_mode_context_register(context, (pid_t)pid);
int status = game_mode_context_register(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", 0);
return sd_bus_reply_method_return(m, "i", status);
}
/**
@ -95,9 +95,9 @@ static int method_unregister_game(sd_bus_message *m, void *userdata,
return ret;
}
game_mode_context_unregister(context, (pid_t)pid);
int status = game_mode_context_unregister(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", 0);
return sd_bus_reply_method_return(m, "i", status);
}
/**
@ -115,7 +115,70 @@ static int method_query_status(sd_bus_message *m, void *userdata,
return ret;
}
int status = game_mode_context_query_status(context, (pid_t)pid);
int status = game_mode_context_query_status(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the RegisterGameByPID D-BUS Method
*/
static int method_register_game_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = game_mode_context_register(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the UnregisterGameByPID D-BUS Method
*/
static int method_unregister_game_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = game_mode_context_unregister(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the QueryStatus D-BUS Method
*/
static int method_query_status_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_query_status(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", status);
}
@ -128,6 +191,12 @@ static const sd_bus_vtable gamemode_vtable[] =
SD_BUS_METHOD("RegisterGame", "i", "i", method_register_game, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGame", "i", "i", method_unregister_game, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatus", "i", "i", method_query_status, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RegisterGameByPID", "ii", "i", method_register_game_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGameByPID", "ii", "i", method_unregister_game_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatusByPID", "ii", "i", method_query_status_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END };
/**

View File

@ -127,12 +127,6 @@ static int run_basic_client_tests(void)
{
LOG_MSG(":: Basic client tests\n");
/* First verify that gamemode is not currently active on the system
* As well as it being currently installed and queryable
*/
if (verify_gamemode_initial() != 0)
return -1;
/* Verify that gamemode_request_start correctly start gamemode */
if (gamemode_request_start() != 0) {
LOG_ERROR("gamemode_request_start failed: %s\n", gamemode_error_string());
@ -576,6 +570,96 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
return status;
}
/* Run a set of tests on the supervisor code */
static int run_supervisor_tests(void)
{
int supervisortests = 0;
int ret = 0;
LOG_MSG(":: Supervisor tests\n");
/* Launch an external dummy process we can leave running and request gamemode for it */
pid_t pid = fork();
if (pid == 0) {
/* Child simply pauses and exits */
pause();
exit(EXIT_SUCCESS);
}
/* Request gamemode for our dummy process */
ret = gamemode_request_start_for(pid);
if (ret != 0) {
LOG_ERROR("gamemode_request_start_for gave unexpected value %d, (expected 0)!\n", ret);
if (ret == -1)
LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
supervisortests = -1;
}
/* Check it's active */
ret = gamemode_query_status();
if (ret != 1) {
LOG_ERROR(
"gamemode_query_status after start request gave unexpected value %d, (expected 1)!\n",
ret);
if (ret == -1)
LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
supervisortests = -1;
}
/* Check it's active for the dummy */
ret = gamemode_query_status_for(pid);
if (ret != 2) {
LOG_ERROR(
"gamemode_query_status_for after start request gave unexpected value %d, (expected "
"2)!\n",
ret);
if (ret == -1)
LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
supervisortests = -1;
}
/* request gamemode end for the client */
ret = gamemode_request_end_for(pid);
if (ret != 0) {
LOG_ERROR("gamemode_request_end_for gave unexpected value %d, (expected 0)!\n", ret);
if (ret == -1)
LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
supervisortests = -1;
}
/* Verify it's not active */
ret = gamemode_query_status();
if (ret != 0) {
LOG_ERROR(
"gamemode_query_status after end request gave unexpected value %d, (expected 0)!\n",
ret);
if (ret == -1)
LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
supervisortests = -1;
}
/* Wake up the child process */
if (kill(pid, SIGUSR1) == -1) {
LOG_ERROR("failed to send continue signal to other child process: %s\n", strerror(errno));
supervisortests = -1;
}
// Wait for the child to finish up
int wstatus;
usleep(100000);
while (waitpid(pid, &wstatus, WNOHANG) == 0) {
LOG_MSG("...Waiting for child to quit...\n");
usleep(100000);
}
if (supervisortests == 0)
LOG_MSG(":: Passed\n\n");
else
LOG_ERROR(":: Failed!\n");
return supervisortests;
}
/**
* game_mode_run_client_tests runs a set of tests of the client code
* we simply verify that the client can request the status and recieves the correct results
@ -595,6 +679,21 @@ int game_mode_run_client_tests()
LOG_MSG(": Running tests\n\n");
/* First verify that gamemode is not currently active on the system
* As well as it being currently installed and queryable
*/
if (verify_gamemode_initial() != 0)
return -1;
/* Controls whether we require a supervisor to actually make requests */
/* TODO: This effects all tests below */
if (config_get_require_supervisor(config) != 0) {
LOG_ERROR("Tests currently unsupported when require_supervisor is set\n");
return -1;
}
/* TODO: Also check blacklist/whitelist values as these may mess up the tests below */
/* Run the basic tests */
if (run_basic_client_tests() != 0)
status = -1;
@ -607,6 +706,10 @@ int game_mode_run_client_tests()
if (run_gamemoderun_and_reaper_tests(config) != 0)
status = -1;
/* Run the supervisor tests */
if (run_supervisor_tests() != 0)
status = -1;
if (status != 0) {
LOG_MSG(": Client tests failed, skipping feature tests\n");
} else {

View File

@ -291,7 +291,7 @@ static void game_mode_context_auto_expire(GameModeContext *self)
if (kill(client->pid, 0) != 0) {
LOG_MSG("Removing expired game [%i]...\n", client->pid);
pthread_rwlock_unlock(&self->rwlock);
game_mode_context_unregister(self, client->pid);
game_mode_context_unregister(self, client->pid, client->pid);
removing = true;
break;
}
@ -335,20 +335,43 @@ static int game_mode_context_num_clients(GameModeContext *self)
return atomic_load(&self->refcount);
}
bool game_mode_context_register(GameModeContext *self, pid_t client)
int game_mode_context_register(GameModeContext *self, pid_t client, pid_t requester)
{
errno = 0;
/* Construct a new client if we can */
GameModeClient *cl = NULL;
char *executable = NULL;
/* Check our requester config first */
if (requester != client) {
/* Lookup the executable first */
executable = game_mode_context_find_exe(requester);
if (!executable) {
return -1;
}
/* Check our blacklist and whitelist */
if (!config_get_supervisor_whitelisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
free(executable);
return -2;
} else if (config_get_supervisor_blacklisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
free(executable);
return -2;
}
} else if (config_get_require_supervisor(self->config)) {
LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
return -2;
}
/* Cap the total number of active clients */
if (game_mode_context_num_clients(self) + 1 > MAX_GAMES) {
LOG_ERROR("Max games (%d) reached, not registering %d\n", MAX_GAMES, client);
return false;
goto error_cleanup;
}
errno = 0;
/* Check the PID first to spare a potentially expensive lookup for the exe */
pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane
const GameModeClient *existing = game_mode_context_has_client(self, client);
@ -407,22 +430,47 @@ bool game_mode_context_register(GameModeContext *self, pid_t client)
/* Apply io priorities */
game_mode_apply_ioprio(self, client);
return true;
return 0;
error_cleanup:
if (errno != 0)
LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno));
free(executable);
game_mode_client_free(cl);
return false;
return -1;
}
bool game_mode_context_unregister(GameModeContext *self, pid_t client)
int game_mode_context_unregister(GameModeContext *self, pid_t client, pid_t requester)
{
GameModeClient *cl = NULL;
GameModeClient *prev = NULL;
bool found = false;
/* Check our requester config first */
if (requester != client) {
/* Lookup the executable first */
char *executable = game_mode_context_find_exe(requester);
if (!executable) {
return -1;
}
/* Check our blacklist and whitelist */
if (!config_get_supervisor_whitelisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
free(executable);
return -2;
} else if (config_get_supervisor_blacklisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
free(executable);
return -2;
}
free(executable);
} else if (config_get_require_supervisor(self->config)) {
LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
return -2;
}
/* Requires locking. */
pthread_rwlock_wrlock(&self->rwlock);
@ -457,7 +505,7 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client)
" -- with a nearby 'Removing expired game' which means we cleaned up properly\n"
" -- (we will log this event). This hint will be displayed only once.\n",
client);
return false;
return -1;
}
/* When we hit bottom then end the game mode */
@ -465,14 +513,35 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client)
game_mode_context_leave(self);
}
return true;
return 0;
}
int game_mode_context_query_status(GameModeContext *self, pid_t client)
int game_mode_context_query_status(GameModeContext *self, pid_t client, pid_t requester)
{
GameModeClient *cl = NULL;
int ret = 0;
/* First check the requester settings if appropriate */
if (client != requester) {
char *executable = game_mode_context_find_exe(requester);
if (!executable) {
return -1;
}
/* Check our blacklist and whitelist */
if (!config_get_supervisor_whitelisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
free(executable);
return -2;
} else if (config_get_supervisor_blacklisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
free(executable);
return -2;
}
free(executable);
}
/*
* Check the current refcount on gamemode, this equates to whether gamemode is active or not,
* see game_mode_context_register and game_mode_context_unregister

View File

@ -67,17 +67,23 @@ void game_mode_context_destroy(GameModeContext *self);
* Register a new game client with the context
*
* @param pid Process ID for the remote client
* @returns True if the new client could be registered
* @param requester Process ID for the remote requestor
* @returns 0 if the request was accepted and the client could be registered
* -1 if the request was accepted but the client could not be registered
* -2 if the request was rejected
*/
bool game_mode_context_register(GameModeContext *self, pid_t pid);
int game_mode_context_register(GameModeContext *self, pid_t pid, pid_t requester);
/**
* Unregister an existing remote game client from the context
*
* @param pid Process ID for the remote client
* @returns True if the client was removed, and existed.
* @param requester Process ID for the remote requestor
* @returns 0 if the request was accepted and the client existed
* -1 if the request was accepted but the client did not exist
* -2 if the request was rejected
*/
bool game_mode_context_unregister(GameModeContext *self, pid_t pid);
int game_mode_context_unregister(GameModeContext *self, pid_t pid, pid_t requester);
/**
* Query the current status of gamemode
@ -86,8 +92,9 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t pid);
* @returns Positive if gamemode is active
* 1 if gamemode is active but the client is not registered
* 2 if gamemode is active and the client is registered
* -2 if this requester was rejected
*/
int game_mode_context_query_status(GameModeContext *self, pid_t pid);
int game_mode_context_query_status(GameModeContext *self, pid_t pid, pid_t requester);
/**
* Query the config of a gamemode context

View File

@ -61,6 +61,16 @@ inhibit_screensaver=1
;amd_core_clock_percentage=0
;amd_mem_clock_percentage=0
[supervisor]
; This section controls the new gamemode functions gamemode_request_start_for and gamemode_request_end_for
; The whilelist and blacklist control which supervisor programs are allowed to make the above requests
;supervisor_whitelist=
;supervisor_blacklist=
; In case you want to allow a supervisor to take full control of gamemode, this option can be set
; This will only allow gamemode clients to be registered by using the above functions by a supervisor client
;require_supervisor=0
[custom]
; Custom scripts (executed using the shell) when gamemode starts and ends
;start=notify-send "GameMode started"

View File

@ -43,7 +43,7 @@ POSSIBILITY OF SUCH DAMAGE.
static char error_string[512] = { 0 };
// Simple requestor function for a gamemode
static int gamemode_request(const char *function)
static int gamemode_request(const char *function, int arg)
{
sd_bus_message *msg = NULL;
sd_bus *bus = NULL;
@ -66,8 +66,9 @@ static int gamemode_request(const char *function)
function,
NULL,
&msg,
"i",
getpid());
arg ? "ii" : "i",
getpid(),
arg);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
@ -97,17 +98,35 @@ extern const char *real_gamemode_error_string(void)
// Wrapper to call RegisterGame
extern int real_gamemode_request_start(void)
{
return gamemode_request("RegisterGame");
return gamemode_request("RegisterGame", 0);
}
// Wrapper to call UnregisterGame
extern int real_gamemode_request_end(void)
{
return gamemode_request("UnregisterGame");
return gamemode_request("UnregisterGame", 0);
}
// Wrapper to call UnregisterGame
// Wrapper to call QueryStatus
extern int real_gamemode_query_status(void)
{
return gamemode_request("QueryStatus");
return gamemode_request("QueryStatus", 0);
}
// Wrapper to call RegisterGameByPID
extern int real_gamemode_request_start_for(pid_t pid)
{
return gamemode_request("RegisterGameByPID", pid);
}
// Wrapper to call UnregisterGameByPID
extern int real_gamemode_request_end_for(pid_t pid)
{
return gamemode_request("UnregisterGameByPID", pid);
}
// Wrapper to call QueryStatusByPID
extern int real_gamemode_query_status_for(pid_t pid)
{
return gamemode_request("QueryStatusByPID", pid);
}

View File

@ -48,6 +48,20 @@ POSSIBILITY OF SUCH DAMAGE.
* 2 if gamemode is active and this client is registered
* -1 if the query failed
*
* int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process
* 0 if the request was sent successfully
* -1 if the request failed
* -2 if the request was rejected
*
* int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process
* 0 if the request was sent successfully
* -1 if the request failed
* -2 if the request was rejected
*
* int gamemode_query_status_for(pid_t pid) - Query the current status of gamemode for another
* process 0 if gamemode is inactive 1 if gamemode is active 2 if gamemode is active and this client
* is registered -1 if the query failed
*
* const char* gamemode_error_string() - Get an error string
* returns a string describing any of the above errors
*/
@ -61,6 +75,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include <errno.h>
#include <string.h>
#include <sys/types.h>
static char internal_gamemode_client_error_string[512] = { 0 };
/**
@ -72,26 +88,26 @@ static char internal_gamemode_client_error_string[512] = { 0 };
static volatile int internal_libgamemode_loaded = 1;
/* Typedefs for the functions to load */
typedef int (*internal_gamemode_request_start)(void);
typedef int (*internal_gamemode_request_end)(void);
typedef int (*internal_gamemode_query_status)(void);
typedef const char *(*internal_gamemode_error_string)(void);
typedef int (*api_call_return_int)(void);
typedef const char *(*api_call_return_cstring)(void);
typedef int (*api_call_pid_return_int)(pid_t);
/* Storage for functors */
static internal_gamemode_request_start REAL_internal_gamemode_request_start = NULL;
static internal_gamemode_request_end REAL_internal_gamemode_request_end = NULL;
static internal_gamemode_query_status REAL_internal_gamemode_query_status = NULL;
static internal_gamemode_error_string REAL_internal_gamemode_error_string = NULL;
static api_call_return_int REAL_internal_gamemode_request_start = NULL;
static api_call_return_int REAL_internal_gamemode_request_end = NULL;
static api_call_return_int REAL_internal_gamemode_query_status = NULL;
static api_call_return_cstring REAL_internal_gamemode_error_string = NULL;
static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL;
static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL;
static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL;
/**
* Internal helper to perform the symbol binding safely.
*
* Returns 0 on success and -1 on failure
*/
__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(void *handle,
const char *name,
void **out_func,
size_t func_size)
__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(
void *handle, const char *name, void **out_func, size_t func_size, bool required)
{
void *symbol_lookup = NULL;
char *dl_error = NULL;
@ -99,7 +115,7 @@ __attribute__((always_inline)) static inline int internal_bind_libgamemode_symbo
/* Safely look up the symbol */
symbol_lookup = dlsym(handle, name);
dl_error = dlerror();
if (dl_error || !symbol_lookup) {
if (required && (dl_error || !symbol_lookup)) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"dlsym failed - %s",
@ -147,6 +163,18 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
(void **)&REAL_internal_gamemode_error_string,
sizeof(REAL_internal_gamemode_error_string),
true },
{ "real_gamemode_request_start_for",
(void **)&REAL_internal_gamemode_request_start_for,
sizeof(REAL_internal_gamemode_request_start_for),
false },
{ "real_gamemode_request_end_for",
(void **)&REAL_internal_gamemode_request_end_for,
sizeof(REAL_internal_gamemode_request_end_for),
false },
{ "real_gamemode_query_status_for",
(void **)&REAL_internal_gamemode_query_status_for,
sizeof(REAL_internal_gamemode_query_status_for),
false },
};
void *libgamemode = NULL;
@ -175,8 +203,8 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
if (internal_bind_libgamemode_symbol(libgamemode,
binder->name,
binder->functor,
binder->func_size) != 0 &&
binder->required) {
binder->func_size,
binder->required)) {
internal_libgamemode_loaded = -1;
return -1;
};
@ -275,4 +303,58 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void)
return REAL_internal_gamemode_query_status();
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_request_start_for == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_request_start_for missing (older host?)");
return -1;
}
return REAL_internal_gamemode_request_start_for(pid);
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_request_end_for == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_request_end_for missing (older host?)");
return -1;
}
return REAL_internal_gamemode_request_end_for(pid);
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_query_status_for == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_query_status_for missing (older host?)");
return -1;
}
return REAL_internal_gamemode_query_status_for(pid);
}
#endif // CLIENT_GAMEMODE_H