-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Martin Pitt
committed
Jul 24, 2012
1 parent
d2f10a4
commit 6d1b93b
Showing
4 changed files
with
336 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |