~ubuntu-branches/ubuntu/quantal/checkbox/quantal

« back to all changes in this revision

Viewing changes to checkbox/parsers/udevadm.py

  • Committer: Package Import Robot
  • Author(s): Daniel Manrique, Marc Tardif, Daniel Manrique, Jeff Lane, Ara Pulido, Brendan Donegan, Javier Collado
  • Date: 2011-11-18 12:46:21 UTC
  • Revision ID: package-import@ubuntu.com-20111118124621-87rjnjm27ool11e6
Tags: 0.13
New upstream release (LP: #892268):

[Marc Tardif]
* Generate a submission.xml file that contains all device and attachment
* Write the report before reporting the validation error.
* Changed device.product to dmi.product for the formfactor (LP: #875312)

[Daniel Manrique]
* Use gettext for string (LP: #869267)
* Move progress indicator to main checkbox dialog instead of a 
  transient window (LP: #868995)
* Ignore malformed dpkg entries in package_resource (LP: #794747)
* Reset window title after finishing a manual test (LP: #874690)
* Handle "@" in locale names (as in ca@valencia).

[Jeff Lane]
* Went through all the job files and:
  * Updated descriptions to match Unity UI structure
  * Added descriptions where necessary
  * Added further details to some descriptions
  * Moved some jobs to more appropriate files
  * Fixed job names in older job files to match new naming scheme 
    (suite/testname)
  * Added jobs to local.txt to ensure all job files are now parsed
    (this allows easier addition of existing tests to whitelists)
  * Changed remaining manual job descriptions to match the new format
* Updated CD and DVD write tests to be more clear about when to skip
  them (LP: #772794)

[Ara Pulido]
* Rewrote all job descriptions to match OEM QA syntax

[Brendan Donegan]  
* Fix the code that assigns keys in checkbox-cli so that it never assigns
  keys which have other uses. (LP: #877467)
* Show details of unmet job requirements (LP: #855852)
* Ensure that connect_wireless chooses a wireless connection from the list
  of available connections (LP: #877752)
* Have the bluetooth/detect tests require a device with the category
  BLUETOOTH to run, thus preventing the test from failing on systems with
  no Bluetooth device (LP: #862322)
* Rename attachment jobs to not have a forward slash in their name
  (LP: #887964)
* Guard against trying to write files to logical partitions on USB sticks
  (which will obviously fail) in usb_test (LP: #887049)
* Make the OpenGL test ignore the return value of glxgears and improve
  the test description (LP: #890725)
* Allow input/mouse test to run if a TOUCH device is present
  (LP: #886129)

[ Javier Collado ]
* Broken job dependencies fixed (LP: #888447)
* Regex support when specifying blacklists and whitelists on the
  commandline (LP: #588647)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# This file is part of Checkbox.
 
3
#
 
4
# Copyright 2011 Canonical Ltd.
 
5
#
 
6
# Checkbox is free software: you can redistribute it and/or modify
 
7
# it under the terms of the GNU General Public License as published by
 
8
# the Free Software Foundation, either version 3 of the License, or
 
9
# (at your option) any later version.
 
10
#
 
11
# Checkbox is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
 
18
#
 
19
import re
 
20
import string
 
21
 
 
22
from checkbox.lib.bit import (
 
23
    get_bitmask,
 
24
    test_bit,
 
25
    )
 
26
from checkbox.lib.input import Input
 
27
from checkbox.lib.pci import Pci
 
28
from checkbox.lib.usb import Usb
 
29
 
 
30
 
 
31
PCI_RE = re.compile(
 
32
    r"^pci:"
 
33
    r"v(?P<vendor_id>[%(hex)s]{8})"
 
34
    r"d(?P<product_id>[%(hex)s]{8})"
 
35
    r"sv(?P<subvendor_id>[%(hex)s]{8})"
 
36
    r"sd(?P<subproduct_id>[%(hex)s]{8})"
 
37
    r"bc(?P<class>[%(hex)s]{2})"
 
38
    r"sc(?P<subclass>[%(hex)s]{2})"
 
39
    r"i(?P<interface>[%(hex)s]{2})"
 
40
    % {"hex": string.hexdigits})
 
41
PNP_RE = re.compile(
 
42
    r"^acpi:"
 
43
    r"(?P<vendor_name>[%(upper)s]{3})"
 
44
    r"(?P<product_id>[%(hex)s]{4}):"
 
45
    % {"upper": string.uppercase, "hex": string.hexdigits})
 
46
USB_RE = re.compile(
 
47
    r"^usb:"
 
48
    r"v(?P<vendor_id>[%(hex)s]{4})"
 
49
    r"p(?P<product_id>[%(hex)s]{4})"
 
50
    r"d(?P<revision>[%(hex)s]{4})"
 
51
    r"dc(?P<class>[%(hex)s]{2})"
 
52
    r"dsc(?P<subclass>[%(hex)s]{2})"
 
53
    r"dp(?P<protocol>[%(hex)s]{2})"
 
54
    r"ic(?P<interface_class>[%(hex)s]{2})"
 
55
    r"isc(?P<interface_subclass>[%(hex)s]{2})"
 
56
    r"ip(?P<interface_protocol>[%(hex)s]{2})"
 
57
    % {"hex": string.hexdigits})
 
58
SCSI_RE = re.compile(
 
59
    r"^scsi:"
 
60
    r"t-0x(?P<type>[%(hex)s]{2})"
 
61
    % {"hex": string.hexdigits})
 
62
 
 
63
 
 
64
class UdevadmDevice:
 
65
    __slots__ = ("_environment", "_bits", "_stack",)
 
66
 
 
67
    def __init__(self, environment, bits=None, stack=[]):
 
68
        self._environment = environment
 
69
        self._bits = bits
 
70
        self._stack = stack
 
71
 
 
72
    @property
 
73
    def bus(self):
 
74
        # Change the bus from 'acpi' to 'pnp' for some devices
 
75
        if PNP_RE.match(self._environment.get("MODALIAS", "")) \
 
76
           and self.path.endswith(":00"):
 
77
            return "pnp"
 
78
 
 
79
        # Change the bus from 'block' to parent
 
80
        if self._environment.get("DEVTYPE") == "disk" and self._stack:
 
81
            return self._stack[-1]._environment.get("SUBSYSTEM")
 
82
 
 
83
        bus = self._environment.get("SUBSYSTEM")
 
84
        if bus == "pnp":
 
85
            return None
 
86
 
 
87
        return bus
 
88
 
 
89
    @property
 
90
    def category(self):
 
91
        if "IFINDEX" in self._environment:
 
92
            return "NETWORK"
 
93
 
 
94
        if "PCI_CLASS" in self._environment:
 
95
            pci_class_string = self._environment["PCI_CLASS"]
 
96
            pci_class = int(pci_class_string, 16)
 
97
 
 
98
            # Strip prog_if if defined
 
99
            if pci_class > 0xFFFF:
 
100
                pci_class >>= 8
 
101
 
 
102
            subclass_id = pci_class & 0xFF
 
103
            class_id = (pci_class >> 8) & 0xFF
 
104
 
 
105
            if class_id == Pci.BASE_CLASS_NETWORK:
 
106
                if subclass_id == Pci.CLASS_NETWORK_WIRELESS:
 
107
                    return "WIRELESS"
 
108
                else:
 
109
                    return "NETWORK"
 
110
 
 
111
            if class_id == Pci.BASE_CLASS_DISPLAY:
 
112
                if subclass_id == Pci.CLASS_DISPLAY_VGA:
 
113
                    return "VIDEO"
 
114
                else:
 
115
                    return "OTHER"
 
116
 
 
117
            if class_id == Pci.BASE_CLASS_SERIAL \
 
118
               and subclass_id == Pci.CLASS_SERIAL_USB:
 
119
                return "USB"
 
120
 
 
121
            if class_id == Pci.BASE_CLASS_STORAGE:
 
122
                if subclass_id == Pci.CLASS_STORAGE_SCSI:
 
123
                    return "SCSI"
 
124
 
 
125
                if subclass_id == Pci.CLASS_STORAGE_IDE:
 
126
                    return "IDE"
 
127
 
 
128
                if subclass_id == Pci.CLASS_STORAGE_FLOPPY:
 
129
                    return "FLOPPY"
 
130
 
 
131
                if subclass_id == Pci.CLASS_STORAGE_RAID:
 
132
                    return "RAID"
 
133
 
 
134
            if class_id == Pci.BASE_CLASS_COMMUNICATION \
 
135
               and subclass_id == Pci.CLASS_COMMUNICATION_MODEM:
 
136
                return "MODEM"
 
137
 
 
138
            if class_id == Pci.BASE_CLASS_INPUT \
 
139
               and subclass_id == Pci.CLASS_INPUT_SCANNER:
 
140
                return "SCANNER"
 
141
 
 
142
            if class_id == Pci.BASE_CLASS_MULTIMEDIA:
 
143
                if subclass_id == Pci.CLASS_MULTIMEDIA_VIDEO:
 
144
                    return "CAPTURE"
 
145
 
 
146
                if subclass_id == Pci.CLASS_MULTIMEDIA_AUDIO \
 
147
                   or subclass_id == Pci.CLASS_MULTIMEDIA_AUDIO_DEVICE:
 
148
                    return "AUDIO"
 
149
 
 
150
            if class_id == Pci.BASE_CLASS_SERIAL \
 
151
               and subclass_id == Pci.CLASS_SERIAL_FIREWIRE:
 
152
                return "FIREWIRE"
 
153
 
 
154
            if class_id == Pci.BASE_CLASS_BRIDGE \
 
155
               and (subclass_id == Pci.CLASS_BRIDGE_PCMCIA \
 
156
                    or subclass_id == Pci.CLASS_BRIDGE_CARDBUS):
 
157
                return "SOCKET"
 
158
 
 
159
        if "TYPE" in self._environment and "INTERFACE" in self._environment:
 
160
            interface_class, interface_subclass, interface_protocol = (
 
161
                int(i) for i in self._environment["INTERFACE"].split("/"))
 
162
 
 
163
            if interface_class == Usb.BASE_CLASS_AUDIO:
 
164
                return "AUDIO"
 
165
 
 
166
            if interface_class == Usb.BASE_CLASS_PRINTER:
 
167
                return "PRINTER"
 
168
 
 
169
            if interface_class == Usb.BASE_CLASS_STORAGE:
 
170
                if interface_subclass == Usb.CLASS_STORAGE_FLOPPY:
 
171
                    return "FLOPPY"
 
172
 
 
173
                if interface_subclass == Usb.CLASS_STORAGE_SCSI:
 
174
                    return "SCSI"
 
175
 
 
176
            if interface_class == Usb.BASE_CLASS_VIDEO:
 
177
                return "CAPTURE"
 
178
 
 
179
            if interface_class == Usb.BASE_CLASS_WIRELESS:
 
180
                if interface_protocol == Usb.PROTOCOL_BLUETOOTH:
 
181
                    return "BLUETOOTH"
 
182
                else:
 
183
                    return "WIRELESS"
 
184
 
 
185
        if "KEY" in self._environment:
 
186
            key = self._environment["KEY"].strip("=")
 
187
            bitmask = get_bitmask(key)
 
188
 
 
189
            for i in range(Input.KEY_Q, Input.KEY_P + 1):
 
190
                if not test_bit(i, bitmask, self._bits):
 
191
                    break
 
192
            else:
 
193
                return "KEYBOARD"
 
194
 
 
195
            if test_bit(Input.KEY_CAMERA, bitmask, self._bits):
 
196
                return "CAPTURE"
 
197
 
 
198
            if test_bit(Input.BTN_TOUCH, bitmask, self._bits):
 
199
                return "TOUCH"
 
200
 
 
201
            if test_bit(Input.BTN_MOUSE, bitmask, self._bits):
 
202
                return "MOUSE"
 
203
 
 
204
        if "ID_TYPE" in self._environment:
 
205
            id_type = self._environment["ID_TYPE"]
 
206
 
 
207
            if id_type == "cd":
 
208
                return "CDROM"
 
209
 
 
210
            if id_type == "disk":
 
211
                return "DISK"
 
212
 
 
213
            if id_type == "video":
 
214
                return "VIDEO"
 
215
 
 
216
        if "DEVTYPE" in self._environment:
 
217
            devtype = self._environment["DEVTYPE"]
 
218
            if devtype == "disk":
 
219
                if "ID_CDROM" in self._environment:
 
220
                    return "CDROM"
 
221
 
 
222
                if "ID_DRIVE_FLOPPY" in self._environment:
 
223
                    return "FLOPPY"
 
224
 
 
225
            if devtype == "scsi_device":
 
226
                match = SCSI_RE.match(self._environment.get("MODALIAS", ""))
 
227
                type = int(match.group("type"), 16) if match else -1
 
228
 
 
229
                # Check FLASH drives, see /lib/udev/rules.d/80-udisks.rules
 
230
                if type in (0, 7, 14) \
 
231
                   and not any(d.driver == "rts_pstor" for d in self._stack):
 
232
                    return "DISK"
 
233
 
 
234
                if type == 1:
 
235
                    return "TAPE"
 
236
 
 
237
                if type == 2:
 
238
                    return "PRINTER"
 
239
 
 
240
                if type in (4, 5):
 
241
                    return "CDROM"
 
242
 
 
243
                if type == 6:
 
244
                    return "SCANNER"
 
245
 
 
246
                if type == 12:
 
247
                    return "RAID"
 
248
 
 
249
        if "DRIVER" in self._environment:
 
250
            if self._environment["DRIVER"] == "floppy":
 
251
                return "FLOPPY"
 
252
 
 
253
        if self.product:
 
254
            return "OTHER"
 
255
 
 
256
        return None
 
257
 
 
258
    @property
 
259
    def driver(self):
 
260
        if "DRIVER" in self._environment:
 
261
            return self._environment["DRIVER"]
 
262
 
 
263
        # Check parent device for driver
 
264
        if self._stack:
 
265
            parent = self._stack[-1]
 
266
            if "DRIVER" in parent._environment:
 
267
                return parent._environment["DRIVER"]
 
268
 
 
269
        return None
 
270
 
 
271
    @property
 
272
    def path(self):
 
273
        return self._environment.get("DEVPATH")
 
274
 
 
275
    @property
 
276
    def product_id(self):
 
277
        # pci
 
278
        match = PCI_RE.match(self._environment.get("MODALIAS", ""))
 
279
        if match:
 
280
            return int(match.group("product_id"), 16)
 
281
 
 
282
        # usb
 
283
        match = USB_RE.match(self._environment.get("MODALIAS", ""))
 
284
        if match:
 
285
            return int(match.group("product_id"), 16)
 
286
 
 
287
        # pnp
 
288
        match = PNP_RE.match(self._environment.get("MODALIAS", ""))
 
289
        if match:
 
290
            product_id = int(match.group("product_id"), 16)
 
291
            # Ignore interrupt controllers
 
292
            if product_id > 0x0100:
 
293
                return product_id
 
294
 
 
295
        return None
 
296
 
 
297
    @property
 
298
    def vendor_id(self):
 
299
        # pci
 
300
        match = PCI_RE.match(self._environment.get("MODALIAS", ""))
 
301
        if match:
 
302
            return int(match.group("vendor_id"), 16)
 
303
 
 
304
        # usb
 
305
        match = USB_RE.match(self._environment.get("MODALIAS", ""))
 
306
        if match:
 
307
            return int(match.group("vendor_id"), 16)
 
308
 
 
309
        return None
 
310
 
 
311
    @property
 
312
    def subproduct_id(self):
 
313
        if "PCI_SUBSYS_ID" in self._environment:
 
314
            pci_subsys_id = self._environment["PCI_SUBSYS_ID"]
 
315
            subvendor_id, subproduct_id = pci_subsys_id.split(":")
 
316
            return int(subproduct_id, 16)
 
317
 
 
318
        return None
 
319
 
 
320
    @property
 
321
    def subvendor_id(self):
 
322
        if "PCI_SUBSYS_ID" in self._environment:
 
323
            pci_subsys_id = self._environment["PCI_SUBSYS_ID"]
 
324
            subvendor_id, subproduct_id = pci_subsys_id.split(":")
 
325
            return int(subvendor_id, 16)
 
326
 
 
327
        return None
 
328
 
 
329
    @property
 
330
    def product(self):
 
331
        for element in ("NAME",
 
332
                        "RFKILL_NAME",
 
333
                        "POWER_SUPPLY_MODEL_NAME"):
 
334
            if element in self._environment:
 
335
                return self._environment[element].strip('"')
 
336
 
 
337
        # disk
 
338
        if self._environment.get("DEVTYPE") == "scsi_device":
 
339
            for device in reversed(self._stack):
 
340
                if device._environment.get("ID_BUS") == "usb":
 
341
                    return decode_id(device._environment["ID_MODEL_ENC"])
 
342
 
 
343
        if self._environment.get("DEVTYPE") == "disk" \
 
344
           and self._environment.get("ID_BUS") == "ata":
 
345
            return decode_id(self._environment["ID_MODEL_ENC"])
 
346
 
 
347
        # floppy
 
348
        if self.driver == "floppy":
 
349
            return u"Platform Device"
 
350
 
 
351
        return None
 
352
 
 
353
    @property
 
354
    def vendor(self):
 
355
        if "RFKILL_NAME" in self._environment:
 
356
            return None
 
357
 
 
358
        if "POWER_SUPPLY_MANUFACTURER" in self._environment:
 
359
            return self._environment["POWER_SUPPLY_MANUFACTURER"]
 
360
 
 
361
        # pnp
 
362
        match = PNP_RE.match(self._environment.get("MODALIAS", ""))
 
363
        if match:
 
364
            return match.group("vendor_name")
 
365
 
 
366
        # disk
 
367
        if self._environment.get("DEVTYPE") == "scsi_device":
 
368
            for device in reversed(self._stack):
 
369
                if device._environment.get("ID_BUS") == "usb":
 
370
                    return decode_id(device._environment["ID_VENDOR_ENC"])
 
371
 
 
372
        return None
 
373
 
 
374
 
 
375
class UdevadmParser:
 
376
    """Parser for the udevadm command."""
 
377
 
 
378
    device_factory = UdevadmDevice
 
379
 
 
380
    def __init__(self, stream, bits=None):
 
381
        self.stream = stream
 
382
        self.bits = bits
 
383
 
 
384
    def _ignoreDevice(self, device):
 
385
        # Ignore devices without bus information
 
386
        if not device.bus:
 
387
            return True
 
388
 
 
389
        # Ignore devices without product information
 
390
        if not device.product and device.product_id is None:
 
391
            return True
 
392
 
 
393
        # Ignore invalid subsystem information
 
394
        if ((device.subproduct_id is None
 
395
             and device.subvendor_id is not None)
 
396
            or (device.subproduct_id is not None
 
397
             and device.subvendor_id is None)):
 
398
            return True
 
399
 
 
400
        # Ignore ACPI devices
 
401
        if device.bus == "acpi":
 
402
            return True
 
403
 
 
404
        return False
 
405
 
 
406
    def getAttributes(self, path):
 
407
        return {}
 
408
 
 
409
    def run(self, result):
 
410
        # Some attribute lines have a space character after the
 
411
        # ':', others don't have it (see udevadm-info.c).
 
412
        line_pattern = re.compile(r"(?P<key>[A-Z]):\s*(?P<value>.*)")
 
413
        multi_pattern = re.compile(r"(?P<key>[^=]+)=(?P<value>.*)")
 
414
 
 
415
        stack = []
 
416
        output = self.stream.read()
 
417
        for record in re.split("\n{2,}", output):
 
418
            record = record.strip()
 
419
            if not record:
 
420
                continue
 
421
 
 
422
            # Determine path and environment
 
423
            path = None
 
424
            element = None
 
425
            environment = {}
 
426
            for line in record.split("\n"):
 
427
                line_match = line_pattern.match(line)
 
428
                if not line_match:
 
429
                    if environment:
 
430
                        # Append to last environment element
 
431
                        environment[element] += line
 
432
                    continue
 
433
 
 
434
                key = line_match.group("key")
 
435
                value = line_match.group("value")
 
436
 
 
437
                if key == "P":
 
438
                    path = value
 
439
                elif key == "E":
 
440
                    key_match = multi_pattern.match(value)
 
441
                    if not key_match:
 
442
                        raise Exception(
 
443
                            "Device property not supported: %s" % value)
 
444
                    element = key_match.group("key")
 
445
                    environment[element] = key_match.group("value")
 
446
 
 
447
            # Update stack
 
448
            while stack:
 
449
                if stack[-1].path + "/" in path:
 
450
                    break
 
451
                stack.pop()
 
452
 
 
453
            # Set default DEVPATH
 
454
            environment.setdefault("DEVPATH", path)
 
455
 
 
456
            device = self.device_factory(environment, self.bits, list(stack))
 
457
            if not self._ignoreDevice(device):
 
458
                result.addDevice(device)
 
459
 
 
460
            stack.append(device)
 
461
 
 
462
 
 
463
def decode_id(id):
 
464
    encoded_id = id.encode("utf-8")
 
465
    decoded_id = encoded_id.decode("string-escape").decode("utf-8")
 
466
    return decoded_id.strip()