udev is a userspace subsystem on Linux that provides system administrators the ability to register userspace handlers for events. In other words, it allows custom actions to be executed upon device plugging in or removing. The device can be physical or virtual, as long as the device node lives under /dev
directory.
udev rules can do pretty powerful things, and I'm only scratching the surface here. It's also amazing how little has changed in terms of syntax and capabilities, since 2004, when udev was first introduced. My learning resources include:
- ArchWik udev
- Ubuntu's man page udev(7)
- Writing udev rules by Daniel Drake (dsd)
- Scripting with udev
The motivation for me to learn udev systematically is due to work requirements. The custom Linux image we are building has to have specific devices shows up under certain /dev/tty
path. This has to work on multiple physical hardware models, forward compatible with future devices, and most importantly, reliably. For example, pinpad shows up as /dev/ttyS6 and weightscale shows up as /dev/ttyS7, no matter what port it plugs into, or what distro it currently uses (CentOS or Ubuntu).
Monitoring events
Upon device plugging in and removing, we can monitor the verbose message by running the monitor sub command. Ideally, we should get important info such as device node (e.g., /dev/ttyUSB0) and environment variables such as "ACTION=add". If it's a USB device, we can also easily use lsusb
to find vendorID and deviceID.
# udevadm monitor --environment --udev
The next step is to use device node path to find all information about this device and its parents.
# udevadm info --attribute-walk --path=$(udevadm info --query=path --name=/dev/ttyUSB0)
Note the message printed out by the above command: "Udevadm info starts with the device specified by the devpath and then walks up the chain of parent devices. It prints for every device found, all possible attributes in the udev rules key format. A rule to match, can be composed by the attributes of the device and the attributes from one single parent device."
It means exactly what it says.
Writing udev rules
Rule files go under /etc/udev/rules.d
and fortunately the path and syntax are distro-agnostic. Some common match keys include "KERNEL/SUBSYSTEM/ATTR". Corresponding match keys for parent device are "KERNELS/SUBSYSTEMS/ATTRS" -- think of them as the plural form of the former words. For a complete list of match keys, refer to man page. A rule to create a symlink of a tty device looks like this:
SUBSYSTEM=="tty", KERNELS=="1-7.3", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="23c3", SYMLINK+="ttyS2"
Here, only the first match key SUBSYTESM is against the device itself. Three other match keys are against a parent device. Note that all parent match keys have to come from the same parent, i.e., you cannot pick and choose match keys from different parent level devices.
Some common mistakes I found in other people's rule files:
- it's not possible to change device name assigned by the kernel (e.g., NAME="myUSB"). The limitation is due to udev being only an userspace program.
- most of the time, it's not necessary to specify ACTION=="add" environment variable match key.
- for symlinks, it's usually not necessary to specify GROUP and MODE as soft links don't inherit ownership and permissions from the original file. Do it only when you know what you are doing.
Some advanced topics include:
- string substitutions: udev uses printf-like string substitution operators
- string matching: much like regular expression, accepts "*", "?" and "[]"
- for removing events, try to leverage environment variable as match keys (ENV{KEY}=="VALUE"), as device attributes may not be accessible.
- run external scripts/programs with RUN+="/path/to/executable"; think of it like a subshell, in which environment variables will differ from the ones in user shell, and no stdout/stderr.
- for systemd intergration, refer to this Scripting with udev
- OPTIONS+="last_rule" (I can't think of a possible use case)
Triggering new rules
After saving the rules files, manually trigger them against existing devices:
# udevadm trigger
You will find out instantly whether your rules work or not. Novice like myself may rely on trail-and-error to develop the first couple of rules, and I shamelessly confess that's how I learned udev. Once I get the basics, it feels like a second language.