Skip to content

Commit

Permalink
Formally remove children when the parent device is removed
Browse files Browse the repository at this point in the history
Although the directory structure for children was being removed when
the parent device was removed, `remove_device()` was not actually being
called on for the children. This meant that (for example) uevents were
not being generated for children when the parent was removed which is
different behavior than on actual hardware.
  • Loading branch information
bobhenz-jabil authored and martinpitt committed Aug 2, 2024
1 parent 48fd5c4 commit 659df15
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 1 deletion.
46 changes: 45 additions & 1 deletion src/umockdev.vala
Original file line number Diff line number Diff line change
Expand Up @@ -637,13 +637,57 @@ public class Testbed: GLib.Object {
public void remove_device (string syspath)
{
string real_path = Path.build_filename(this.root_dir, syspath);
string devname = Path.get_basename(syspath);

if (!FileUtils.test(real_path, FileTest.IS_DIR)) {
critical("umockdev_testbed_remove_device(): device %s does not exist", syspath);
return;
}

string path = Path.build_filename(real_path, "uevent");
if (!FileUtils.test(path, FileTest.IS_REGULAR)) {
critical("umockdev_testbed_remove_device(): device %s does not appear to be a device", syspath);
return;
}

remove_with_children(syspath);
}

private void remove_with_children (string syspath)
{
string real_path = Path.build_filename(this.root_dir, syspath);

try {
Dir dir = Dir.open(real_path);
string? name = null;
while (( name = dir.read_name( )) != null) {
string path = Path.build_filename(real_path, name);
// Skip over symlinks
if (FileUtils.test(path, FileTest.IS_SYMLINK)) {
continue;
}

// Recurse into the directory and remove any children therein.
if (FileUtils.test(path, FileTest.IS_DIR)) {
string child_syspath = Path.build_filename(syspath, name);
remove_with_children(child_syspath);
}
}
} catch (FileError e) {
critical("umockdev_testbed_remove_device(): cannot determine children of %s: %s",
syspath, e.message);
}

// See if this syspath actually corresponds to a device by seeing if
// there is a "uevent" file in the directory. If not, then there is
// no device to remove so the work is finished.
string path = Path.build_filename(real_path, "uevent");
if (!FileUtils.test(path, FileTest.IS_REGULAR)) {
return;
}

// This syspath corresponds to a device, so remove it.

string devname = Path.get_basename(syspath);
string subsystem;

try {
Expand Down
53 changes: 53 additions & 0 deletions tests/test-umockdev.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,59 @@ def on_uevent(client, action, device, counters):
mainloop.run()
self.assertEqual(counter, [0, 1, 0, syspath, None, '1'])

def test_remove_usb_parent_device(self):
'''testbed removing a USB parent device removes children'''

counter = {}

def on_uevent(client, action, device, counters):
key = device.get_sysfs_path()
counters.setdefault(key, [0, 0])
if action == 'add':
counters[key][0] += 1
elif action == 'remove':
counters[key][1] += 1

# set up listener for uevent signal
client = GUdev.Client.new(['usb'])
client.connect('uevent', on_uevent, counter)
mainloop = GLib.MainLoop()

parent_syspath = self.testbed.add_device('usb', 'myparentdev', None, ['idVendor', '0815'], ['ID_INPUT', '1'])
self.assertNotEqual(parent_syspath, None)

child1_syspath = self.testbed.add_device('usb', 'child1', parent_syspath, ['idVendor', '0815'], ['ID_INPUT', '1'])
self.assertNotEqual(child1_syspath, None)

child2_syspath = self.testbed.add_device('usb', 'child2', parent_syspath, ['idVendor', '0815'], ['ID_INPUT', '1'])
self.assertNotEqual(child2_syspath, None)

# Run the main loop for 0.5 seconds to catch the "add" uevent, which
# should be automatically generated by the call to `add_device()`.
GLib.timeout_add(500, mainloop.quit)
mainloop.run()
self.assertIn(parent_syspath, counter)
self.assertIn(child1_syspath, counter)
self.assertIn(child2_syspath, counter)
self.assertEqual(counter[parent_syspath], [1, 0])
self.assertEqual(counter[child1_syspath], [1, 0])
self.assertEqual(counter[child2_syspath], [1, 0])

# Just remove the parent, the children should also be removed.
self.testbed.remove_device(parent_syspath)

# Run the main loop for 0.5 seconds to catch the "remove" uevent, which
# should be automatically generated by the call to `remove_device()`.
GLib.timeout_add(500, mainloop.quit)
mainloop.run()
self.assertEqual(counter[parent_syspath], [1, 1])
self.assertEqual(counter[child1_syspath], [1, 1])
self.assertEqual(counter[child2_syspath], [1, 1])

# Verify that the parent syspath has been removed (and therefore the
# children syspaths have also been removed.)
self.assertFalse(os.path.exists(parent_syspath))

def test_add_from_string(self):
self.assertTrue(self.testbed.add_from_string('''P: /devices/dev1
E: SIMPLE_PROP=1
Expand Down

0 comments on commit 659df15

Please sign in to comment.