import os import logging import re from glob import iglob from Registry import Registry # type: ignore from . import log __all__ = ["get_linux_os_info", "get_windows_os_info"] L = logging.getLogger(__name__) @log def get_linux_os_info(path): """Find and parse the os-release file. See also: https://www.freedesktop.org/software/systemd/man/os-release.html Args: path (str): Path to the mounted filesystem. Returns: Name and version of linux distribution as a dictionary. For example: {'name': 'CentOS Linux', 'version': '6.0 (Final)', 'package_manager': 'rpm'} """ release_files = {} for root, dirs, _ in os.walk(path): if "etc" in dirs: for f in iglob(f"{root}/etc/*release"): # Hack for immutable operating systems of Fedora. if not os.path.exists(f): release_files.clear() break if os.path.isfile(f): release_files[os.path.basename(f)] = f else: if release_files: break continue if not release_files: L.debug("no release file found") return {} L.debug("release files: %s", release_files) name = version = package_manager = "" if "centos-release" in release_files: L.debug("parsing %s", release_files["centos-release"]) # CentOS 6.* doesn't have os-release, but centos-release. # For consistency, use always centos-release first. with open(release_files["centos-release"]) as f: # AlmaLinux 8.* and Rocky Linux 8.* also have centos-release. if m := re.match(r"^(.*)\srelease\s(.*)$", f.read()): name, version = m.groups() package_manager = "rpm" elif "gentoo-release" in release_files: # Gentoo now has a VERSION_ID tag in os-release, which did not exist # before. See also https://bugs.gentoo.org/788190. For consistency, # gentoo-release is parsed before os-release at this point. L.debug("parsing %s", release_files["gentoo-release"]) with open(release_files["gentoo-release"]) as f: if m := re.match(r"^(Gentoo).*release\s(.*)$", f.read()): name, version = m.groups() package_manager = "portage" elif "os-release" in release_files: L.debug("parsing %s", release_files["os-release"]) with open(release_files["os-release"]) as f: kv = {} for line in f: if not line or line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) kv[k] = v name = kv.get("NAME", "") version = kv.get("VERSION", kv.get("VERSION_ID", "")) # Arch-based distros have neither VERSION nor VERSION_ID. # These are using a rolling release model. if not version: version = kv.get("BUILD_ID", "") id_like = kv.get("ID_LIKE", kv.get("ID", "")) id_like = id_like.strip().strip("'\"").split() for i in id_like: if i == "alpine": package_manager = "apk" elif i in ("debian", "ubuntu"): package_manager = "dpkg" elif i == "arch": package_manager = "pacman" elif i in ("centos", "fedora", "rhel"): package_manager = "rpm" elif i in ("opensuse", "suse"): package_manager = "rpm" elif i == "ol": # Oracle Linux 6.10 package_manager = "rpm" else: continue break elif "system-release" in release_files: # RedHat (RHEL) provides the redhat-release file. However, it does not # seem to be reliable for determining which operating system it is. # Therefore, for distributions such as Scientific Linux 6.* and # Oracle Linux 6.*, use system-release instead. L.debug("parsing %s", release_files["system-release"]) with open(release_files["system-release"]) as f: if m := re.match(r"^(.*)\srelease\s(.*)$", f.read()): name, version = m.groups() package_manager = "rpm" if not name or not version: return {} name = name.strip().strip("'\"") version = version.strip().strip("'\"") return { "name": name, "version": version, "package_manager": package_manager } @log def get_windows_os_info(path): """Find and parse the software registry. Args: path (str): Path to the mounted filesystem. Returns: Name and version of windows distribution as a dictionary. For example: {'name': 'Microsoft Windows XP', 'version': '5.1', 'package_manager': 'win'} """ software = None locations = [ "WINDOWS/system32/config/software", # xp "Windows/System32/config/SOFTWARE", # others ] for location in locations: software_path = os.path.join(path, location) if os.path.isfile(software_path): software = software_path break if not software: return {} try: registry = Registry.Registry(software) except Exception as e: L.error("failed to open registry file %s: %r", software, e) return {} hive_path = "Microsoft\\Windows NT\\CurrentVersion" try: key = registry.open(hive_path) except Exception as e: L.error("%s not found in %s: %r", hive_path, software, e) return {} name = version = "" for k in key.values(): if k.name() == "ProductName": name = k.value() if k.name() == "CurrentVersion": version = k.value() if not name or not version: return {} return {"name": name, "version": version, "package_manager": "win"}