wiki:Howto/UdevCreateDeviceMatchingRules

Udev: Create Device Matching Rules

(first published 2009-04-16 on  weather-watch.com)

I've noticed several topics regarding how to correctly set the device access permissions so that software such as Weather Display can access the device without needing 'root' superuser privilege escalation.

There is some confusion about the correct way to do it with many suggestions to manually change permissions of device nodes rather than set a system-wide automatically applied rule. I've responded to a couple but suspect the location of the information is not perfect for anyone finding my postings later. I've also had some requests to explain how to do it for a variety of devices. Instead of replying individually it would be better to share the information here for everyone to learn from and ask questions of.

The commands shown here are executed on an Ubuntu system. For other distributions the locations and semantics of the commands might vary slightly.

Udev Overview

Most GNU/Linux distributions now use udev to automatically create device nodes and set ownership, permissions, and symbolic links when devices are recognised by the kernel. udev is a short-form of User-space DEVice management. It is a mechanism the kernel developers created to separate the recognition of new devices from the policy issues of naming and locating them in the file-system.

When a kernel module discovers a device, either at start-up (a.k.a. boot-time) or when a device is subsequently connected, it sends a User event to a user-space process listening for uevents. Udev's is called udevd (udev daemon). This process listens for notification of new devices and then uses a series of rules to create and configure the device node. The rules are text files that declare conditions that a rule must match and an action to take when it does.

Udev rules files are by convention named using a leading number followed by a short-name. The number causes the file-system to automatically list the rules in low-to-high order without udev needing to apply any logic to ordering. Lower numbered rules are evaluated before higher numbered rules, so the rule 15-early-rule.rules will be evaluated before 65-set-attributes.rules.

I'm working on Ubuntu. Some distributions might change the location of the rules files so if what you read here about rules locations doesn't match with your system you'll need to find out where that distribution puts the rules. There are usually two locations where rules files will be kept: the distribution-provided location and the local user location. Using a package-manager it is often possible to find out; for example on Debian-based system like Ubuntu there is a command to list the contents of any installed package:

dpkg-query -L udev | grep rules

This will reveal that there are rules in:

/lib/udev/rules.d/
/etc/udev/rules.d/

The first is the system-wide rules set provided by the distribution - in this case Ubuntu. The second is the local system's user-created rules. /etc/udev/rules.d/ is where you should install any rules of your own.

Identifying Devices

Before you can create a udev rule you'll need to know some unique attributes that identify just that device. If you don't do this the rule would be applied to every device in the system and would likely create mayhem!

You might want to read the  Writing udev rules tutorial.

I'll give two examples here:

  • Example A is a WH-1081 USB-connected device
  • Example B is a serial port

The WH-1081 might be connected at any time whereas a serial port is likely a fixed part of the system that is always there. udev handles them both the same way; what is different is when it processes the rules.

Removable devices

For removable devices (such as USB) ensure the device is disconnected then monitor the kernel log whilst connecting the device:

 tail -f -n 0 /var/log/kern.log 

Now connect the device and you'll see some reports as it is recognised by kernel modules

 Apr 16 13:05:09 hephaestion kernel: [102070.249075] usb 3-1: new low speed USB device using uhci_hcd and address 9
 Apr 16 13:05:09 hephaestion kernel: [102070.438408] usb 3-1: configuration #1 chosen from 1 choice
 Apr 16 13:05:09 hephaestion kernel: [102070.481257] generic-usb 0003:1941:8021.0010: hiddev96,hidraw8: USB HID v1.00 Device [HID 1941:8021] on usb-0000:00:1d.1-1/input0 

Interrupt and stop the tail command by pressing Ctrl+C. Disconnect the device. Now start the udev administration utility to monitor what udev does, then reconnect the device. There'll be several event reports starting with the most basic device recognition and ending with specifics. I'll only show the first and relevant events here to avoid too much noise:

 udevadm monitor --environment

monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[1239883836.898960] add      /devices/pci0000:00/0000:00:1d.1/usb3/3-1 (usb)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1d.1/usb3/3-1
SUBSYSTEM=usb
DEVTYPE=usb_device
DEVICE=/proc/bus/usb/003/010
PRODUCT=1941/8021/100
TYPE=0/0/0
BUSNUM=003
DEVNUM=010
SEQNUM=2285
MAJOR=189
MINOR=265 

... and this event is significant for us since it declares the DEVNAME which is what the WeatherDisplay? application will need to use:

