linux_course_doc/modules/qualifying/learning_udev.md

29 KiB
Raw Permalink Blame History

udev

What is udev?

Udev (userspace /dev) is a Linux sub-system for dynamic device detection and management, since kernel version 2.6. Its 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) ✗ 

As the rule triggers the script, we can make changes to this script without reloading the rules. I advise to de-couple the development of the rule from the development of the script. First make sure your rule works as expected with some sort of script placeholder you can verify, next develop your script and make it as robust as possible.

Conclusion

This is just an introduction the fundamental concepts and usage of udev. We'll tie it together with systemd once we've investigated dbus.