Skip to content
This repository
Browse code

Add umockdump tool

This tool dumps Linux devices and their ancestors from sysfs/udev.

All attributes and properties are included, non-ASCII ones get printed in hex.
The dump is written to the standard output.

This format will be readable/loadable by UMockdevTestbed.
  • Loading branch information...
commit 6d1b93ba2180897182d57251152b10728f846b3b 1 parent d2f10a4
authored July 24, 2012
20  Makefile.am
@@ -32,6 +32,9 @@ EXTRA_DIST = COPYING
32 32
 DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
33 33
 TESTS_ENVIRONMENT = LD_LIBRARY_PATH=.libs:$$LD_LIBRARY_PATH $(top_srcdir)/src/umockdev-wrapper
34 34
 
  35
+# use this for running Python tests
  36
+PYTEST = env LD_LIBRARY_PATH=$(top_builddir)/.libs GI_TYPELIB_PATH=$(top_builddir)/src/ $(top_srcdir)/src/umockdev-wrapper $(PYTHON) -Wd -Werror::PendingDeprecationWarning -Werror::DeprecationWarning
  37
+
35 38
 AM_CFLAGS = -Werror -Wall
36 39
 
37 40
 # -------------------------------------------------------------
@@ -144,6 +147,20 @@ CLEANFILES += $(gir_DATA) $(typelib_DATA)
144 147
 endif
145 148
 
146 149
 # -------------------------------------------------------------
  150
+# tools
  151
+if HAVE_PYTHON
  152
+
  153
+bin_SCRIPTS += src/umockdump
  154
+EXTRA_DIST += src/umockdump
  155
+
  156
+# adjust shebang line to detected PYTHON
  157
+umockdump-install-hook:
  158
+	sed -i '1 s_[^/]\+$$_$(PYTHON)_' $(DESTDIR)$(bindir)/umockdump
  159
+
  160
+INSTALL_EXEC_HOOKS += umockdump-install-hook
  161
+endif
  162
+
  163
+# -------------------------------------------------------------
147 164
 # tests
148 165
 
149 166
 noinst_PROGRAMS += test-umockdev
@@ -170,7 +187,8 @@ if HAVE_INTROSPECTION
170 187
 if HAVE_PYTHON
171 188
 check-local: $(INTROSPECTION_GIRS)
172 189
 	@echo "  Running gobject-introspection test with $(PYTHON)"
173  
-	LD_LIBRARY_PATH=$(top_builddir)/.libs GI_TYPELIB_PATH=$(top_builddir)/src/ $(top_srcdir)/src/umockdev-wrapper $(PYTHON) -Wd -Werror::PendingDeprecationWarning -Werror::DeprecationWarning $(srcdir)/src/test-umockdev.py
  190
+	$(PYTEST) $(srcdir)/src/test-umockdev.py
  191
+	$(PYTEST) $(srcdir)/src/test-umockdump
174 192
 endif
175 193
 endif
176 194
 
9  configure.ac
@@ -75,5 +75,12 @@ AC_MSG_RESULT([
75 75
   CPPFLAGS:				${CPPFLAGS}
76 76
   CFLAGS:				${CFLAGS}
77 77
   LDFLAGS:				${LDFLAGS}
78  
-
79 78
 ])
  79
+
  80
+if test x$PYTHON != x; then
  81
+  AC_MSG_RESULT([  Python:				${PYTHON}
  82
+	        ])
  83
+else
  84
+  AC_MSG_RESULT([  WARNING! No Python interpreter found. Some tools and tests are not available.
  85
+		])
  86
+fi
127  src/test-umockdump
... ...
@@ -0,0 +1,127 @@
  1
+#!/usr/bin/python3
  2
+
  3
+'''umockdump tests'''
  4
+
  5
+__copyright__ = 'Copyright (C) 2012 Canonical Ltd.'
  6
+__author__ = 'Martin Pitt <martin.pitt@ubuntu.com>'
  7
+
  8
+# umockdev is free software; you can redistribute it and/or
  9
+# modify it under the terms of the GNU Lesser General Public
  10
+# License as published by the Free Software Foundation; either
  11
+# version 2.1 of the License, or (at your option) any later version.
  12
+#
  13
