Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
Martin Pitt authored
Showing with 336 additions and 2 deletions.
  1. +19 −1 Makefile.am
  2. +8 −1 configure.ac
  3. +127 −0 src/test-umockdump
  4. +182 −0 src/umockdump
View
20 Makefile.am
@@ -32,6 +32,9 @@ EXTRA_DIST = COPYING
DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
TESTS_ENVIRONMENT = LD_LIBRARY_PATH=.libs:$$LD_LIBRARY_PATH $(top_srcdir)/src/umockdev-wrapper
+# use this for running Python tests
+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
+
AM_CFLAGS = -Werror -Wall
# -------------------------------------------------------------
@@ -144,6 +147,20 @@ CLEANFILES += $(gir_DATA) $(typelib_DATA)
endif
# -------------------------------------------------------------
+# tools
+if HAVE_PYTHON
+
+bin_SCRIPTS += src/umockdump
+EXTRA_DIST += src/umockdump
+
+# adjust shebang line to detected PYTHON
+umockdump-install-hook:
+ sed -i '1 s_[^/]\+$$_$(PYTHON)_' $(DESTDIR)$(bindir)/umockdump
+
+INSTALL_EXEC_HOOKS += umockdump-install-hook
+endif
+
+# -------------------------------------------------------------
# tests
noinst_PROGRAMS += test-umockdev
@@ -170,7 +187,8 @@ if HAVE_INTROSPECTION
if HAVE_PYTHON
check-local: $(INTROSPECTION_GIRS)
@echo " Running gobject-introspection test with $(PYTHON)"
- 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
+ $(PYTEST) $(srcdir)/src/test-umockdev.py
+ $(PYTEST) $(srcdir)/src/test-umockdump
endif
endif
View
9 configure.ac
@@ -75,5 +75,12 @@ AC_MSG_RESULT([
CPPFLAGS: ${CPPFLAGS}
CFLAGS: ${CFLAGS}
LDFLAGS: ${LDFLAGS}
-
])
+
+if test x$PYTHON != x; then
+ AC_MSG_RESULT([ Python: ${PYTHON}
+ ])
+else
+ AC_MSG_RESULT([ WARNING! No Python interpreter found. Some tools and tests are not available.
+ ])
+fi
View
127 src/test-umockdump
@@ -0,0 +1,127 @@
+#!/usr/bin/python3
+
+'''umockdump tests'''
+
+__copyright__ = 'Copyright (C) 2012 Canonical Ltd.'
+__author__ = 'Martin Pitt <martin.pitt@ubuntu.com>'
+
+# umockdev is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# umockdev 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+import os.path
+import sys
+import subprocess
+import unittest
+
+from gi.repository import UMockdev
+
+umockdump_path = os.path.join(os.path.dirname(__file__), 'umockdump')
+
+def call(args):
+ '''Call umockdump with given arguments.
+
+ Return (code, stdout, stderr) tuple.
+ '''
+ umockdump = subprocess.Popen([umockdump_path] + args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True)
+ (out, err) = umockdump.communicate()
+ out = out.strip()
+ err = err.strip()
+ return (umockdump.returncode, out, err)
+
+class Testbed(unittest.TestCase):
+ def setUp(self):
+ self.testbed = UMockdev.Testbed()
+
+ def test_all_empty(self):
+ '''--all on empty testbed'''
+
+ (code, out, err) = call(['--all'])
+ self.assertEqual(err, '')
+ self.assertEqual(out, '')
+ self.assertEqual(code, 0)
+
+ def test_one(self):
+ '''one device'''
+
+ syspath = self.testbed.add_device(
+ 'pci', 'dev1', None,
+ ['simple_attr', '1', 'multiline_attr', 'a\\b\nc\\d\nlast'],
+ ['SIMPLE_PROP', '1'])
+ self.testbed.set_attribute_binary(syspath, 'binary_attr', b'\x41\xFF\x00\x05\xFF\x00')
+
+ (code, out, err) = call([syspath])
+ self.assertEqual(err, '')
+ self.assertEqual(code, 0)
+ self.assertEqual(out, '''P: /devices/dev1
+E: SIMPLE_PROP=1
+E: SUBSYSTEM=pci
+H: binary_attr=41FF0005FF00
+A: multiline_attr=a\\\\b\\nc\\\\d\\nlast
+A: simple_attr=1''')
+
+ def test_multiple(self):
+ '''multiple devices'''
+
+ dev1 = self.testbed.add_device(
+ 'pci', 'dev1', None, ['dev1color', 'green'], ['DEV1COLOR', 'GREEN'])
+ subdev1 = self.testbed.add_device(
+ 'pci', 'subdev1', dev1, ['subdev1color', 'yellow'],
+ ['SUBDEV1COLOR', 'YELLOW'])
+ dev2 = self.testbed.add_device(
+ 'usb', 'dev2', None, ['dev2color', 'brown'], ['DEV2COLOR', 'BROWN'])
+
+ # should grab device and all parents
+ (code, out, err) = call([subdev1])
+ self.assertEqual(err, '')
+ self.assertEqual(code, 0)
+ self.assertEqual(out, '''P: /devices/dev1/subdev1
+E: SUBDEV1COLOR=YELLOW
+E: SUBSYSTEM=pci
+A: subdev1color=yellow
+
+P: /devices/dev1
+E: DEV1COLOR=GREEN
+E: SUBSYSTEM=pci
+A: dev1color=green''')
+
+ (code, out, err) = call([dev1])
+ self.assertEqual(err, '')
+ self.assertEqual(code, 0)
+ self.assertEqual(out, '''P: /devices/dev1
+E: DEV1COLOR=GREEN
+E: SUBSYSTEM=pci
+A: dev1color=green''')
+
+ # with --all it should have all three
+ (code, out, err) = call(['--all'])
+ self.assertEqual(err, '')
+ self.assertEqual(code, 0)
+ self.assertTrue('P: /devices/dev1/subdev1\n' in out, out)
+ self.assertTrue('P: /devices/dev1\n' in out, out)
+ self.assertTrue('P: /devices/dev2\n' in out, out)
+
+
+class System(unittest.TestCase):
+ def test_all(self):
+ '''umockdump --all has no errors and some output on system /sys'''
+
+ (code, out, err) = call(['--all'])
+ self.assertEqual(err, '')
+ self.assertEqual(code, 0)
+ self.assertTrue(out.startswith('P:'), out[:100] + '[..]')
+ self.assertGreater(len(out), 100, out[:100] + '[..]')
+
+unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
View
182 src/umockdump
@@ -0,0 +1,182 @@
+#!/usr/bin/python3
+
+'''Dump 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.
+'''
+
+__copyright__ = "Copyright (C) 2012 Canonical Ltd."
+__author__ = "Martin Pitt <martin.pitt@ubuntu.com>"
+
+# umockdev is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# umockdev 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import subprocess
+import os
+import stat
+import sys
+import errno
+from gettext import gettext as _
+
+_py2 = sys.version_info.major < 3
+
+def fatal(msg):
+ sys.stderr.write(msg)
+ sys.stderr.write('\n')
+ sys.exit(1)
+
+def all_devices():
+ devs = []
+ for base, dirs, files in os.walk('/sys/devices'):
+ if 'uevent' in files:
+ devs.append(base)
+ return devs
+
+def parse_args():
+ ap = argparse.ArgumentParser(
+ description=_('Dump Linux devices and their ancestors from sysfs/udev.'))
+ ap.add_argument('devices', metavar='DEVICE', nargs="*",
+ help=_('Path of a device in /dev or /sys.'))
+ ap.add_argument('--all', '-a', action='store_true',
+ help=_('Dump all devices'))
+
+ args = ap.parse_args()
+
+ if args.all and args.devices:
+ ap.error(_('Specifying a device list together with --all is invalid.'))
+ if not args.all and not args.devices:
+ ap.error(_('Need to specify at least one device or --all.'))
+
+ if args.all:
+ args.devices = all_devices()
+
+ return args
+
+def resolve(dev):
+ '''If dev is a block or character device, convert it to a sysfs path.'''
+ try:
+ st = os.stat(dev)
+ except OSError as e:
+ fatal(str(e))
+
+ # character device?
+ if stat.S_ISCHR(st.st_mode):
+ link = '/sys/dev/char/%i:%i' % (os.major(st.st_rdev), os.minor(st.st_rdev))
+ elif stat.S_ISBLK(st.st_mode):
+ link = '/sys/dev/block/%i:%i' % (os.major(st.st_rdev), os.minor(st.st_rdev))
+ else:
+ link = dev
+
+ if not os.path.exists(link):
+ fatal('Cannot resolve device %s to a sysfs path, %s does not exist' % (dev, link))
+
+ dev = os.path.realpath(link)
+ if not os.path.exists(os.path.join(dev, 'uevent')):
+ fatal('Invalid device %s, has no uevent attribute' % dev)
+
+ return dev
+
+def format_attr(value):
+ # first, try text
+ try:
+ text = value.decode('ASCII')
+ # escape line breaks and backslashes
+ if text.endswith('\n'):
+ text = text[:-1]
+ text = text.replace('\\', '\\\\')
+ text = text.replace('\n', '\\n')
+ return ('A', text)
+ except UnicodeDecodeError:
+ pass
+
+ # something binary, encode as hex
+ text = ''
+ if _py2:
+ # Python 2 does not consider elements in byte strings as numbers
+ for byte in value:
+ text += '%02X' % ord(byte)
+ else:
+ for byte in value:
+ text += '%02X' % byte
+ return ('H', text)
+
+def dump_device(dev):
+ '''Dump a single device'''
+
+ prop_blacklist = ['DEVPATH', 'UDEV_LOG', 'USEC_INITIALIZED']
+ attr_blacklist = ['subsystem', 'firmware_node', 'driver', 'uevent']
+
+ # we start with udevadm dump of this device, which will include all udev
+ # properties
+ udevadm = subprocess.Popen(['udevadm', 'info', '--query=all', '--path', dev],
+ stdout=subprocess.PIPE, universal_newlines=True)
+ out = udevadm.communicate()[0]
+ # filter out redundant/uninteresting properties
+ for line in out.splitlines():
+ if not line:
+ continue
+ for bl in prop_blacklist:
+ if line.startswith('E: %s=' % bl):
+ break
+ else:
+ sys.stdout.write(line)
+ sys.stdout.write('\n')
+
+ # now append all attributes
+ for attr_name in sorted(os.listdir(dev)):
+ if attr_name in attr_blacklist:
+ continue
+ attr_path = os.path.join(dev, attr_name)
+ # only look at files or symlinks
+ if not os.path.isfile(attr_path):
+ continue
+ if os.path.islink(attr_path):
+ sys.stdout.write('L: %s=%s\n' % (attr_name, os.readlink(attr_path)))
+ else:
+ try:
+ with open(attr_path, 'rb') as f:
+ (cls, value) = format_attr(f.read())
+ except IOError as e:
+ # some attributes are EACCES, or "no such device", etc.
+ continue
+ sys.stdout.write('%s: %s=%s\n' % (cls, attr_name, value))
+
+ sys.stdout.write('\n')
+
+def parent(dev):
+ '''Get a device's parent'''
+
+ dev = os.path.dirname(dev)
+ if not dev.startswith('/sys'):
+ return None
+
+ if os.path.exists(os.path.join(dev, 'uevent')):
+ return dev
+
+ # we might have intermediate directories without uevent, so try the next
+ # higher one
+ return parent(dev)
+
+#
+# main
+#
+
+args = parse_args()
+
+for device in args.devices:
+ while device:
+ device = resolve(device)
+ dump_device(device)
+ device = parent(device)
Please sign in to comment.
Something went wrong with that request. Please try again.