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. 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 ## Contributions

View File

@ -94,6 +94,10 @@ struct GameModeConfig {
long nv_perf_level; long nv_perf_level;
long amd_core_clock_percentage; long amd_core_clock_percentage;
long amd_mem_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; } values;
}; };
@ -171,6 +175,21 @@ static bool get_long_value_hex(const char *value_name, const char *value, long *
return true; 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 * 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) { } else if (strcmp(name, "amd_mem_clock_percentage") == 0) {
valid = get_long_value(name, value, &self->values.amd_mem_clock_percentage); 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) { } else if (strcmp(section, "custom") == 0) {
/* Custom subsection */ /* Custom subsection */
if (strcmp(name, "start") == 0) { 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 * Check if the value is found in our whitelist
* Currently is a simple strstr check, but could be modified for wildcards etc. * Currently is a simple strstr check, but could be modified for wildcards etc.
*/ */
found = false; found = config_string_list_contains(client, self->values.whitelist);
for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->values.whitelist[i][0]; i++) {
if (strstr(client, self->values.whitelist[i])) {
found = true;
}
}
} }
/* release the lock */ /* 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 * Check if the value is found in our whitelist
* Currently is a simple strstr check, but could be modified for wildcards etc. * Currently is a simple strstr check, but could be modified for wildcards etc.
*/ */
bool found = false; bool found = config_string_list_contains(client, self->values.blacklist);
for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->values.blacklist[i][0]; i++) {
if (strstr(client, self->values.blacklist[i])) {
found = true;
}
}
/* release the lock */ /* release the lock */
pthread_rwlock_unlock(&self->rwlock); 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(nv_perf_level)
DEFINE_CONFIG_GET(amd_core_clock_percentage) DEFINE_CONFIG_GET(amd_core_clock_percentage)
DEFINE_CONFIG_GET(amd_mem_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_nv_perf_level(GameModeConfig *self);
long config_get_amd_core_clock_percentage(GameModeConfig *self); long config_get_amd_core_clock_percentage(GameModeConfig *self);
long config_get_amd_mem_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; 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; 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; 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); 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("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("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("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 }; SD_BUS_VTABLE_END };
/** /**

View File

@ -127,12 +127,6 @@ static int run_basic_client_tests(void)
{ {
LOG_MSG(":: Basic client tests\n"); 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 */ /* Verify that gamemode_request_start correctly start gamemode */
if (gamemode_request_start() != 0) { if (gamemode_request_start() != 0) {
LOG_ERROR("gamemode_request_start failed: %s\n", gamemode_error_string()); 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; 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 * 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 * 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"); 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 */ /* Run the basic tests */
if (run_basic_client_tests() != 0) if (run_basic_client_tests() != 0)
status = -1; status = -1;
@ -607,6 +706,10 @@ int game_mode_run_client_tests()
if (run_gamemoderun_and_reaper_tests(config) != 0) if (run_gamemoderun_and_reaper_tests(config) != 0)
status = -1; status = -1;
/* Run the supervisor tests */
if (run_supervisor_tests() != 0)
status = -1;
if (status != 0) { if (status != 0) {
LOG_MSG(": Client tests failed, skipping feature tests\n"); LOG_MSG(": Client tests failed, skipping feature tests\n");
} else { } else {

View File

@ -291,7 +291,7 @@ static void game_mode_context_auto_expire(GameModeContext *self)
if (kill(client->pid, 0) != 0) { if (kill(client->pid, 0) != 0) {
LOG_MSG("Removing expired game [%i]...\n", client->pid); LOG_MSG("Removing expired game [%i]...\n", client->pid);
pthread_rwlock_unlock(&self->rwlock); pthread_rwlock_unlock(&self->rwlock);
game_mode_context_unregister(self, client->pid); game_mode_context_unregister(self, client->pid, client->pid);
removing = true; removing = true;
break; break;
} }
@ -335,20 +335,43 @@ static int game_mode_context_num_clients(GameModeContext *self)
return atomic_load(&self->refcount); 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 */ /* Construct a new client if we can */
GameModeClient *cl = NULL; GameModeClient *cl = NULL;
char *executable = 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 */ /* Cap the total number of active clients */
if (game_mode_context_num_clients(self) + 1 > MAX_GAMES) { if (game_mode_context_num_clients(self) + 1 > MAX_GAMES) {
LOG_ERROR("Max games (%d) reached, not registering %d\n", MAX_GAMES, client); 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 */ /* Check the PID first to spare a potentially expensive lookup for the exe */
pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane
const GameModeClient *existing = game_mode_context_has_client(self, client); 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 */ /* Apply io priorities */
game_mode_apply_ioprio(self, client); game_mode_apply_ioprio(self, client);
return true; return 0;
error_cleanup: error_cleanup:
if (errno != 0) if (errno != 0)
LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno)); LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno));
free(executable); free(executable);
game_mode_client_free(cl); 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 *cl = NULL;
GameModeClient *prev = NULL; GameModeClient *prev = NULL;
bool found = false; 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. */ /* Requires locking. */
pthread_rwlock_wrlock(&self->rwlock); 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" " -- 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", " -- (we will log this event). This hint will be displayed only once.\n",
client); client);
return false; return -1;
} }
/* When we hit bottom then end the game mode */ /* 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); 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; GameModeClient *cl = NULL;
int ret = 0; 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, * 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 * 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 * Register a new game client with the context
* *
* @param pid Process ID for the remote client * @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 * Unregister an existing remote game client from the context
* *
* @param pid Process ID for the remote client * @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 * 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 * @returns Positive if gamemode is active
* 1 if gamemode is active but the client is not registered * 1 if gamemode is active but the client is not registered
* 2 if gamemode is active and the client is 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 * Query the config of a gamemode context

View File

@ -61,6 +61,16 @@ inhibit_screensaver=1
;amd_core_clock_percentage=0 ;amd_core_clock_percentage=0
;amd_mem_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]
; Custom scripts (executed using the shell) when gamemode starts and ends ; Custom scripts (executed using the shell) when gamemode starts and ends
;start=notify-send "GameMode started" ;start=notify-send "GameMode started"

View File

@ -43,7 +43,7 @@ POSSIBILITY OF SUCH DAMAGE.
static char error_string[512] = { 0 }; static char error_string[512] = { 0 };
// Simple requestor function for a gamemode // 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_message *msg = NULL;
sd_bus *bus = NULL; sd_bus *bus = NULL;
@ -66,8 +66,9 @@ static int gamemode_request(const char *function)
function, function,
NULL, NULL,
&msg, &msg,
"i", arg ? "ii" : "i",
getpid()); getpid(),
arg);
if (ret < 0) { if (ret < 0) {
snprintf(error_string, snprintf(error_string,
sizeof(error_string), sizeof(error_string),
@ -97,17 +98,35 @@ extern const char *real_gamemode_error_string(void)
// Wrapper to call RegisterGame // Wrapper to call RegisterGame
extern int real_gamemode_request_start(void) extern int real_gamemode_request_start(void)
{ {
return gamemode_request("RegisterGame"); return gamemode_request("RegisterGame", 0);
} }
// Wrapper to call UnregisterGame // Wrapper to call UnregisterGame
extern int real_gamemode_request_end(void) 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) 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 * 2 if gamemode is active and this client is registered
* -1 if the query failed * -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 * const char* gamemode_error_string() - Get an error string
* returns a string describing any of the above errors * returns a string describing any of the above errors
*/ */
@ -61,6 +75,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <sys/types.h>
static char internal_gamemode_client_error_string[512] = { 0 }; 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; static volatile int internal_libgamemode_loaded = 1;
/* Typedefs for the functions to load */ /* Typedefs for the functions to load */
typedef int (*internal_gamemode_request_start)(void); typedef int (*api_call_return_int)(void);
typedef int (*internal_gamemode_request_end)(void); typedef const char *(*api_call_return_cstring)(void);
typedef int (*internal_gamemode_query_status)(void); typedef int (*api_call_pid_return_int)(pid_t);
typedef const char *(*internal_gamemode_error_string)(void);
/* Storage for functors */ /* Storage for functors */
static internal_gamemode_request_start REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_start = NULL;
static internal_gamemode_request_end REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL;
static internal_gamemode_query_status REAL_internal_gamemode_query_status = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL;
static internal_gamemode_error_string REAL_internal_gamemode_error_string = 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. * Internal helper to perform the symbol binding safely.
* *
* Returns 0 on success and -1 on failure * Returns 0 on success and -1 on failure
*/ */
__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(void *handle, __attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(
const char *name, void *handle, const char *name, void **out_func, size_t func_size, bool required)
void **out_func,
size_t func_size)
{ {
void *symbol_lookup = NULL; void *symbol_lookup = NULL;
char *dl_error = NULL; char *dl_error = NULL;
@ -99,7 +115,7 @@ __attribute__((always_inline)) static inline int internal_bind_libgamemode_symbo
/* Safely look up the symbol */ /* Safely look up the symbol */
symbol_lookup = dlsym(handle, name); symbol_lookup = dlsym(handle, name);
dl_error = dlerror(); dl_error = dlerror();
if (dl_error || !symbol_lookup) { if (required && (dl_error || !symbol_lookup)) {
snprintf(internal_gamemode_client_error_string, snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string), sizeof(internal_gamemode_client_error_string),
"dlsym failed - %s", "dlsym failed - %s",
@ -147,6 +163,18 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
(void **)&REAL_internal_gamemode_error_string, (void **)&REAL_internal_gamemode_error_string,
sizeof(REAL_internal_gamemode_error_string), sizeof(REAL_internal_gamemode_error_string),
true }, 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; void *libgamemode = NULL;
@ -175,8 +203,8 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
if (internal_bind_libgamemode_symbol(libgamemode, if (internal_bind_libgamemode_symbol(libgamemode,
binder->name, binder->name,
binder->functor, binder->functor,
binder->func_size) != 0 && binder->func_size,
binder->required) { binder->required)) {
internal_libgamemode_loaded = -1; internal_libgamemode_loaded = -1;
return -1; return -1;
}; };
@ -275,4 +303,58 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void)
return REAL_internal_gamemode_query_status(); 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 #endif // CLIENT_GAMEMODE_H