UDEV  [1239883836.954289] add
DEVPATH=/devices/pci0000:00/0000:00:1d.1/usb3/3-1/3-1:1.0/usb/hiddev0 (usb)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1d.1/usb3/3-1/3-1:1.0/usb/hiddev0
SUBSYSTEM=usb
SEQNUM=2289
DEVNAME=/dev/usb/hiddev0
MAJOR=180
MINOR=96
DEVLINKS=/dev/char/180:96 

Interrupt and stop udevadm by pressing Ctrl+C.

Use the DEVNAME to identify a likely clue for the next stage.

Discovering Udev Attributes

If examining a removable device ensure it is connected. If examining a static device (such as a serial port) you'll likely already know the device name (e.g. ttyS0).

Now examine what udev knows about the device.

Examining Removable Devices

Give the following shell-script the clue

 CLUE=hiddev0 for DEVICE in $(find /sys ! -type l -iname "*${CLUE}*"); do ls -dl $DEVICE; udevadm info -a -p $DEVICE; done 

In this case the script does find some relevant information. If your particular clue didn't reveal the device you'll need to alter the clue and explore some more - it is very much down to using your brain to determine what is relevant to the device you are matching.

This is the output for the WH-1081:

 drwxr-xr-x 3 root root 0 2009-04-16 13:10 /sys/devices/pci0000:00/0000:00:1d.1/usb3/3-1/3-1:1.0/usb/hiddev0 

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.

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.

  looking at device '/devices/pci0000:00/0000:00:1d.1/usb3/3-1/3-1:1.0/usb/hiddev0 ':
  KERNEL=="hiddev0"
  SUBSYSTEM=="usb"
  DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.1/usb3/3-1/3-1:1.0 ':
    KERNELS=="3-1:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="usbhid"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{bInterfaceClass}=="03"
    ATTRS{bInterfaceSubClass}=="00"
    ATTRS{bInterfaceProtocol}=="00"
    ATTRS{modalias}=="usb:v1941p8021d0100dc00dsc00dp00ic03isc00ip00 "
    ATTRS{supports_autosuspend}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1d.1/usb3/3-1 ':
    KERNELS=="3-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="10"
    ATTRS{idVendor}=="1941"
    ATTRS{idProduct}=="8021"
    ATTRS{bcdDevice}=="0100"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{speed}=="1.5"
    ATTRS{busnum}=="3"
    ATTRS{devnum}=="10"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1d.1/usb3 ':
    KERNELS=="usb3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bMaxPower}=="  0mA"
    ATTRS{urbnum}=="202"
    ATTRS{idVendor}=="1d6b"
    ATTRS{idProduct}=="0001"
    ATTRS{bcdDevice}=="0206"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="12" ATTRS{busnum}=="3"
    ATTRS{devnum}=="1"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="2"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Linux 2.6.28-11-generic uhci_hcd"
    ATTRS{product}=="UHCI Host Controller"
    ATTRS{serial}=="0000:00:1d.1"
    ATTRS{authorized_default}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1d.1 ':
    KERNELS=="0000:00:1d.1"
    SUBSYSTEMS=="pci"
    DRIVERS=="uhci_hcd"
    ATTRS{vendor}=="0x8086"
    ATTRS{device}=="0x27c9"
    ATTRS{subsystem_vendor}=="0x104d"
    ATTRS{subsystem_device}=="0x81ef"
    ATTRS{class}=="0x0c0300"
    ATTRS{irq}=="17"
    ATTRS{local_cpus}=="ffffffff,ffffffff"
    ATTRS{local_cpulist}=="0-63"
    ATTRS{modalias}=="pci:v00008086d000027C9sv0000104Dsd000081EFbc0Csc03i00 "
    ATTRS{broken_parity_status}=="0"
    ATTRS{msi_bus}==""

  looking at parent device '/devices/pci0000:00 ':
    KERNELS=="pci0000:00 "
    SUBSYSTEMS==""
    DRIVERS==""

All of these name-value pairs can be used in a udev rule to help target the device. Some will be more specific than others. For example, matching just on SUBSYSTEM=="usb" would not be helpful since that will be matching all USB devices. However, matching against the device's unique Vendor:Product ID would be extremely specific (ATTRS{idVendor}=="1941" and ATTRS{idProduct}=="8021").

Examining Static Devices

Give the following shell-script the clue

 CLUE=ttyS0 for DEVICE in $(find /sys ! -type l -iname "*${CLUE}*"); do ls -dl $DEVICE; udevadm info -a -p $DEVICE; done 

