diff --git a/umu/umu_run.py b/umu/umu_run.py index 5e0e21601..073cc45cb 100755 --- a/umu/umu_run.py +++ b/umu/umu_run.py @@ -49,6 +49,7 @@ from umu.umu_runtime import setup_umu from umu.umu_util import ( get_libc, + get_library_paths, get_osrelease_id, is_installed_verb, is_winetricks_verb, @@ -305,18 +306,6 @@ def enable_steam_game_drive(env: dict[str, str]) -> dict[str, str]: """Enable Steam Game Drive functionality.""" paths: set[str] = set() root: Path = Path("/") - libc: str = get_libc() - - # All library paths that are currently supported by the container framework - # See https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/distro-assumptions.md#filesystem-layout - # Non-FHS filesystems should run in a FHS chroot to comply - steamrt_path_candidates: tuple[str, ...] = ( - "/usr/lib64", - "/usr/lib32", - "/usr/lib", - "/usr/lib/x86_64-linux-gnu", - "/usr/lib/i386-linux-gnu", - ) # Check for mount points going up toward the root # NOTE: Subvolumes can be mount points @@ -336,36 +325,14 @@ def enable_steam_game_drive(env: dict[str, str]) -> dict[str, str]: if env["STEAM_COMPAT_INSTALL_PATH"]: paths.add(env["STEAM_COMPAT_INSTALL_PATH"]) - # When libc.so could not be found, depend on LD_LIBRARY_PATH - # In some cases, using ldconfig to determine library paths can fail in non- - # FHS compliant filesystems (e.g., NixOS). - # See https://github.com/Open-Wine-Components/umu-launcher/issues/106 - if not libc: - log.warning("libc.so could not be found") - env["STEAM_RUNTIME_LIBRARY_PATH"] = ":".join(paths) - return env - - # Set the shared library paths of the system after finding libc.so - set_steamrt_paths(steamrt_path_candidates, paths, libc) + # Set the shared library paths of the system + paths |= get_library_paths() env["STEAM_RUNTIME_LIBRARY_PATH"] = ":".join(paths) return env -def set_steamrt_paths( - steamrt_path_candidiates: tuple[str, ...], - steamrt_paths: set[str], - libc: str, -) -> set[str]: - """Set the shared library paths for the Steam Runtime.""" - for rtpath in steamrt_path_candidiates: - if (libc_path := Path(rtpath, libc).resolve()).is_file(): - steamrt_paths.add(str(libc_path.parent)) - - return steamrt_paths - - def build_command( env: dict[str, str], local: Path, diff --git a/umu/umu_test.py b/umu/umu_test.py index b0e6c63fb..cf3d6b4e8 100644 --- a/umu/umu_test.py +++ b/umu/umu_test.py @@ -209,68 +209,6 @@ def test_rearrange_gamescope_baselayer_order(self): f"Expected {expected}, received {result}", ) - def test_set_steamrt_paths(self): - """Test set_steamrt_paths to ensure resolved filesystem paths. - - set_steamrt_path will find path containing the libc.so file from the - system, resolving any symbolic links in its path. - - Expects a set to contain strings representing the user's shared - library paths and for the paths to not contain symbolic links. - """ - lib64_link = f"{self.test_usr}/lib64" - lib64 = f"{self.test_usr}/lib" - libc = "libc.so.6" - steamrt_path_candidates = ( - lib64_link, - lib64, - f"{self.test_usr}/lib32", - f"{self.test_usr}/lib/x86_64-linux-gnu", - f"{self.test_usr}/lib/i386-linux-gnu", - ) - steamrt_paths = set() - - # Mock shared library paths and libc.so.6 - for path in steamrt_path_candidates: - if path == lib64_link: - Path(lib64_link).symlink_to("lib") - continue - Path(path).mkdir() - - Path(lib64, libc).touch() - - # Find shared lib paths containing libc - for path in steamrt_path_candidates: - if Path(path, libc).is_file(): - steamrt_paths.add(path) - - # Assert mocked runtime paths - self.assertEqual( - len(steamrt_paths), - 2, - f"Expected 2 elements for '{steamrt_paths}'", - ) - self.assertTrue( - lib64 in steamrt_paths and lib64_link in steamrt_paths, - f"Expected '{steamrt_paths}' to contain linked and resolved path", - ) - self.assertEqual( - Path(lib64_link).resolve(), - Path(lib64).absolute(), - "Expected linked shared library path to resolve to real path", - ) - - result = umu_run.set_steamrt_paths( - steamrt_path_candidates, set(), libc - ) - - # Ensure the resolved shared library paths is not the unresolved paths - self.assertNotEqual( - steamrt_paths, - result, - "Expected linked shared library paths to not be resolved paths", - ) - def test_run_command(self): """Test run_command.""" mock_exe = "foo" @@ -1190,13 +1128,6 @@ def test_game_drive_libpath_empty(self): "Expected two elements in STEAM_RUNTIME_LIBRARY_PATHS", ) - # Expect LD_LIBRARY_PATH was added ontop of /usr/lib and /usr/lib64 - self.assertEqual( - len(self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":")), - 2, - "Expected two values in STEAM_RUNTIME_LIBRARY_PATH", - ) - # An error should be raised if /usr/lib or /usr/lib64 is found twice lib_paths = set() for path in self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":"): @@ -1325,13 +1256,6 @@ def test_game_drive_empty(self): args = None result_gamedrive = None # Expected library paths for the container runtime framework - libpaths = { - "/usr/lib64", - "/usr/lib32", - "/usr/lib", - "/usr/lib/x86_64-linux-gnu", - "/usr/lib/i386-linux-gnu", - } Path(self.test_file + "/proton").touch() # Replicate main's execution and test up until enable_steam_game_drive @@ -1375,19 +1299,6 @@ def test_game_drive_empty(self): "Expected two elements in STEAM_RUNTIME_LIBRARY_PATHS", ) - # We just expect /usr/lib and /usr/lib32 since LD_LIBRARY_PATH is unset - self.assertEqual( - len(self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":")), - 2, - "Expected two values in STEAM_RUNTIME_LIBRARY_PATH", - ) - - # Check that there are no trailing colons, unexpected characters - # and is officially supported - str1, str2 = self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":") - self.assertTrue(str1 in libpaths, f"Expected a path in: {libpaths}") - self.assertTrue(str2 in libpaths, f"Expected a path in: {libpaths}") - # Ensure that umu sets the resolved shared library paths. The only time # this variable will contain links is from the LD_LIBRARY_PATH set in # the user's environment or client @@ -1395,6 +1306,11 @@ def test_game_drive_empty(self): if Path(path).is_symlink(): err = f"Symbolic link found: {path}" raise AssertionError(err) + if path.endswith( + (":", "/", ".") + ): # There should be no trailing colons, slashes or periods + err = f"Trailing character in path: {path[-1]}" + raise AssertionError(err) # Both of these values should be empty still after calling # enable_steam_game_drive diff --git a/umu/umu_util.py b/umu/umu_util.py index d2560f44d..3ed6a8c11 100644 --- a/umu/umu_util.py +++ b/umu/umu_util.py @@ -17,6 +17,43 @@ def get_libc() -> str: return find_library("c") or "" +@lru_cache +def get_library_paths() -> set[str]: + """Find the shared library paths from the user's system.""" + library_paths: set[str] = set() + ldconfig: str = which("ldconfig") or "" + + if not ldconfig: + log.warning("ldconfig not found in $PATH, cannot find library paths") + return library_paths + + # Find all shared library path prefixes within the assumptions of the + # Steam Runtime container framework. The framework already works hard by + # attempting to work with various distibutions' quirks. Unless it's Flatpak + # related, let's continue to make it their job. + try: + # Here, opt to using the ld.so cache similar to the stdlib + # implementation of _findSoname_ldconfig. + with Popen( + (ldconfig, "-p"), + text=True, + encoding="utf-8", + stdout=PIPE, + stderr=PIPE, + env={"LC_ALL": "C", "LANG": "C"}, + ) as proc: + stdout, _ = proc.communicate() + library_paths |= { + os.path.realpath(line[: line.rfind("/")]) + for line in stdout.split() + if line.startswith("/") + } + except OSError as e: + log.exception(e) + + return library_paths + + def run_zenity(command: str, opts: list[str], msg: str) -> int: """Execute the command and pipe the output to zenity.