~kissiel/checkbox/fix-1420352

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
#
# This file is part of Checkbox.
#
# Copyright 2011 Canonical Ltd.
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.

#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
#
import re

from string import (
    hexdigits,
    ascii_uppercase,
    )

from checkbox.lib.dmi import (
    Dmi,
    DmiDevice,
    )


HANDLE_RE = re.compile(
    r"^Handle (?P<handle>0x[%s]{4}), "
    r"DMI type (?P<type>\d+), "
    r"(?P<size>\d+) bytes$"
    % hexdigits)
KEY_VALUE_RE = re.compile(
    r"^\t(?P<key>[%s].+):( (?P<value>.+))?$"
    % ascii_uppercase)


class DmidecodeParser:
    """Parser for the dmidecode command."""

    _key_map = {
        "ID": "serial",
        "Manufacturer": "vendor",
        "Product Name": "name",
        "Serial Number": "serial",
        "Type": "type",
        "Vendor": "vendor",
        "Version": "version",
        "Size": "size",
        "Form Factor": "form",
        }

    def __init__(self, stream):
        self.stream = stream

    def _parseKey(self, key):
        return self._key_map.get(key)

    def _parseValue(self, value):
        if value is not None:
            value = value.strip()
            if not value:
                value = None

        return value

    def run(self, result):
        output = self.stream.read()
        for record in re.split(r"\n{2,}", output):
            record = record.strip()
            # Skip empty records
            if not record:
                continue

            # Skip header record
            lines = record.split("\n")
            line = lines.pop(0)
            if line.startswith("#"):
                continue

            # Skip records with an unsupported handle
            match = HANDLE_RE.match(line)
            if not match:
                continue

            # Skip records that are empty or inactive
            if not lines or lines.pop(0) == "Inactive":
                continue

            # Skip disabled entries and end-of-table marker
            type_index = int(match.group("type"))
            if type_index >= len(Dmi.type_names):
                continue

            category = Dmi.type_names[type_index]
            category = category.upper().split(" ")[-1]
            if category not in (
                "BOARD", "BIOS", "CHASSIS", "DEVICE", "PROCESSOR", "SYSTEM"):
                continue

            # Parse attributes
            attributes = {}

            for line in lines:
                # Skip lines with an unsupported key/value pair
                match = KEY_VALUE_RE.match(line)
                if not match:
                    continue

                # Skip lines with an unsupported key
                key = self._parseKey(match.group("key"))
                if not key:
                    continue

                key = "%s_%s" % (category.lower(), key)
                value = self._parseValue(match.group("value"))
                attributes[key] = value

            device = DmiDevice(attributes, category)
            result.addDmiDevice(device)

        return result