"""Static host, display, filesystem, and process probes for telemetry.""" from __future__ import annotations from collections.abc import Callable, Mapping import json import math import os from pathlib import Path import platform import re import resource import sys from ..paths import AppPaths from ..system_identity import ( SystemIdentity, detect_system_identity, read_os_release as read_system_os_release, ) NETWORK_FILESYSTEMS = frozenset( {"9p", "afs", "ceph", "cifs", "nfs", "nfs4", "smb3", "sshfs"} ) MOUNT_ESCAPE_PATTERN = re.compile(r"\\([0-7]{3})") def operating_system_info( identity: SystemIdentity | None = None, *, detect_identity: Callable[[], SystemIdentity] = detect_system_identity, system_name: Callable[[], str] = platform.system, kernel_release: Callable[[], str] = platform.release, machine_name: Callable[[], str] = platform.machine, libc_version: Callable[[], tuple[str, str]] = platform.libc_ver, ) -> dict[str, object]: identity = detect_identity() if identity is None else identity release = identity.operating_system_release return { "name": release.get("NAME", system_name() or "unknown"), "id": release.get("ID", "unknown"), "version": release.get("PRETTY_NAME", release.get("VERSION", "unknown")), "version_id": release.get("VERSION_ID", "unknown"), "kernel": kernel_release(), "architecture": machine_name() or "unknown", "machine_architecture": machine_name() or "unknown", "glibc_version": libc_version()[1] or None, "identity_source": ( "container_host_os_release" if identity.host_release is not None else "execution_os_release" ), } def read_os_release( path: Path = Path("/etc/os-release"), *, reader: Callable[[Path], dict[str, str]] = read_system_os_release, ) -> dict[str, str]: return reader(path) def execution_environment_info( identity: SystemIdentity, *, libc_version: Callable[[], tuple[str, str]] = platform.libc_ver, ) -> dict[str, object] | None: if not identity.running_inside_steam_runtime: return None release = identity.execution_release return { "containerized": True, "kind": "steam_runtime", "id": release.get("ID", "steamrt"), "name": release.get("NAME", "Steam Runtime"), "version": release.get( "PRETTY_NAME", release.get("VERSION", "unknown"), ), "version_id": release.get("VERSION_ID", "unknown"), "host_os_release_available": identity.host_release is not None, "launcher_inside_game_runtime": True, "glibc_version": libc_version()[1] or None, } def display_info(environment: Mapping[str, str]) -> dict[str, object]: session_type = environment.get("XDG_SESSION_TYPE", "").casefold() if session_type not in {"wayland", "x11"}: if environment.get("WAYLAND_DISPLAY"): session_type = "wayland" elif environment.get("DISPLAY"): session_type = "x11" else: session_type = "unknown" desktop = environment.get("XDG_CURRENT_DESKTOP", "") desktop = desktop.split(":", 1)[0].strip().casefold() or "unknown" session = environment.get("DESKTOP_SESSION", "").strip().casefold() or "unknown" return { "server": session_type, "desktop": desktop[:64], "session": session[:64], } Mount = tuple[Path, str, set[str]] def filesystem_info( target: Path, *, read_mounts: Callable[[], list[Mount]] | None = None, access: Callable[[Path, int], bool] = os.access, writable_mode: int = os.W_OK, network_filesystems: frozenset[str] = NETWORK_FILESYSTEMS, ) -> dict[str, object]: existing = target while not existing.exists() and existing != existing.parent: existing = existing.parent filesystem_type = "unknown" readonly = False noexec = False try: mounts = read_mountinfo() if read_mounts is None else read_mounts() resolved = existing.resolve(strict=True) matches = [ mount for mount in mounts if resolved == mount[0] or resolved.is_relative_to(mount[0]) ] if matches: _mountpoint, filesystem_type, options = max( matches, key=lambda item: len(item[0].parts), ) readonly = "ro" in options noexec = "noexec" in options except (OSError, RuntimeError, ValueError): pass try: writable = access(existing, writable_mode) and not readonly except OSError: writable = False return { "type": filesystem_type, "writable": writable, "readonly": readonly, "noexec": noexec, "network": filesystem_type.casefold() in network_filesystems, } def read_mountinfo( path: Path = Path("/proc/self/mountinfo"), *, unescape: Callable[[str], str] | None = None, ) -> list[Mount]: decode = unescape_mount_field if unescape is None else unescape mounts: list[Mount] = [] for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): fields = line.split() try: separator = fields.index("-") except ValueError: continue if separator < 6 or len(fields) <= separator + 3: continue mountpoint = Path(decode(fields[4])) filesystem_type = fields[separator + 1] options = set(fields[5].split(",")) | set(fields[separator + 3].split(",")) mounts.append((mountpoint, filesystem_type, options)) return mounts def unescape_mount_field(value: str) -> str: return MOUNT_ESCAPE_PATTERN.sub( lambda match: chr(int(match.group(1), 8)), value, ) def install_is_external(paths: AppPaths, environment: Mapping[str, str]) -> bool: home_value = environment.get("HOME") if not home_value: return False try: home = Path(home_value).expanduser().resolve() data = paths.data.resolve() return data != home and not data.is_relative_to(home) except (OSError, RuntimeError): return False def current_game_version( path: Path, *, json_loader: Callable[[str], object] = json.loads, ) -> str | None: try: value = json_loader(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return None if not isinstance(value, dict): return None version = value.get("version") return version[:32] if isinstance(version, str) and version else None def settings_schema_version() -> int: try: from ..settings import SETTINGS_SCHEMA return SETTINGS_SCHEMA except Exception: return 1 def resource_usage( *, get_usage: Callable[[int], object] = resource.getrusage, usage_target: int = resource.RUSAGE_SELF, platform_name: str = sys.platform, is_finite: Callable[[float], bool] = math.isfinite, ) -> dict[str, object]: """Read cumulative CPU time and peak resident memory for the selected process.""" try: usage = get_usage(usage_target) peak_value = float(getattr(usage, "ru_maxrss")) peak_divisor = 1024 * 1024 if platform_name == "darwin" else 1024 peak_memory_mb = peak_value / peak_divisor cpu_time_seconds = float(getattr(usage, "ru_utime")) + float( getattr(usage, "ru_stime") ) except (OSError, TypeError, ValueError, OverflowError): return {} if ( not is_finite(peak_memory_mb) or not is_finite(cpu_time_seconds) or peak_memory_mb < 0 or cpu_time_seconds < 0 ): return {} return { "peak_memory_mb": round(peak_memory_mb, 2), "cpu_time_seconds": round(cpu_time_seconds, 3), } __all__ = [ "MOUNT_ESCAPE_PATTERN", "Mount", "NETWORK_FILESYSTEMS", "current_game_version", "display_info", "execution_environment_info", "filesystem_info", "install_is_external", "operating_system_info", "read_mountinfo", "read_os_release", "resource_usage", "settings_schema_version", "unescape_mount_field", ]