Linux udev rule to create persistent device name

udev is targeted at Linux kernels 2.6 and beyond to provide a userspace solution for a dynamic /dev directory, with persistent device naming. The previous /dev implementation, devfs, is now deprecated, and udev is seen as the successor.

Background: udev and sysfs

The original /dev directories were just populated with every device that might possibly appear in the system. /dev directories were typically very large because of this. devfs came along to provide a more manageable approach (noticeably, it only populated /dev with hardware that is plugged into the system), as well as some other functionality, but the system proved to have problems which could not be easily fixed.

udev is the “new” way of managing /dev directories, designed to clear up some issues with previous /dev implementations, and provide a robust path forward. In order to create and name /dev device nodes corresponding to devices that are present in the system, udev relies on matching information provided by sysfs with rules provided by the user.

sysfs is a new filesystem to the 2.6 kernels. It is managed by the kernel, and exports basic information about the devices currently plugged into your system. udev can use this information to create device nodes corresponding to your hardware. sysfs is mounted at /sys and is browseable.

what udev rule can do?

udev rules are flexible and very powerful. Here are some of the things you can use rules to achieve:

  • Rename a device node from the default name to something else
  • Provide an alternative/persistent name for a device node by creating a symbolic link to the default device node
  • Name a device node based on the output of a program
  • Change permissions and ownership of a device node
  • Launch a script when a device node is created or deleted (typically when a device is attached or unplugged)
  • Rename network interfaces

Built-in persistent naming schemes

udev provides persistent naming for some device types out of the box. This is a very useful feature, and in many circumstances means that your journey ends here: you do not have to write any rules.

udev provides out-of-the-box persistent naming for storage devices in the /dev/disk directory. To view the persistent names which have been created for your storage hardware, you can use the following command:

[root@sg-centos-hv1 dev]# ls -lR /dev/disk/
/dev/disk/:
total 0
drwxr-xr-x. 2 root root 400 Jun 19 10:15 by-id
drwxr-xr-x. 2 root root 180 Jun 19 10:15 by-path
drwxr-xr-x. 2 root root 120 Jun 19 10:15 by-uuid

/dev/disk/by-id:
total 0
lrwxrwxrwx. 1 root root  9 Jun 19 10:16 ata-ST91000640NS_9XG1BQRV -> ../../sda
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 ata-ST91000640NS_9XG1BQRV-part1 -> ../../sda1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 ata-ST91000640NS_9XG1BQRV-part2 -> ../../sda2
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 dm-name-vg_sgcentoshv1-lv_home -> ../../dm-2
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 dm-name-vg_sgcentoshv1-lv_root -> ../../dm-0
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 dm-name-vg_sgcentoshv1-lv_swap -> ../../dm-1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 dm-uuid-LVM-3CQarSxJz8rzmI2nxhj4pKqUFrwrRXkiBpTs4P7cIHC1eetajjlqx2I2PqA9gy3s -> ../../dm-1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 dm-uuid-LVM-3CQarSxJz8rzmI2nxhj4pKqUFrwrRXkiebnL5kGmac6gqEPPF59925qRUMVH24Co -> ../../dm-0
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 dm-uuid-LVM-3CQarSxJz8rzmI2nxhj4pKqUFrwrRXkiqZYut3A0vRuKrOfNR622Jpoa0zNqmfKy -> ../../dm-2
lrwxrwxrwx. 1 root root  9 Jun 19 10:16 scsi-SATA_ST91000640NS_9XG1BQRV -> ../../sda
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 scsi-SATA_ST91000640NS_9XG1BQRV-part1 -> ../../sda1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 scsi-SATA_ST91000640NS_9XG1BQRV-part2 -> ../../sda2
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 usb-Cisco_Virtual_CD_DVD_20080930-1-0:0 -> ../../sr1
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 usb-Cisco_Virtual_FDD_HDD_20080930-1-0:1 -> ../../sdb
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 usb-Cisco_Virtual_Floppy_20080930-1-0:2 -> ../../sdc
lrwxrwxrwx. 1 root root  9 Jun 19 10:16 wwn-0x5000c5004067bd70 -> ../../sda
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 wwn-0x5000c5004067bd70-part1 -> ../../sda1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 wwn-0x5000c5004067bd70-part2 -> ../../sda2

