September 3rd 2017

linux, udev

Creating Udev Rules


This article only improves on this older article. If you feel some information is missing, you can probably find it there.

Important note: Most people will be using systemd now, so systemctl should be used instead of init/service etc.

Goal

Write a udev rule that executes a script when a smartphone is connected via USB.

First: Gathering Information

We can do this with udevadm info ..., but we have to tell it which device we're interested in, by giving it a path. But we know nothing about the device yet, so how do we find that path?

Maybe like this:
dmesg -w
Now connect the phone:
[ 2443.530093] usb 1-3: new high-speed USB device number 8 using xhci_hcd

OK, so currently it's usb 1-3. But still no path. man udevadm says something about /sys, so let's try this:

$ find /sys -name '*1-3*'
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.1
/sys/bus/usb/devices/1-3:1.0
/sys/bus/usb/devices/1-3
/sys/bus/usb/devices/1-3:1.1
/sys/bus/usb/drivers/usbfs/1-3:1.1
/sys/bus/usb/drivers/usb/1-3

Bingo! It looks like we can use any of these paths with udevadm info, however: the phone contains two devices (1-3:1.0 and 1-3:1.1) with the same vendor and product ID, and the script could be called twice, so we need to specify another value to trigger the script only once. Also some consequent actions might not work until the device is fully registered & initialised, so let's assume that the device registered last is the one we want.

We investigate further by using udevadm info on both devices (1-3:1.0 and 1-3:1.1). One variable in the list reads USEC_INITIALIZED. Sounds good, and 1-3:1.1 has a larger number than 1-3:1.0, which proves that it is indeed the last device to get registered.
But we cannot use this value, it is likely to be different each time. Compare the outputs of udevadm info -a (this time with the -a switch), until we find something that will make this device unique.
There's various choices that are more-or-less device-specific, but to me the difference that made most sense was ATTR{interface}=="MTP" (only present in the latter device), so I wrote the udev rule like this:

The Udev Rule

ACTION=="add", SUBSYSTEM=="usb", \
ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="XXXX", \
ATTR{interface}=="MTP", \
RUN+="/path/to/script/phoneconnect"

saved as /etc/udev/rules.d/51-my-phone-add.rules.

I had to do some testing, some variables did not work even though they seemed valid. It's also necessary to run udevadm control -R after changing rules.

First Test: A Notification

Now you can call this from inside the phoneconnect script with e.g.

notify "Phone connected:" "Device name & model"

This is only a proof-of-concept - the next step would be to make the phoneconnect script execute things, like copy files to/from the phone etc.

A little improvement for Sending Notifications to the Current User from a Script Executed by Root

To understand what I mean, see the original article.

Instead of calling a wrapper script as a different user, create a function inside your bash script:

function notify {
    sudo -i -u username DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus" notify-send "$1" "$2"
}

The user is hardwired here; this is just a hack that will work on most systems.
Please note running sudo with the -i option!
Please note that, according to the article two more environment variables are needed, but DBUS_SESSION_BUS_ADDRESS was sufficient for me. YMMV.