Merge ~sylvain-pineau/plainbox-provider-snappy:add_lsusb.py into plainbox-provider-snappy: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)
Reviewer Review Type Date Requested Status
Checkbox Developers Pending
Review via email: mp+300458@code.launchpad.net

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
1diff --git a/plainbox-provider-snappy/bin/lsusb.py b/plainbox-provider-snappy/bin/lsusb.py
2new file mode 100755
3index 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)
545diff --git a/plainbox-provider-snappy/units/usb.pxu b/plainbox-provider-snappy/units/usb.pxu
546index 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.

Subscribers

People subscribed via source and target branches