mirror of
https://github.com/FeralInteractive/gamemode.git
synced 2025-06-26 17:31:45 +02:00
Add config file parsing
Checks for a gamemode.ini in /usr/share/gamemode/ (or in the cwd for debugging) Currently allows for blacklisting and whitelisting clients based on rudimentary needle-haystack executable name checks See the example/gamemode.ini file for expected syntax Using the BSD licensed inih library (with additional meson.build file)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
build/
|
||||
subprojects/inih/
|
||||
*.swp
|
||||
|
10
README.md
10
README.md
@ -101,6 +101,16 @@ Add `-lgamemodeauto` to linker arguments and distribute `libgamemodeauto.so` wit
|
||||
#### Option 3: Distribute and script
|
||||
Distribute `libgamemodeauto.so` with the game and add to LD\_PRELOAD in a launch script
|
||||
|
||||
---
|
||||
## Configuration
|
||||
|
||||
The daemon can currently be configured using a `gamemode.ini` file in `/usr/share/gamemode/`. It will load the file when starting up.
|
||||
|
||||
An example of what the file could look like is found in the `example` directory.
|
||||
|
||||
The file parsing uses [inih](https://github.com/benhoyt/inih).
|
||||
|
||||
|
||||
---
|
||||
## Pull Requests
|
||||
Pull requests must match with the coding style found in the `.clang-format` file
|
||||
|
245
daemon/daemon_config.c
Normal file
245
daemon/daemon_config.c
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
|
||||
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 "daemon_config.h"
|
||||
#include "logging.h"
|
||||
|
||||
/* Ben Hoyt's inih library */
|
||||
#include "ini.h"
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Name and possible location of the config file */
|
||||
#define CONFIG_NAME "gamemode.ini"
|
||||
#define CONFIG_DIR "/usr/share/gamemode/"
|
||||
|
||||
/* Maximum values in the whilelist and blacklist */
|
||||
#define MAX_LIST_VALUES 32
|
||||
|
||||
/* Maximum length of values in the whilelist or blacklist */
|
||||
#define MAX_LIST_VALUE_LENGTH 256
|
||||
|
||||
/**
|
||||
* The config holds various details as needed
|
||||
* and a rwlock to allow config_reload to be called
|
||||
*/
|
||||
struct GameModeConfig {
|
||||
pthread_rwlock_t rwlock;
|
||||
int inotfd;
|
||||
int inotwd;
|
||||
|
||||
char whitelist[MAX_LIST_VALUES][MAX_LIST_VALUE_LENGTH];
|
||||
char blacklist[MAX_LIST_VALUES][MAX_LIST_VALUE_LENGTH];
|
||||
};
|
||||
|
||||
/*
|
||||
* Handler for the inih callback
|
||||
*/
|
||||
static int inih_handler(void *user, const char *section, const char *name, const char *value)
|
||||
{
|
||||
GameModeConfig *self = (GameModeConfig *)user;
|
||||
bool valid = false;
|
||||
|
||||
/* Filter subsection */
|
||||
if (strcmp(section, "filter") == 0) {
|
||||
if (strcmp(name, "whitelist") == 0) {
|
||||
valid = true;
|
||||
|
||||
unsigned int i = 0;
|
||||
while (*self->whitelist[i] && ++i < MAX_LIST_VALUES)
|
||||
;
|
||||
|
||||
if (i < MAX_LIST_VALUES) {
|
||||
strncpy(self->whitelist[i], value, MAX_LIST_VALUE_LENGTH);
|
||||
} else {
|
||||
LOG_MSG("Could not add [%s] to the whitelist, exceeds limit of %d\n",
|
||||
value,
|
||||
MAX_LIST_VALUES);
|
||||
}
|
||||
} else if (strcmp(name, "blacklist") == 0) {
|
||||
valid = true;
|
||||
|
||||
unsigned int i = 0;
|
||||
while (*self->blacklist[i] && ++i < MAX_LIST_VALUES)
|
||||
;
|
||||
|
||||
if (i < MAX_LIST_VALUES) {
|
||||
strncpy(self->blacklist[i], value, MAX_LIST_VALUE_LENGTH);
|
||||
} else {
|
||||
LOG_MSG("Could not add [%s] to the blacklist, exceeds limit of %d\n",
|
||||
value,
|
||||
MAX_LIST_VALUES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
/* We hit an unknown section succeed but with a log */
|
||||
LOG_MSG("Unknown value in config file [%s] %s=%s\n", section, name, value);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load the config file
|
||||
*/
|
||||
static void load_config_file(GameModeConfig *self)
|
||||
{
|
||||
/* Take the write lock for the internal data */
|
||||
pthread_rwlock_wrlock(&self->rwlock);
|
||||
|
||||
/* Clear our config values */
|
||||
memset(self->whitelist, 0, sizeof(self->whitelist));
|
||||
memset(self->blacklist, 0, sizeof(self->blacklist));
|
||||
|
||||
/* try locally first */
|
||||
FILE *f = fopen(CONFIG_NAME, "r");
|
||||
if (!f) {
|
||||
f = fopen(CONFIG_DIR CONFIG_NAME, "r");
|
||||
if (!f) {
|
||||
/* Failure here isn't fatal */
|
||||
LOG_ERROR("Note: No config file found [%s] in working directory or in [%s]\n",
|
||||
CONFIG_NAME,
|
||||
CONFIG_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
if (f) {
|
||||
int error = ini_parse_file(f, inih_handler, (void *)self);
|
||||
|
||||
/* Failure here isn't fatal */
|
||||
if (error) {
|
||||
LOG_MSG("Failed to parse config file - error on line %d!\n", error);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* Release the lock */
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a context object
|
||||
*/
|
||||
GameModeConfig *config_create(void)
|
||||
{
|
||||
GameModeConfig *newconfig = (GameModeConfig *)malloc(sizeof(GameModeConfig));
|
||||
|
||||
return newconfig;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialise the config
|
||||
*/
|
||||
void config_init(GameModeConfig *self)
|
||||
{
|
||||
pthread_rwlock_init(&self->rwlock, NULL);
|
||||
|
||||
/* load the initial config */
|
||||
load_config_file(self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-load the config file
|
||||
*/
|
||||
void config_reload(GameModeConfig *self)
|
||||
{
|
||||
load_config_file(self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the config
|
||||
*/
|
||||
void config_destroy(GameModeConfig *self)
|
||||
{
|
||||
pthread_rwlock_destroy(&self->rwlock);
|
||||
|
||||
/* Finally, free the memory */
|
||||
free(self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the client is whitelisted
|
||||
*/
|
||||
bool config_get_client_whitelisted(GameModeConfig *self, const char *client)
|
||||
{
|
||||
/* 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->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 = false;
|
||||
for (unsigned int i = 0; i < MAX_LIST_VALUES && self->whitelist[i][0]; i++) {
|
||||
if (strstr(client, self->whitelist[i])) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* release the lock */
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the client is blacklisted
|
||||
*/
|
||||
bool config_get_client_blacklisted(GameModeConfig *self, const char *client)
|
||||
{
|
||||
/* 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 = false;
|
||||
for (unsigned int i = 0; i < MAX_LIST_VALUES && self->blacklist[i][0]; i++) {
|
||||
if (strstr(client, self->blacklist[i])) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* release the lock */
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
return found;
|
||||
}
|
72
daemon/daemon_config.h
Normal file
72
daemon/daemon_config.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
|
||||
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 <stdbool.h>
|
||||
|
||||
/*
|
||||
* Opaque config context type
|
||||
*/
|
||||
typedef struct GameModeConfig GameModeConfig;
|
||||
|
||||
/*
|
||||
* Initialise a config
|
||||
*/
|
||||
GameModeConfig *config_create(void);
|
||||
|
||||
/*
|
||||
* Initialise a config
|
||||
* Must be called before using any config later config functions
|
||||
*/
|
||||
void config_init(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Reload a config from disk
|
||||
* Thread safe to call
|
||||
*/
|
||||
void config_reload(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Destroy a config
|
||||
* Invalidates the config
|
||||
*/
|
||||
void config_destroy(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Get if the client is in the whitelist
|
||||
* returns false for an empty whitelist
|
||||
*/
|
||||
bool config_get_client_whitelisted(GameModeConfig *self, const char *client);
|
||||
|
||||
/*
|
||||
* Get if the client is in the blacklist
|
||||
*/
|
||||
bool config_get_client_blacklisted(GameModeConfig *self, const char *client);
|
@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "gamemode.h"
|
||||
#include "daemon_config.h"
|
||||
#include "governors.h"
|
||||
#include "logging.h"
|
||||
|
||||
@ -56,6 +57,8 @@ struct GameModeContext {
|
||||
_Atomic int refcount; /**<Allow cycling the game mode */
|
||||
GameModeClient *client; /**<Pointer to first client */
|
||||
|
||||
GameModeConfig *config; /**<Pointer to config object */
|
||||
|
||||
bool performance_mode; /**<Only updates when we can */
|
||||
|
||||
/* Reaper control */
|
||||
@ -98,6 +101,10 @@ void game_mode_context_init(GameModeContext *self)
|
||||
had_context_init = true;
|
||||
self->refcount = ATOMIC_VAR_INIT(0);
|
||||
|
||||
/* Initialise the config */
|
||||
self->config = config_create();
|
||||
config_init(self->config);
|
||||
|
||||
/* 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());
|
||||
@ -139,6 +146,9 @@ void game_mode_context_destroy(GameModeContext *self)
|
||||
pthread_cond_destroy(&self->reaper.condition);
|
||||
pthread_mutex_destroy(&self->reaper.mutex);
|
||||
|
||||
/* Destroy the config object */
|
||||
config_destroy(self->config);
|
||||
|
||||
pthread_rwlock_destroy(&self->rwlock);
|
||||
}
|
||||
|
||||
@ -253,6 +263,15 @@ bool game_mode_context_register(GameModeContext *self, pid_t client)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check our blacklist and whitelist */
|
||||
if (!config_get_client_whitelisted(self->config, cl->executable)) {
|
||||
LOG_MSG("Client [%s] was rejected (not in whitelist)\n", cl->executable);
|
||||
return false;
|
||||
} else if (config_get_client_blacklisted(self->config, cl->executable)) {
|
||||
LOG_MSG("Client [%s] was rejected (in blacklist)\n", cl->executable);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Begin a write lock now to insert our new client at list start */
|
||||
pthread_rwlock_wrlock(&self->rwlock);
|
||||
|
||||
|
@ -21,6 +21,7 @@ daemon_sources = [
|
||||
'daemonize.c',
|
||||
'dbus_messaging.c',
|
||||
'governors.c',
|
||||
'daemon_config.c',
|
||||
]
|
||||
|
||||
executable(
|
||||
@ -30,6 +31,7 @@ executable(
|
||||
link_daemon_common,
|
||||
dep_threads,
|
||||
dep_systemd,
|
||||
inih_dependency,
|
||||
],
|
||||
include_directories: [
|
||||
config_h_dir,
|
||||
|
@ -66,6 +66,27 @@ Atlernatively developers can define \fBGAMEMODE_AUTO\fR to mimic the behaviour o
|
||||
|
||||
Or, distribute \fBlibgamemodeauto.so\fR and either link with \fB\-lgamemodeauto\fR or inject it as above with \fBLD\_PRELOAD\fR.
|
||||
|
||||
.SH CONFIG
|
||||
|
||||
\fBgamemoded\fR can be configured with a \fBgamemode.ini\fR file found in \fB/usr/share/gamemode/\fR. The daemon will load the config file on start-up if it exists.
|
||||
|
||||
Behaviour of the config file can be explained by presenting a commented example:
|
||||
|
||||
.RS 4
|
||||
.nf
|
||||
[filter]
|
||||
; If "whitelist" entry has a value(s)
|
||||
; gamemode will reject anything not in the whitelist
|
||||
;whitelist=RiseOfTheTombRaider
|
||||
|
||||
; Gamemode will always reject anything in the blacklist
|
||||
blacklist=HalfLife3
|
||||
glxgears
|
||||
.fi
|
||||
.RE
|
||||
|
||||
This config file will currently reject any games that match \fIHalfLife3\fR or \fIglxgears\fR, but can be modified to only accept \fIRiseOfTheTombRaider\fR by removing the semicolon preceding the fourth line.
|
||||
|
||||
.SH SEE ALSO
|
||||
systemd(1)
|
||||
|
||||
|
9
example/gamemode.ini
Normal file
9
example/gamemode.ini
Normal file
@ -0,0 +1,9 @@
|
||||
[filter]
|
||||
; Gamemode will always reject anything in the blacklist
|
||||
blacklist=HalfLife3
|
||||
glxgears
|
||||
steam
|
||||
|
||||
; If "whitelist" entry has a value(s)
|
||||
; gamemode will reject anything not in the whitelist
|
||||
;whitelist=RiseOfTheTombRaider
|
@ -82,6 +82,10 @@ subdir('lib')
|
||||
# The daemon can be disabled if necessary, allowing multilib builds of the
|
||||
# main library
|
||||
if with_daemon == true
|
||||
# inih currently only needed by the daemon
|
||||
inih = subproject('inih')
|
||||
inih_dependency = inih.get_variable('inih_dependency')
|
||||
|
||||
subdir('daemon')
|
||||
|
||||
# All installed data is currently daemon specific
|
||||
|
5
subprojects/inih.wrap
Normal file
5
subprojects/inih.wrap
Normal file
@ -0,0 +1,5 @@
|
||||
[wrap-git]
|
||||
directory=inih
|
||||
url=https://github.com/FeralInteractive/inih.git
|
||||
revision=head
|
||||
|
Reference in New Issue
Block a user