diff --git a/daemon/gamemode-wine.c b/daemon/gamemode-wine.c new file mode 100644 index 0000000..c2cfc66 --- /dev/null +++ b/daemon/gamemode-wine.c @@ -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 +#include +#include +#include + +/** + * 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; +} diff --git a/daemon/gamemode.c b/daemon/gamemode.c index d046d1b..c1a2864 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -38,8 +38,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "helpers.h" #include "logging.h" -#include -#include #include #include #include @@ -562,113 +560,6 @@ GameModeConfig *game_mode_config_from_context(const GameModeContext *context) return context ? context->config : 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; - - /* 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; -} - /** * Attempt to locate the exe for the process. * We might run into issues if the process is running under an odd umask. @@ -687,11 +578,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; } diff --git a/daemon/gamemode.h b/daemon/gamemode.h index 083b389..f7f5c96 100644 --- a/daemon/gamemode.h +++ b/daemon/gamemode.h @@ -123,3 +123,11 @@ int game_mode_close_proc(const procfd_t procfd); */ 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); diff --git a/daemon/meson.build b/daemon/meson.build index ebcf1d6..1e9fec8 100644 --- a/daemon/meson.build +++ b/daemon/meson.build @@ -22,6 +22,7 @@ daemon_sources = [ 'gamemode-ioprio.c', 'gamemode-proc.c', 'gamemode-sched.c', + 'gamemode-wine.c', 'daemonize.c', 'dbus_messaging.c', 'governors.c',