summaryrefslogblamecommitdiffstats
path: root/tools/inspect_os.py
blob: acfe14fc6390f0efd05bf2b1bcf549e72333d4a2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12




                      
                                             





                                                      












                                                                            

                                                          



                                       


                                                                 
                                         
                         





                                                          






                                               
                                         







                                                                         
                                       







                                                                            
                                           










                                                                       



                                                                     

















                                                          








                                                                              
                                       





                                          





                                          










                                                                              

                                                          







































                                                                   
                                                                       
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"}