summaryrefslogtreecommitdiffstats
path: root/tools/inspect_os.py
blob: ed4ee456a249fef17379e86279ca03338dcd2a95 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import os
import logging
import re

from glob import iglob
from . import log

__all__ = ["get_linux_os_info", "get_windows_os_info"]

L = logging.getLogger(__name__)

try:
    from Registry import Registry  # type: ignore
except ModuleNotFoundError:
    L.error(
        "You need to install the following package:\n"
        "pip3 install python-registry"
        )
    raise


@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)'}
    """
    release_files = {}
    for root, dirs, _ in os.walk(path):
        if "etc" in dirs:
            skip = False
            for f in iglob(f"{root}/etc/*release"):
                # Hack for immutable operating systems of Fedora.
                if not os.path.exists(f):
                    skip = True
                    break
                release_files[os.path.basename(f)] = f
            if release_files and not skip:
                break

    if not release_files:
        L.debug("no release file found")
        return {}

    L.debug("release files: %s", release_files)

    name = version = ""
    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()
    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", ""))
    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()

    if not name or not version:
        return {}

    name = name.strip().strip("'\"")
    version = version.strip().strip("'\"")
    return {"name": name, "version": version}


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