diff --git a/daemon/cpugovctl.c b/daemon/cpugovctl.c index 745e9b1..4fed7fc 100644 --- a/daemon/cpugovctl.c +++ b/daemon/cpugovctl.c @@ -31,118 +31,15 @@ POSSIBILITY OF SUCH DAMAGE. #define _GNU_SOURCE +#include "governors-query.h" #include "logging.h" #include -#include -#include #include -#define MAX_GOVERNORS 128 -#define MAX_GOVERNOR_LENGTH PATH_MAX + 1 - -// Governers are located at /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor -static int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH]) -{ - const char *cpu_base_path = "/sys/devices/system/cpu/"; - DIR *dir = opendir(cpu_base_path); - if (!dir) { - FATAL_ERRORNO("cpu device path not found"); - } - - int num_governors = 0; - - // Explore the directory - struct dirent *ent; - while ((ent = readdir(dir)) && num_governors < MAX_GOVERNORS) { - // CPU directories all start with "cpu" - if (strncmp(ent->d_name, "cpu", 3) == 0) { - // Check if this matches "cpu\d+" - const int len = strlen(ent->d_name); - if (len > 3 && len < 5 && isdigit(ent->d_name[3])) { - // Construct the full path - char path[PATH_MAX] = {}; - snprintf(path, - sizeof(path), - "%s%s/cpufreq/scaling_governor", - cpu_base_path, - ent->d_name); - - // Get the real path to the file - // Traditionally cpufreq symlinks to a policy directory that can be - // shared So let's prevent duplicates - char fullpath[PATH_MAX] = {}; - const char *ptr = realpath(path, fullpath); - if (fullpath != ptr) { - continue; - } - - // Only add if unique - for (int i = 0; i < num_governors; i++) { - if (strncmp(fullpath, governors[i], MAX_GOVERNOR_LENGTH) == 0) { - continue; - } - } - - strncpy(governors[num_governors], fullpath, MAX_GOVERNOR_LENGTH); - num_governors++; - } - } - } - closedir(dir); - - return num_governors; -} - -// Get the current governor state -const char *get_gov_state() -{ - // To be returned - static char governor[64] = { 0 }; - - // State of all the overnors - char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH] = { { 0 } }; - int num = fetch_governors(governors); - - // Check the list - for (int i = 0; i < num; i++) { - const char *gov = governors[i]; - - FILE *f = fopen(gov, "r"); - if (!f) { - LOG_ERROR("Failed to open file for read %s\n", gov); - continue; - } - - // Pull out the file contents - fseek(f, 0, SEEK_END); - int length = ftell(f); - fseek(f, 0, SEEK_SET); - - char contents[length]; - - if (fread(contents, 1, length, f) > 0) { - // Files have a newline - strtok(contents, "\n"); - if (strlen(governor) > 0 && strncmp(governor, contents, 64) != 0) { - // Don't handle the mixed case, this shouldn't ever happen - // But it is a clear sign we shouldn't carry on - LOG_ERROR("Governors malformed: got \"%s\", expected \"%s\"", contents, governor); - return "malformed"; - } - - strncpy(governor, contents, sizeof(governor)); - } else { - LOG_ERROR("Failed to read contents of %s\n", gov); - } - - fclose(f); - } - - return governor; -} - -// Sets all governors to a value +/** + * Sets all governors to a value + */ void set_gov_state(const char *value) { char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH] = { { 0 } }; @@ -162,20 +59,31 @@ void set_gov_state(const char *value) } } -// Main entry point +/** + * Main entry point, dispatch to the appropriate helper + */ int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "usage: cpugovctl [get] [set VALUE]\n"); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } if (strncmp(argv[1], "get", 3) == 0) { printf("%s", get_gov_state()); } else if (strncmp(argv[1], "set", 3) == 0) { const char *value = argv[2]; + + /* Must be root to set the state */ + if (geteuid() != 0) { + fprintf(stderr, "This program must be run as root\n"); + return EXIT_FAILURE; + } + set_gov_state(value); } else { - exit(EXIT_FAILURE); + return EXIT_FAILURE; } + + return EXIT_SUCCESS; } diff --git a/daemon/daemonize.c b/daemon/daemonize.c index f5d1cca..a62bc51 100644 --- a/daemon/daemonize.c +++ b/daemon/daemonize.c @@ -36,10 +36,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -// function to daemonize the process +/** + * Helper to perform standard UNIX daemonization + */ void daemonize(char *name) { - // Fork once + /* Initial fork */ pid_t pid = fork(); if (pid < 0) { FATAL_ERRORNO("Failed to fork"); @@ -50,7 +52,7 @@ void daemonize(char *name) exit(EXIT_SUCCESS); } - // Fork a second time + /* Fork a second time */ pid = fork(); if (pid < 0) { FATAL_ERRORNO("Failed to fork"); @@ -58,7 +60,7 @@ void daemonize(char *name) exit(EXIT_SUCCESS); } - // Continue to set up as a daemon + /* Now continue execution */ umask(0); if (setsid() < 0) { FATAL_ERRORNO("Failed to create process group\n"); diff --git a/daemon/daemonize.h b/daemon/daemonize.h index 883d09f..234ec05 100644 --- a/daemon/daemonize.h +++ b/daemon/daemonize.h @@ -28,11 +28,11 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _DAEMONIZE_GAMEMODE_H_ -#define _DAEMONIZE_GAMEMODE_H_ -// Function to daemonize the process -// Exits with error in case of failure +#pragma once + +/** + * Attempt daemonization of the process. + * If this fails, the process will exit + */ void daemonize(char *name); - -#endif // _DAEMONIZE_GAMEMODE_H_ diff --git a/daemon/dbus_messaging.c b/daemon/dbus_messaging.c index a6f59df..587a9f9 100644 --- a/daemon/dbus_messaging.c +++ b/daemon/dbus_messaging.c @@ -28,6 +28,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "dbus_messaging.h" #include "daemonize.h" #include "gamemode.h" @@ -38,11 +39,13 @@ POSSIBILITY OF SUCH DAMAGE. #include -// sd-bus tracker values +/* systemd dbus components */ static sd_bus *bus = NULL; static sd_bus_slot *slot = NULL; -// Clean up any resources as needed +/** + * Clean up our private dbus state + */ static void clean_up() { if (slot) { @@ -55,10 +58,13 @@ static void clean_up() bus = NULL; } -// Callback for RegisterGame +/** + * Handles the RegisterGame D-BUS Method + */ static int method_register_game(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { int pid = 0; + GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "i", &pid); if (ret < 0) { @@ -66,15 +72,18 @@ static int method_register_game(sd_bus_message *m, void *userdata, sd_bus_error return ret; } - register_game(pid); + game_mode_context_register(context, (pid_t)pid); return sd_bus_reply_method_return(m, "i", 0); } -// Callback for UnregisterGame +/** + * Handles the UnregisterGame D-BUS Method + */ static int method_unregister_game(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { int pid = 0; + GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "i", &pid); if (ret < 0) { @@ -82,49 +91,49 @@ static int method_unregister_game(sd_bus_message *m, void *userdata, sd_bus_erro return ret; } - unregister_game(pid); + game_mode_context_unregister(context, (pid_t)pid); return sd_bus_reply_method_return(m, "i", 0); } -// Vtable for function dispatch +/** + * D-BUS vtable to dispatch virtual methods + */ 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_VTABLE_END }; -// Main loop, will not return until something request a quit -void run_dbus_main_loop(bool system_dbus) +/** + * Main process loop for the daemon. Run until quitting has been requested. + */ +void game_mode_context_loop(GameModeContext *context) { - // Set up function to handle clean up of resources + /* Set up function to handle clean up of resources */ atexit(clean_up); int ret = 0; - // Connec to the desired bus - if (system_dbus) { - ret = sd_bus_open_system(&bus); - } else { - ret = sd_bus_open_user(&bus); - } + /* Connect to the session bus */ + ret = sd_bus_open_user(&bus); if (ret < 0) { FATAL_ERROR("Failed to connect to the bus: %s", strerror(-ret)); } - // Create the object to allow connections + /* Create the object to allow connections */ ret = sd_bus_add_object_vtable(bus, &slot, "/com/feralinteractive/GameMode", "com.feralinteractive.GameMode", gamemode_vtable, - NULL); + context); if (ret < 0) { FATAL_ERROR("Failed to install GameMode object: %s", strerror(-ret)); } - // Request our name + /* Request our name */ ret = sd_bus_request_name(bus, "com.feralinteractive.GameMode", 0); if (ret < 0) { FATAL_ERROR("Failed to acquire service name: %s", strerror(-ret)); @@ -132,19 +141,19 @@ void run_dbus_main_loop(bool system_dbus) LOG_MSG("Successfully initialised bus with name [%s]...\n", "com.feralinteractive.GameMode"); - // Now loop, waiting for callbacks + /* Now loop, waiting for callbacks */ for (;;) { ret = sd_bus_process(bus, NULL); if (ret < 0) { FATAL_ERROR("Failure when processing the bus: %s", strerror(-ret)); } - // We're done processing + /* We're done processing */ if (ret > 0) { continue; } - // Wait for more + /* Wait for more */ ret = sd_bus_wait(bus, (uint64_t)-1); if (ret < 0 && -ret != EINTR) { FATAL_ERROR("Failure when waiting on bus: %s", strerror(-ret)); diff --git a/daemon/dbus_messaging.h b/daemon/dbus_messaging.h index bd41677..f9167c7 100644 --- a/daemon/dbus_messaging.h +++ b/daemon/dbus_messaging.h @@ -28,13 +28,14 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _DBUS_MESSAGING_GAMEMODE_H_ -#define _DBUS_MESSAGING_GAMEMODE_H_ + +#pragma once #include -// Run the main dbus loop -// Will not return until finished -void run_dbus_main_loop(bool system_dbus); +#include "gamemode.h" -#endif // _DBUS_MESSAGING_GAMEMODE_H_ +/** + * Run the main D-BUS loop "forever" + */ +void game_mode_context_loop(GameModeContext *context); diff --git a/daemon/gamemode.c b/daemon/gamemode.c index 80fcc2f..1ef32b8 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -35,142 +35,371 @@ POSSIBILITY OF SUCH DAMAGE. #include "governors.h" #include "logging.h" +#include +#include #include +#include #include -// Storage for game PIDs -#define MAX_GAMES 16 -static int game_pids[MAX_GAMES]; -static int num_games = 0; +/** + * The GameModeClient encapsulates the remote connection, providing a list + * form to contain the pid and credentials. + */ +typedef struct GameModeClient { + pid_t pid; /**< Process ID */ + struct GameModeClient *next; /**refcount = ATOMIC_VAR_INIT(0); - // Check if games are alive at all - for (int i = 0; i < num_games;) { - int game = game_pids[i]; - - if (kill(game, 0) != 0) { - LOG_MSG("Removing expired game [%i]...\n", game); - memmove(&game_pids[i], &game_pids[i + 1], MAX_GAMES - (i + 1)); - num_games--; - } else { - i++; - } - } - - // Either trigger another alarm, or reset the governors - if (num_games) { - start_alarm_timer(); - } else { - leave_game_mode(); - } -} - -// Call to trigger starting the alarm timer for pid checks -static void start_alarm_timer() -{ - // Set up process check alarms - signal(SIGALRM, alarm_handler); - alarm(wakeup_timer); -} - -// Intialise any game mode needs -void init_game_mode() -{ - // Read current governer state before setting up any message handling + /* Read current governer state before setting up any message handling */ update_initial_gov_state(); LOG_MSG("governor is set to [%s]\n", get_initial_governor()); -} -// Called on exit to clean up the governors if appropriate -void term_game_mode() -{ - if (num_games) { - leave_game_mode(); + pthread_mutex_init(&self->reaper.mutex, NULL); + pthread_cond_init(&self->reaper.condition, NULL); + + /* Get the reaper thread going */ + self->reaper.running = true; + if (pthread_create(&self->reaper.thread, NULL, game_mode_context_reaper, self) != 0) { + FATAL_ERROR("Couldn't construct a new thread"); } } -// Register a game pid with the game mode -// Will trigger enter game mode if appropriate -void register_game(int pid) +void game_mode_context_destroy(GameModeContext *self) { - // Check for duplicates - for (int i = 0; i < num_games; i++) { - if (game_pids[i] == pid) { - LOG_ERROR("Addition requested for already known process [%i]\n", pid); - return; - } - } - - // Check we've not already hit max - if (num_games == MAX_GAMES) { - LOG_ERROR("Max games (%i) reached, could not add [%i]\n", MAX_GAMES, pid); + if (!had_context_init) { return; } - // Add the game to the database - LOG_MSG("Adding game: %i\n", pid); - game_pids[num_games] = pid; - num_games++; + /* Leave game mode now */ + if (game_mode_context_num_clients(self) > 0) { + game_mode_context_leave(self); + } - if (num_games == 1) { - enter_game_mode(); + had_context_init = false; + game_mode_client_free(self->client); + self->reaper.running = false; + + /* We might be stuck waiting, so wake it up again */ + pthread_mutex_lock(&self->reaper.mutex); + pthread_cond_signal(&self->reaper.condition); + pthread_mutex_unlock(&self->reaper.mutex); + + /* Join the thread as soon as possible */ + pthread_join(self->reaper.thread, NULL); + + pthread_cond_destroy(&self->reaper.condition); + pthread_mutex_destroy(&self->reaper.mutex); + + pthread_rwlock_destroy(&self->rwlock); +} + +/** + * Pivot into game mode. + * + * This is only possible after game_mode_context_init has made a GameModeContext + * usable, and should always be followed by a game_mode_context_leave. + */ +static void game_mode_context_enter(GameModeContext *self) +{ + LOG_MSG("Entering Game Mode...\n"); + if (!self->performance_mode && set_governors("performance")) { + self->performance_mode = true; } } -// Remove a game from game mode -// Will exit game mode if appropriate -void unregister_game(int pid) +/** + * Pivot out of game mode. + * + * Should only be called after both init and game_mode_context_enter have + * been performed. + */ +static void game_mode_context_leave(GameModeContext *self) +{ + LOG_MSG("Leaving Game Mode...\n"); + if (self->performance_mode && set_governors(NULL)) { + self->performance_mode = false; + } +} + +/** + * Automatically expire all dead processes + * + * This has to take special care to ensure thread safety and ensuring that our + * pointer is never cached incorrectly. + */ +static void game_mode_context_auto_expire(GameModeContext *self) +{ + bool removing = true; + + while (removing) { + pthread_rwlock_rdlock(&self->rwlock); + removing = false; + + /* Each time we hit an expired game, start the loop back */ + for (GameModeClient *client = self->client; client; client = client->next) { + 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); + removing = true; + break; + } + } + + if (!removing) { + pthread_rwlock_unlock(&self->rwlock); + break; + } + } +} + +/** + * Determine if the client is already known to the context + */ +static bool game_mode_context_has_client(GameModeContext *self, pid_t client) { bool found = false; + pthread_rwlock_rdlock(&self->rwlock); - // Check list even contains this entry - for (int i = 0; i < num_games; i++) { - if (game_pids[i] == pid) { - LOG_MSG("Removing game: %i\n", pid); - memmove(&game_pids[i], &game_pids[i + 1], MAX_GAMES - (i + 1)); - num_games--; + /* Walk all clients and find a matching pid */ + for (GameModeClient *cl = self->client; cl; cl = cl->next) { + if (cl->pid == client) { found = true; + break; } } - if (!found) { - LOG_ERROR("Removal requested for unknown process [%i]\n", pid); - return; + pthread_rwlock_unlock(&self->rwlock); + return found; +} + +/** + * Helper to grab the current number of clients we know about + */ +static int game_mode_context_num_clients(GameModeContext *self) +{ + return atomic_load(&self->refcount); +} + +bool game_mode_context_register(GameModeContext *self, pid_t client) +{ + /* Construct a new client if we can */ + GameModeClient *cl = NULL; + + cl = game_mode_client_new(client); + if (!cl) { + fputs("OOM\n", stderr); + return false; + } + cl->executable = game_mode_context_find_exe(client); + + if (game_mode_context_has_client(self, client)) { + LOG_ERROR("Addition requested for already known process [%d]\n", client); + return false; } - // Leave game mode if needed - if (num_games == 0) { - leave_game_mode(); + /* 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; } + + /* Begin a write lock now to insert our new client at list start */ + pthread_rwlock_wrlock(&self->rwlock); + + LOG_MSG("Adding game: %d [%s]\n", client, cl->executable); + + /* Update the list */ + cl->next = self->client; + self->client = cl; + pthread_rwlock_unlock(&self->rwlock); + + /* First add, init */ + if (atomic_fetch_add_explicit(&self->refcount, 1, memory_order_seq_cst) == 0) { + game_mode_context_enter(self); + } + + return true; +} + +bool game_mode_context_unregister(GameModeContext *self, pid_t client) +{ + GameModeClient *cl = NULL; + GameModeClient *prev = NULL; + bool found = false; + + /* Requires locking. */ + pthread_rwlock_wrlock(&self->rwlock); + + for (prev = cl = self->client; cl; cl = cl->next) { + if (cl->pid != client) { + prev = cl; + continue; + } + + LOG_MSG("Removing game: %d [%s]\n", client, cl->executable); + + /* Found it */ + found = true; + prev->next = cl->next; + if (cl == self->client) { + self->client = cl->next; + } + cl->next = NULL; + game_mode_client_free(cl); + break; + } + + /* Unlock here, potentially yielding */ + pthread_rwlock_unlock(&self->rwlock); + + if (!found) { + LOG_ERROR("Removal requested for unknown process [%d]\n", client); + return false; + } + + /* When we hit bottom then end the game mode */ + if (atomic_fetch_sub_explicit(&self->refcount, 1, memory_order_seq_cst) == 1) { + game_mode_context_leave(self); + } + + return true; +} + +/** + * Construct a new GameModeClient for the given process ID + * + * This is deliberately OOM safe + */ +static GameModeClient *game_mode_client_new(pid_t pid) +{ + GameModeClient c = { + .next = NULL, + .pid = pid, + }; + GameModeClient *ret = NULL; + + ret = calloc(1, sizeof(struct GameModeClient)); + if (!ret) { + return NULL; + } + *ret = c; + return ret; +} + +/** + * Free a client and the next element in the list. + */ +static void game_mode_client_free(GameModeClient *client) +{ + if (!client) { + return; + } + if (client->next) { + game_mode_client_free(client->next); + } + if (client->executable) { + free(client->executable); + } + free(client); +} + +/** + * We continuously run until told otherwise. + */ +static void *game_mode_context_reaper(void *userdata) +{ + /* Stack, not allocated, won't disappear. */ + GameModeContext *self = userdata; + struct timespec ts = { 0, 0 }; + ts.tv_sec = time(NULL) + SLEEP_INTERVAL; + + while (self->reaper.running) { + /* Wait for condition */ + pthread_mutex_lock(&self->reaper.mutex); + pthread_cond_timedwait(&self->reaper.condition, &self->reaper.mutex, &ts); + pthread_mutex_unlock(&self->reaper.mutex); + + /* Highly possible the main thread woke us up to exit */ + if (!self->reaper.running) { + return NULL; + } + + /* Expire remaining entries */ + game_mode_context_auto_expire(self); + + ts.tv_sec = time(NULL) + SLEEP_INTERVAL; + } + + return NULL; +} + +GameModeContext *game_mode_context_instance() +{ + return &instance; +} + +/** + * Attempt to locate the exe for the process. + * We might run into issues if the process is running under an odd umask. + */ +static char *game_mode_context_find_exe(pid_t pid) +{ + static char proc_path[PATH_MAX] = { 0 }; + + if (snprintf(proc_path, sizeof(proc_path), "/proc/%d/exe", pid) < 0) { + LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno)); + return NULL; + } + + /* Allocate the realpath if possible */ + return realpath(proc_path, NULL); } diff --git a/daemon/gamemode.h b/daemon/gamemode.h index 4c89e11..91de093 100644 --- a/daemon/gamemode.h +++ b/daemon/gamemode.h @@ -28,16 +28,48 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _GAME_MODE_GAMEMODE_H_ -#define _GAME_MODE_GAMEMODE_H_ -// Initialise or terminate the game mode system -void init_game_mode(); -void term_game_mode(); +#pragma once -// Add or remove games to the tracker -// Tracker will automatically start and stop game mode as appropriate -void register_game(int pid); -void unregister_game(int pid); +#include +#include -#endif // _GAME_MODE_GAMEMODE_H_ +/** + * Opaque context + */ +typedef struct GameModeContext GameModeContext; + +/** + * Return the singleton instance + */ +GameModeContext *game_mode_context_instance(void); + +/** + * Initialise the GameModeContext + * + * This is performed in a thread-safe fashion. + */ +void game_mode_context_init(GameModeContext *self); + +/** + * Destroy the previously initialised GameModeContext. + * + * This is performed in a thread safe fashion. + */ +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 + */ +bool game_mode_context_register(GameModeContext *self, pid_t pid); + +/** + * 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. + */ +bool game_mode_context_unregister(GameModeContext *self, pid_t pid); diff --git a/daemon/governors-query.c b/daemon/governors-query.c new file mode 100644 index 0000000..55ff600 --- /dev/null +++ b/daemon/governors-query.c @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2017, Feral Interactive +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ + +#define _GNU_SOURCE + +#include "governors-query.h" +#include "logging.h" + +#include +#include +#include + +/** + * Discover all governers on the system. + * + * Located at /sys/devices/system/cpu/cpu(*)/cpufreq/scaling_governor + */ +int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH]) +{ + glob_t glo = { 0 }; + static const char *path = "/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"; + + /* Assert some sanity on this glob */ + if (glob(path, GLOB_NOSORT, NULL, &glo) != 0) { + FATAL_ERRORNO("Broken glob implementation"); + } + + if (glo.gl_pathc < 1) { + globfree(&glo); + FATAL_ERROR("cpu device path not found"); + } + + int num_governors = 0; + + /* Walk the glob set */ + for (size_t i = 0; i < glo.gl_pathc; i++) { + if (i >= MAX_GOVERNORS) { + break; + } + + /* Get the real path to the file. + * Traditionally cpufreq symlinks to a policy directory that can + * be shared, so let's prevent duplicates. + */ + char fullpath[PATH_MAX] = { 0 }; + const char *ptr = realpath(glo.gl_pathv[i], fullpath); + if (fullpath != ptr) { + continue; + } + + /* Only add this governor if it is unique */ + for (int i = 0; i < num_governors; i++) { + if (strncmp(fullpath, governors[i], MAX_GOVERNOR_LENGTH) == 0) { + continue; + } + } + + /* Copy this governor into the output set */ + strncpy(governors[num_governors], fullpath, MAX_GOVERNOR_LENGTH); + num_governors++; + } + + globfree(&glo); + return num_governors; +} + +/** + * Return the current governor state + */ +const char *get_gov_state() +{ + /* Cached primary governor state */ + static char governor[64] = { 0 }; + + /* State for all governors */ + char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH] = { { 0 } }; + int num = fetch_governors(governors); + + /* Check the list */ + for (int i = 0; i < num; i++) { + const char *gov = governors[i]; + + FILE *f = fopen(gov, "r"); + if (!f) { + LOG_ERROR("Failed to open file for read %s\n", gov); + continue; + } + + /* Grab the file length */ + fseek(f, 0, SEEK_END); + int length = ftell(f); + fseek(f, 0, SEEK_SET); + + char contents[length]; + + if (fread(contents, 1, length, f) > 0) { + /* Files have a newline */ + strtok(contents, "\n"); + if (strlen(governor) > 0 && strncmp(governor, contents, 64) != 0) { + /* Don't handle the mixed case, this shouldn't ever happen + * But it is a clear sign we shouldn't carry on */ + LOG_ERROR("Governors malformed: got \"%s\", expected \"%s\"", contents, governor); + return "malformed"; + } + + strncpy(governor, contents, sizeof(governor)); + } else { + LOG_ERROR("Failed to read contents of %s\n", gov); + } + + fclose(f); + } + + return governor; +} diff --git a/daemon/governors-query.h b/daemon/governors-query.h new file mode 100644 index 0000000..151ae87 --- /dev/null +++ b/daemon/governors-query.h @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2017, Feral Interactive +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ + +#pragma once + +#include + +#define MAX_GOVERNORS 128 +#define MAX_GOVERNOR_LENGTH PATH_MAX + 1 + +/** + * Grab all of the governors + */ +int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH]); + +/** + * Get the current governor state + */ +const char *get_gov_state(); diff --git a/daemon/governors.c b/daemon/governors.c index 2cf30e7..b69b8b5 100644 --- a/daemon/governors.c +++ b/daemon/governors.c @@ -32,51 +32,74 @@ POSSIBILITY OF SUCH DAMAGE. #define _GNU_SOURCE #include "governors.h" +#include "config.h" +#include "governors-query.h" #include "logging.h" #include #include +#include #include -static char initial[32]; +static const char *initial = NULL; -// Store the initial governor state to be referenced later +/** + * Cache the governor state as seen at startup + */ void update_initial_gov_state() { - static char *command = "cpugovctl get"; - - FILE *f = popen(command, "r"); - if (!f) { - FATAL_ERRORNO("Failed to launch \"%s\" script", command); - } - - if (!fgets(initial, sizeof(initial) - 1, f)) { - FATAL_ERROR("Failed to get output from \"%s\"", command); - } - - pclose(f); - - strtok(initial, "\n"); + initial = get_gov_state(); } -// Sets all governors to a value, if NULL argument provided, will reset them back -void set_governors(const char *value) +/** + * Update the governors to the given argument, via pkexec + */ +bool set_governors(const char *value) { - const char *newval = value ? value : initial; - LOG_MSG("Setting governors to %s\n", newval ? newval : "initial values"); + pid_t p; + int status = 0; + int ret = 0; + int r = -1; - char command[PATH_MAX] = {}; - snprintf(command, sizeof(command), "cpugovctl set %s", newval); + const char *govern = value ? value : initial; + char *exec_args[] = { + "/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", (char *)govern, NULL, + }; - FILE *f = popen(command, "r"); - if (!f) { - FATAL_ERRORNO("Failed to launch %s script", command); + LOG_MSG("Requesting update of governor policy to %s\n", govern); + + if ((p = fork()) < 0) { + LOG_ERROR("Failed to fork(): %s\n", strerror(errno)); + return false; + } else if (p == 0) { + /* Execute the command */ + if ((r = execv(exec_args[0], exec_args)) != 0) { + LOG_ERROR("Failed to execute cpugovctl helper: %s %s\n", exec_args[1], strerror(errno)); + exit(EXIT_FAILURE); + } + _exit(EXIT_SUCCESS); + } else { + if (waitpid(p, &status, 0) < 0) { + LOG_ERROR("Failed to waitpid(%d): %s\n", (int)p, strerror(errno)); + return false; + } + /* i.e. sigsev */ + if (!WIFEXITED(status)) { + LOG_ERROR("Child process '%s' exited abnormally\n", exec_args[0]); + } } - pclose(f); + if ((ret = WEXITSTATUS(status)) != 0) { + LOG_ERROR("Failed to update cpu governor policy\n"); + return false; + } + + return true; } -// Return the initial governor +/** + * Return the cached governor seen at startup + */ const char *get_initial_governor() { return initial; diff --git a/daemon/governors.h b/daemon/governors.h index 19277bf..b66a160 100644 --- a/daemon/governors.h +++ b/daemon/governors.h @@ -28,16 +28,23 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _GOVERNORS_GAMEMODE_H_ -#define _GOVERNORS_GAMEMODE_H_ -// Store the initial governor state to be referenced later +#pragma once + +#include + +/** + * Store initial governor so we can use it again + */ void update_initial_gov_state(); -// Get the initial governor state +/** + * Return the governer set in update_initial_gov_state + */ const char *get_initial_governor(); -// Sets all governors to a value, if null argument provided, will reset them back -void set_governors(const char *value); - -#endif // _GOVERNORS_GAMEMODE_H_ +/** + * Update all governors to the given value. If this is NULL, restore the + * initial governor. + */ +bool set_governors(const char *value); diff --git a/daemon/logging.c b/daemon/logging.c index c6fe4f2..e22e0ff 100644 --- a/daemon/logging.c +++ b/daemon/logging.c @@ -28,20 +28,25 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "logging.h" #include "syslog.h" static bool use_syslog = false; -// Control if we want to use the system logger +/** + * Control if we want to use the system logger + */ void set_use_syslog(const char *name) { - // Open the syslog + /* Open the syslog */ openlog(name, LOG_PID, LOG_DAEMON); use_syslog = true; } -// Simple getter for the syslog var +/** + * Simple getter for the syslog var + */ bool get_use_syslog() { return use_syslog; diff --git a/daemon/logging.h b/daemon/logging.h index 37b1126..d8efc54 100644 --- a/daemon/logging.h +++ b/daemon/logging.h @@ -28,8 +28,8 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _LOGGING_GAMEMODE_H_ -#define _LOGGING_GAMEMODE_H_ + +#pragma once #include #include @@ -39,7 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -// Logging helpers +/* Macros to help with basic logging */ #define PLOG_MSG(msg, ...) printf(msg, ##__VA_ARGS__) #define SYSLOG_MSG(msg, ...) syslog(LOG_INFO, msg, ##__VA_ARGS__) #define LOG_MSG(msg, ...) \ @@ -62,7 +62,7 @@ POSSIBILITY OF SUCH DAMAGE. } \ } while (0) -// Fatal errors trigger an exit +/* Fatal warnings trigger an exit */ #define FATAL_ERRORNO(msg, ...) \ do { \ LOG_ERROR(msg " (%s)\n", ##__VA_ARGS__, strerror(errno)); \ @@ -74,8 +74,8 @@ POSSIBILITY OF SUCH DAMAGE. exit(EXIT_FAILURE); \ } while (0) -// Control if we want to use the system logger +/** + * Control if and how how we use syslog + */ void set_use_syslog(const char *name); bool get_use_syslog(); - -#endif //_LOGGING_GAMEMODE_H_ diff --git a/daemon/main.c b/daemon/main.c index 55863b7..b55111c 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -29,7 +29,23 @@ POSSIBILITY OF SUCH DAMAGE. */ -// Simple daemon to allow user space programs to control the CPU governors +/** + * Simple daemon to allow user space programs to control the CPU governors + * + * The main process is responsible for bootstrapping the D-BUS daemon, caching + * the initial governor settings, and then responding to requests over D-BUS. + * + * Clients register their pid(s) with the service, which are routinely checked + * to see if they've expired. Once we reach our first actively registered client + * we put the system into "game mode", i.e. move the CPU governor into a performance + * mode. + * + * Upon exit, or when all clients have stopped running, we put the system back + * into the default governor policy, which is invariably powersave or similar + * on laptops. This ensures that the system is obtaining the maximum performance + * whilst gaming, and allowed to sanely return to idle once the workload is + * complete. + */ #define _GNU_SOURCE @@ -46,59 +62,62 @@ static void sigint_handler(int signo) { LOG_MSG("Quitting by request...\n"); - // Terminate the game mode - term_game_mode(); + /* Clean up nicely */ + game_mode_context_destroy(game_mode_context_instance()); exit(EXIT_SUCCESS); } -// Main entry point +/** + * Main bootstrap entry into gamemoded + */ int main(int argc, char *argv[]) { - // Gather command line options + GameModeContext *context = NULL; + + /* Gather command line options */ bool daemon = false; - bool system_dbus = false; bool use_syslog = false; int opt = 0; - while ((opt = getopt(argc, argv, "dsl")) != -1) { + while ((opt = getopt(argc, argv, "dl")) != -1) { switch (opt) { case 'd': daemon = true; break; - case 's': - system_dbus = true; - break; case 'l': use_syslog = true; break; default: - fprintf(stderr, "Usage: %s [-d] [-s] [-l]\n", argv[0]); + fprintf(stderr, "Usage: %s [-d] [-l]\n", argv[0]); exit(EXIT_FAILURE); break; } } - // Use syslog if requested + /* If syslog is requested, set it up with our process name */ if (use_syslog) { set_use_syslog(argv[0]); } - // Daemonize ourselves first if asked + /* Daemonize ourselves first if asked */ if (daemon) { daemonize(argv[0]); } - // Set up the game mode - init_game_mode(); + /* Set up the game mode context */ + context = game_mode_context_instance(); + game_mode_context_init(context); - // Set up the SIGINT handler + /* Handle quits cleanly */ if (signal(SIGINT, sigint_handler) == SIG_ERR) { FATAL_ERRORNO("Could not catch SIGINT"); } - // Run the main dbus message loop - run_dbus_main_loop(system_dbus); + /* Run the main dbus message loop */ + game_mode_context_loop(context); - // Log we're finished + game_mode_context_destroy(context); + + /* Log we're finished */ LOG_MSG("Quitting naturally...\n"); } diff --git a/daemon/meson.build b/daemon/meson.build index 4e27902..cbc8d96 100644 --- a/daemon/meson.build +++ b/daemon/meson.build @@ -1,6 +1,7 @@ # Convenience library for the duplicated logging functionality common_sources = [ 'logging.c', + 'governors-query.c', ] daemon_common = static_library( @@ -27,8 +28,12 @@ executable( sources: daemon_sources, dependencies: [ link_daemon_common, + dep_threads, dep_systemd, ], + include_directories: [ + config_h_dir, + ], install: true, ) @@ -44,4 +49,5 @@ cpugovctl = executable( link_daemon_common, ], install: true, + install_dir: path_libexecdir, ) diff --git a/data/com.feralinteractive.GameMode.policy.in b/data/com.feralinteractive.GameMode.policy.in new file mode 100644 index 0000000..8165162 --- /dev/null +++ b/data/com.feralinteractive.GameMode.policy.in @@ -0,0 +1,26 @@ + + + + + + + Feral GameMode Activation + http://www.feralinteractive.com + + + Modify the CPU governor + Authentication is required to modify the CPU governor + + no + no + yes + + @LIBEXECDIR@/cpugovctl + + + diff --git a/data/com.feralinteractive.GameMode.service.in b/data/com.feralinteractive.GameMode.service.in new file mode 100644 index 0000000..95a594e --- /dev/null +++ b/data/com.feralinteractive.GameMode.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=com.feralinteractive.GameMode +Exec=@BINDIR@/gamemoded -d diff --git a/data/cpugovctl_perms.sh b/data/cpugovctl_perms.sh deleted file mode 100644 index 60b640e..0000000 --- a/data/cpugovctl_perms.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Allow cpugovctl to control the governors -PREFIX=${MESON_INSTALL_PREFIX:-/usr} -chmod +4555 ${DESTDIR}${PREFIX}/bin/cpugovctl diff --git a/data/gamemoded.service b/data/gamemoded.service deleted file mode 100644 index 0fbc899..0000000 --- a/data/gamemoded.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=gamemoded - -[Service] -Type=dbus -BusName=com.feralinteractive.GameMode -ExecStart=/usr/bin/gamemoded -l - -[Install] -WantedBy=default.target diff --git a/data/meson.build b/data/meson.build index a75cee4..616cc88 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,8 +1,19 @@ -# Install the service file -install_data('gamemoded.service', install_dir: path_systemd_unit_dir) +data_conf = configuration_data() +data_conf.set('BINDIR', path_bindir) +data_conf.set('LIBEXECDIR', path_libexecdir) -# Give cpugovctl the permissions it needs -meson.add_install_script( - 'cpugovctl_perms.sh', - dependencies: cpugovctl, +# Install the D-BUS service file +configure_file( + input: 'com.feralinteractive.GameMode.service.in', + output: 'com.feralinteractive.GameMode.service', + configuration: data_conf, + install_dir: path_dbus_service_dir, +) + +# Install the Polkit action file +configure_file( + input: 'com.feralinteractive.GameMode.policy.in', + output: 'com.feralinteractive.GameMode.policy', + configuration: data_conf, + install_dir: path_polkit_action_dir, ) diff --git a/example/main.c b/example/main.c index 0112a3e..38d93ef 100644 --- a/example/main.c +++ b/example/main.c @@ -28,6 +28,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "gamemode_client.h" #include @@ -35,15 +36,15 @@ POSSIBILITY OF SUCH DAMAGE. int main() { - // Request we start game mode + /* Request we start game mode */ if (gamemode_request_start() != 0) { printf("Failed to request gamemode start: %s...\n", gamemode_error_string()); } - // Simulate running a game + /* Simulate running a game */ sleep(10); - // Request we end game mode (optional) + /* Request we end game mode (optional) */ if (gamemode_request_end() != 0) { printf("Failed to request gamemode end: %s...\n", gamemode_error_string()); } diff --git a/lib/gamemode_client.h b/lib/gamemode_client.h index 400688e..d357be2 100644 --- a/lib/gamemode_client.h +++ b/lib/gamemode_client.h @@ -40,34 +40,39 @@ POSSIBILITY OF SUCH DAMAGE. char _client_error_string[512] = {}; -// Load libgamemode dynamically to dislodge us from most dependencies -// This allows clients to link and/or use this regardless of runtime -// See SDL2 for an example of the reasoning behind this in terms of -// dynamic versioning as well -int _libgamemode_loaded = 1; +/** + * Load libgamemode dynamically to dislodge us from most dependencies. + * This allows clients to link and/or use this regardless of runtime. + * See SDL2 for an example of the reasoning behind this in terms of + * dynamic versioning as well. + */ +volatile int _libgamemode_loaded = 1; -// Typedefs for the functions to load +/* Typedefs for the functions to load */ typedef int (*_gamemode_request_start)(); typedef int (*_gamemode_request_end)(); typedef const char *(*_gamemode_error_string)(); -// Storage for functors +/* Storage for functors */ _gamemode_request_start _REAL_gamemode_request_start = NULL; _gamemode_request_end _REAL_gamemode_request_end = NULL; _gamemode_error_string _REAL_gamemode_error_string = NULL; -// Loads libgamemode and needed functions -// returns 0 on success and -1 on failure +/** + * Loads libgamemode and needed functions + * + * Returns 0 on success and -1 on failure + */ __attribute__((always_inline)) inline int _load_libgamemode() { - // We start at 1, 0 is a success and -1 is a fail + /* We start at 1, 0 is a success and -1 is a fail */ if (_libgamemode_loaded != 1) { return _libgamemode_loaded; } void *libgamemode = NULL; - // Try and load libgamemode + /* Try and load libgamemode */ libgamemode = dlopen("libgamemode.so", RTLD_NOW); if (!libgamemode) { snprintf(_client_error_string, @@ -82,7 +87,7 @@ __attribute__((always_inline)) inline int _load_libgamemode() _REAL_gamemode_error_string = (_gamemode_error_string)dlsym(libgamemode, "real_gamemode_error_string"); - // Verify we have the functions we want + /* Verify we have the functions we want */ if (_REAL_gamemode_request_start && _REAL_gamemode_request_end && _REAL_gamemode_error_string) { _libgamemode_loaded = 0; @@ -99,10 +104,12 @@ __attribute__((always_inline)) inline int _load_libgamemode() return -1; } -// Redirect to the real libgamemode +/** + * Redirect to the real libgamemode + */ __attribute__((always_inline)) inline const char *gamemode_error_string() { - // If we fail to load the system gamemode, return our error string + /* If we fail to load the system gamemode, return our error string */ if (_load_libgamemode() < 0) { return _client_error_string; } @@ -110,9 +117,11 @@ __attribute__((always_inline)) inline const char *gamemode_error_string() return _REAL_gamemode_error_string(); } -// Redirect to the real libgamemode -// Allow automatically requesting game mode -// Also prints errors as they happen +/** + * Redirect to the real libgamemod + * Allow automatically requesting game mode + * Also prints errors as they happen. + */ #ifdef GAMEMODE_AUTO __attribute__((constructor)) #else @@ -120,7 +129,7 @@ __attribute__((always_inline)) inline #endif int gamemode_request_start() { - // Need to load gamemode + /* Need to load gamemode */ if (_load_libgamemode() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); @@ -138,7 +147,7 @@ int gamemode_request_start() return 0; } -// Redirect to the real libgamemode +/* Redirect to the real libgamemode */ #ifdef GAMEMODE_AUTO __attribute__((destructor)) #else @@ -146,7 +155,7 @@ __attribute__((always_inline)) inline #endif int gamemode_request_end() { - // Need to load gamemode + /* Need to load gamemode */ if (_load_libgamemode() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); diff --git a/meson.build b/meson.build index bb0d28b..31905e2 100644 --- a/meson.build +++ b/meson.build @@ -13,24 +13,37 @@ path_bindir = join_paths(path_prefix, get_option('bindir')) path_datadir = join_paths(path_prefix, get_option('datadir')) path_includedir = join_paths(path_prefix, get_option('includedir')) path_libdir = join_paths(path_prefix, get_option('libdir')) +path_libexecdir = join_paths(path_prefix, get_option('libexecdir')) # Find systemd via pkgconfig dep_systemd = dependency('libsystemd') +# Allow meson to figure out how the compiler sets up threading +dep_threads = dependency('threads') + # On non glibc systems this might be a stub, i.e. for musl libdl = cc.find_library('dl', required: false) -# If the path isn't explicitly set, ask systemd for the systemd user unit directory -path_systemd_unit_dir = get_option('with-systemd-user-unit-dir') -if path_systemd_unit_dir == '' - message('Asking pkg-config for systemd\'s directories') - pkgconfig_systemd = dependency('systemd') - path_systemd_unit_dir = pkgconfig_systemd.get_pkgconfig_variable('systemduserunitdir') +# Set the dbus path as appropriate. +path_dbus_service_dir = get_option('with-dbus-service-dir') +if path_dbus_service_dir == '' + path_dbus_service_dir = join_paths(path_datadir, 'dbus-1', 'services') endif +path_polkit_action_dir = join_paths(path_datadir, 'polkit-1', 'actions') + with_daemon = get_option('with-daemon') with_examples = get_option('with-examples') +# Provide config.h so the daemon knows where the helper is +cdata = configuration_data() +cdata.set_quoted('LIBEXECDIR', path_libexecdir) +config_h = configure_file( + configuration: cdata, + output: 'config.h', +) +config_h_dir = include_directories('.') + # Library is always required subdir('lib') @@ -57,8 +70,10 @@ report = [ ' bindir: @0@'.format(path_bindir), ' datadir: @0@'.format(path_datadir), ' libdir: @0@'.format(path_libdir), + ' libexecdir: @0@'.format(path_libexecdir), ' includedir: @0@'.format(path_includedir), - ' systemd user unit directory: @0@'.format(path_systemd_unit_dir), + ' D-BUS service directory: @0@'.format(path_dbus_service_dir), + ' PolKit Action Directory: @0@'.format(path_polkit_action_dir), '', ' Options:', ' ========', diff --git a/meson_options.txt b/meson_options.txt index a7dd6f0..dfc93d3 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,3 @@ -option('with-systemd-user-unit-dir', type: 'string', description: 'Explicitly set the systemd user unit directory') +option('with-dbus-service-dir', type: 'string', description: 'Explicitly set the D-BUS session directory') option('with-examples', type: 'boolean', description: 'Build sample programs', value: 'true') option('with-daemon', type: 'boolean', description: 'Build the daemon', value: 'true')