28 KiB
udev
What is udev?
Udev (userspace /dev) is a Linux sub-system for dynamic device detection and management, since kernel version 2.6. It’s a replacement of devfs and hotplug. It dynamically creates or removes device nodes (an interface to a device driver that appears in a file system as if it were an ordinary file, stored under the /dev directory) at boot time or if you add a device to or remove a device from the system. It then propagates information about a device or changes to its state to user space. The goal of udev, as stated by the project is:
- Run in user space.
- Create persistent device names, take the device naming out of kernel space and implement rule based device naming.
- Create a dynamic /dev with device nodes for devices present in the system and allocate major/minor numbers dynamically.
- Provide a user space API to access the device information in the system.
One of the pros of udev is that it can use persistent device names to guarantee consistent naming of devices across reboots, despite their order of discovery. This feature is useful because the kernel simply assigns unpredictable device names based on the order of discovery.
How does it work?
As udev runs in userland, it runs a service which on most modern distributions is controlled by systemd.
If we have a look at our running processes and search for udev
we find the following.
➜ ~ git:(master) ✗ ps aux | grep udev
root 430 0.0 0.0 23252 4164 ? Ss Aug06 0:01 /lib/systemd/systemd-udevd
waldek 11628 0.0 0.0 6208 896 pts/4 S+ 10:11 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn udev
➜ ~ git:(master) ✗
The first line hints us to a process run by root with a pretty low PID
.
It seems to be part of the systemd
suite of programs.
As always, let's have a look at the man systemd-udevd
to learn what it is and how we can interact with it.
DESCRIPTION
systemd-udevd listens to kernel uevents. For every event, systemd-udevd executes matching instructions
specified in udev rules. See udev(7).
The behavior of the daemon can be configured using udev.conf(5), its command line options, environment
variables, and on the kernel command line, or changed dynamically with udevadm control.
The description is pretty solid and points us to a specific program we can use to talk to the daemon.
This is very similar to how we have been talking to systemd
via systemctl
but here, to talk to systemd-udevd
, we'll use udevadm
.
Interacting with udev
Monitoring events
To analyse what udev is receiving we can monitor it with sudo udevadm monitor
.
You'll see see the following output with a blinking cursor on the last line to show we're monitoring live.
➜ ~ git:(master) ✗ sudo udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
If we now plug in a device we'll see a bunch of messages arriving. He I added a device by plugging it into the USB port. You can see a lot of sound and midi messages so it's safe to say I plugged in a MIDI keyboard. Do notice that once the keyboard is fully recognized and initialized by the system the messages stop. Udev has done it's work and will remain silent until some changes happen to the system.
KERNEL[212709.657308] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
KERNEL[212709.657495] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
KERNEL[212709.657545] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1 (sound)
KERNEL[212709.657658] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midiC1D0 (sound)
KERNEL[212709.657725] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midi1 (sound)
KERNEL[212709.657774] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/dmmidi1 (sound)
KERNEL[212709.657801] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
KERNEL[212709.657829] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
KERNEL[212709.657910] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/controlC1 (sound)
KERNEL[212709.657967] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/mixer1 (sound)
KERNEL[212709.658046] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
KERNEL[212709.658086] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
KERNEL[212709.658121] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
KERNEL[212709.658166] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
UDEV [212709.673568] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
UDEV [212709.675863] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
UDEV [212709.676560] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
UDEV [212709.677850] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1 (sound)
UDEV [212709.678004] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
UDEV [212709.690171] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midiC1D0 (sound)
UDEV [212709.690273] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midi1 (sound)
KERNEL[212709.692086] change /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1 (sound)
UDEV [212709.692888] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
UDEV [212709.693659] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/mixer1 (sound)
UDEV [212709.694897] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
UDEV [212709.696017] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/dmmidi1 (sound)
UDEV [212709.705167] add /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/controlC1 (sound)
UDEV [212709.706615] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
UDEV [212709.710665] bind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
UDEV [212709.712669] change /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1 (sound)
When I unplug the MIDI keyboard we get the following output. Notice the sequence of events here. The kernel reports changes which udev uses to trigger events and actions.
KERNEL[213014.417312] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/mixer1 (sound)
KERNEL[213014.417546] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/controlC1 (sound)
KERNEL[213014.417574] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
KERNEL[213014.417593] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
KERNEL[213014.417655] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midi1 (sound)
KERNEL[213014.417711] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/dmmidi1 (sound)
KERNEL[213014.417782] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midiC1D0 (sound)
KERNEL[213014.417808] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1 (sound)
KERNEL[213014.417837] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
KERNEL[213014.417870] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
KERNEL[213014.417904] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
KERNEL[213014.417939] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
KERNEL[213014.418036] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
KERNEL[213014.418080] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
UDEV [213014.419887] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/controlC1 (sound)
UDEV [213014.420067] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/mixer1 (sound)
UDEV [213014.420472] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
UDEV [213014.421955] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/seq-midi-1-0 (snd_seq)
UDEV [213014.422602] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midiC1D0 (sound)
UDEV [213014.422717] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/midi1 (sound)
UDEV [213014.424188] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1/dmmidi1 (sound)
UDEV [213014.424383] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
UDEV [213014.425445] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.1 (usb)
UDEV [213014.425718] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/sound/card1 (sound)
UDEV [213014.427640] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
UDEV [213014.430208] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0 (usb)
UDEV [213014.432609] unbind /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
UDEV [213014.438393] remove /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
In virtualbox you can add or remove USB devices via the menu bar of you virtual machine.
We can use udevadm
for more than just monitoring.
Tab complete to the rescue!
➜ ~ git:(master) ✗ sudo udevadm
control -- control the udev daemon
info -- query sysfs or the udev database
monitor -- listen to kernel and udev events
settle -- wait for the event queue to finish
test -- test an event run
test-builtin -- test a built-in command
trigger -- request events from the kernel
You can query the help information of a specific sub command by adding the --help
argument.
A full manual can be read via man udevadm
.
➜ ~ git:(master) ✗ sudo udevadm monitor --help
udevadm monitor [OPTIONS]
Listen to kernel and udev events.
-h --help Show this help
-V --version Show package version
-p --property Print the event properties
-k --kernel Print kernel uevents
-u --udev Print udev events
-s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem
-t --tag-match=TAG Filter events by tag
➜ ~ git:(master) ✗
Listing information
Via external programs
Udev can be used to query device information via the info
subcommand but there are a few other programs that are very handy to know.
We'll focus on USB devices for now, but keep in mind that udev manages everything connected to our system.
My MIDI keyboad is not plugged in for now and below is the output of lsusb
.
➜ ~ git:(master) ✗ lsusb
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 04f2:b217 Chicony Electronics Co., Ltd Lenovo Integrated Camera (0.3MP)
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
➜ ~ git:(master) ✗
Next I plug it in and review the output of lsusb
.
A device is added but it's not saying much!
This is vedor dependant and this company did not fully document it's device.
Most of the time however, you'll see a short description of the device.
➜ ~ git:(master) ✗ lsusb
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 04f2:b217 Chicony Electronics Co., Ltd Lenovo Integrated Camera (0.3MP)
Bus 001 Device 006: ID 2702:1110
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
➜ ~ git:(master) ✗
The hexadecimal numbers you see listed are VENDOR_ID:PROCUCT_ID
and can be used to digg deeper into a specific device.
For this we use the -d VENDOR_ID:PROCUCT_ID
argument, together with -v
for verbosity.
➜ ~ git:(master) ✗ lsusb -d 2702:1110 -v
Bus 001 Device 006: ID 2702:1110
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x2702
idProduct 0x1110
bcdDevice 0.01
iManufacturer 0
iProduct 0
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0065
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x00
(Missing must-be-set bit!)
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 1 Control Device
bInterfaceProtocol 0
iInterface 0
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 1 (HEADER)
bcdADC 1.00
wTotalLength 0x0009
bInCollection 1
baInterfaceNr(0) 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 1 Audio
bInterfaceSubClass 3 MIDI Streaming
bInterfaceProtocol 0
iInterface 0
MIDIStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (HEADER)
bcdADC 1.00
wTotalLength 0x0041
MIDIStreaming Interface Descriptor:
bLength 6
bDescriptorType 36
bDescriptorSubtype 2 (MIDI_IN_JACK)
bJackType 1 Embedded
bJackID 1
iJack 0
MIDIStreaming Interface Descriptor:
bLength 6
bDescriptorType 36
bDescriptorSubtype 2 (MIDI_IN_JACK)
bJackType 2 External
bJackID 2
iJack 0
MIDIStreaming Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (MIDI_OUT_JACK)
bJackType 1 Embedded
bJackID 3
bNrInputPins 1
baSourceID( 0) 2
BaSourcePin( 0) 1
iJack 0
MIDIStreaming Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (MIDI_OUT_JACK)
bJackType 2 External
bJackID 4
bNrInputPins 1
baSourceID( 0) 1
BaSourcePin( 0) 1
iJack 0
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
bRefresh 0
bSynchAddress 0
MIDIStreaming Endpoint Descriptor:
bLength 5
bDescriptorType 37
bDescriptorSubtype 1 (GENERAL)
bNumEmbMIDIJack 1
baAssocJackID( 0) 1
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
bRefresh 0
bSynchAddress 0
MIDIStreaming Endpoint Descriptor:
bLength 5
bDescriptorType 37
bDescriptorSubtype 1 (GENERAL)
bNumEmbMIDIJack 1
baAssocJackID( 0) 3
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0000
(Bus Powered)
➜ ~ git:(master) ✗
A USB stick presents itself as follows and offers quite a bit more information than our generic MIDI keyboard. We get a unique serial number, speeds, power usage and more.
➜ ~ git:(master) ✗ lsusb -d 0781:5591 -v
Bus 001 Device 007: ID 0781:5591 SanDisk Corp. Ultra Flair
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0781 SanDisk Corp.
idProduct 0x5591 Ultra Flair
bcdDevice 1.00
iManufacturer 1 USB
iProduct 2 SanDisk 3.2Gen1
iSerial 3 0101afb5176b84241e77e68fb386ea23b31fe4cf9eafe43b465def85b4531ad93da300000000000000000000014b07c6ff8d260091558107b52921c2
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0020
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 224mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 8 Mass Storage
bInterfaceSubClass 6 SCSI
bInterfaceProtocol 80 Bulk-Only
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Binary Object Store Descriptor:
bLength 5
bDescriptorType 15
wTotalLength 0x0016
bNumDeviceCaps 2
USB 2.0 Extension Device Capability:
bLength 7
bDescriptorType 16
bDevCapabilityType 2
bmAttributes 0x00000002
HIRD Link Power Management (LPM) Supported
SuperSpeed USB Device Capability:
bLength 10
bDescriptorType 16
bDevCapabilityType 3
bmAttributes 0x00
wSpeedsSupported 0x000e
Device can operate at Full Speed (12Mbps)
Device can operate at High Speed (480Mbps)
Device can operate at SuperSpeed (5Gbps)
bFunctionalitySupport 1
Lowest fully-functional device speed is Full Speed (12Mbps)
bU1DevExitLat 10 micro seconds
bU2DevExitLat 256 micro seconds
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0000
(Bus Powered)
➜ ~ git:(master) ✗
Via udevadm
We can get the same information via udevadm
with the info
subcommand.
We'll have to specify which device we want to query and this can be done via different identifiers.
The USB stick I inserted is still plugged in and represented in my filesystem via /dev/sdc
.
Remember TAB complete!
➜ ~ git:(master) ✗ sudo udevadm info --path /sys/block/sdc
[sudo] password for waldek:
P: /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6/target6:0:0/6:0:0:0/block/sdc
N: sdc
L: 0
S: disk/by-id/usb-USB_SanDisk_3.2Gen1_0101afb5176b84241e77e68fb386ea23b31fe4cf9eafe43b465def85b4531ad93da300000000000000000000014b07c6ff8d260091558107b52921c2-0:0
S: disk/by-path/pci-0000:00:1a.0-usb-0:1.2:1.0-scsi-0:0:0:0
E: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6/target6:0:0/6:0:0:0/block/sdc
E: DEVNAME=/dev/sdc
E: DEVTYPE=disk
E: MAJOR=8
E: MINOR=32
E: SUBSYSTEM=block
E: USEC_INITIALIZED=216426658045
E: ID_VENDOR=USB
E: ID_VENDOR_ENC=\x20USB\x20\x20\x20\x20
E: ID_VENDOR_ID=0781
E: ID_MODEL=SanDisk_3.2Gen1
E: ID_MODEL_ENC=\x20SanDisk\x203.2Gen1
E: ID_MODEL_ID=5591
E: ID_REVISION=1.00
E: ID_SERIAL=USB_SanDisk_3.2Gen1_0101afb5176b84241e77e68fb386ea23b31fe4cf9eafe43b465def85b4531ad93da300000000000000000000014b07c6ff8d260091558107b52921c2-0:0
E: ID_SERIAL_SHORT=0101afb5176b84241e77e68fb386ea23b31fe4cf9eafe43b465def85b4531ad93da300000000000000000000014b07c6ff8d260091558107b52921c2
E: ID_TYPE=disk
E: ID_INSTANCE=0:0
E: ID_BUS=usb
E: ID_USB_INTERFACES=:080650:
E: ID_USB_INTERFACE_NUM=00
E: ID_USB_DRIVER=usb-storage
E: ID_PATH=pci-0000:00:1a.0-usb-0:1.2:1.0-scsi-0:0:0:0
E: ID_PATH_TAG=pci-0000_00_1a_0-usb-0_1_2_1_0-scsi-0_0_0_0
E: ID_PART_TABLE_UUID=2ab572a6-8db9-4324-9d21-1a3640f4fb19
E: ID_PART_TABLE_TYPE=gpt
E: DEVLINKS=/dev/disk/by-id/usb-USB_SanDisk_3.2Gen1_0101afb5176b84241e77e68fb386ea23b31fe4cf9eafe43b465def85b4531ad93da300000000000000000000014b07c6ff8d260091558107b52921c2-0:0 /dev/disk/by-path/pci-0000:00:1a.0-usb-0:1.2:1.0-scsi-0:0:0:0
E: TAGS=:systemd:
➜ ~ git:(master) ✗
We can also inspect information about the partions on the disk, even without mounting them.
I'll spare you the output of the command but you would do sudo udevadm info --name=/dev/sdc1
wich gives us the label, type of format, etc.
Using the information
It's nice to know more about a device but doing something useful is even better.
This is the real beauty of udev
.
We can use the device information to trigger different actions when a device is added, removed or modified.
This is done by adding rules which can be defined in /etc/udev/rules.d/
.
Let's have a look at what's present on one of my servers.
➜ ~ ls -l /etc/udev/rules.d/
total 4
-rw-r--r-- 1 root root 96 Mar 4 12:31 70-persistent-net.rules
➜ ~ cat /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="fa:16:3e:9a:9f:3f", NAME="eth0"
➜ ~
This is a rule that fixes the network card with a specific mac address to a fixed name, eth0, each time this device is added.
A complete description of what can be used to form rules and actions can be read in the man udev
pages.
A simple rule
Let's add a rule so that whenever I plug in a specific USB stick, it always has a fixed path in the /dev
tree.
For this I need find a unique value of this USB drive so that my rule will only appy to this specific drive.
I can find the serial number for the first partition by executing sudo udevadm info --path=/sys/block/sdc -a | grep -E --color=auto 'serial|$'
.
The grep
command is a handy way of highlighting one specific word (do you understand what the |$
means?).
Based on the output from this command I can construct the following rule.
➜ ~ git:(master) ✗ cat /etc/udev/rules.d/classes.rules
ACTION=="add", ATTRS{serial}=="0101afb5176b84241e77e68fb386ea23b31fe4cf9eafe43b465def85b4531ad93da300000000000000000000014b07c6ff8d260091558107b52921c2", SYMLINK+="sparedisk"
➜ ~ git:(master) ✗
Now that the rule has been created we need to tell udev
to take this new rule into account.
This is very similar to how systemd
needs a systemctl daemon-reload
when we make changes to service files.
This is done with the following command.
Once this is done I can plug the drive out and in I'll see my /dev/sparedisk
apprear!
sudo udevadm control --reload
➜ ~ git:(master) ✗ ls /dev/sparedisk -l
ls: cannot access '/dev/sparedisk': No such file or directory
➜ ~ git:(master) ✗ ls /dev/sparedisk -l
lrwxrwxrwx 1 root root 4 Aug 20 13:29 /dev/sparedisk -> sdc1
➜ ~ git:(master) ✗
A more practical rule
There is a lot more we can do with rules so let's create a more complicated one.
I could like to log every block device connected to the system to a specific file for security monitoring.
For this I'll write a simple bash
script and a udev
rule that should trigger at each add of a block device.
Let's do the rule first.
➜ ~ git:(master) ✗ cat /etc/udev/rules.d/classes.rules
SUBSYSTEM=="block", ACTION=="add", RUN="/home/waldek/bin/block_monitor.sh"
➜ ~ git:(master) ✗
The script is just a placeholder for now to prove the script get's triggered.
➜ ~ git:(master) ✗ cat bin/block_monitor.sh
#!/bin/bash
echo "helloworld: $(date)" >> /tmp/block_monitor.log
➜ ~ git:(master) ✗
And after plugging the USB stick in 2 times I get the following output. Does this look normal to you? Why are we getting two entries per plug in?
➜ ~ git:(master) ✗ cat /tmp/block_monitor.log
helloworld: Fri Aug 20 12:35:42 BST 2021
helloworld: Fri Aug 20 12:35:43 BST 2021
helloworld: Fri Aug 20 12:36:04 BST 2021
helloworld: Fri Aug 20 12:36:04 BST 2021
➜ ~ git:(master) ✗
OK, the proof of concept is working but let's make it an actual useful log.
There is a very nice feature hidden in the man udev
pages towards the end.
I'll outline the base but please go have a look to see what more you can do with it.
The NAME, SYMLINK, PROGRAM, OWNER, GROUP, MODE, SECLABEL, and RUN
fields support simple string substitutions. The RUN substitutions
are performed after all rules have been processed, right before
the program is executed, allowing for the use of device
properties set by earlier matching rules. For all other fields,
substitutions are performed while the individual rule is being
processed. The available substitutions are:
With this in mind we can pass more information on to our script.
I'll start by using the %p
and $name
arguments to the RUN call.
The rule now looks as follows (and I reload the rules to take the changes into account).
➜ ~ git:(master) ✗ cat /etc/udev/rules.d/classes.rules
SUBSYSTEM=="block", ACTION=="add", RUN="/home/waldek/bin/block_monitor.sh %p $name"
➜ ~ git:(master) ✗ sudo udevadm control --reload
➜ ~ git:(master) ✗
I'll have to modify the script a bit to write these changes to my log file as well.
Remember the $1
and $2
meaning in bash
?
➜ ~ git:(master) ✗ cat bin/block_monitor.sh
#!/bin/bash
echo "helloworld: $(date) - $1 - $2" >> /tmp/block_monitor.log
➜ ~ git:(master) ✗ tail -n 4 /tmp/block_monitor.log
helloworld: Fri Aug 20 12:46:29 BST 2021 - /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6/target6:0:0/6:0:0:0/block/sdc - sdc
helloworld: Fri Aug 20 12:46:29 BST 2021 - /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6/target6:0:0/6:0:0:0/block/sdc/sdc1 - sdc1
helloworld: Fri Aug 20 12:50:33 BST 2021 - /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6/target6:0:0/6:0:0:0/block/sdc - sdc
helloworld: Fri Aug 20 12:50:33 BST 2021 - /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6/target6:0:0/6:0:0:0/block/sdc/sdc1 - sdc1
➜ ~ git:(master) ✗