diff --git a/README.md b/README.md index c625e67..eb27a59 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/daemon/daemon_config.c b/daemon/daemon_config.c index db2d3ff..41ae584 100644 --- a/daemon/daemon_config.c +++ b/daemon/daemon_config.c @@ -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; +} diff --git a/daemon/daemon_config.h b/daemon/daemon_config.h index 01acd9d..f0d6900 100644 --- a/daemon/daemon_config.h +++ b/daemon/daemon_config.h @@ -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); diff --git a/daemon/dbus_messaging.c b/daemon/dbus_messaging.c index f777612..40da5d6 100644 --- a/daemon/dbus_messaging.c +++ b/daemon/dbus_messaging.c @@ -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 }; /** diff --git a/daemon/gamemode-tests.c b/daemon/gamemode-tests.c index a5d36bc..552d655 100644 --- a/daemon/gamemode-tests.c +++ b/daemon/gamemode-tests.c @@ -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 { diff --git a/daemon/gamemode.c b/daemon/gamemode.c index e366ff0..6f206a2 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -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 diff --git a/daemon/gamemode.h b/daemon/gamemode.h index 6e9124f..9a30e52 100644 --- a/daemon/gamemode.h +++ b/daemon/gamemode.h @@ -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 diff --git a/example/gamemode.ini b/example/gamemode.ini index da101e4..beac406 100644 --- a/example/gamemode.ini +++ b/example/gamemode.ini @@ -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" diff --git a/lib/client_impl.c b/lib/client_impl.c index 00eeed3..4c23278 100644 --- a/lib/client_impl.c +++ b/lib/client_impl.c @@ -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); } diff --git a/lib/gamemode_client.h b/lib/gamemode_client.h index 83971b7..2ae83a4 100644 --- a/lib/gamemode_client.h +++ b/lib/gamemode_client.h @@ -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 #include +#include + 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