diff --git a/hydra/doctor.py b/hydra/doctor.py index 29eb37a..dd1c5d1 100644 --- a/hydra/doctor.py +++ b/hydra/doctor.py @@ -138,9 +138,11 @@ def doctor_report(*, proc_root=Path('/proc'), sys_root=Path('/sys'), dev_root=Pa inputs = [p for p in _entries(sys_root / 'class/input') if re.fullmatch(r'event[0-9]+', p.name)] keyboards = 0 for path in inputs: - value = _read(path / 'device/capabilities/key', 256).strip().replace(' ', '') - if re.fullmatch(r'[0-9a-fA-F]{1,256}', value): - bits = int(value, 16) + words = _read(path / 'device/capabilities/key', 256).split() + if words and all(re.fullmatch(r'[0-9a-fA-F]{1,16}', word) for word in words): + # x86_64 sysfs prints most-significant unsigned-long words first, + # without zero padding. KEY_A (30) and KEY_Z (44) are in the low word. + bits = int(words[-1], 16) keyboards += bool(bits & (1 << 30) and bits & (1 << 44)) return { 'format': 1, diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 6f8e4d1..158598b 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -78,6 +78,16 @@ class DoctorTests(unittest.TestCase): for secret in ('PRIVATE_SERIAL', 'PRIVATE_USER', 'PRIVATE_SSID', 'aa:bb:cc:dd:ee:ff'): self.assertNotIn(secret, encoded) + def test_keyboard_detection_preserves_64_bit_capability_word_boundaries(self): + capability = self.file('sys/class/input/event0/device/capabilities/key') + for bitmap, expected in (('10004000000 0\n', 0), + ('1 100040000000\n', 1), + ('100040000000\n', 1), + ('10004000000 invalid\n', 0)): + with self.subTest(bitmap=bitmap): + capability.write_text(bitmap) + self.assertEqual(self.report()['input']['keyboards'], expected) + def test_probes_timeout_independently_and_discard_untrusted_output(self): calls = [] def run(command, **kwargs):