Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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.