QLC+ Greedily Consumes USB Devices

All the topics related to QLC+ on the Raspberry Pi
Post Reply
jforest1
Posts: 2
Joined: Sat Feb 08, 2025 9:56 pm
Real Name: j forest

I have a custom C++ application that I'm attempting to convert to a systemd service on my Pi, and I've spent 4 days trying to safeguard its access to USB devices from QLC+. Oh ancient gods of QLC, udev, USB, and raspberry pi, please help me!

Here's the deal. The custom application binds to and on occasion releases USB connections to a controller, /dev/input/js0 (and /dev/input/event4 on my Rpi). It then reclaims it, reinitializes it, and carries on with execution. There's several reasons why it might legitimately release it. Point is, it works fine whilst QLC+ is not running throughout disconnects/reconnects. However, the program relies on QLC+, because it integrates with QLC+'s websocket API.

NOW we come to the problem. With QLC+ running in parallel to the program, when the program release the USB device, QLC+'s (hotplugging scanner?) USB integration comes and swipes the USB device, such that the program cannot reclaim it. And then everything goes to hell.

Sigh. Luckily for me, the program is running as root (yes, I know), and QLC+ is running as my normal user. So, THEORETICALLY, I could use udev to set these devices to be owned by root and restrict permissions to 600 and viola QLC+ couldn't claim them because it couldn't access them. Here's my setup:

Code: Select all

$ cat zzzz-98-usb.rules 
# Match the input device (such as /dev/input/js0)
SUBSYSTEM=="input", ATTRS{idVendor}=="0e6f", ATTRS{idProduct}=="0103", KERNEL=="event*|js*", ACTION=="add|bind|change|online", OWNER="root", GROUP="root", MODE="0600", TAG+="systemd", RUN:="/opt/udev/guard.sh /dev/input/%k"
and the script at /opt/udev/guard.sh just to MAKE ABSOLUTELY SURE:

Code: Select all

#!/usr/bin/bash

LOGFILE=/var/log/guard.log

DEVICE_PATH=$1


echo "${DEVICE_PATH}: $(/usr/bin/date) Device plugged in." >> $LOGFILE

echo "${DEVICE_PATH}: Unix Permissions" >> $LOGFILE
echo "${DEVICE_PATH}: $(ls -l "${DEVICE_PATH}")" >> $LOGFILE

# Log the ACL before setting it
echo "${DEVICE_PATH}: ACL:" >> $LOGFILE
echo "${DEVICE_PATH}: $(getfacl "${DEVICE_PATH}")" >> $LOGFILE

# Restrict permissions
chown root:root "${DEVICE_PATH}"
chmod 600 "${DEVICE_PATH}"

# Remove ACLs
/usr/bin/setfacl -b "${DEVICE_PATH}"

echo "${DEVICE_PATH}: Permissions set and ACLs removed." >> $LOGFILE

echo "${DEVICE_PATH}: Unix Permissions" >> $LOGFILE
echo "${DEVICE_PATH}: $(ls -l "${DEVICE_PATH}")" >> $LOGFILE

# Log the ACL after setting
echo "${DEVICE_PATH}: ACL:" >> $LOGFILE
echo "${DEVICE_PATH}: $(getfacl "${DEVICE_PATH}")" >> $LOGFILE
I boot up, log in, plug in the device:

Code: Select all

$ ls -l /dev/input
total 0
drwxr-xr-x 2 root root     120 Feb  8 17:12 by-id
drwxr-xr-x 2 root root     160 Feb  8 17:12 by-path
crw-rw---- 1 root input 13, 64 Feb  8 13:18 event0
crw-rw---- 1 root input 13, 65 Feb  8 13:18 event1
crw-rw---- 1 root input 13, 66 Feb  8 13:18 event2
crw-rw---- 1 root input 13, 67 Feb  8 13:18 event3
crw------- 1 root root  13, 68 Feb  8 17:12 event4
crw------- 1 root root  13, 69 Feb  8 17:12 event5
crw------- 1 root root  13,  0 Feb  8 17:12 js0
crw------- 1 root root  13,  1 Feb  8 17:12 js1
Look at those sweet, sweet perms. root:root 600 on js0/1 and event 4/5. Just as expected. I run my program, and they disappear:

Code: Select all