This is the output on a laptop that has a serial device but no physical port to connect to (in other words, your output might differ significantly from this).

 drwxr-xr-x 3 root root 0 2009-04-16 12:27 /sys/devices/platform/serial8250/tty/ttyS0 


 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.

  looking at device '/devices/platform/serial8250/tty/ttyS0':
    KERNEL=="ttyS0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/platform/serial8250':
    KERNELS=="serial8250"
    SUBSYSTEMS=="platform"
    DRIVERS=="serial8250"
    ATTRS{modalias}=="platform:serial8250 "

  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""

Device Permissions

Now we need to decide on what a matching udev rule should do. In general you should leave the specific system-set device permissions as they are since they have been determined to be the best combination over-all. When a non-privileged user account needs access to a privileged device the best way to do it is by assigning a different group as owner, so for example instead of:

 ls -l /dev/usb/hiddev0 crw-rw---- 1 root root 180, 96 2009-04-16 13:10 /dev/usb/hiddev0 

Where "root root" shows the user and group that own this device you would want something like this:

 crw-rw---- 1 root weather 180, 96 2009-04-16 13:12 /dev/usb/hiddev0 

The permissions on the left of the output ("crw-rw----") can be read as:


  c = character device rw- = owner permissions: read, write, no-execute rw- = group permissions: read, write, no-execute --- = all users permissions; no-read, no-write, no-execute


Udev Rules

We now have all the ingredients to create a udev rule. I'll show the rule and then explain it. For the removable WH-1081 device:

# WH-1081 Weather Station
ACTION!="add|change", GOTO="weather_station_end"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1941", ATTRS{idProduct}=="8021", GROUP="weather"

LABEL="weather_station_end" 

The first line is a comment (preceeded by #)

"ACTION!=" means if the current udev action isn't add or change, go to the end of the file - in other words, don't try to use these matching rules

The actual match rule is simple and based upon what was discovered using "udevadm info". It matches (using the == equality-test operator) the USB sub-system and a device with matching Vendor and Product ID and when all those matches are true, it assigns (using the = assignment operator) the group "weather".

The last line is the target label for the earlier GOTO.

For the serial device something like this might be sufficient:

# Serial-port Weather Station
ACTION!="add|change", GOTO="weather_station_end"

SUBSYSTEM=="tty", KERNEL=="ttyS0", GROUP="weather"

LABEL="weather_station_end" 

This file is saved to the local udev rules location, usually /etc/udev/rules.d/. I've named it "38-weather-station.rules". It is perfectly acceptable to have multiple rules in a single file provided you ensure that any GOTOs don't cause one set of rules to be ignored.

Writing to Privileged Locations

Writing files to this location requires superuser privileges so start a text-editor with those permissions. On a Gnome-based graphical user interface you'd press Alt+F2 to open the program-launcher dialog and then issue the command "gksudo gedit /etc/udev/rules.d/38-weather-station.rules".

At the command-line you might prefer "sudo nano /etc/udev/rules.d/38-weather-station.rules".

Save the rule.

User Group

Before testing the rule create the group being assigned if it doesn't already exist, then add the required user(s) to that group:

sudo addgroup --system weather
Adding group `weather' (GID 127) ... Done.

sudo adduser tj weather
Adding user `tj' to group `weather' ...
Adding user tj to group weather Done.

Note: addgroup and adduser are Debian-specific commands that simplify the standard useradd and groupadd commands. Additionally, adduser is used in place of usermod for changing an account. On distributions not based on Debian you'll need to figure out and use different commands and options. In particular, do not accidentally remove existing group memberships with the moduser command - make sure to use the append form:

sudo moduser --append --groups weather tj 
# equivalent to moduser -aG weather tj 

If the user is currently logged-in they will need to log-out then log-in before the new group membership will take effect.

groups

tj adm dialout cdrom video plugdev staff users lpadmin admin sambashare weather vm 

Testing Udev Rules

With everything now ready all that is needed is to disconnect and reconnect the device. For static devices (like serial ports) it means either restarting the PC, or more easily, restarting the udev daemon so it applies the new rules:

sudo /etc/init.d/udev restart 

Now start the application from the user account added to the "weather" group and check it can correctly access the device.

If there are problems you can use combinations of "udevadm info -a -p $DEVICE" and "udevadm monitor --environment" to diagnose what is going wrong.