+# umockdev is distributed in the hope that it will be useful, but
  14
+# WITHOUT ANY WARRANTY; without even the implied warranty of
  15
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16
+# Lesser General Public License for more details.
  17
+#
  18
+# You should have received a copy of the GNU Lesser General Public License
  19
+# along with this program; If not, see <http://www.gnu.org/licenses/>.
  20
+
  21
+import os.path
  22
+import sys
  23
+import subprocess
  24
+import unittest
  25
+
  26
+from gi.repository import UMockdev
  27
+
  28
+umockdump_path = os.path.join(os.path.dirname(__file__), 'umockdump')
  29
+
  30
+def call(args):
  31
+    '''Call umockdump with given arguments.
  32
+
  33
+    Return (code, stdout, stderr) tuple.
  34
+    '''
  35
+    umockdump = subprocess.Popen([umockdump_path] + args,
  36
+                                 stdout=subprocess.PIPE,
  37
+                                 stderr=subprocess.PIPE,
  38
+                                 universal_newlines=True)
  39
+    (out, err) = umockdump.communicate()
  40
+    out = out.strip()
  41
+    err = err.strip()
  42
+    return (umockdump.returncode, out, err)
  43
+
  44
+class Testbed(unittest.TestCase):
  45
+    def setUp(self):
  46
+        self.testbed = UMockdev.Testbed()
  47
+
  48
+    def test_all_empty(self):
  49
+        '''--all on empty testbed'''
  50
+
  51
+        (code, out, err) = call(['--all'])
  52
+        self.assertEqual(err, '')
  53
+        self.assertEqual(out, '')
  54
+        self.assertEqual(code, 0)
  55
+
  56
+    def test_one(self):
  57
+        '''one device'''
  58
+
  59
+        syspath = self.testbed.add_device(
  60
+            'pci', 'dev1', None,
  61
+            ['simple_attr', '1', 'multiline_attr', 'a\\b\nc\\d\nlast'],
  62
+            ['SIMPLE_PROP', '1'])
  63
+        self.testbed.set_attribute_binary(syspath, 'binary_attr', b'\x41\xFF\x00\x05\xFF\x00')
  64
+
  65
+        (code, out, err) = call([syspath])
  66
+        self.assertEqual(err, '')
  67
+        self.assertEqual(code, 0)
  68
+        self.assertEqual(out, '''P: /devices/dev1
  69
+E: SIMPLE_PROP=1
  70
+E: SUBSYSTEM=pci
  71
+H: binary_attr=41FF0005FF00
  72
+A: multiline_attr=a\\\\b\\nc\\\\d\\nlast
  73
+A: simple_attr=1''')
  74
+
  75
+    def test_multiple(self):
  76
+        '''multiple devices'''
  77
+
  78
+        dev1 = self.testbed.add_device(
  79
+            'pci', 'dev1', None, ['dev1color', 'green'], ['DEV1COLOR', 'GREEN'])
  80
+        subdev1 = self.testbed.add_device(
  81
+            'pci', 'subdev1', dev1, ['subdev1color', 'yellow'],
  82
+            ['SUBDEV1COLOR', 'YELLOW'])
  83
+        dev2 = self.testbed.add_device(
  84
+            'usb', 'dev2', None, ['dev2color', 'brown'], ['DEV2COLOR', 'BROWN'])
  85
+
  86
+        # should grab device and all parents
  87
+        (code, out, err) = call([subdev1])
  88
+        self.assertEqual(err, '')
  89
+        self.assertEqual(code, 0)
  90
+        self.assertEqual(out, '''P: /devices/dev1/subdev1
  91
+E: SUBDEV1COLOR=YELLOW
  92
+E: SUBSYSTEM=pci
  93
+A: subdev1color=yellow
  94
+
  95
+P: /devices/dev1
  96
+E: DEV1COLOR=GREEN
  97
+E: SUBSYSTEM=pci
  98
+A: dev1color=green''')
  99
+
  100
+        (code, out, err) = call([dev1])
  101
+        self.assertEqual(err, '')
  102
+        self.assertEqual(code, 0)
  103
+        self.assertEqual(out, '''P: /devices/dev1
  104
+E: DEV1COLOR=GREEN
  105
+E: SUBSYSTEM=pci
  106
+A: dev1color=green''')
  107
