mirror of
https://github.com/FeralInteractive/gamemode.git
synced 2025-06-26 17:31:45 +02:00
Merge pull request #86 from kakra/modular-refactor
Tidy up the code and refactor into modules
This commit is contained in:
96
daemon/gamemode-env.c
Normal file
96
daemon/gamemode-env.c
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2018, 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 "gamemode.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Lookup the process environment for a specific variable or return NULL.
|
||||
* Requires an open directory FD from /proc/PID.
|
||||
*/
|
||||
char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var)
|
||||
{
|
||||
char *environ = NULL;
|
||||
|
||||
int fd = openat(proc_fd, "environ", O_RDONLY | O_CLOEXEC);
|
||||
if (fd != -1) {
|
||||
FILE *stream = fdopen(fd, "r");
|
||||
if (stream) {
|
||||
/* Read every \0 terminated line from the environment */
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t pos = strlen(var) + 1;
|
||||
while (!environ && (getdelim(&line, &len, 0, stream) != -1)) {
|
||||
/* Find a match including the "=" suffix */
|
||||
if ((len > pos) && (strncmp(line, var, strlen(var)) == 0) && (line[pos - 1] == '='))
|
||||
environ = strndup(line + pos, len - pos);
|
||||
}
|
||||
free(line);
|
||||
fclose(stream);
|
||||
} else
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* If found variable is empty, skip it */
|
||||
if (environ && !strlen(environ)) {
|
||||
free(environ);
|
||||
environ = NULL;
|
||||
}
|
||||
|
||||
return environ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the home directory of the user in a safe way.
|
||||
*/
|
||||
char *game_mode_lookup_user_home(void)
|
||||
{
|
||||
/* Try loading env HOME first */
|
||||
const char *home = secure_getenv("HOME");
|
||||
if (!home) {
|
||||
/* If HOME is not defined (or out of context), fall back to passwd */
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (!pw)
|
||||
return NULL;
|
||||
home = pw->pw_dir;
|
||||
}
|
||||
|
||||
/* Try to allocate into our heap */
|
||||
return home ? strdup(home) : NULL;
|
||||
}
|
142
daemon/gamemode-ioprio.c
Normal file
142
daemon/gamemode-ioprio.c
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2018, 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 "gamemode.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Define the syscall interface in Linux because it is missing from glibc
|
||||
*/
|
||||
|
||||
#ifndef IOPRIO_BITS
|
||||
#define IOPRIO_BITS (16)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_CLASS_SHIFT
|
||||
#define IOPRIO_CLASS_SHIFT (13)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_MASK
|
||||
#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_CLASS
|
||||
#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_DATA
|
||||
#define IOPRIO_PRIO_DATA(mask) ((mask)&IOPRIO_PRIO_MASK)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_VALUE
|
||||
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
|
||||
#endif
|
||||
|
||||
enum {
|
||||
IOPRIO_CLASS_NONE,
|
||||
IOPRIO_CLASS_RT,
|
||||
IOPRIO_CLASS_BE,
|
||||
IOPRIO_CLASS_IDLE,
|
||||
};
|
||||
|
||||
enum {
|
||||
IOPRIO_WHO_PROCESS = 1,
|
||||
IOPRIO_WHO_PGRP,
|
||||
IOPRIO_WHO_USER,
|
||||
};
|
||||
|
||||
static inline int ioprio_set(int which, int who, int ioprio)
|
||||
{
|
||||
return (int)syscall(SYS_ioprio_set, which, who, ioprio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply io priorities
|
||||
*
|
||||
* This tries to change the io priority of the client to a value specified
|
||||
* and can possibly reduce lags or latency when a game has to load assets
|
||||
* on demand.
|
||||
*/
|
||||
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client)
|
||||
{
|
||||
GameModeConfig *config = game_mode_config_from_context(self);
|
||||
|
||||
LOG_MSG("Setting scheduling policies...\n");
|
||||
|
||||
/*
|
||||
* read configuration "ioprio" (0..7)
|
||||
*/
|
||||
int ioprio = 0;
|
||||
config_get_ioprio_value(config, &ioprio);
|
||||
if (IOPRIO_RESET_DEFAULT == ioprio) {
|
||||
LOG_MSG("IO priority will be reset to default behavior (based on CPU priority).\n");
|
||||
ioprio = 0;
|
||||
} else if (IOPRIO_DONT_SET == ioprio) {
|
||||
return;
|
||||
} else {
|
||||
/* maybe clamp the value */
|
||||
int invalid_ioprio = ioprio;
|
||||
ioprio = CLAMP(0, 7, ioprio);
|
||||
if (ioprio != invalid_ioprio)
|
||||
LOG_ONCE(ERROR,
|
||||
"IO priority value %d invalid, clamping to %d\n",
|
||||
invalid_ioprio,
|
||||
ioprio);
|
||||
|
||||
/* We support only IOPRIO_CLASS_BE as IOPRIO_CLASS_RT required CAP_SYS_ADMIN */
|
||||
ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, ioprio);
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually apply the io priority
|
||||
*/
|
||||
int c = IOPRIO_PRIO_CLASS(ioprio), p = IOPRIO_PRIO_DATA(ioprio);
|
||||
if (ioprio_set(IOPRIO_WHO_PROCESS, client, ioprio) == 0) {
|
||||
if (0 == ioprio)
|
||||
LOG_MSG("Resetting client [%d] IO priority.\n", client);
|
||||
else
|
||||
LOG_MSG("Setting client [%d] IO priority to (%d,%d).\n", client, c, p);
|
||||
} else {
|
||||
LOG_ERROR("Setting client [%d] IO priority to (%d,%d) failed with error %d, ignoring\n",
|
||||
client,
|
||||
c,
|
||||
p,
|
||||
errno);
|
||||
}
|
||||
}
|
@ -29,54 +29,33 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "gamemode.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Define the syscall interface in Linux because it is missing from glibc
|
||||
* Opens the process environment for a specific PID and returns
|
||||
* a file descriptor to the directory /proc/PID. Doing it that way prevents
|
||||
* the directory going MIA when a process exits while we are looking at it
|
||||
* and allows us to handle fewer error cases.
|
||||
*/
|
||||
|
||||
#ifndef IOPRIO_BITS
|
||||
#define IOPRIO_BITS (16)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_CLASS_SHIFT
|
||||
#define IOPRIO_CLASS_SHIFT (13)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_MASK
|
||||
#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_CLASS
|
||||
#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_DATA
|
||||
#define IOPRIO_PRIO_DATA(mask) ((mask)&IOPRIO_PRIO_MASK)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_VALUE
|
||||
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
|
||||
#endif
|
||||
|
||||
enum {
|
||||
IOPRIO_CLASS_NONE,
|
||||
IOPRIO_CLASS_RT,
|
||||
IOPRIO_CLASS_BE,
|
||||
IOPRIO_CLASS_IDLE,
|
||||
};
|
||||
|
||||
enum {
|
||||
IOPRIO_WHO_PROCESS = 1,
|
||||
IOPRIO_WHO_PGRP,
|
||||
IOPRIO_WHO_USER,
|
||||
};
|
||||
|
||||
static inline int ioprio_set(int which, int who, int ioprio)
|
||||
procfd_t game_mode_open_proc(const pid_t pid)
|
||||
{
|
||||
return (int)syscall(SYS_ioprio_set, which, who, ioprio);
|
||||
char buffer[PATH_MAX];
|
||||
const char *proc_path = buffered_snprintf(buffer, "/proc/%d", pid);
|
||||
|
||||
return proc_path ? open(proc_path, O_RDONLY | O_CLOEXEC) : INVALID_PROCFD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the process environment.
|
||||
*/
|
||||
int game_mode_close_proc(const procfd_t procfd)
|
||||
{
|
||||
return close(procfd);
|
||||
}
|
149
daemon/gamemode-sched.c
Normal file
149
daemon/gamemode-sched.c
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2018, 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 "gamemode.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sched.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
/**
|
||||
* Priority to renice the process to.
|
||||
*/
|
||||
#define NICE_DEFAULT_PRIORITY -4
|
||||
|
||||
/**
|
||||
* Apply scheduling policies
|
||||
*
|
||||
* This tries to change the scheduler of the client to soft realtime mode
|
||||
* available in some kernels as SCHED_ISO. It also tries to adjust the nice
|
||||
* level. If some of each fail, ignore this and log a warning.
|
||||
*
|
||||
* We don't need to store the current values because when the client exits,
|
||||
* everything will be good: Scheduling is only applied to the client and
|
||||
* its children.
|
||||
*/
|
||||
void game_mode_apply_renice(const GameModeContext *self, const pid_t client)
|
||||
{
|
||||
GameModeConfig *config = game_mode_config_from_context(self);
|
||||
|
||||
/*
|
||||
* read configuration "renice" (1..20)
|
||||
*/
|
||||
long int renice = 0;
|
||||
config_get_renice_value(config, &renice);
|
||||
if ((renice < 1) || (renice > 20)) {
|
||||
LOG_ONCE(ERROR,
|
||||
"Invalid renice value '%ld' reset to default: %d.\n",
|
||||
renice,
|
||||
-NICE_DEFAULT_PRIORITY);
|
||||
renice = NICE_DEFAULT_PRIORITY;
|
||||
} else {
|
||||
renice = -renice;
|
||||
}
|
||||
|
||||
/*
|
||||
* don't adjust priority if it was already adjusted
|
||||
*/
|
||||
if (getpriority(PRIO_PROCESS, (id_t)client) != 0) {
|
||||
LOG_ERROR("Refused to renice client [%d]: already reniced\n", client);
|
||||
} else if (setpriority(PRIO_PROCESS, (id_t)client, (int)renice)) {
|
||||
LOG_HINTED(ERROR,
|
||||
"Failed to renice client [%d], ignoring error condition: %s\n",
|
||||
" -- Your user may not have permission to do this. Please read the docs\n"
|
||||
" -- to learn how to adjust the pam limits.\n",
|
||||
client,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void game_mode_apply_scheduling(const GameModeContext *self, const pid_t client)
|
||||
{
|
||||
GameModeConfig *config = game_mode_config_from_context(self);
|
||||
|
||||
/*
|
||||
* read configuration "softrealtime" (on, off, auto)
|
||||
*/
|
||||
char softrealtime[CONFIG_VALUE_MAX] = { 0 };
|
||||
config_get_soft_realtime(config, softrealtime);
|
||||
|
||||
/*
|
||||
* Enable unconditionally or auto-detect soft realtime usage,
|
||||
* auto detection is based on observations where dual-core CPU suffered
|
||||
* priority inversion problems with the graphics driver thus running
|
||||
* slower as a result, so enable only with more than 3 cores.
|
||||
*/
|
||||
bool enable_softrealtime = (strcmp(softrealtime, "on") == 0) || (get_nprocs() > 3);
|
||||
|
||||
/*
|
||||
* Actually apply the scheduler policy if not explicitly turned off
|
||||
*/
|
||||
if (!(strcmp(softrealtime, "off") == 0) && (enable_softrealtime)) {
|
||||
const struct sched_param p = { .sched_priority = 0 };
|
||||
if (sched_setscheduler(client, SCHED_ISO | SCHED_RESET_ON_FORK, &p)) {
|
||||
const char *hint = "";
|
||||
HINT_ONCE_ON(
|
||||
errno == EPERM,
|
||||
hint,
|
||||
" -- The error indicates that you may be running a resource management\n"
|
||||
" -- daemon managing your game launcher and it leaks lower scheduling\n"
|
||||
" -- classes into the games. This is likely a bug in the management daemon\n"
|
||||
" -- and not a bug in GameMode, it should be reported upstream.\n"
|
||||
" -- If unsure, please also look here:\n"
|
||||
" -- https://github.com/FeralInteractive/gamemode/issues/68\n");
|
||||
HINT_ONCE_ON(
|
||||
errno == EINVAL,
|
||||
hint,
|
||||
" -- The error indicates that your kernel may not support this. If you\n"
|
||||
" -- don't know what SCHED_ISO means, you can safely ignore this. If you\n"
|
||||
" -- expected it to work, ensure you're running a kernel with MuQSS or\n"
|
||||
" -- PDS scheduler.\n"
|
||||
" -- For further technical reading on the topic start here:\n"
|
||||
" -- https://lwn.net/Articles/720227/\n");
|
||||
LOG_ERROR(
|
||||
"Failed setting client [%d] into SCHED_ISO mode, ignoring error condition: %s\n"
|
||||
"%s",
|
||||
client,
|
||||
strerror(errno),
|
||||
hint);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Skipped setting client [%d] into SCHED_ISO mode: softrealtime setting is '%s'\n",
|
||||
client,
|
||||
softrealtime);
|
||||
}
|
||||
}
|
164
daemon/gamemode-wine.c
Normal file
164
daemon/gamemode-wine.c
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2018, 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 "gamemode.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Detect if the process is a wine preloader process
|
||||
*/
|
||||
bool game_mode_detect_wine_preloader(const char *exe)
|
||||
{
|
||||
return (strtail(exe, "/wine-preloader") || strtail(exe, "/wine64-preloader"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the process is a wine loader process
|
||||
*/
|
||||
bool game_mode_detect_wine_loader(const char *exe)
|
||||
{
|
||||
return (strtail(exe, "/wine") || strtail(exe, "/wine64"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to resolve the exe for wine-preloader.
|
||||
* This function is used if game_mode_context_find_exe() identified the
|
||||
* process as wine-preloader. Returns NULL when resolve fails.
|
||||
*/
|
||||
char *game_mode_resolve_wine_preloader(const pid_t pid)
|
||||
{
|
||||
char buffer[PATH_MAX];
|
||||
char *proc_path = NULL, *wine_exe = NULL, *wineprefix = NULL;
|
||||
|
||||
/* Open the directory, we are potentially reading multiple files from it */
|
||||
procfd_t proc_fd = game_mode_open_proc(pid);
|
||||
|
||||
if (proc_fd == INVALID_PROCFD)
|
||||
goto fail_proc;
|
||||
|
||||
/* Open the command line */
|
||||
int fd = openat(proc_fd, "cmdline", O_RDONLY | O_CLOEXEC);
|
||||
if (fd != -1) {
|
||||
FILE *stream = fdopen(fd, "r");
|
||||
if (stream) {
|
||||
char *argv = NULL;
|
||||
size_t args = 0;
|
||||
int argc = 0;
|
||||
while (!wine_exe && (argc++ < 2) && (getdelim(&argv, &args, 0, stream) != -1)) {
|
||||
/* If we see the wine loader here, we have to use the next argument */
|
||||
if (strtail(argv, "/wine") || strtail(argv, "/wine64"))
|
||||
continue;
|
||||
free(wine_exe); // just in case
|
||||
/* Check presence of the drive letter, we assume that below */
|
||||
wine_exe = args > 2 && argv[1] == ':' ? strndup(argv, args) : NULL;
|
||||
}
|
||||
free(argv);
|
||||
fclose(stream);
|
||||
} else
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* Did we get wine exe from cmdline? */
|
||||
if (wine_exe)
|
||||
LOG_MSG("Detected wine exe for client %d [%s].\n", pid, wine_exe);
|
||||
else
|
||||
goto fail_cmdline;
|
||||
|
||||
/* Open the process environment and find the WINEPREFIX */
|
||||
errno = 0;
|
||||
if (!(wineprefix = game_mode_lookup_proc_env(proc_fd, "WINEPREFIX"))) {
|
||||
/* Lookup user home instead only if there was no error */
|
||||
char *home = NULL;
|
||||
if (errno == 0)
|
||||
home = game_mode_lookup_user_home();
|
||||
|
||||
/* Append "/.wine" if we found the user home */
|
||||
if (home)
|
||||
wineprefix = safe_snprintf(buffer, "%s/.wine", home);
|
||||
|
||||
/* Cleanup and check result */
|
||||
free(home);
|
||||
if (!wineprefix)
|
||||
goto fail_env;
|
||||
}
|
||||
|
||||
/* Wine prefix was detected, log this for diagnostics */
|
||||
LOG_MSG("Detected wine prefix for client %d: '%s'\n", pid, wineprefix);
|
||||
|
||||
/* Convert Windows to Unix path separators */
|
||||
char *ix = wine_exe;
|
||||
while (ix != NULL)
|
||||
(ix = strchr(ix, '\\')) && (*ix++ = '/');
|
||||
|
||||
/* Convert the drive letter to lcase because wine handles it this way in the prefix */
|
||||
wine_exe[0] = (char)tolower(wine_exe[0]);
|
||||
|
||||
/* Convert relative wine exe path to full unix path */
|
||||
char *wine_path = buffered_snprintf(buffer, "%s/dosdevices/%s", wineprefix, wine_exe);
|
||||
free(wine_exe);
|
||||
wine_exe = wine_path ? realpath(wine_path, NULL) : NULL;
|
||||
|
||||
/* Fine? Successo? Fortuna! */
|
||||
if (wine_exe)
|
||||
LOG_MSG("Successfully mapped wine client %d [%s].\n", pid, wine_exe);
|
||||
else
|
||||
goto fail;
|
||||
|
||||
error_cleanup:
|
||||
game_mode_close_proc(proc_fd);
|
||||
free(wineprefix);
|
||||
free(proc_path);
|
||||
return wine_exe;
|
||||
|
||||
fail:
|
||||
LOG_ERROR("Unable to find wine executable for client %d: %s\n", pid, strerror(errno));
|
||||
goto error_cleanup;
|
||||
|
||||
fail_cmdline:
|
||||
LOG_ERROR("Wine loader has no accepted cmdline for client %d yet, deferring.\n", pid);
|
||||
goto error_cleanup;
|
||||
|
||||
fail_env:
|
||||
LOG_ERROR("Failed to access process environment in '%s': %s\n", proc_path, strerror(errno));
|
||||
goto error_cleanup;
|
||||
|
||||
fail_proc:
|
||||
LOG_ERROR("Failed to access process data in '%s': %s\n", proc_path, strerror(errno));
|
||||
goto error_cleanup;
|
||||
}
|
@ -35,24 +35,12 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "daemon_config.h"
|
||||
#include "governors-query.h"
|
||||
#include "governors.h"
|
||||
#include "ioprio.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/sched.h>
|
||||
#include <pthread.h>
|
||||
#include <pwd.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/types.h>
|
||||
#include <systemd/sd-daemon.h>
|
||||
|
||||
/* SCHED_ISO may not be defined as it is a reserved value not yet
|
||||
@ -62,35 +50,6 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#define SCHED_ISO 4
|
||||
#endif
|
||||
|
||||
/* Priority to renice the process to.
|
||||
*/
|
||||
#define NICE_DEFAULT_PRIORITY -4
|
||||
|
||||
/* Value clamping helper.
|
||||
*/
|
||||
#define CLAMP(lbound, ubound, value) MIN(MIN(lbound, ubound), MAX(MAX(lbound, ubound), value))
|
||||
|
||||
/* Little helper to safely print into a buffer, returns a pointer into the buffer
|
||||
*/
|
||||
#define buffered_snprintf(b, s, ...) \
|
||||
(snprintf(b, sizeof(b), s, __VA_ARGS__) < (ssize_t)sizeof(b) ? b : NULL)
|
||||
|
||||
/* Little helper to safely print into a buffer, returns a newly allocated string
|
||||
*/
|
||||
#define safe_snprintf(b, s, ...) \
|
||||
(snprintf(b, sizeof(b), s, __VA_ARGS__) < (ssize_t)sizeof(b) ? strndup(b, sizeof(b)) : NULL)
|
||||
|
||||
/**
|
||||
* Helper function: Test, if haystack ends with needle.
|
||||
*/
|
||||
static inline const char *strtail(const char *haystack, const char *needle)
|
||||
{
|
||||
char *pos = strstr(haystack, needle);
|
||||
if (pos && (strlen(pos) == strlen(needle)))
|
||||
return pos;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The GameModeClient encapsulates the remote connection, providing a list
|
||||
* form to contain the pid and credentials.
|
||||
@ -197,159 +156,6 @@ void game_mode_context_destroy(GameModeContext *self)
|
||||
pthread_rwlock_destroy(&self->rwlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply scheduling policies
|
||||
*
|
||||
* This tries to change the scheduler of the client to soft realtime mode
|
||||
* available in some kernels as SCHED_ISO. It also tries to adjust the nice
|
||||
* level. If some of each fail, ignore this and log a warning.
|
||||
*
|
||||
* We don't need to store the current values because when the client exits,
|
||||
* everything will be good: Scheduling is only applied to the client and
|
||||
* its children.
|
||||
*/
|
||||
static void game_mode_apply_scheduler(GameModeContext *self, pid_t client)
|
||||
{
|
||||
/*
|
||||
* read configuration "renice" (1..20)
|
||||
*/
|
||||
long int renice = 0;
|
||||
config_get_renice_value(self->config, &renice);
|
||||
if ((renice < 1) || (renice > 20)) {
|
||||
LOG_ERROR("Invalid renice value '%ld' reset to default: %d.\n",
|
||||
renice,
|
||||
-NICE_DEFAULT_PRIORITY);
|
||||
renice = NICE_DEFAULT_PRIORITY;
|
||||
} else {
|
||||
renice = -renice;
|
||||
}
|
||||
|
||||
/*
|
||||
* don't adjust priority if it was already adjusted
|
||||
*/
|
||||
if (getpriority(PRIO_PROCESS, (id_t)client) != 0) {
|
||||
LOG_ERROR("Refused to renice client [%d]: already reniced\n", client);
|
||||
} else if (setpriority(PRIO_PROCESS, (id_t)client, (int)renice)) {
|
||||
LOG_ERROR(
|
||||
"Failed to renice client [%d], ignoring error condition: %s\n"
|
||||
" -- Your user may not have permission to do this. Please read the docs\n"
|
||||
" -- to learn how to adjust the pam limits.\n",
|
||||
client,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
/*
|
||||
* read configuration "softrealtime" (on, off, auto)
|
||||
*/
|
||||
char softrealtime[CONFIG_VALUE_MAX] = { 0 };
|
||||
config_get_soft_realtime(self->config, softrealtime);
|
||||
|
||||
/*
|
||||
* Enable unconditionally or auto-detect soft realtime usage,
|
||||
* auto detection is based on observations where dual-core CPU suffered
|
||||
* priority inversion problems with the graphics driver thus running
|
||||
* slower as a result, so enable only with more than 3 cores.
|
||||
*/
|
||||
bool enable_softrealtime = (strcmp(softrealtime, "on") == 0) || (get_nprocs() > 3);
|
||||
|
||||
/*
|
||||
* Actually apply the scheduler policy if not explicitly turned off
|
||||
*/
|
||||
if (!(strcmp(softrealtime, "off") == 0) && (enable_softrealtime)) {
|
||||
const struct sched_param p = { .sched_priority = 0 };
|
||||
if (sched_setscheduler(client, SCHED_ISO | SCHED_RESET_ON_FORK, &p)) {
|
||||
const char *additional_message = "";
|
||||
switch (errno) {
|
||||
case EPERM: {
|
||||
static int once = 0;
|
||||
if (once++)
|
||||
break;
|
||||
additional_message =
|
||||
" -- The error indicates that you may be running a resource management\n"
|
||||
" -- daemon managing your game launcher and it leaks lower scheduling\n"
|
||||
" -- classes into the games. This is likely a bug in the management daemon\n"
|
||||
" -- and not a bug in GameMode, it should be reported upstream.\n"
|
||||
" -- If unsure, please also look here:\n"
|
||||
" -- https://github.com/FeralInteractive/gamemode/issues/68\n";
|
||||
break;
|
||||
}
|
||||
case EINVAL: {
|
||||
static int once = 0;
|
||||
if (once++)
|
||||
break;
|
||||
additional_message =
|
||||
" -- The error indicates that your kernel may not support this. If you\n"
|
||||
" -- don't know what SCHED_ISO means, you can safely ignore this. If you\n"
|
||||
" -- expected it to work, ensure you're running a kernel with MuQSS or\n"
|
||||
" -- PDS scheduler.\n"
|
||||
" -- For further technical reading on the topic start here:\n"
|
||||
" -- https://lwn.net/Articles/720227/\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_ERROR(
|
||||
"Failed setting client [%d] into SCHED_ISO mode, ignoring error condition: %s\n%s",
|
||||
client,
|
||||
strerror(errno),
|
||||
additional_message);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Skipped setting client [%d] into SCHED_ISO mode: softrealtime setting is '%s'\n",
|
||||
client,
|
||||
softrealtime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply io priorities
|
||||
*
|
||||
* This tries to change the io priority of the client to a value specified
|
||||
* and can possibly reduce lags or latency when a game has to load assets
|
||||
* on demand.
|
||||
*/
|
||||
static void game_mode_apply_ioprio(GameModeContext *self, pid_t client)
|
||||
{
|
||||
LOG_MSG("Setting scheduling policies...\n");
|
||||
|
||||
/*
|
||||
* read configuration "ioprio" (0..7)
|
||||
*/
|
||||
int ioprio = 0;
|
||||
config_get_ioprio_value(self->config, &ioprio);
|
||||
if (IOPRIO_RESET_DEFAULT == ioprio) {
|
||||
LOG_MSG("IO priority will be reset to default behavior (based on CPU priority).\n");
|
||||
ioprio = 0;
|
||||
} else if (IOPRIO_DONT_SET == ioprio) {
|
||||
return;
|
||||
} else {
|
||||
/* maybe clamp the value */
|
||||
int invalid_ioprio = ioprio;
|
||||
ioprio = CLAMP(0, 7, ioprio);
|
||||
if (ioprio != invalid_ioprio)
|
||||
LOG_ERROR("IO priority value %d invalid, clamping to %d\n", invalid_ioprio, ioprio);
|
||||
|
||||
/* We support only IOPRIO_CLASS_BE as IOPRIO_CLASS_RT required CAP_SYS_ADMIN */
|
||||
ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, ioprio);
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually apply the io priority
|
||||
*/
|
||||
int c = IOPRIO_PRIO_CLASS(ioprio), p = IOPRIO_PRIO_DATA(ioprio);
|
||||
if (ioprio_set(IOPRIO_WHO_PROCESS, client, ioprio) == 0) {
|
||||
if (0 == ioprio)
|
||||
LOG_MSG("Resetting client [%d] IO priority.\n", client);
|
||||
else
|
||||
LOG_MSG("Setting client [%d] IO priority to (%d,%d).\n", client, c, p);
|
||||
} else {
|
||||
LOG_ERROR("Setting client [%d] IO priority to (%d,%d) failed with error %d, ignoring\n",
|
||||
client,
|
||||
c,
|
||||
p,
|
||||
errno);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pivot into game mode.
|
||||
*
|
||||
@ -517,16 +323,12 @@ bool game_mode_context_register(GameModeContext *self, pid_t client)
|
||||
pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane
|
||||
const GameModeClient *existing = game_mode_context_has_client(self, client);
|
||||
if (existing) {
|
||||
static int once = 0;
|
||||
const char *additional_message =
|
||||
(once++
|
||||
? ""
|
||||
: " -- This may happen due to using exec or shell wrappers. You may want to\n"
|
||||
" -- blacklist this client so GameMode can see its final name here.\n");
|
||||
LOG_ERROR("Addition requested for already known client %d [%s].\n%s",
|
||||
existing->pid,
|
||||
existing->executable,
|
||||
additional_message);
|
||||
LOG_HINTED(ERROR,
|
||||
"Addition requested for already known client %d [%s].\n",
|
||||
" -- This may happen due to using exec or shell wrappers. You may want to\n"
|
||||
" -- blacklist this client so GameMode can see its final name here.\n",
|
||||
existing->pid,
|
||||
existing->executable);
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
goto error_cleanup;
|
||||
}
|
||||
@ -569,7 +371,8 @@ bool game_mode_context_register(GameModeContext *self, pid_t client)
|
||||
}
|
||||
|
||||
/* Apply scheduler policies */
|
||||
game_mode_apply_scheduler(self, client);
|
||||
game_mode_apply_renice(self, client);
|
||||
game_mode_apply_scheduling(self, client);
|
||||
|
||||
/* Apply io priorities */
|
||||
game_mode_apply_ioprio(self, client);
|
||||
@ -616,15 +419,14 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client)
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
|
||||
if (!found) {
|
||||
static int once = 0;
|
||||
const char *additional_message =
|
||||
(once++
|
||||
? ""
|
||||
: " -- The parent process probably forked and tries to unregister from the\n"
|
||||
" -- wrong process now. We cannot work around this. This message will\n"
|
||||
" -- likely be paired with a nearby 'Removing expired game' which means we\n"
|
||||
" -- cleaned up properly (we will log this event).\n");
|
||||
LOG_ERROR("Removal requested for unknown process [%d].\n%s", client, additional_message);
|
||||
LOG_HINTED(
|
||||
ERROR,
|
||||
"Removal requested for unknown process [%d].\n",
|
||||
" -- The parent process probably forked and tries to unregister from the wrong\n"
|
||||
" -- process now. We cannot work around this. This message will likely be paired\n"
|
||||
" -- with a nearby 'Removing expired game' which means we cleaned up properly\n"
|
||||
" -- (we will log this event). This hint will be displayed only once.\n",
|
||||
client);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -748,173 +550,9 @@ GameModeContext *game_mode_context_instance()
|
||||
return &instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the process environment for a specific variable or return NULL.
|
||||
* Requires an open directory FD from /proc/PID.
|
||||
*/
|
||||
static char *game_mode_lookup_proc_env(int proc_fd, const char *var)
|
||||
GameModeConfig *game_mode_config_from_context(const GameModeContext *context)
|
||||
{
|
||||
char *environ = NULL;
|
||||
|
||||
int fd = openat(proc_fd, "environ", O_RDONLY | O_CLOEXEC);
|
||||
if (fd != -1) {
|
||||
FILE *stream = fdopen(fd, "r");
|
||||
if (stream) {
|
||||
/* Read every \0 terminated line from the environment */
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t pos = strlen(var) + 1;
|
||||
while (!environ && (getdelim(&line, &len, 0, stream) != -1)) {
|
||||
/* Find a match including the "=" suffix */
|
||||
if ((len > pos) && (strncmp(line, var, strlen(var)) == 0) && (line[pos - 1] == '='))
|
||||
environ = strndup(line + pos, len - pos);
|
||||
}
|
||||
free(line);
|
||||
fclose(stream);
|
||||
} else
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* If found variable is empty, skip it */
|
||||
if (environ && !strlen(environ)) {
|
||||
free(environ);
|
||||
environ = NULL;
|
||||
}
|
||||
|
||||
return environ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the home directory of the user in a safe way.
|
||||
*/
|
||||
static char *game_mode_lookup_user_home(void)
|
||||
{
|
||||
/* Try loading env HOME first */
|
||||
const char *home = secure_getenv("HOME");
|
||||
if (!home) {
|
||||
/* If HOME is not defined (or out of context), fall back to passwd */
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (!pw)
|
||||
return NULL;
|
||||
home = pw->pw_dir;
|
||||
}
|
||||
|
||||
/* Try to allocate into our heap */
|
||||
return home ? strdup(home) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to resolve the exe for wine-preloader.
|
||||
* This function is used if game_mode_context_find_exe() identified the
|
||||
* process as wine-preloader. Returns NULL when resolve fails.
|
||||
*/
|
||||
static char *game_mode_resolve_wine_preloader(pid_t pid)
|
||||
{
|
||||
char buffer[PATH_MAX];
|
||||
char *proc_path = NULL, *wine_exe = NULL, *wineprefix = NULL;
|
||||
int proc_fd = -1;
|
||||
|
||||
/* We could use the buffered_snprintf() helper here but it may potentially
|
||||
* overwrite proc_path when the buffer is re-used later and usage of
|
||||
* proc_path has not been discarded yet (i.e., it's used in the fail path).
|
||||
* Let's not introduce this non-obvious pitfall.
|
||||
*/
|
||||
if (!(proc_path = safe_snprintf(buffer, "/proc/%d", pid)))
|
||||
goto fail;
|
||||
|
||||
/* Open the directory, we are potentially reading multiple files from it */
|
||||
if (-1 == (proc_fd = open(proc_path, O_RDONLY | O_CLOEXEC)))
|
||||
goto fail_proc;
|
||||
|
||||
/* Open the command line */
|
||||
int fd = openat(proc_fd, "cmdline", O_RDONLY | O_CLOEXEC);
|
||||
if (fd != -1) {
|
||||
FILE *stream = fdopen(fd, "r");
|
||||
if (stream) {
|
||||
char *argv = NULL;
|
||||
size_t args = 0;
|
||||
int argc = 0;
|
||||
while (!wine_exe && (argc++ < 2) && (getdelim(&argv, &args, 0, stream) != -1)) {
|
||||
/* If we see the wine loader here, we have to use the next argument */
|
||||
if (strtail(argv, "/wine") || strtail(argv, "/wine64"))
|
||||
continue;
|
||||
free(wine_exe); // just in case
|
||||
/* Check presence of the drive letter, we assume that below */
|
||||
wine_exe = args > 2 && argv[1] == ':' ? strndup(argv, args) : NULL;
|
||||
}
|
||||
free(argv);
|
||||
fclose(stream);
|
||||
} else
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* Did we get wine exe from cmdline? */
|
||||
if (wine_exe)
|
||||
LOG_MSG("Detected wine exe for client %d [%s].\n", pid, wine_exe);
|
||||
else
|
||||
goto fail_cmdline;
|
||||
|
||||
/* Open the process environment and find the WINEPREFIX */
|
||||
errno = 0;
|
||||
if (!(wineprefix = game_mode_lookup_proc_env(proc_fd, "WINEPREFIX"))) {
|
||||
/* Lookup user home instead only if there was no error */
|
||||
char *home = NULL;
|
||||
if (errno == 0)
|
||||
home = game_mode_lookup_user_home();
|
||||
|
||||
/* Append "/.wine" if we found the user home */
|
||||
if (home)
|
||||
wineprefix = safe_snprintf(buffer, "%s/.wine", home);
|
||||
|
||||
/* Cleanup and check result */
|
||||
free(home);
|
||||
if (!wineprefix)
|
||||
goto fail_env;
|
||||
}
|
||||
|
||||
/* Wine prefix was detected, log this for diagnostics */
|
||||
LOG_MSG("Detected wine prefix for client %d: '%s'\n", pid, wineprefix);
|
||||
|
||||
/* Convert Windows to Unix path separators */
|
||||
char *ix = wine_exe;
|
||||
while (ix != NULL)
|
||||
(ix = strchr(ix, '\\')) && (*ix++ = '/');
|
||||
|
||||
/* Convert the drive letter to lcase because wine handles it this way in the prefix */
|
||||
wine_exe[0] = (char)tolower(wine_exe[0]);
|
||||
|
||||
/* Convert relative wine exe path to full unix path */
|
||||
char *wine_path = buffered_snprintf(buffer, "%s/dosdevices/%s", wineprefix, wine_exe);
|
||||
free(wine_exe);
|
||||
wine_exe = wine_path ? realpath(wine_path, NULL) : NULL;
|
||||
|
||||
/* Fine? Successo? Fortuna! */
|
||||
if (wine_exe)
|
||||
LOG_MSG("Successfully mapped wine client %d [%s].\n", pid, wine_exe);
|
||||
else
|
||||
goto fail;
|
||||
|
||||
error_cleanup:
|
||||
close(proc_fd);
|
||||
free(wineprefix);
|
||||
free(proc_path);
|
||||
return wine_exe;
|
||||
|
||||
fail:
|
||||
LOG_ERROR("Unable to find wine executable for client %d: %s\n", pid, strerror(errno));
|
||||
goto error_cleanup;
|
||||
|
||||
fail_cmdline:
|
||||
LOG_ERROR("Wine loader has no accepted cmdline for client %d yet, deferring.\n", pid);
|
||||
goto error_cleanup;
|
||||
|
||||
fail_env:
|
||||
LOG_ERROR("Failed to access process environment in '%s': %s\n", proc_path, strerror(errno));
|
||||
goto error_cleanup;
|
||||
|
||||
fail_proc:
|
||||
LOG_ERROR("Failed to access process data in '%s': %s\n", proc_path, strerror(errno));
|
||||
goto error_cleanup;
|
||||
return context ? context->config : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -935,11 +573,11 @@ static char *game_mode_context_find_exe(pid_t pid)
|
||||
goto fail;
|
||||
|
||||
/* Detect if the process is a wine loader process */
|
||||
if (strtail(exe, "/wine-preloader") || strtail(exe, "/wine64-preloader")) {
|
||||
if (game_mode_detect_wine_preloader(exe)) {
|
||||
LOG_MSG("Detected wine preloader for client %d [%s].\n", pid, exe);
|
||||
goto wine_preloader;
|
||||
}
|
||||
if (strtail(exe, "/wine") || strtail(exe, "/wine64")) {
|
||||
if (game_mode_detect_wine_loader(exe)) {
|
||||
LOG_MSG("Detected wine loader for client %d [%s].\n", pid, exe);
|
||||
goto wine_preloader;
|
||||
}
|
||||
|
@ -34,10 +34,15 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define INVALID_PROCFD -1
|
||||
|
||||
typedef int procfd_t;
|
||||
|
||||
/**
|
||||
* Opaque context
|
||||
* Opaque types
|
||||
*/
|
||||
typedef struct GameModeContext GameModeContext;
|
||||
typedef struct GameModeConfig GameModeConfig;
|
||||
|
||||
/**
|
||||
* Return the singleton instance
|
||||
@ -83,3 +88,46 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t pid);
|
||||
* 2 if gamemode is active and the client is registered
|
||||
*/
|
||||
int game_mode_context_query_status(GameModeContext *self, pid_t pid);
|
||||
|
||||
/**
|
||||
* Query the config of a gamemode context
|
||||
*
|
||||
* @param context A gamemode context
|
||||
* @returns Configuration from the gamemode context
|
||||
*/
|
||||
GameModeConfig *game_mode_config_from_context(const GameModeContext *context);
|
||||
|
||||
/** gamemode-env.c
|
||||
* Provides internal API functions specific to working environment
|
||||
* variables.
|
||||
*/
|
||||
char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var);
|
||||
char *game_mode_lookup_user_home(void);
|
||||
|
||||
/** gamemode-ioprio.c
|
||||
* Provides internal API functions specific to adjusting process
|
||||
* IO priorities.
|
||||
*/
|
||||
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client);
|
||||
|
||||
/** gamemode-proc.c
|
||||
* Provides internal API functions specific to working with process
|
||||
* environments.
|
||||
*/
|
||||
procfd_t game_mode_open_proc(const pid_t pid);
|
||||
int game_mode_close_proc(const procfd_t procfd);
|
||||
|
||||
/** gamemode-sched.c
|
||||
* Provides internal API functions specific to adjusting process
|
||||
* scheduling.
|
||||
*/
|
||||
void game_mode_apply_renice(const GameModeContext *self, const pid_t client);
|
||||
void game_mode_apply_scheduling(const GameModeContext *self, const pid_t client);
|
||||
|
||||
/** gamemode-wine.c
|
||||
* Provides internal API functions specific to handling wine
|
||||
* prefixes.
|
||||
*/
|
||||
bool game_mode_detect_wine_loader(const char *exe);
|
||||
bool game_mode_detect_wine_preloader(const char *exe);
|
||||
char *game_mode_resolve_wine_preloader(const pid_t pid);
|
||||
|
64
daemon/helpers.h
Normal file
64
daemon/helpers.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2018, 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
/**
|
||||
* Value clamping helper, works like MIN/MAX but constraints a value within the range.
|
||||
*/
|
||||
#define CLAMP(lbound, ubound, value) MIN(MIN(lbound, ubound), MAX(MAX(lbound, ubound), value))
|
||||
|
||||
/**
|
||||
* Little helper to safely print into a buffer, returns a pointer into the buffer
|
||||
*/
|
||||
#define buffered_snprintf(b, s, ...) \
|
||||
(snprintf(b, sizeof(b), s, __VA_ARGS__) < (ssize_t)sizeof(b) ? b : NULL)
|
||||
|
||||
/**
|
||||
* Little helper to safely print into a buffer, returns a newly allocated string
|
||||
*/
|
||||
#define safe_snprintf(b, s, ...) \
|
||||
(snprintf(b, sizeof(b), s, __VA_ARGS__) < (ssize_t)sizeof(b) ? strndup(b, sizeof(b)) : NULL)
|
||||
|
||||
/**
|
||||
* Helper function: Test, if haystack ends with needle.
|
||||
*/
|
||||
static inline const char *strtail(const char *haystack, const char *needle)
|
||||
{
|
||||
char *pos = strstr(haystack, needle);
|
||||
if (pos && (strlen(pos) == strlen(needle)))
|
||||
return pos;
|
||||
return NULL;
|
||||
}
|
@ -62,6 +62,13 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define LOG_ONCE(type, ...) \
|
||||
do { \
|
||||
static int __once = 0; \
|
||||
if (!__once++) \
|
||||
LOG_##type(__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/* Fatal warnings trigger an exit */
|
||||
#define FATAL_ERRORNO(msg) \
|
||||
do { \
|
||||
@ -74,6 +81,26 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
/* Hinting helpers */
|
||||
#define HINT_ONCE(name, hint) \
|
||||
do { \
|
||||
static int __once = 0; \
|
||||
name = (!__once++ ? hint : ""); \
|
||||
} while (0)
|
||||
|
||||
#define HINT_ONCE_ON(cond, ...) \
|
||||
do { \
|
||||
if (cond) \
|
||||
HINT_ONCE(__VA_ARGS__); \
|
||||
} while (0);
|
||||
|
||||
#define LOG_HINTED(type, msg, hint, ...) \
|
||||
do { \
|
||||
const char *__arg; \
|
||||
HINT_ONCE(__arg, hint); \
|
||||
LOG_##type(msg "%s", __VA_ARGS__, __arg); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Control if and how how we use syslog
|
||||
*/
|
||||
|
@ -18,6 +18,11 @@ link_daemon_common = declare_dependency(
|
||||
daemon_sources = [
|
||||
'main.c',
|
||||
'gamemode.c',
|
||||
'gamemode-env.c',
|
||||
'gamemode-ioprio.c',
|
||||
'gamemode-proc.c',
|
||||
'gamemode-sched.c',
|
||||
'gamemode-wine.c',
|
||||
'daemonize.c',
|
||||
'dbus_messaging.c',
|
||||
'governors.c',
|
||||
|
@ -4,7 +4,7 @@
|
||||
# Ensure we are at the project root
|
||||
cd "$(dirname $0)"/..
|
||||
|
||||
wget -Nq https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/git-clang-format
|
||||
wget -Nq -T3 -t1 https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/git-clang-format
|
||||
|
||||
if chmod +x git-clang-format; then
|
||||
if [[ "$1" == "--pre-commit" ]]; then
|
||||
|
Reference in New Issue
Block a user