fix(udev): restrict mode switching to intended devices and events

Take the aic.rules portion of #129: restrict 1111:1111 switching to
88M80 USB devices, target the event's bus/device address, and eject only
whole disks on add. Add selection fixtures and document the product
restriction. Keep installer paths and existing firmware handling unchanged.

Co-authored-by: rtk94 <richardtknepp@gmail.com>
This commit is contained in:
Shen Mintaoandrtk94 committed 2026-11-02 14:31:52 +08:00
1 parent bee40068b9
commit e2c5835767
3 files changed
+89 -11

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Check device/event selection using fixtures; does not switch real devices.
This models the basic equality/glob predicates used by aic.rules. Run
udevadm verify separately to validate syntax with the real udev parser.
"""
from fnmatch import fnmatchcase
from pathlib import Path
import re
import unittest
RULES = (Path(__file__).resolve().parents[2] / "aic.rules").read_text().splitlines()
def matching_rules(device):
result = []
for rule in RULES:
if not rule.strip() or rule.lstrip().startswith("#"):
continue
predicates = re.findall(r'([A-Z]+(?:\{[^}]+\})?)=="([^"]*)"', rule)
if predicates and all(fnmatchcase(device.get(key, ""), value) for key, value in predicates):
result.append(rule)
return result
class UdevTests(unittest.TestCase):
def pandora(self, **changes):
device = {"ACTION": "add", "SUBSYSTEM": "usb", "ENV{DEVTYPE}": "usb_device",
"ATTR{idVendor}": "1111", "ATTR{idProduct}": "1111", "ATTR{product}": "88M80"}
device.update(changes)
return device
def test_88m80_uses_event_bus_and_device_numbers(self):
for product in ("88M80", "88M80 USB WiFi"):
with self.subTest(product=product):
matches = matching_rules(self.pandora(**{"ATTR{product}": product}))
self.assertEqual(len(matches), 1)
# Both possible usb_modeswitch executable locations target this event's device.
self.assertEqual(matches[0].count("-b $attr{busnum} -g $attr{devnum}"), 2)
def test_generic_1111_flash_disk_is_not_switched(self):
for product in ("FLASH UDISK 2.30", "USB DISK", ""):
with self.subTest(product=product):
self.assertEqual(matching_rules(self.pandora(**{"ATTR{product}": product})), [])
def test_wrong_ids_interfaces_and_non_add_events_are_not_switched(self):
for changes in ({"ACTION": "change"}, {"ACTION": "remove"},
{"ENV{DEVTYPE}": "usb_interface"}, {"ATTR{idVendor}": "2222"},
{"ATTR{idProduct}": "8d80"}):
with self.subTest(changes=changes):
self.assertEqual(matching_rules(self.pandora(**changes)), [])
def test_eject_only_on_add_of_a_whole_supported_disk(self):
for product in ("5721", "5722", "5723", "5724", "5725", "5726", "5727", "572a", "572c", "572f"):
disk = {"ACTION": "add", "SUBSYSTEM": "block", "ENV{DEVTYPE}": "disk", "KERNEL": "sdb",
"ATTRS{idVendor}": "a69c", "ATTRS{idProduct}": product}
with self.subTest(product=product):
matches = matching_rules(disk)
self.assertEqual(len(matches), 1)
self.assertIn('/usr/bin/eject /dev/%k', matches[0])
for changes in ({"ACTION": "change"}, {"ACTION": "remove"},
{"ENV{DEVTYPE}": "partition", "KERNEL": "sdb1"}):
self.assertEqual(matching_rules(dict(disk, **changes)), [])
if __name__ == "__main__":
unittest.main(verbosity=2)