sudo ls -la /dev/input/
total 0
drwxr-xr-x  3 root root     160 Feb  8 17:19 .
drwxr-xr-x 17 root root    4060 Feb  8 17:14 ..
drwxr-xr-x  2 root root      80 Feb  8 17:19 by-path
crw-rw----  1 root input 13, 64 Feb  8 13:18 event0
crw-rw----  1 root input 13, 65 Feb  8 13:18 event1
crw-rw----  1 root input 13, 66 Feb  8 13:18 event2
crw-rw----  1 root input 13, 67 Feb  8 13:18 event3
crw-rw----  1 root input 13, 63 Feb  8 13:18 mice
Great, it's gotten a handle on them. Cool. And when it releases, it looks like it did in the beginning. Now, I launch QLC+....next time it releases:

Code: Select all

$ ls -la /dev/input/
total 0
drwxr-xr-x  4 root root     220 Feb  8 17:23 .
drwxr-xr-x 17 root root    4060 Feb  8 17:14 ..
drwxr-xr-x  2 root root      80 Feb  8 17:23 by-id
drwxr-xr-x  2 root root     120 Feb  8 17:23 by-path
crw-rw----  1 root input 13, 64 Feb  8 13:18 event0
crw-rw----  1 root input 13, 65 Feb  8 13:18 event1
crw-rw----  1 root input 13, 66 Feb  8 13:18 event2
crw-rw----  1 root input 13, 67 Feb  8 13:18 event3
crw-------  1 root root  13, 69 Feb  8 17:23 event5
crw-------  1 root root  13,  1 Feb  8 17:23 js1
crw-rw----  1 root input 13, 63 Feb  8 13:18 mice
QLC+ snarfs one of them (js0/event4 in this case). How did it do that!? udev should've protected it. QLC+ is running as regular user:

Code: Select all

jforest1        3153       1  4 17:22 pts/1    00:00:08 /usr/bin/qlcplus --open /opt/qlcplus.qxw --web --operate
What in the ACL is going on!?

Is QLC+ magical? I do have a USB device that QLC+ needs for DMX512 output, but how do I keep it away from the other USB devices that I don't want it touching?

Help, I'm at the end of my rope.
jforest1
Posts: 2
Joined: Sat Feb 08, 2025 9:56 pm
Real Name: j forest

I'll add here is the relevant piece of the QLC+ debug output:

Code: Select all

virtual void HPMPrivate::run() Unhandled udev action: "unbind"
void HotPlugMonitor::emitDeviceRemoved(uint, uint) 3695 259
void DMXUSB::slotDeviceRemoved(uint, uint) "e6f" "103"
void DMXUSB::slotDeviceRemoved(uint, uint) Invalid DMX USB device, nothing to do
void HIDPlugin::slotDeviceRemoved(uint, uint) "e6f" "103"
void HotPlugMonitor::emitDeviceAdded(uint, uint) 3695 259
void MidiPlugin::slotDeviceRemoved(uint, uint) "e6f" "103"
void MidiEnumerator::rescan()
void MidiEnumeratorPrivate::rescan()
ALSA Port name:  "Midi Through Port-0"
ALSA Port name:  "Midi Through Port-0"
ALSA Port name:  "input"
ALSA Port name:  "input"
ALSA Port name:  "input"
ALSA Port name:  "input"
void Peperoni::slotDeviceRemoved(uint, uint) "e6f" "103"
void Peperoni::slotDeviceRemoved(uint, uint) not a Peperoni device
void DMXUSB::slotDeviceAdded(uint, uint) "e6f" "103"
void DMXUSB::slotDeviceAdded(uint, uint) Invalid DMX USB device, nothing to do
void HIDPlugin::slotDeviceAdded(uint, uint) "e6f" "103"
void MidiPlugin::slotDeviceAdded(uint, uint) "e6f" "103"
void MidiEnumerator::rescan()
void MidiEnumeratorPrivate::rescan()
ALSA Port name:  "Midi Through Port-0"
ALSA Port name:  "Midi Through Port-0"
ALSA Port name:  "input"
ALSA Port name:  "input"
ALSA Port name:  "input"
ALSA Port name:  "input"
void Peperoni::slotDeviceAdded(uint, uint) "e6f" "103"
void Peperoni::slotDeviceAdded(uint, uint) not a Peperoni device
virtual void HPMPrivate::run() Unhandled udev action: "bind"
Post Reply