dockerusbsymlinkudevlibusb-1.0

docker --device works with absolute device path, fails with symlink


I have a USB GPIO device with some 3rd party drivers that I want to interface with in a container. Compiles, works fine on host. Also compiles, works fine in the container if I pass --device=/dev/bus/usb/001/$NUM where $NUM is the autogenerated path when the device is plugged in; I presume udev is assigning this. However I want a deterministic bind point, so I modified the udev rules to assign a symlink:

SUBSYSTEM=="usb", ATTR{idVendor}=="09db", 
ATTR{idProduct}=="0075", MODE="0666", 
TAG+="uaccess", TAG+="udev-acl", 
SYMLINK+="mcc_daq"

And that gives me a symlink at /dev/mcc_daq to /dev/bus/usb/whatever. Still works fine on host.

But, if I run with:

docker run --rm -it \
        --device=/dev/mcc_daq \
        mcc_daq1

I get

usb_device_find_USB_MCC: libusb_open failed.: No such file or directory 
Failure, did not find a USB 2408 or 2408_2AO!

which is what the test program spits out when it can't find the device (uses libusb_get_device_descriptor).

When I run in the container udevadm info -q all /dev/mcc_daq, I get the same exact output as udevadm info -q all /dev/bus/usb/001/004 when I start the container with --device=/.../004. However, if I pass in the symlink path to docker, I can't query the absolute path.

The udev output is more verbose on the host system, I'm not sure if this is part of the problem or expected behavior. My intuition is the lack of those extra entries means that libusb can find the device, but it can't find the vendor ID of the product. However, the fact that the udev output is the same in the container (regardless whether symlink or hard path) makes me question that.

Update: New fun fact: when I run strace on the test program, I find:

open("/dev/bus/usb/001/004", O_RDWR)    = -1 ENOENT (No such file or directory)

So that means the driver is looking for ../004, even though I mounted ---device=/dev/mcc_daq. I'm not sure where it's getting this information.

In short, I think using docker run --device=/dev/symlink does not introduce the same udev context as using the hard path, even though output of udevadm info is the same.

Host: udevadm info -q all /dev/mcc_daq

P: /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
N: bus/usb/001/004
S: mcc_daq
E: BUSNUM=001
E: DEVLINKS=/dev/mcc_daq
E: DEVNAME=/dev/bus/usb/001/004
E: DEVNUM=004
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
E: DEVTYPE=usb_device
E: DRIVER=usb
E: ID_BUS=usb
E: ID_FOR_SEAT=usb-pci-0000_00_14_0-usb-0_8_2
E: ID_MODEL=USB-2408-2AO
E: ID_MODEL_ENC=USB-2408-2AO
E: ID_MODEL_ID=00fe
E: ID_PATH=pci-0000:00:14.0-usb-0:8.2
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_8_2
E: ID_REVISION=0101
E: ID_SERIAL=MCC_USB-2408-2AO_01DA523C
E: ID_SERIAL_SHORT=01DA523C
E: ID_USB_INTERFACES=:ffff00:
E: ID_VENDOR=MCC
E: ID_VENDOR_ENC=MCC
E: ID_VENDOR_FROM_DATABASE=Measurement Computing Corp.
E: ID_VENDOR_ID=09db
E: MAJOR=189
E: MINOR=3
E: PRODUCT=9db/fe/101
E: SUBSYSTEM=usb
E: TAGS=:uaccess:seat:udev-acl:
E: TYPE=255/255/0
E: USEC_INITIALIZED=19197013

Docker: udevadm info -q all /dev/mcc_daq

P: /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
N: bus/usb/001/004
E: BUSNUM=001
E: DEVNAME=/dev/bus/usb/001/004
E: DEVNUM=004
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
E: DEVTYPE=usb_device
E: DRIVER=usb
E: MAJOR=189
E: MINOR=3
E: PRODUCT=9db/fe/101
E: SUBSYSTEM=usb
E: TYPE=255/255/0

Docker: udevadm info -q all /dev/bus/usb/001/004

P: /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
N: bus/usb/001/004
E: BUSNUM=001
E: DEVNAME=/dev/bus/usb/001/004
E: DEVNUM=004
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
E: DEVTYPE=usb_device
E: DRIVER=usb
E: MAJOR=189
E: MINOR=3
E: PRODUCT=9db/fe/101
E: SUBSYSTEM=usb
E: TYPE=255/255/0

Solution

  • From this discussion I concluded that if your docker run command is how you actually use it, you can do a workaround using the bash subsition below helped by the tool 'readlink':

    docker run --rm -it \
            --device=$(readlink -f /dev/mcc_daq) \
            mcc_daq1
    

    However if you use docker-compose like me, this workaround get's slightly more awkward because you would need to do some preparatory juggling with environment variables I guess.

    One of the problems with this workaround is of course that if you replug your device the path will be invalid. So you would have to restart the container I guess..