+
  108
+        # with --all it should have all three
  109
+        (code, out, err) = call(['--all'])
  110
+        self.assertEqual(err, '')
  111
+        self.assertEqual(code, 0)
  112
+        self.assertTrue('P: /devices/dev1/subdev1\n' in out, out)
  113
+        self.assertTrue('P: /devices/dev1\n' in out, out)
  114
+        self.assertTrue('P: /devices/dev2\n' in out, out)
  115
+
  116
+
  117
+class System(unittest.TestCase):
  118
+    def test_all(self):
  119
+        '''umockdump --all has no errors and some output on system /sys'''
  120
+
  121
+        (code, out, err) = call(['--all'])
  122
+        self.assertEqual(err, '')
  123
+        self.assertEqual(code, 0)
  124
+        self.assertTrue(out.startswith('P:'), out[:100] + '[..]')
  125
+        self.assertGreater(len(out), 100, out[:100] + '[..]')
  126
+
  127
+unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
182  src/umockdump
... ...
@@ -0,0 +1,182 @@
  1
+#!/usr/bin/python3
  2
+
  3
+'''Dump Linux devices and their ancestors from sysfs/udev.
  4
+
  5
+All attributes and properties are included, non-ASCII ones get printed in hex.
  6
+The dump is written to the standard output.
  7
+'''
  8
+
  9
+__copyright__ = "Copyright (C) 2012 Canonical Ltd."
  10
+__author__ = "Martin Pitt <martin.pitt@ubuntu.com>"
  11
+
  12
+# umockdev is free software; you can redistribute it and/or
  13
+# modify it under the terms of the GNU Lesser General Public
  14
+# License as published by the Free Software Foundation; either
  15
+# version 2.1 of the License, or (at your option) any later version.
  16
+#
  17
+# umockdev is distributed in the hope that it will be useful, but
  18
+# WITHOUT ANY WARRANTY; without even the implied warranty of
  19
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  20
+# Lesser General Public License for more details.
  21
+#
  22
+# You should have received a copy of the GNU Lesser General Public License
  23
+# along with this program; If not, see <http://www.gnu.org/licenses/>.
  24
+
  25
+import argparse
  26
+import subprocess
  27
+import os
  28
+import stat
  29
+import sys
  30
+import errno
  31
+from gettext import gettext as _
  32
+
  33
+_py2 = sys.version_info.major < 3
  34
+
  35
+def fatal(msg):
  36
+    sys.stderr.write(msg)
  37
+    sys.stderr.write('\n')
  38
+    sys.exit(1)
  39
+
  40
+def all_devices():
  41
+    devs = []
  42
+    for base, dirs, files in os.walk('/sys/devices'):
  43
+        if 'uevent' in files:
  44
+            devs.append(base)
  45
+    return devs
  46
+
  47
+def parse_args():
  48
+    ap = argparse.ArgumentParser(
  49
+        description=_('Dump Linux devices and their ancestors from sysfs/udev.'))
  50
+    ap.add_argument('devices', metavar='DEVICE', nargs="*",
  51
+                    help=_('Path of a device in /dev or /sys.'))
  52
+    ap.add_argument('--all', '-a', action='store_true',
  53
+                    help=_('Dump all devices'))
  54
+
  55
+    args = ap.parse_args()
  56
+
  57
+    if args.all and args.devices:
  58
+        ap.error(_('Specifying a device list together with --all is invalid.'))
  59
+    if not args.all and not args.devices:
  60
+        ap.error(_('Need to specify at least one device or --all.'))
  61
+
  62
+    if args.all:
  63
+        args.devices = all_devices()
  64
+
  65
+    return args
  66
+
  67
+def resolve(dev):
  68
+    '''If dev is a block or character device, convert it to a sysfs path.'''
  69
+    try:
  70
+        st = os.stat(dev)
  71
+    except OSError as e:
  72
+        fatal(str(e))
  73
+
  74
+    # character device?
  75
+    if stat.S_ISCHR(st.st_mode):
  76
+        link = '/sys/dev/char/%i:%i' % (os.major(st.st_rdev), os.minor(st.st_rdev))
  77
+    elif stat.S_ISBLK(st.st_mode):
  78
