Merge ~sylvain-pineau/plainbox-provider-snappy:add_lsusb.py into plainbox-provider-snappy:master
- Git
- lp:~sylvain-pineau/plainbox-provider-snappy
- add_lsusb.py
- Merge into master
Proposed by
Sylvain Pineau
Status: | Merged |
---|---|
Merged at revision: | 770c38c897ce059455f01f971dd901427bbc7b63 |
Proposed branch: | ~sylvain-pineau/plainbox-provider-snappy:add_lsusb.py |
Merge into: | plainbox-provider-snappy:master |
Diff against target: |
557 lines (+539/-1) 2 files modified
plainbox-provider-snappy/bin/lsusb.py (+538/-0) plainbox-provider-snappy/units/usb.pxu (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Checkbox Developers | Pending | ||
Review via email: mp+300458@code.launchpad.net |
Commit message
Description of the change
Replace the lsusb command by the lsusb.py script to have a working lsusb_attachment job.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/plainbox-provider-snappy/bin/lsusb.py b/plainbox-provider-snappy/bin/lsusb.py |
2 | new file mode 100755 |
3 | index 0000000..22239d8 |
4 | --- /dev/null |
5 | +++ b/plainbox-provider-snappy/bin/lsusb.py |
6 | @@ -0,0 +1,538 @@ |
7 | +#!/usr/bin/env python3 |
8 | +# lsusb.py |
9 | +# Displays your USB devices in reasonable form. |
10 | +# (c) Kurt Garloff <garloff@suse.de>, 2/2009, GPL v2 or v3. |
11 | +# |
12 | +# Copyright 2016 Canonical Ltd. |
13 | +# Sylvain Pineau <sylvain.pineau@canonical.com> |
14 | +# |
15 | +# Usage: See usage() |
16 | + |
17 | +from functools import total_ordering |
18 | +import getopt |
19 | +import os |
20 | +import re |
21 | +import sys |
22 | + |
23 | +# Global options |
24 | +showint = False |
25 | +showhubint = False |
26 | +noemptyhub = False |
27 | +nohub = False |
28 | +warnsort = False |
29 | + |
30 | +prefix = "/sys/bus/usb/devices/" |
31 | +usbids = "/usr/share/usb.ids" |
32 | + |
33 | +esc = chr(27) |
34 | +norm = esc + "[0;0m" |
35 | +bold = esc + "[0;1m" |
36 | +red = esc + "[0;31m" |
37 | +green = esc + "[0;32m" |
38 | +amber = esc + "[0;33m" |
39 | + |
40 | +cols = ("", "", "", "", "") |
41 | + |
42 | +usbvendors = [] |
43 | +usbproducts = [] |
44 | +usbclasses = [] |
45 | + |
46 | +devlst = ( |
47 | + 'host', # usb-storage |
48 | + 'video4linux/video', # uvcvideo et al. |
49 | + 'sound/card', # snd-usb-audio |
50 | + 'net/', # cdc_ether, ... |
51 | + 'input/input', # usbhid |
52 | + 'usb:hiddev', # usb hid |
53 | + 'bluetooth/hci', # btusb |
54 | + 'ttyUSB', # btusb |
55 | + 'tty/', # cdc_acm |
56 | + 'usb:lp', # usblp |
57 | + 'usb/', # hiddev, usblp |
58 | + ) |
59 | + |
60 | + |
61 | +def readattr(path, name): |
62 | + "Read attribute from sysfs and return as string" |
63 | + f = open(prefix + path + "/" + name) |
64 | + return f.readline().rstrip("\n") |
65 | + |
66 | + |
67 | +def readlink(path, name): |
68 | + "Read symlink and return basename" |
69 | + return os.path.basename(os.readlink(prefix + path + "/" + name)) |
70 | + |
71 | + |
72 | +@total_ordering |
73 | +class UsbClass: |
74 | + "Container for USB Class/Subclass/Protocol" |
75 | + |
76 | + def __init__(self, cl, sc, pr, str=""): |
77 | + self.pclass = cl |
78 | + self.subclass = sc |
79 | + self.proto = pr |
80 | + self.desc = str |
81 | + |
82 | + def __repr__(self): |
83 | + return self.desc |
84 | + |
85 | + def __lt__(self, oth): |
86 | + if self.pclass != oth.pclass: |
87 | + return self.pclass - oth.pclass |
88 | + if self.subclass != oth.subclass: |
89 | + return self.subclass - oth.subclass |
90 | + return self.proto - oth.proto |
91 | + |
92 | + def __eq__(self, oth): |
93 | + if self.pclass != oth.pclass: |
94 | + return False |
95 | + if self.subclass != oth.subclass: |
96 | + return False |
97 | + return self.proto == oth.proto |
98 | + |
99 | + |
100 | +@total_ordering |
101 | +class UsbVendor: |
102 | + "Container for USB Vendors" |
103 | + |
104 | + def __init__(self, vid, vname=""): |
105 | + self.vid = vid |
106 | + self.vname = vname |
107 | + |
108 | + def __repr__(self): |
109 | + return self.vname |
110 | + |
111 | + def __lt__(self, oth): |
112 | + return self.vid - oth.vid |
113 | + |
114 | + def __eq__(self, oth): |
115 | + return self.vid == oth.vid |
116 | + |
117 | + |
118 | +@total_ordering |
119 | +class UsbProduct: |
120 | + "Container for USB VID:PID devices" |
121 | + |
122 | + def __init__(self, vid, pid, pname=""): |
123 | + self.vid = vid |
124 | + self.pid = pid |
125 | + self.pname = pname |
126 | + |
127 | + def __repr__(self): |
128 | + return self.pname |
129 | + |
130 | + def __lt__(self, oth): |
131 | + if self.vid != oth.vid: |
132 | + return self.vid - oth.vid |
133 | + return self.pid - oth.pid |
134 | + |
135 | + def __eq__(self, oth): |
136 | + if self.vid != oth.vid: |
137 | + return False |
138 | + return self.pid == oth.pid |
139 | + |
140 | + |
141 | +def ishexdigit(str): |
142 | + "return True if all digits are valid hex digits" |
143 | + for dg in str: |
144 | + if not dg.isdigit() and dg not in 'abcdef': |
145 | + return False |
146 | + return True |
147 | + |
148 | + |
149 | +def parse_usb_ids(): |
150 | + "Parse /usr/share/usb.ids and fill usbvendors, usbproducts, usbclasses" |
151 | + id = 0 |
152 | + sid = 0 |
153 | + mode = 0 |
154 | + strg = "" |
155 | + cstrg = "" |
156 | + with open(usbids, encoding="utf-8", errors='ignore') as f: |
157 | + for ln in f: |
158 | + if ln[0] == '#': |
159 | + continue |
160 | + ln = ln.rstrip('\n') |
161 | + if len(ln) == 0: |
162 | + continue |
163 | + if ishexdigit(ln[0:4]): |
164 | + mode = 0 |
165 | + id = int(ln[:4], 16) |
166 | + usbvendors.append(UsbVendor(id, ln[6:])) |
167 | + continue |
168 | + if ln[0] == '\t' and ishexdigit(ln[1:3]): |
169 | + sid = int(ln[1:5], 16) |
170 | + # USB devices |
171 | + if mode == 0: |
172 | + usbproducts.append(UsbProduct(id, sid, ln[7:])) |
173 | + continue |
174 | + elif mode == 1: |
175 | + nm = ln[5:] |
176 | + if nm != "Unused": |
177 | + strg = cstrg + ":" + nm |
178 | + else: |
179 | + strg = cstrg + ":" |
180 | + usbclasses.append(UsbClass(id, sid, -1, strg)) |
181 | + continue |
182 | + if ln[0] == 'C': |
183 | + mode = 1 |
184 | + id = int(ln[2:4], 16) |
185 | + cstrg = ln[6:] |
186 | + usbclasses.append(UsbClass(id, -1, -1, cstrg)) |
187 | + continue |
188 | + if ( |
189 | + mode == 1 and ln[0] == '\t' and ln[1] == '\t' and |
190 | + ishexdigit(ln[2:4]) |
191 | + ): |
192 | + prid = int(ln[2:4], 16) |
193 | + usbclasses.append(UsbClass(id, sid, prid, strg + ":" + ln[6:])) |
194 | + continue |
195 | + mode = 2 |
196 | + |
197 | + |
198 | +def find_usb_prod(vid, pid): |
199 | + "Return device name from USB Vendor:Product list" |
200 | + strg = "" |
201 | + dev = UsbVendor(vid, "") |
202 | + try: |
203 | + strg = [v for v in usbvendors if v == dev][0].__repr__() |
204 | + except IndexError: |
205 | + return "" |
206 | + dev = UsbProduct(vid, pid, "") |
207 | + try: |
208 | + strg += " " + [p for p in usbproducts if p == dev][0].__repr__() |
209 | + except IndexError: |
210 | + return strg |
211 | + return strg |
212 | + |
213 | + |
214 | +def find_usb_class(cid, sid, pid): |
215 | + "Return USB protocol from usbclasses list" |
216 | + if cid == 0xff and sid == 0xff and pid == 0xff: |
217 | + return "Vendor Specific" |
218 | + dev = UsbClass(cid, sid, pid, "") |
219 | + try: |
220 | + return [c for c in usbclasses if c == dev][0].__repr__() |
221 | + except IndexError: |
222 | + pass |
223 | + dev = UsbClass(cid, sid, -1, "") |
224 | + try: |
225 | + return [c for c in usbclasses if c == dev][0].__repr__() |
226 | + except IndexError: |
227 | + pass |
228 | + dev = UsbClass(cid, -1, -1, "") |
229 | + try: |
230 | + return [c for c in usbclasses if c == dev][0].__repr__() |
231 | + except IndexError: |
232 | + return "" |
233 | + |
234 | + |
235 | +def find_storage(hostno): |
236 | + "Return SCSI block dev names for host" |
237 | + res = "" |
238 | + for ent in os.listdir("/sys/class/scsi_device/"): |
239 | + (host, bus, tgt, lun) = ent.split(":") |
240 | + if host == hostno: |
241 | + try: |
242 | + path = "/sys/class/scsi_device/%s/device/block" % ent |
243 | + for ent2 in os.listdir(path): |
244 | + res += ent2 + " " |
245 | + except: |
246 | + pass |
247 | + return res |
248 | + |
249 | + |
250 | +def find_dev(driver, usbname): |
251 | + "Return pseudo devname that's driven by driver" |
252 | + res = "" |
253 | + for nm in devlst: |
254 | + dir = prefix + usbname |
255 | + prep = "" |
256 | + idx = nm.find('/') |
257 | + if idx != -1: |
258 | + prep = nm[:idx+1] |
259 | + dir += "/" + nm[:idx] |
260 | + nm = nm[idx+1:] |
261 | + ln = len(nm) |
262 | + try: |
263 | + for ent in os.listdir(dir): |
264 | + if ent[:ln] == nm: |
265 | + res += prep+ent+" " |
266 | + if nm == "host": |
267 | + res += "(" + find_storage(ent[ln:])[:-1] + ")" |
268 | + except: |
269 | + pass |
270 | + return res |
271 | + |
272 | + |
273 | +class UsbInterface: |
274 | + "Container for USB interface info" |
275 | + |
276 | + def __init__(self, parent=None, level=1): |
277 | + self.parent = parent |
278 | + self.level = level |
279 | + self.fname = "" |
280 | + self.iclass = 0 |
281 | + self.isclass = 0 |
282 | + self.iproto = 0 |
283 | + self.noep = 0 |
284 | + self.driver = "" |
285 | + self.devname = "" |
286 | + self.protoname = "" |
287 | + |
288 | + def read(self, fname): |
289 | + fullpath = "" |
290 | + if self.parent: |
291 | + fullpath += self.parent.fname + "/" |
292 | + fullpath += fname |
293 | + self.fname = fname |
294 | + self.iclass = int(readattr(fullpath, "bInterfaceClass"), 16) |
295 | + self.isclass = int(readattr(fullpath, "bInterfaceSubClass"), 16) |
296 | + self.iproto = int(readattr(fullpath, "bInterfaceProtocol"), 16) |
297 | + self.noep = int(readattr(fullpath, "bNumEndpoints")) |
298 | + try: |
299 | + self.driver = readlink(fname, "driver") |
300 | + self.devname = find_dev(self.driver, fname) |
301 | + except: |
302 | + pass |
303 | + self.protoname = find_usb_class(self.iclass, self.isclass, self.iproto) |
304 | + |
305 | + def __str__(self): |
306 | + return "%-16s(IF) %02x:%02x:%02x %iEPs (%s) %s%s %s%s%s\n" % \ |
307 | + (" " * self.level+self.fname, self.iclass, |
308 | + self.isclass, self.iproto, self.noep, |
309 | + self.protoname, |
310 | + cols[3], self.driver, |
311 | + cols[4], self.devname, cols[0]) |
312 | + |
313 | + |
314 | +class UsbDevice: |
315 | + "Container for USB device info" |
316 | + |
317 | + def __init__(self, parent=None, level=0): |
318 | + self.parent = parent |
319 | + self.level = level |
320 | + self.display_name = "" |
321 | + self.fname = "" |
322 | + self.busnum = 0 |
323 | + self.devnum = 0 |
324 | + self.iclass = 0 |
325 | + self.isclass = 0 |
326 | + self.iproto = 0 |
327 | + self.vid = 0 |
328 | + self.pid = 0 |
329 | + self.name = "" |
330 | + self.usbver = "" |
331 | + self.speed = "" |
332 | + self.maxpower = "" |
333 | + self.noports = 0 |
334 | + self.nointerfaces = 0 |
335 | + self.driver = "" |
336 | + self.devname = "" |
337 | + self.interfaces = [] |
338 | + self.children = [] |
339 | + |
340 | + def read(self, fname): |
341 | + self.fname = fname |
342 | + self.iclass = int(readattr(fname, "bDeviceClass"), 16) |
343 | + self.isclass = int(readattr(fname, "bDeviceSubClass"), 16) |
344 | + self.iproto = int(readattr(fname, "bDeviceProtocol"), 16) |
345 | + self.vid = int(readattr(fname, "idVendor"), 16) |
346 | + self.pid = int(readattr(fname, "idProduct"), 16) |
347 | + self.busnum = int(readattr(fname, "busnum")) |
348 | + self.devnum = int(readattr(fname, "devnum")) |
349 | + self.usbver = readattr(fname, "version") |
350 | + try: |
351 | + self.name = readattr(fname, "manufacturer") + " " \ |
352 | + + readattr(fname, "product") |
353 | + if self.name[:5] == "Linux": |
354 | + rx = re.compile(r"Linux [^ ]* .hci[-_]hcd") |
355 | + mch = rx.match(self.name) |
356 | + if mch: |
357 | + self.name = "Linux Foundation %.2f root hub" % float( |
358 | + self.usbver) |
359 | + except: |
360 | + pass |
361 | + if not self.name: |
362 | + self.name = find_usb_prod(self.vid, self.pid) |
363 | + # Some USB Card readers have a better name than Generic ... |
364 | + if self.name[:7] == "Generic": |
365 | + oldnm = self.name |
366 | + self.name = find_usb_prod(self.vid, self.pid) |
367 | + if not self.name: |
368 | + self.name = oldnm |
369 | + self.speed = readattr(fname, "speed") |
370 | + self.maxpower = readattr(fname, "bMaxPower") |
371 | + self.noports = int(readattr(fname, "maxchild")) |
372 | + try: |
373 | + self.nointerfaces = int(readattr(fname, "bNumInterfaces")) |
374 | + except: |
375 | + self.nointerfaces = 0 |
376 | + try: |
377 | + self.driver = readlink(fname, "driver") |
378 | + self.devname = find_dev(self.driver, fname) |
379 | + except: |
380 | + pass |
381 | + |
382 | + def readchildren(self): |
383 | + if self.fname[0:3] == "usb": |
384 | + fname = self.fname[3:] |
385 | + else: |
386 | + fname = self.fname |
387 | + for dirent in os.listdir(prefix + self.fname): |
388 | + if not dirent[0:1].isdigit(): |
389 | + continue |
390 | + if os.access(prefix + dirent + "/bInterfaceClass", os.R_OK): |
391 | + iface = UsbInterface(self, self.level+1) |
392 | + iface.read(dirent) |
393 | + self.interfaces.append(iface) |
394 | + else: |
395 | + usbdev = UsbDevice(self, self.level+1) |
396 | + usbdev.read(dirent) |
397 | + usbdev.readchildren() |
398 | + self.children.append(usbdev) |
399 | + |
400 | + def __str__(self): |
401 | + if self.iclass == 9: |
402 | + col = cols[2] |
403 | + if noemptyhub and len(self.children) == 0: |
404 | + return "" |
405 | + if nohub: |
406 | + str = "" |
407 | + else: |
408 | + col = cols[1] |
409 | + if not nohub or self.iclass != 9: |
410 | + str = "Bus %03d Device %03d: ID %04x:%04x %s" % \ |
411 | + (self.busnum, self.devnum, self.vid, self.pid, self.name) |
412 | + str += "\n" |
413 | + if showint: |
414 | + for iface in self.interfaces: |
415 | + str += iface.__str__() |
416 | + for child in self.children: |
417 | + str += child.__str__() |
418 | + return str |
419 | + |
420 | + |
421 | +def deepcopy(lst): |
422 | + "Returns a deep copy from the list lst" |
423 | + copy = [] |
424 | + for item in lst: |
425 | + copy.append(item) |
426 | + return copy |
427 | + |
428 | + |
429 | +def display_diff(lst1, lst2, fmtstr, args): |
430 | + "Compare lists (same length!) and display differences" |
431 | + for idx in range(0, len(lst1)): |
432 | + if lst1[idx] != lst2[idx]: |
433 | + print("Warning: " + fmtstr % args(lst2[idx])) |
434 | + |
435 | + |
436 | +def fix_usbvend(): |
437 | + "Sort USB vendor list and (optionally) display diffs" |
438 | + if warnsort: |
439 | + oldusbvend = deepcopy(usbvendors) |
440 | + usbvendors.sort() |
441 | + if warnsort: |
442 | + display_diff(usbvendors, oldusbvend, "Unsorted Vendor ID %04x", |
443 | + lambda x: (x.vid,)) |
444 | + |
445 | + |
446 | +def fix_usbprod(): |
447 | + "Sort USB products list" |
448 | + if warnsort: |
449 | + oldusbprod = deepcopy(usbproducts) |
450 | + usbproducts.sort() |
451 | + if warnsort: |
452 | + display_diff(usbproducts, oldusbprod, |
453 | + "Unsorted Vendor:Product ID %04x:%04x", |
454 | + lambda x: (x.vid, x.pid)) |
455 | + |
456 | + |
457 | +def fix_usbclass(): |
458 | + "Sort USB class list" |
459 | + if warnsort: |
460 | + oldusbcls = deepcopy(usbclasses) |
461 | + usbclasses.sort() |
462 | + if warnsort: |
463 | + display_diff(usbclasses, oldusbcls, |
464 | + "Unsorted USB class %02x:%02x:%02x", |
465 | + lambda x: (x.pclass, x.subclass, x.proto)) |
466 | + |
467 | + |
468 | +def usage(): |
469 | + "Displays usage information" |
470 | + print("Usage: lsusb.py [options]") |
471 | + print("Options:") |
472 | + print(" -h display this help") |
473 | + print(" -i display interface information") |
474 | + print(" -I display interface information, even for hubs") |
475 | + print(" -u suppress empty hubs") |
476 | + print(" -U suppress all hubs") |
477 | + print(" -c use colors") |
478 | + print(" -w display warning if usb.ids is not sorted correctly") |
479 | + print(" -f FILE override filename for /usr/share/usb.ids") |
480 | + return 2 |
481 | + |
482 | + |
483 | +def read_usb(): |
484 | + "Read toplevel USB entries and print" |
485 | + for dirent in os.listdir(prefix): |
486 | + if not dirent[0:3] == "usb": |
487 | + continue |
488 | + usbdev = UsbDevice(None, 0) |
489 | + usbdev.read(dirent) |
490 | + usbdev.readchildren() |
491 | + print(usbdev.__str__(), end="") |
492 | + |
493 | + |
494 | +def main(argv): |
495 | + "main entry point" |
496 | + global showint, showhubint, noemptyhub, nohub, warnsort, cols, usbids |
497 | + try: |
498 | + (optlist, args) = getopt.gnu_getopt(argv[1:], "hiIuUwcf:", ("help",)) |
499 | + except getopt.GetoptError as exc: |
500 | + print("Error:", exc) |
501 | + sys.exit(usage()) |
502 | + for opt in optlist: |
503 | + if opt[0] == "-h" or opt[0] == "--help": |
504 | + usage() |
505 | + sys.exit(0) |
506 | + if opt[0] == "-i": |
507 | + showint = True |
508 | + continue |
509 | + if opt[0] == "-I": |
510 | + showint = True |
511 | + showhubint = True |
512 | + continue |
513 | + if opt[0] == "-u": |
514 | + noemptyhub = True |
515 | + continue |
516 | + if opt[0] == "-U": |
517 | + noemptyhub = True |
518 | + nohub = True |
519 | + continue |
520 | + if opt[0] == "-c": |
521 | + cols = (norm, bold, red, green, amber) |
522 | + continue |
523 | + if opt[0] == "-w": |
524 | + warnsort = True |
525 | + continue |
526 | + if opt[0] == "-f": |
527 | + usbids = opt[1] |
528 | + continue |
529 | + if len(args) > 0: |
530 | + print("Error: excess args %s ..." % args[0]) |
531 | + sys.exit(usage()) |
532 | + try: |
533 | + parse_usb_ids() |
534 | + fix_usbvend() |
535 | + fix_usbprod() |
536 | + fix_usbclass() |
537 | + except: |
538 | + print(" WARNING: Failure to read usb.ids", file=sys.stderr) |
539 | + print(sys.exc_info(), file=sys.stderr) |
540 | + read_usb() |
541 | + |
542 | +# Entry point |
543 | +if __name__ == "__main__": |
544 | + main(sys.argv) |
545 | diff --git a/plainbox-provider-snappy/units/usb.pxu b/plainbox-provider-snappy/units/usb.pxu |
546 | index fedae4c..c948dd8 100644 |
547 | --- a/plainbox-provider-snappy/units/usb.pxu |
548 | +++ b/plainbox-provider-snappy/units/usb.pxu |
549 | @@ -116,7 +116,7 @@ id: lsusb_attachment |
550 | plugin: attachment |
551 | category_id: 2013.com.canonical.plainbox::info |
552 | user: root |
553 | -command: lsusb -vv | iconv -t 'utf-8' -c |
554 | +command: lsusb.py -f $SNAP/var/lib/usbutils/usb.ids | iconv -t 'utf-8' -c |
555 | estimated_duration: 0.700 |
556 | _summary: Attach output of lsusb |
557 | _description: Attaches a list of detected USB devices. |