From f458fd4c83ddf251654402021a28b607cef0f04f Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 5 Aug 2024 16:50:18 -0400 Subject: [PATCH 1/3] Work around account switching failing to open the CEF debugger socket this automates lsof and gdb to force close the socket before steam finishes shutting down (from RegisterForShutdownStart) --- .../localplatform/localplatform.py | 3 ++ .../localplatform/localplatformlinux.py | 38 +++++++++++++++++++ .../localplatform/localplatformwin.py | 5 ++- backend/decky_loader/utilities.py | 8 +++- frontend/src/steamfixes/index.ts | 9 +++-- frontend/src/steamfixes/socket.ts | 16 ++++++++ 6 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 frontend/src/steamfixes/socket.ts diff --git a/backend/decky_loader/localplatform/localplatform.py b/backend/decky_loader/localplatform/localplatform.py index 028eff8fc..c7085cd1d 100644 --- a/backend/decky_loader/localplatform/localplatform.py +++ b/backend/decky_loader/localplatform/localplatform.py @@ -37,6 +37,9 @@ def get_live_reload() -> bool: def get_keep_systemd_service() -> bool: return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1" +def get_use_cef_close_workaround() -> bool: + return ON_LINUX and os.getenv("USE_CEF_CLOSE_WORKAROUND", "1") == "1" + def get_log_level() -> int: return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[ os.getenv("LOG_LEVEL", "INFO") diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index f22cb465d..45086aef1 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -1,3 +1,5 @@ +from re import compile +from asyncio import Lock import os, pwd, grp, sys, logging from subprocess import call, run, DEVNULL, PIPE, STDOUT from ..enums import UserType @@ -227,3 +229,39 @@ def get_unprivileged_user() -> str: user = 'deck' return user + +# Works around the CEF debugger TCP socket not closing properly when Steam restarts +# Group 1 is PID, group 2 is FD. this also filters for "steamwebhelper" in the process name. +cef_socket_lsof_regex = compile(r"^p(\d+)(?:\s|.)+csteamwebhelper(?:\s|.)+f(\d+)(?:\s|.)+TST=LISTEN") +close_cef_socket_lock = Lock() + +async def close_cef_socket(): + async with close_cef_socket_lock: + if _get_effective_user_id() != 0: + logger.warn("Can't close CEF socket as Decky isn't running as root.") + return + # Look for anything listening TCP on port 8080 + lsof = run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], capture_output=True, text=True) + if lsof.returncode != 0 or len(lsof.stdout) < 1: + logger.error(f"lsof call failed in close_cef_socket! return code: {str(lsof.returncode)}") + return + + lsof_data = cef_socket_lsof_regex.match(lsof.stdout) + + if not lsof_data: + logger.error("lsof regex match failed in close_cef_socket!") + return + + pid = lsof_data.group(1) + fd = lsof_data.group(2) + + logger.info(f"Closing CEF socket with PID {pid} and FD {fd}") + + # Use gdb to inject a close() call for the socket fd into steamwebhelper + gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"]) + + if gdb_ret.returncode != 0: + logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True) + return + + logger.info("CEF socket closed") diff --git a/backend/decky_loader/localplatform/localplatformwin.py b/backend/decky_loader/localplatform/localplatformwin.py index 0724b59e2..52ade07c6 100644 --- a/backend/decky_loader/localplatform/localplatformwin.py +++ b/backend/decky_loader/localplatform/localplatformwin.py @@ -55,4 +55,7 @@ def get_unprivileged_user() -> str: return os.getenv("UNPRIVILEGED_USER", os.getlogin()) async def restart_webhelper() -> bool: - return True # Stubbed \ No newline at end of file + return True # Stubbed + +async def close_cef_socket(): + return # Stubbed \ No newline at end of file diff --git a/backend/decky_loader/utilities.py b/backend/decky_loader/utilities.py index 4850cdef1..17226ebcb 100644 --- a/backend/decky_loader/utilities.py +++ b/backend/decky_loader/utilities.py @@ -20,9 +20,8 @@ if TYPE_CHECKING: from .main import PluginManager from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab -from .localplatform.localplatform import ON_WINDOWS from . import helpers -from .localplatform.localplatform import service_stop, service_start, get_home_path, get_username +from .localplatform.localplatform import ON_WINDOWS, service_stop, service_start, get_home_path, get_username, get_use_cef_close_workaround, close_cef_socket class FilePickerObj(TypedDict): file: Path @@ -78,6 +77,7 @@ def __init__(self, context: PluginManager) -> None: context.ws.add_route("utilities/get_tab_id", self.get_tab_id) context.ws.add_route("utilities/get_user_info", self.get_user_info) context.ws.add_route("utilities/http_request", self.http_request_legacy) + context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket) context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility) context.web_app.add_routes([ @@ -287,6 +287,10 @@ async def stop_ssh(self): await service_stop(helpers.SSHD_UNIT) return True + async def close_cef_socket(self): + if get_use_cef_close_workaround(): + await close_cef_socket() + async def filepicker_ls(self, path: str | None = None, include_files: bool = True, diff --git a/frontend/src/steamfixes/index.ts b/frontend/src/steamfixes/index.ts index e3f2b2848..f0e04c4d7 100644 --- a/frontend/src/steamfixes/index.ts +++ b/frontend/src/steamfixes/index.ts @@ -1,5 +1,6 @@ -// import reloadFix from './reload'; -import restartFix from './restart'; +// import restartFix from './restart'; +import cefSocketFix from "./socket"; + let fixes: Function[] = []; export function deinitSteamFixes() { @@ -7,6 +8,6 @@ export function deinitSteamFixes() { } export async function initSteamFixes() { - // fixes.push(await reloadFix()); - fixes.push(await restartFix()); + fixes.push(cefSocketFix()); + // fixes.push(await restartFix()); } diff --git a/frontend/src/steamfixes/socket.ts b/frontend/src/steamfixes/socket.ts new file mode 100644 index 000000000..a003b417c --- /dev/null +++ b/frontend/src/steamfixes/socket.ts @@ -0,0 +1,16 @@ +import Logger from "../logger"; + +const logger = new Logger('CEFSocketFix'); + +const closeCEFSocket = DeckyBackend.callable<[], void>("utilities/close_cef_socket"); + +export default function cefSocketFix() { + const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => { + logger.log("Closing CEF socket before shutdown"); + await closeCEFSocket(); + }); + + if (reg) logger.debug("CEF shutdown handler ready"); + + return () => reg?.unregister(); +} \ No newline at end of file From 30cacd691ca4bab203ca63ee25be54aeee36a4fb Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 5 Aug 2024 16:57:29 -0400 Subject: [PATCH 2/3] lint --- frontend/src/steamfixes/index.ts | 2 +- frontend/src/steamfixes/socket.ts | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/steamfixes/index.ts b/frontend/src/steamfixes/index.ts index f0e04c4d7..3b3ee75f8 100644 --- a/frontend/src/steamfixes/index.ts +++ b/frontend/src/steamfixes/index.ts @@ -1,5 +1,5 @@ // import restartFix from './restart'; -import cefSocketFix from "./socket"; +import cefSocketFix from './socket'; let fixes: Function[] = []; diff --git a/frontend/src/steamfixes/socket.ts b/frontend/src/steamfixes/socket.ts index a003b417c..26f6afee0 100644 --- a/frontend/src/steamfixes/socket.ts +++ b/frontend/src/steamfixes/socket.ts @@ -1,16 +1,16 @@ -import Logger from "../logger"; +import Logger from '../logger'; const logger = new Logger('CEFSocketFix'); -const closeCEFSocket = DeckyBackend.callable<[], void>("utilities/close_cef_socket"); +const closeCEFSocket = DeckyBackend.callable<[], void>('utilities/close_cef_socket'); export default function cefSocketFix() { - const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => { - logger.log("Closing CEF socket before shutdown"); - await closeCEFSocket(); - }); + const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => { + logger.log('Closing CEF socket before shutdown'); + await closeCEFSocket(); + }); - if (reg) logger.debug("CEF shutdown handler ready"); + if (reg) logger.debug('CEF shutdown handler ready'); - return () => reg?.unregister(); -} \ No newline at end of file + return () => reg?.unregister(); +} From 73fce12fff1beb88f3ffe9a2de735d6ab63e6a6e Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 5 Aug 2024 17:08:06 -0400 Subject: [PATCH 3/3] fix LD_LIBRARY_PATH for gdb --- backend/decky_loader/localplatform/localplatformlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 45086aef1..2c92124f3 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -258,7 +258,7 @@ async def close_cef_socket(): logger.info(f"Closing CEF socket with PID {pid} and FD {fd}") # Use gdb to inject a close() call for the socket fd into steamwebhelper - gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"]) + gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""}) if gdb_ret.returncode != 0: logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)