/dev/disk/by-path:
total 0
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 pci-0000:00:1a.7-usb-0:4:1.0-scsi-0:0:0:0 -> ../../sr1
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 pci-0000:00:1a.7-usb-0:4:1.0-scsi-0:0:0:1 -> ../../sdb
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 pci-0000:00:1a.7-usb-0:4:1.0-scsi-0:0:0:2 -> ../../sdc
lrwxrwxrwx. 1 root root  9 Jun 19 10:16 pci-0000:00:1f.2-scsi-0:0:0:0 -> ../../sda
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 pci-0000:00:1f.2-scsi-0:0:0:0-part1 -> ../../sda1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 pci-0000:00:1f.2-scsi-0:0:0:0-part2 -> ../../sda2
lrwxrwxrwx. 1 root root  9 Jun 19 10:15 pci-0000:00:1f.5-scsi-1:0:0:0 -> ../../sr0

/dev/disk/by-uuid:
total 0
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 24a65001-96cb-4eec-9149-a297f2a0bbaf -> ../../sda1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 32b80fc5-9055-4732-8991-1e24d46d0665 -> ../../dm-1
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 e2f3cb08-02be-4d27-89be-4c4a58912886 -> ../../dm-0
lrwxrwxrwx. 1 root root 10 Jun 19 10:15 f77f5f9e-d434-47ed-b310-e6b5fda624f4 -> ../../dm-2
[root@sg-centos-hv1 dev]# 

This works for all storage types.

Rule writing

These udev rule files are kept in the /etc/udev/rules.d directory, and they all must have the .rules suffix, and parsed in lexical order, and in some circumstances, the order in which rules are parsed is important. In general, you want your own rules to be parsed before the defaults, so it is good idea to create a file at /etc/udev/rules.d/10-local.rules and write all your rules into this file.

Rule syntax

Each rule is constructed from a series of key-value pairs, which are separated by commas. match keys are conditions used to identify the device which the rule is acting upon. When all match keys in a rule correspond to the device being handled, then the rule is applied and the actions of the assignment keys are invoked. Every rule should consist of at least one match key and at least one assignment key. e.g. KERNEL==”hdb”, NAME=”my_special_disk”

Basic Rules

