pyudev / pyudev

Python bindings to libudev (with support for PyQt4, PySide, pygobject and wx)
http://pyudev.readthedocs.org
GNU Lesser General Public License v2.1
166 stars 50 forks source link

context.list_devices(BUSNUM=a, DEVNUM=b) yields unexpected behaviour #516

Closed enok71 closed 3 weeks ago

enok71 commented 3 weeks ago

I am using pyudev 0.24.3. I want to list the USB devices filtering on given Bus and Device numbers. I have the following devices attached:

>$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 04f2:b61e Chicony Electronics Co., Ltd Integrated Camera
Bus 001 Device 004: ID 0cf3:e500 Qualcomm Atheros Communications 
Bus 001 Device 043: ID 27c6:5584 Shenzhen Goodix Technology Co.,Ltd. Fingerprint Reader
Bus 001 Device 050: ID 03f0:0610 HP, Inc Z24i Monitor Hub
Bus 001 Device 051: ID 03f0:0610 HP, Inc Z24i Monitor Hub
Bus 001 Device 052: ID 046d:c534 Logitech, Inc. Unifying Receiver
Bus 001 Device 091: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 016: ID 03f0:0620 HP, Inc USB3.1 Hub
Bus 002 Device 017: ID 03f0:0620 HP, Inc USB3.1 Hub

But it seems that if both BUSNUM and DEVNUM arguments are given to list_devices(BUSNUM=a, DEVNUM=b) the resulting list contains the devices that matches either BUSNUM=a or DEVNUM=b. I would have expected to only have devices listed that match both BUSNUM=a and DEVNUM=b.

Looks like a bug. E.g.:

>>> for d in context.list_devices(BUSNUM='001', DEVNUM='001'):
...     print(d.get('BUSNUM'), d.get('DEVNUM'))
001 001
001 091
001 004
001 050
001 051
001 052
001 002
001 043
002 001
... 

If I skip 'BUSNUM' argument the 'DEVNUM' works fine:

>>> for d in context.list_devices(DEVNUM='001'):
...     print(d.get('BUSNUM'), d.get('DEVNUM'))
... 
001 001
002 001

The order of 'BUSNUM' and 'DEVNUM' doesn't matter:

>>> for d in context.list_devices(DEVNUM='001', BUSNUM='001'):
...     print(d.get('BUSNUM'), d.get('DEVNUM'))
... 
001 001
001 091
001 004
001 050
001 051
001 052
001 002
001 043
002 001
enok71 commented 3 weeks ago

Ok I found now that this is by design. See the documentation string for Enumerator class. Still I can't see the practical reason though. A design bug at least? Or perhaps this is a design feature in libudev?

https://github.com/pyudev/pyudev/blob/8b758cd9d75d575075c3ada9e1370d71bf8035c8/src/pyudev/core.py#L152C1-L165C1

    Before iteration the device list can be filtered by subsystem or by
    property values using :meth:`match_subsystem` and
    :meth:`match_property`.  Multiple subsystem (property) filters are
    combined using a logical OR, filters of different types are combined
    using a logical AND.  The following filter for instance::

        devices.match_subsystem('block').match_property(
            'ID_TYPE', 'disk').match_property('DEVTYPE', 'disk')

    means the following::

        subsystem == 'block' and (ID_TYPE == 'disk' or DEVTYPE == 'disk')
mulkieran commented 3 weeks ago

Ok I found now that this is by design. See the documentation string for Enumerator class. Still I can't see the practical reason though. A design bug at least? Or perhaps this is a design feature in libudev?

https://github.com/pyudev/pyudev/blob/8b758cd9d75d575075c3ada9e1370d71bf8035c8/src/pyudev/core.py#L152C1-L165C1

    Before iteration the device list can be filtered by subsystem or by
    property values using :meth:`match_subsystem` and
    :meth:`match_property`.  Multiple subsystem (property) filters are
    combined using a logical OR, filters of different types are combined
    using a logical AND.  The following filter for instance::

        devices.match_subsystem('block').match_property(
            'ID_TYPE', 'disk').match_property('DEVTYPE', 'disk')

    means the following::

        subsystem == 'block' and (ID_TYPE == 'disk' or DEVTYPE == 'disk')

Yes. I think this exactly reflects the libudev design (and that probably reflects the systemd design).

enok71 commented 3 weeks ago

Yes. I think this exactly reflects the libudev design (and that probably reflects the systemd design).

How inconvenient. And hard to fix. But workaround is to write explicit additional filtering code of course. And to remember to not specify more than one property argument (unless that surprising OR between properties is desired):

for d in context.list_devices(subsystem='usb', DEVNUM='001'):
    if d.get('BUSNUM') != '001':
        continue
    print(d.get('BUSNUM'), d.get('DEVNUM'))