From 4f3bc2c9a28e28651d9b1cb29804c4d96e0987be Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 25 Apr 2018 14:39:05 +0100 Subject: [PATCH] Add gamemode_query_status and teach gamemoded '-s' This allows the client to query the daemon about the status of gamemode. Returns the following: 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 Passing -s to gamemoded will simply query and print the current status. Allows for more comprehensive testing when using 'gamemoded -r' as well as more reactionary program behaviour --- README.md | 3 +-- daemon/dbus_messaging.c | 21 +++++++++++++++++++++ daemon/gamemode.c | 34 ++++++++++++++++++++++++++++++++++ daemon/gamemode.h | 10 ++++++++++ daemon/main.c | 29 +++++++++++++++++++++++++++-- data/gamemoded.1 | 3 +++ lib/client_impl.c | 6 ++++++ lib/gamemode_client.h | 38 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 140 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5ee85d0..166a6da 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your link --- ## Components -**gamemoded** runs in the background, activates game mode on request, refcounts and also checks caller PID lifetime. Accepts `-d` (daemonize) and `-l` (log to syslog). +**gamemoded** runs in the background, activates game mode on request, refcounts and also checks caller PID lifetime. Run `man gamemoded` for command line options. **libgamemode** is an internal library used to dispatch requests to the daemon. Note: `libgamemode` should never be linked with directly. @@ -95,7 +95,6 @@ clang-format -i $(find . -name '*.[ch]' -not -path "*subprojects/*") ### Planned Features * Additional mode-switch plugins * Improved client state tracking (PID is unreliable) -* API to query if game mode is active ### Maintained by Feral Interactive diff --git a/daemon/dbus_messaging.c b/daemon/dbus_messaging.c index dee8e4c..03068b8 100644 --- a/daemon/dbus_messaging.c +++ b/daemon/dbus_messaging.c @@ -101,6 +101,26 @@ static int method_unregister_game(sd_bus_message *m, void *userdata, return sd_bus_reply_method_return(m, "i", 0); } +/** + * Handles the QueryStatus D-BUS Method + */ +static int method_query_status(sd_bus_message *m, void *userdata, + __attribute__((unused)) sd_bus_error *ret_error) +{ + int pid = 0; + GameModeContext *context = userdata; + + int ret = sd_bus_message_read(m, "i", &pid); + 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)pid); + + return sd_bus_reply_method_return(m, "i", status); +} + /** * D-BUS vtable to dispatch virtual methods */ @@ -108,6 +128,7 @@ static const sd_bus_vtable gamemode_vtable[] = { SD_BUS_VTABLE_START(0), 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_VTABLE_END }; /** diff --git a/daemon/gamemode.c b/daemon/gamemode.c index 46fb6eb..53d509c 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -366,6 +366,40 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client) return true; } +int game_mode_context_query_status(GameModeContext *self, pid_t client) +{ + GameModeClient *cl = NULL; + int ret = 0; + + /* + * 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 + */ + if (atomic_load_explicit(&self->refcount, memory_order_seq_cst)) { + ret++; + + /* Check if the current client is registered */ + + /* Requires locking. */ + pthread_rwlock_rdlock(&self->rwlock); + + for (cl = self->client; cl; cl = cl->next) { + if (cl->pid != client) { + continue; + } + + /* Found it */ + ret++; + break; + } + + /* Unlock here, potentially yielding */ + pthread_rwlock_unlock(&self->rwlock); + } + + return ret; +} + /** * Construct a new GameModeClient for the given process ID * diff --git a/daemon/gamemode.h b/daemon/gamemode.h index c418446..681f50a 100644 --- a/daemon/gamemode.h +++ b/daemon/gamemode.h @@ -73,3 +73,13 @@ bool game_mode_context_register(GameModeContext *self, pid_t pid); * @returns True if the client was removed, and existed. */ bool game_mode_context_unregister(GameModeContext *self, pid_t pid); + +/** + * Query the current status of gamemode + * + * @param pid Process ID for the remote client + * @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 + */ +int game_mode_context_query_status(GameModeContext *self, pid_t pid); diff --git a/daemon/main.c b/daemon/main.c index 2116486..df723ee 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -96,7 +96,7 @@ int main(int argc, char *argv[]) bool daemon = false; bool use_syslog = false; int opt = 0; - while ((opt = getopt(argc, argv, "dlrvh")) != -1) { + while ((opt = getopt(argc, argv, "dlsrvh")) != -1) { switch (opt) { case 'd': daemon = true; @@ -104,13 +104,38 @@ int main(int argc, char *argv[]) case 'l': use_syslog = true; break; + case 's': { + int status; + + if ((status = gamemode_query_status()) < 0) { + fprintf(stderr, "gamemode status request failed: %s\n", gamemode_error_string()); + exit(EXIT_FAILURE); + } else if (status > 0) { + fprintf(stdout, "gamemode is active\n"); + } else { + fprintf(stdout, "gamemode is inactive\n"); + } + + exit(EXIT_SUCCESS); + break; + } case 'r': if (gamemode_request_start() < 0) { fprintf(stderr, "gamemode request failed: %s\n", gamemode_error_string()); exit(EXIT_FAILURE); } - fprintf(stdout, "gamemode request succeeded...\n"); + int status = gamemode_query_status(); + if (status == 2) { + fprintf(stdout, "gamemode request succeeded and is active\n"); + } else if (status == 1) { + fprintf(stderr, + "gamemode request succeeded and is active but registration failed\n"); + exit(EXIT_FAILURE); + } else { + fprintf(stderr, "gamemode request succeeded but is not active\n"); + exit(EXIT_FAILURE); + } // Simply pause and wait for any signal pause(); diff --git a/data/gamemoded.1 b/data/gamemoded.1 index fa78ac9..7483282 100644 --- a/data/gamemoded.1 +++ b/data/gamemoded.1 @@ -22,6 +22,9 @@ Log to syslog .B \-r Request gamemode and wait for any signal .TP 8 +.B \-s +Query the current status of gamemode +.TP 8 .B \-h Print help text .TP 8 diff --git a/lib/client_impl.c b/lib/client_impl.c index 822fb26..00eeed3 100644 --- a/lib/client_impl.c +++ b/lib/client_impl.c @@ -105,3 +105,9 @@ extern int real_gamemode_request_end(void) { return gamemode_request("UnregisterGame"); } + +// Wrapper to call UnregisterGame +extern int real_gamemode_query_status(void) +{ + return gamemode_request("QueryStatus"); +} diff --git a/lib/gamemode_client.h b/lib/gamemode_client.h index be6aeb1..526bbcc 100644 --- a/lib/gamemode_client.h +++ b/lib/gamemode_client.h @@ -30,6 +30,28 @@ POSSIBILITY OF SUCH DAMAGE. */ #ifndef CLIENT_GAMEMODE_H #define CLIENT_GAMEMODE_H +/* + * GameMode supports the following client functions + * Requests are refcounted in the daemon + * + * int gamemode_request_start() - Request gamemode starts + * 0 if the request was sent successfully + * -1 if the request failed + * + * int gamemode_request_end() - Request gamemode ends + * 0 if the request was sent successfully + * -1 if the request failed + * + * int gamemode_query_status() - Query the current status of gamemode + * 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 + */ + #include #include #include @@ -51,11 +73,13 @@ 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); /* 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; /** @@ -111,6 +135,9 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) { "real_gamemode_request_end", (void **)&REAL_internal_gamemode_request_end, sizeof(REAL_internal_gamemode_request_end) }, + { "real_gamemode_query_status", + (void **)&REAL_internal_gamemode_query_status, + sizeof(REAL_internal_gamemode_query_status) }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string) }, @@ -216,4 +243,15 @@ int gamemode_request_end(void) return 0; } +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_query_status(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + return REAL_internal_gamemode_query_status(); +} + #endif // CLIENT_GAMEMODE_H