udev provides several different match keys which can be used to write rules which match devices very precisely. Some of the most common keys are introduced below, others will be introduced later in this document. For a complete list, see the udev man page.

  • KERNEL - match against the kernel name for the device
  • SUBSYSTEM - match against the subsystem of the device
  • DRIVER - match against the name of the driver backing the device
  • After you have used a series of match keys to precisely match a device, udev gives you fine control over what happens next, through a range of assignment keys. For a complete list of possible assignment keys, see the udev man page. The most basic assignment keys are introduced below. Others will be introduced later in this document.
    • NAME - the name that shall be used for the device node
    • SYMLINK - a list of symbolic links which act as alternative names for the device node
    • As hinted above, udev only creates one true device node for one device. If you wish to provide alternate names for this device node, you use the symbolic link functionality. With the SYMLINK assignment, you are actually maintaining a list of symbolic links, all of which will be pointed at the real device node. To manipulate these links, we introduce a new operator for appending to lists: +=. You can append multiple symlinks to the list from any one rule by separating each one with a space. example #1: KERNEL=="hdb", NAME="my_spare_disk" The above rule says: match a device which was named by the kernel as hdb, and instead of calling it hdb, name the device node as my_spare_disk. The device node appears at /dev/my_spare_disk. example #2: KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk" The above rule says: match a device which was named by the kernel as hdb AND where the driver is ide-disk. Name the device node with the default name and create a symbolic link to it named sparedisk. ### Matching sysfs attributes and find them from sysfs To make the match criteria more granular, usually it is needed to get attributes from sysfs, which is exposed through /sys/. There is linux tool "udevadm" which can retrieve sysfs information and show it in a proper way: e.g. to get a disk /dev/sda info, this can be done below:
      iot@iot-sparrow:~$ udevadm info -help
      udevadm info [OPTIONS] [DEVPATH|FILE]
      
      Query sysfs or the udev database.
      
        -h --help                   Print this message
           --version                Print version of the program
        -q --query=TYPE             Query device information:
             name                     Name of device node
             symlink                  Pointing to node
             path                     sysfs device path
             property                 The device properties
             all                      All values
        -p --path=SYSPATH           sysfs device path used for query or attribute walk
        -n --name=NAME              Node or symlink name used for query or attribute walk
        -r --root                   Prepend dev directory to path names
        -a --attribute-walk         Print all key matches walking along the chain
                                    of parent devices
        -d --device-id-of-file=FILE Print major:minor of device containing this file
        -x --export                 Export key/value pairs
        -P --export-prefix          Export the key name with a prefix
        -e --export-db              Export the content of the udev database
        -c --cleanup-db             Clean up the udev database
      iot@iot-sparrow:~$ 
      iot@iot-sparrow:~$ udevadm info -q all -n /dev/sda
      P: /devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
      N: sda
      S: disk/by-id/ata-WDC_WD2502ABYS-02B7A0_WD-WCAT1H620711
      S: disk/by-id/wwn-0x50014ee1038de847
      S: disk/by-path/pci-0000:00:1f.2-ata-1
      E: DEVLINKS=/dev/disk/by-id/ata-WDC_WD2502ABYS-02B7A0_WD-WCAT1H620711 /dev/disk/by-path/pci-0000:00:1f.2-ata-1 /dev/disk/by-id/wwn-0x50014ee1038de847
      E: DEVNAME=/dev/sda
      E: DEVPATH=/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
      E: DEVTYPE=disk
      E: ID_ATA=1
      E: ID_ATA_DOWNLOAD_MICROCODE=1
      E: ID_ATA_FEATURE_SET_AAM=1
      E: ID_ATA_FEATURE_SET_AAM_CURRENT_VALUE=254
      E: ID_ATA_FEATURE_SET_AAM_ENABLED=1
      E: ID_ATA_FEATURE_SET_AAM_VENDOR_RECOMMENDED_VALUE=128
      E: ID_ATA_FEATURE_SET_HPA=1
      E: ID_ATA_FEATURE_SET_HPA_ENABLED=1
      E: ID_ATA_FEATURE_SET_PM=1
      E: ID_ATA_FEATURE_SET_PM_ENABLED=1
      E: ID_ATA_FEATURE_SET_PUIS=1
      E: ID_ATA_FEATURE_SET_PUIS_ENABLED=0
      E: ID_ATA_FEATURE_SET_SECURITY=1
      E: ID_ATA_FEATURE_SET_SECURITY_ENABLED=0
      E: ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=48
      E: ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=48
      E: ID_ATA_FEATURE_SET_SECURITY_FROZEN=1
      E: ID_ATA_FEATURE_SET_SMART=1
      E: ID_ATA_FEATURE_SET_SMART_ENABLED=1
      E: ID_ATA_ROTATION_RATE_RPM=7200
      E: ID_ATA_SATA=1
      E: ID_ATA_SATA_SIGNAL_RATE_GEN1=1
      E: ID_ATA_SATA_SIGNAL_RATE_GEN2=1
      E: ID_ATA_WRITE_CACHE=1
      E: ID_ATA_WRITE_CACHE_ENABLED=1
      E: ID_BUS=ata
      E: ID_MODEL=WDC_WD2502ABYS-02B7A0
      E: ID_MODEL_ENC=WDC\x20WD2502ABYS-02B7A0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
      E: ID_PART_TABLE_TYPE=dos
      E: ID_PART_TABLE_UUID=00087b41
      E: ID_PATH=pci-0000:00:1f.2-ata-1
      E: ID_PATH_TAG=pci-0000_00_1f_2-ata-1
      E: ID_REVISION=02.03B03
      E: ID_SERIAL=WDC_WD2502ABYS-02B7A0_WD-WCAT1H620711
      E: ID_SERIAL_SHORT=WD-WCAT1H620711
      E: ID_TYPE=disk
      E: ID_WWN=0x50014ee1038de847
      E: ID_WWN_WITH_EXTENSION=0x50014ee1038de847
      E: MAJOR=8
      E: MINOR=0
      E: SUBSYSTEM=block
      E: TAGS=:systemd:
      E: USEC_INITIALIZED=2100399
      
      iot@iot-sparrow:~$ 
      Another example, there are many USB serail adapters are connected to the same linux box, they are named as based on the time it is plugged. So sometimes it is very important to create a persistent name based on the USB port it is connected to.
      iot@iot-sparrow:~$ ls -l /dev/ttyUSB*
      crw-rw---- 1 root dialout 188,  0 Jul 21 15:20 /dev/ttyUSB0
      crw-rw---- 1 root dialout 188,  1 Jul 20 16:15 /dev/ttyUSB1
      crw-rw---- 1 root dialout 188, 10 Jul 21 14:55 /dev/ttyUSB10
      crw-rw---- 1 root dialout 188, 11 Jul 22 22:58 /dev/ttyUSB11
      crw-rw---- 1 root dialout 188, 12 Jul 21 16:32 /dev/ttyUSB12
      crw-rw---- 1 root dialout 188,  2 Jul 23 23:03 /dev/ttyUSB2
      crw-rw---- 1 root dialout 188,  3 Jul 21 12:31 /dev/ttyUSB3
      crw-rw---- 1 root dialout 188,  4 Jul 21 17:23 /dev/ttyUSB4
      crw-rw---- 1 root dialout 188,  5 Jul 23 13:05 /dev/ttyUSB5
      crw-rw---- 1 root dialout 188,  6 Jul 20 16:15 /dev/ttyUSB6
      crw-rw---- 1 root dialout 188,  7 Jul 20 17:02 /dev/ttyUSB7
      crw-rw---- 1 root dialout 188,  8 Jul 21 07:24 /dev/ttyUSB8
      crw-rw---- 1 root dialout 188,  9 Jul 22 19:07 /dev/ttyUSB9
      e.g. I want "/dev/ttyUSB0" named as /dev/UUT03 no matter when it is pluggeed into the box. to achieve that, the following rule is needed:
      #first find the path of USB serail adapter for /dev/ttyUSB0
      iot@iot-sparrow:~$ udevadm info -q path -n /dev/ttyUSB0
      /devices/pci0000:00/0000:00:1d.7/usb2/2-4/2-4.2/2-4.2:1.0/ttyUSB0/tty/ttyUSB0
      
      # second create a udev rule for it
      iot@iot-sparrow:~$ cat /etc/udev/rules.d/10-usbconsoles.rules  | grep UUT03
      KERNELS=="2-4.2:1.0",SUBSYSTEMS=="usb",SYMLINK+="UUT03"
      
      # with rule in place, we have below:
      iot@iot-sparrow:~$ ls -l /dev/UUT03
      lrwxrwxrwx 1 root root 7 Jul 20 16:15 /dev/UUT03 -> ttyUSB0
      Please note if you are adding a new rule, you need possible restart udev by "udevadm control --reload-rules" For network devices, there is no entry under /dev/, but still you can find sysfs info using udevadm command, e.g.
      iot@iot-sparrow:~$ udevadm info -a -p /sys/class/net/enp1s0
      
      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:1e.0/0000:01:00.0/net/enp1s0':
          KERNEL=="enp1s0"
          SUBSYSTEM=="net"
          DRIVER==""
          ATTR{addr_assign_type}=="0"
          ATTR{addr_len}=="6"
          ATTR{address}=="00:1b:21:9b:11:d1"
          ATTR{broadcast}=="ff:ff:ff:ff:ff:ff"
          ATTR{carrier}=="1"
          ATTR{carrier_changes}=="2"
          ATTR{dev_id}=="0x0"
          ATTR{dev_port}=="0"
          ATTR{dormant}=="0"
          ATTR{duplex}=="full"
          ATTR{flags}=="0x1003"
          ATTR{gro_flush_timeout}=="0"
          ATTR{ifalias}==""
          ATTR{ifindex}=="3"
          ATTR{iflink}=="3"
          ATTR{link_mode}=="0"
          ATTR{mtu}=="1500"
          ATTR{name_assign_type}=="4"
          ATTR{netdev_group}=="0"
          ATTR{operstate}=="up"
          ATTR{proto_down}=="0"
          ATTR{speed}=="100"
          ATTR{tx_queue_len}=="1000"
          ATTR{type}=="1"
      
        looking at parent device '/devices/pci0000:00/0000:00:1e.0/0000:01:00.0':
          KERNELS=="0000:01:00.0"
          SUBSYSTEMS=="pci"
          DRIVERS=="e1000"
          ATTRS{broken_parity_status}=="0"
          ATTRS{class}=="0x020000"
          ATTRS{consistent_dma_mask_bits}=="32"
          ATTRS{d3cold_allowed}=="1"
          ATTRS{device}=="0x107c"
          ATTRS{dma_mask_bits}=="32"
          ATTRS{driver_override}=="(null)"
          ATTRS{enable}=="1"
          ATTRS{irq}=="16"
          ATTRS{local_cpulist}=="0-1"
          ATTRS{local_cpus}=="3"
          ATTRS{msi_bus}=="1"
          ATTRS{numa_node}=="-1"
          ATTRS{subsystem_device}=="0x1376"
          ATTRS{subsystem_vendor}=="0x8086"
          ATTRS{vendor}=="0x8086"
      
        looking at parent device '/devices/pci0000:00/0000:00:1e.0':
          KERNELS=="0000:00:1e.0"
          SUBSYSTEMS=="pci"
          DRIVERS==""
          ATTRS{broken_parity_status}=="0"
          ATTRS{class}=="0x060401"
          ATTRS{consistent_dma_mask_bits}=="32"
          ATTRS{d3cold_allowed}=="0"
          ATTRS{device}=="0x244e"
          ATTRS{dma_mask_bits}=="32"
          ATTRS{driver_override}=="(null)"
          ATTRS{enable}=="1"
          ATTRS{irq}=="0"
          ATTRS{local_cpulist}=="0-1"
          ATTRS{local_cpus}=="3"
          ATTRS{msi_bus}=="1"
          ATTRS{numa_node}=="-1"
          ATTRS{subsystem_device}=="0x83ca"
          ATTRS{subsystem_vendor}=="0x1043"
          ATTRS{vendor}=="0x8086"
      
        looking at parent device '/devices/pci0000:00':
          KERNELS=="pci0000:00"
          SUBSYSTEMS==""
          DRIVERS==""
      
      iot@iot-sparrow:~$ 
      From the output, we can see that the device has MAC address "00:1b:21:9b:11:d1", and we could possiblely to make the device name to be something else like "myLan" KERNEL=="eth*", ATTR{address}=="00:1b:21:9b:11:d1", NAME="myLan" ### monitor udev event udevadm can show udev event so that you could learn better about the device.
      iot@iot-sparrow:~$ udevadm monitor -e
      monitor will print the received events for:
      UDEV - the event which udev sends out after rule processing
      KERNEL - the kernel uevent
      # Reference: http://reactivated.net/writing_udev_rules.html