+        link = '/sys/dev/block/%i:%i' % (os.major(st.st_rdev), os.minor(st.st_rdev))
  79
+    else:
  80
+        link = dev
  81
+
  82
+    if not os.path.exists(link):
  83
+        fatal('Cannot resolve device %s to a sysfs path, %s does not exist' % (dev, link))
  84
+
  85
+    dev = os.path.realpath(link)
  86
+    if not os.path.exists(os.path.join(dev, 'uevent')):
  87
+        fatal('Invalid device %s, has no uevent attribute' % dev)
  88
+
  89
+    return dev
  90
+
  91
+def format_attr(value):
  92
+    # first, try text
  93
+    try:
  94
+        text = value.decode('ASCII')
  95
+        # escape line breaks and backslashes
  96
+        if text.endswith('\n'):
  97
+            text = text[:-1]
  98
+        text = text.replace('\\', '\\\\')
  99
+        text = text.replace('\n', '\\n')
  100
+        return ('A', text)
  101
+    except UnicodeDecodeError:
  102
+        pass
  103
+
  104
+    # something binary, encode as hex
  105
+    text = ''
  106
+    if _py2:
  107
+        # Python 2 does not consider elements in byte strings as numbers
  108
+        for byte in value:
  109
+            text += '%02X' % ord(byte)
  110
+    else:
  111
+        for byte in value:
  112
+            text += '%02X' % byte
  113
+    return ('H', text)
  114
+
  115
+def dump_device(dev):
  116
+    '''Dump a single device'''
  117
+
  118
+    prop_blacklist = ['DEVPATH', 'UDEV_LOG', 'USEC_INITIALIZED']
  119
+    attr_blacklist = ['subsystem', 'firmware_node', 'driver', 'uevent']
  120
+
  121
+    # we start with udevadm dump of this device, which will include all udev
  122
+    # properties
  123
+    udevadm = subprocess.Popen(['udevadm', 'info', '--query=all', '--path', dev], 
  124
+                               stdout=subprocess.PIPE, universal_newlines=True)
  125
+    out = udevadm.communicate()[0]
  126
+    # filter out redundant/uninteresting properties
  127
+    for line in out.splitlines():
  128
+        if not line:
  129
+            continue
  130
+        for bl in prop_blacklist:
  131
+            if line.startswith('E: %s=' % bl):
  132
+                break
  133
+        else:
  134
+            sys.stdout.write(line)
  135
+            sys.stdout.write('\n')
  136
+
  137
+    # now append all attributes
  138
+    for attr_name in sorted(os.listdir(dev)):
  139
+        if attr_name in attr_blacklist:
  140
+            continue
  141
+        attr_path = os.path.join(dev, attr_name)
  142
+        # only look at files or symlinks
  143
+        if not os.path.isfile(attr_path):
  144
+            continue
  145
+        if os.path.islink(attr_path):
  146
+            sys.stdout.write('L: %s=%s\n' % (attr_name, os.readlink(attr_path)))
  147
+        else:
  148
+            try:
  149
+                with open(attr_path, 'rb') as f:
  150
+                    (cls, value) = format_attr(f.read())
  151
+            except IOError as e:
  152
+                # some attributes are EACCES, or "no such device", etc.
  153
+                continue
  154
+            sys.stdout.write('%s: %s=%s\n' % (cls, attr_name, value))
  155
+
  156
+    sys.stdout.write('\n')
  157
+
  158
+def parent(dev):
  159
+    '''Get a device's parent'''
  160
+
  161
+    dev = os.path.dirname(dev)
  162
+    if not dev.startswith('/sys'):
  163
+        return None
  164
+
  165
+    if os.path.exists(os.path.join(dev, 'uevent')):
  166
+        return dev
  167
+
  168
+    # we might have intermediate directories without uevent, so try the next
  169
+    # higher one
  170
+    return parent(dev)
  171
+
  172
+#
  173
+# main
  174
+#
  175
+
  176
+args = parse_args()
  177
+
  178
+for device in args.devices:
  179
+    while device:
  180
+        device = resolve(device)
  181
+        dump_device(device)
  182
+        device = parent(device)

0 notes on commit 6d1b93b

Please sign in to comment.
Something went wrong with that request. Please try again.