A udev rule for my USB stick (disk-on-key)
Introduction
I use USB flash sticks for backing up my system periodically by creating an image of the filesystem, and raw-writing it directly to e.g. /dev/sdd1. It’s just a matter of time before I wipe my hard disk by selecting the wrong /dev/something. Or just some other USB stick that happened to be plugged in.
To avoid this, I wrote a udev rule for detecting the two specific USB devices that I use for backing up, and change their ownership, so I won’t be root while doing this (this protects the real disks somewhat). Also, rename the device file to something that can’t be confused with anything else. Ehm, it’s 2018, and systemd’s udev machinery don’t agree renaming /dev files. So add a symbolic link instead. It’s as good, as long as I don’t have to write /dev/sdd-something in the command wiping whatever’s there.
Writing udev rules is well-documented all over the web, but I still wrote down my own quick summary, so I have it handy for the next time. Well, actually twice. The first time was in 2011, and then in 2018, both are on this page. So this is two posts in one.
On systemd-enabled system (that’s all of them by 2018?), the system’s udev rules reside in /lib/udev/. The rules in /etc/udev take precedence, and it’s still the correct place to put local rules. Just don’t be surprised that it’s empty.
And I have another post with udev hacking (and one related to NICs). But now, let’s start with the 2018 post. Updated to Linux Mint 19, with systemd all over the place (and I love it, frankly).
And a little tip to myself and others: man udev. Really. Give it a go every now and then.
Obtaining matching parameters
The built-in udev rules in /lib/udev/rules.d set up a lot of environment variables, which are, among others, printed by e.g.
# udevadm info -q all -n /dev/sdd1
So don’t. It’s better to stick to the actual attributes, as given by
# udevadm info -a -n /dev/sdd1
Note that each segment of this output is a separate device, and hence a separate udev event. All matches in the udev rule must relate to one segment only.
Building the rule
The udevadm info command outputs a very important comment, which is worth repeating:
A rule to match can be composed by the attributes of the device and the attributes from one single parent device.
Based upon the info obtained with udevadm info (see just below), the following rule was set up:
KERNEL=="sd?1", SUBSYSTEM=="block", ATTRS{idProduct}=="5591", ATTRS{idVendor}=="0781", ATTRS{serial}=="1234567890", OWNER:="eli", SYMLINK+="backupstick_a"
One may ask why I bothered to add the match against KERNEL==”sd?1″ and SUBSYSTEM==”block”.
Without these, the match works against e.g. /dev/sdd and /dev/sdd1, even though the symbolic link ends up pointing at /dev/sdd1. However the ownership is set for both, clearly indicating that both sdd and sdd1 were processed. Having some trust in udev, I would expect it to behave in a consistent manner on this, but I don’t want to rely on it. Hence the rule is specific on a first partition (I could have gone ATTR{partition}==”1″ as well, but why).
So the rule above takes advantage of the comment in bold above: Matching from a device and its parent. And it works.
Testing the rules
Note that “udevadm test” performs the actions for real. It’s not a dry run.
There no need for any reload command for testing the rule as follows:
# udevadm test -a add $(udevadm info -q path /dev/sdd) 2>&1 | less
But in order to get it working, a reload is required:
# udevadm control --reload
Picking the correct subsystem
The SUBSYSTEM match determines which device file the rule will apply to. For example, a USB fax/modem device appears both under the “usb” subsystem, which relates to the /dev/bus/usb/XXX/XXX device, and the “tty” subsystem, in which case it goes with the e.g. /dev/ttyACM0 device.
Likewise, when testing, the rule will take effect on one of the device files, and not the other.
The SYMLINK+= command can be useful to tell what device file is related to, but if the symlink is created, odds are we got it right anyhow…
udevadm info of this case
# udevadm info -a -n /dev/sdd1 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:14.0/usb1/1-2/1-2:1.0/host8/target8:0:0/8:0:0:0/block/sdd/sdd1': KERNEL=="sdd1" SUBSYSTEM=="block" DRIVER=="" ATTR{alignment_offset}=="0" ATTR{discard_alignment}=="0" ATTR{inflight}==" 0 0" ATTR{partition}=="1" ATTR{ro}=="0" ATTR{size}=="242614240" ATTR{start}=="32" ATTR{stat}==" 131 0 6184 308 0 0 0 0 0 204 308" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/host8/target8:0:0/8:0:0:0/block/sdd': KERNELS=="sdd" SUBSYSTEMS=="block" DRIVERS=="" ATTRS{alignment_offset}=="0" ATTRS{capability}=="51" ATTRS{discard_alignment}=="0" ATTRS{events}=="media_change" ATTRS{events_async}=="" ATTRS{events_poll_msecs}=="-1" ATTRS{ext_range}=="256" ATTRS{hidden}=="0" ATTRS{inflight}==" 0 0" ATTRS{range}=="16" ATTRS{removable}=="1" ATTRS{ro}=="0" ATTRS{size}=="242614272" ATTRS{stat}==" 145 0 6296 340 0 0 0 0 0 228 340" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/host8/target8:0:0/8:0:0:0': KERNELS=="8:0:0:0" SUBSYSTEMS=="scsi" DRIVERS=="sd" ATTRS{blacklist}=="" ATTRS{device_blocked}=="0" ATTRS{device_busy}=="0" ATTRS{dh_state}=="detached" ATTRS{eh_timeout}=="10" ATTRS{evt_capacity_change_reported}=="0" ATTRS{evt_inquiry_change_reported}=="0" ATTRS{evt_lun_change_reported}=="0" ATTRS{evt_media_change}=="0" ATTRS{evt_mode_parameter_change_reported}=="0" ATTRS{evt_soft_threshold_reached}=="0" ATTRS{inquiry}=="" ATTRS{iocounterbits}=="32" ATTRS{iodone_cnt}=="0x2ac" ATTRS{ioerr_cnt}=="0x1" ATTRS{iorequest_cnt}=="0x2ac" ATTRS{max_sectors}=="240" ATTRS{model}=="Ultra USB 3.0 " ATTRS{queue_depth}=="1" ATTRS{queue_type}=="none" ATTRS{rev}=="1.00" ATTRS{scsi_level}=="7" ATTRS{state}=="running" ATTRS{timeout}=="30" ATTRS{type}=="0" ATTRS{vendor}=="SanDisk " looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/host8/target8:0:0': KERNELS=="target8:0:0" SUBSYSTEMS=="scsi" DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/host8': KERNELS=="host8" SUBSYSTEMS=="scsi" DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0': KERNELS=="1-2:1.0" SUBSYSTEMS=="usb" DRIVERS=="usb-storage" ATTRS{authorized}=="1" ATTRS{bAlternateSetting}==" 0" ATTRS{bInterfaceClass}=="08" ATTRS{bInterfaceNumber}=="00" ATTRS{bInterfaceProtocol}=="50" ATTRS{bInterfaceSubClass}=="06" ATTRS{bNumEndpoints}=="02" ATTRS{supports_autosuspend}=="1" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-2': KERNELS=="1-2" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{authorized}=="1" ATTRS{avoid_reset_quirk}=="0" ATTRS{bConfigurationValue}=="1" ATTRS{bDeviceClass}=="00" ATTRS{bDeviceProtocol}=="00" ATTRS{bDeviceSubClass}=="00" ATTRS{bMaxPacketSize0}=="64" ATTRS{bMaxPower}=="224mA" ATTRS{bNumConfigurations}=="1" ATTRS{bNumInterfaces}==" 1" ATTRS{bcdDevice}=="0100" ATTRS{bmAttributes}=="80" ATTRS{busnum}=="1" ATTRS{configuration}=="" ATTRS{devnum}=="10" ATTRS{devpath}=="2" ATTRS{idProduct}=="5591" ATTRS{idVendor}=="0781" ATTRS{ltm_capable}=="no" ATTRS{manufacturer}=="SanDisk" ATTRS{maxchild}=="0" ATTRS{product}=="Ultra USB 3.0" ATTRS{quirks}=="0x0" ATTRS{removable}=="removable" ATTRS{serial}=="1234567890" ATTRS{speed}=="480" ATTRS{urbnum}=="1580" ATTRS{version}==" 2.10" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1': KERNELS=="usb1" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{authorized}=="1" ATTRS{authorized_default}=="1" ATTRS{avoid_reset_quirk}=="0" ATTRS{bConfigurationValue}=="1" ATTRS{bDeviceClass}=="09" ATTRS{bDeviceProtocol}=="01" ATTRS{bDeviceSubClass}=="00" ATTRS{bMaxPacketSize0}=="64" ATTRS{bMaxPower}=="0mA" ATTRS{bNumConfigurations}=="1" ATTRS{bNumInterfaces}==" 1" ATTRS{bcdDevice}=="0415" ATTRS{bmAttributes}=="e0" ATTRS{busnum}=="1" ATTRS{configuration}=="" ATTRS{devnum}=="1" ATTRS{devpath}=="0" ATTRS{idProduct}=="0002" ATTRS{idVendor}=="1d6b" ATTRS{interface_authorized_default}=="1" ATTRS{ltm_capable}=="no" ATTRS{manufacturer}=="Linux 4.15.0-20-generic xhci-hcd" ATTRS{maxchild}=="16" ATTRS{product}=="xHCI Host Controller" ATTRS{quirks}=="0x0" ATTRS{removable}=="unknown" ATTRS{serial}=="0000:00:14.0" ATTRS{speed}=="480" ATTRS{urbnum}=="134" ATTRS{version}==" 2.00" looking at parent device '/devices/pci0000:00/0000:00:14.0': KERNELS=="0000:00:14.0" SUBSYSTEMS=="pci" DRIVERS=="xhci_hcd" ATTRS{broken_parity_status}=="0" ATTRS{class}=="0x0c0330" ATTRS{consistent_dma_mask_bits}=="64" ATTRS{d3cold_allowed}=="1" ATTRS{dbc}=="disabled" ATTRS{device}=="0xa2af" ATTRS{dma_mask_bits}=="64" ATTRS{driver_override}=="(null)" ATTRS{enable}=="1" ATTRS{irq}=="35" ATTRS{local_cpulist}=="0-11" ATTRS{local_cpus}=="0,00000000,00000fff" ATTRS{msi_bus}=="1" ATTRS{numa_node}=="0" ATTRS{revision}=="0x00" ATTRS{subsystem_device}=="0x5007" ATTRS{subsystem_vendor}=="0x1458" ATTRS{vendor}=="0x8086" looking at parent device '/devices/pci0000:00': KERNELS=="pci0000:00" SUBSYSTEMS=="" DRIVERS==""
The post from 2011 starts here
Following this excellent guide, I plugged in the USB stick and went
$ udevadm info -a -n /dev/sdd
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.
On another computer a similar command could be
$ udevinfo -q all -n /dev/sda
The output from the first command was:
looking at device '/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host15/target15:0:0/15:0:0:0/block/sdd': KERNEL=="sdd" SUBSYSTEM=="block" DRIVER=="" ATTR{range}=="16" ATTR{ext_range}=="256" ATTR{removable}=="1" ATTR{ro}=="0" ATTR{size}=="7821312" ATTR{alignment_offset}=="0" ATTR{discard_alignment}=="0" ATTR{capability}=="51" ATTR{stat}==" 49 438 3182 274 0 0 0 0 0 127 274" ATTR{inflight}==" 0 0" looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host15/target15:0:0/15:0:0:0': KERNELS=="15:0:0:0" SUBSYSTEMS=="scsi" DRIVERS=="sd" ATTRS{device_blocked}=="0" ATTRS{type}=="0" ATTRS{scsi_level}=="3" ATTRS{vendor}=="SanDisk " ATTRS{model}=="Cruzer Blade " ATTRS{rev}=="1.01" ATTRS{state}=="running" ATTRS{timeout}=="30" ATTRS{iocounterbits}=="32" ATTRS{iorequest_cnt}=="0x1a8" ATTRS{iodone_cnt}=="0x1a8" ATTRS{ioerr_cnt}=="0x1" ATTRS{modalias}=="scsi:t-0x00" ATTRS{evt_media_change}=="0" ATTRS{dh_state}=="detached" ATTRS{queue_depth}=="1" ATTRS{queue_type}=="none" ATTRS{max_sectors}=="240" looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host15/target15:0:0': KERNELS=="target15:0:0" SUBSYSTEMS=="scsi" DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host15': KERNELS=="host15" SUBSYSTEMS=="scsi" DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0': KERNELS=="2-1:1.0" SUBSYSTEMS=="usb" DRIVERS=="usb-storage" ATTRS{bInterfaceNumber}=="00" ATTRS{bAlternateSetting}==" 0" ATTRS{bNumEndpoints}=="02" ATTRS{bInterfaceClass}=="08" ATTRS{bInterfaceSubClass}=="06" ATTRS{bInterfaceProtocol}=="50" ATTRS{modalias}=="usb:v0781p5567d0100dc00dsc00dp00ic08isc06ip50" ATTRS{supports_autosuspend}=="0" looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb2/2-1': KERNELS=="2-1" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{configuration}=="" ATTRS{bNumInterfaces}==" 1" ATTRS{bConfigurationValue}=="1" ATTRS{bmAttributes}=="80" ATTRS{bMaxPower}=="200mA" ATTRS{urbnum}=="938" ATTRS{idVendor}=="0781" ATTRS{idProduct}=="5567" ATTRS{bcdDevice}=="0100" ATTRS{bDeviceClass}=="00" ATTRS{bDeviceSubClass}=="00" ATTRS{bDeviceProtocol}=="00" ATTRS{bNumConfigurations}=="1" ATTRS{bMaxPacketSize0}=="64" ATTRS{speed}=="480" ATTRS{busnum}=="2" ATTRS{devnum}=="8" ATTRS{devpath}=="1" ATTRS{version}==" 2.00" ATTRS{maxchild}=="0" ATTRS{quirks}=="0x0" ATTRS{avoid_reset_quirk}=="0" ATTRS{authorized}=="1" ATTRS{manufacturer}=="SanDisk" ATTRS{product}=="Cruzer Blade" ATTRS{serial}=="200610803009F0712206" looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb2': KERNELS=="usb2" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{configuration}=="" ATTRS{bNumInterfaces}==" 1" ATTRS{bConfigurationValue}=="1" ATTRS{bmAttributes}=="e0" ATTRS{bMaxPower}==" 0mA" ATTRS{urbnum}=="4053" ATTRS{idVendor}=="1d6b" ATTRS{idProduct}=="0002" ATTRS{bcdDevice}=="0206" ATTRS{bDeviceClass}=="09" ATTRS{bDeviceSubClass}=="00" ATTRS{bDeviceProtocol}=="00" ATTRS{bNumConfigurations}=="1" ATTRS{bMaxPacketSize0}=="64" ATTRS{speed}=="480" ATTRS{busnum}=="2" ATTRS{devnum}=="1" ATTRS{devpath}=="0" ATTRS{version}==" 2.00" ATTRS{maxchild}=="8" ATTRS{quirks}=="0x0" ATTRS{avoid_reset_quirk}=="0" ATTRS{authorized}=="1" ATTRS{manufacturer}=="Linux 2.6.35.4-OCHO1 ehci_hcd" ATTRS{product}=="EHCI Host Controller" ATTRS{serial}=="0000:00:1d.7" ATTRS{authorized_default}=="1" looking at parent device '/devices/pci0000:00/0000:00:1d.7': KERNELS=="0000:00:1d.7" SUBSYSTEMS=="pci" DRIVERS=="ehci_hcd" ATTRS{vendor}=="0x8086" ATTRS{device}=="0x3b34" ATTRS{subsystem_vendor}=="0x1458" ATTRS{subsystem_device}=="0x5006" ATTRS{class}=="0x0c0320" ATTRS{irq}=="23" ATTRS{local_cpus}=="00000000,00000000,00000000,00000000,00000000,00000000,00000000,000000ff" ATTRS{local_cpulist}=="0-7" ATTRS{modalias}=="pci:v00008086d00003B34sv00001458sd00005006bc0Csc03i20" ATTRS{numa_node}=="-1" ATTRS{dma_mask_bits}=="32" ATTRS{consistent_dma_mask_bits}=="32" ATTRS{broken_parity_status}=="0" ATTRS{msi_bus}=="" ATTRS{companion}=="" looking at parent device '/devices/pci0000:00': KERNELS=="pci0000:00" SUBSYSTEMS=="" DRIVERS==""
From which I extracted the idVendor, idProduct and most important, serial attributes, marked in red above.
So I generated /etc/udev/rules.d/10-local-usbstick-rules saying:
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0781", ATTRS{idProduct}=="5567", ATTRS{serial}=="200610803009F0712206", NAME="usbstick_trials", OWNER="eli"
Note that I set myself as the owner of the device file. This means that the data on the USB stick is not so safe, because I can accidentally write data directly to the device file. The upside is that root privileges are not necessary, so the chances for messing up a real hard disk are significantly diminished.
That’s it. /dev/sdd doesn’t appear now, and neither do partition device files, so it’s a bit more difficult to mount the disk (but there’s a trick). On the other hand, raw writing to it just became much safer.
Plug out, plug in again and we have:
$ ls -l /dev/usbstick_trials brw-rw----. 1 eli disk 8, 48 2011-06-16 17:54 /dev/usbstick_trials
On the other computer (Centos 5.5) the rule was /etc/udev/rules.d/90-local-imagedisk.rules as follows:
KERNEL=="sda1", ENV{ID_SERIAL}=="SATA_WDC_WD5000AAKX-_WD-WCAYUFH69712", NAME="playdisk", OWNER="eli", MODE="666", OPTIONS="last_rule"
The reason for placing the rule late is to allow 50-udev.rules to set up the ID_SERIAL environment variable, which is created from the disk’s serial number. So no other disk gets messed up this way accidentally
Alternative: Monitor events
# udevadm monitor --udev --property
This prints out the udev events as they happen. The relevant info is there too. Useful when you’re not sure which device belongs to what, but a plug-in event makes it very clear. Unfortunately, it doesn’t print out any actions udev takes as it applies the rules.
Reader Comments
“/dev/sdd doesn’t appear now, and neither do partition device files, so it’s a bit more difficult to mount the disk”
can’t you just add an additional rule for the partitions, with smth like
SUBSYSTEM==”block”, ATTR{partition}==”1″, ATTRS{serial}==”200610803009F0712206″, NAME=”usbstick_trials1″, SYMLINK+=”%k”
That sounds like a good idea: Doing the raw writing to a device file with a distinct name, and then mount a classic device. Thanks.
Thanks, interesting post. Maybe instead of OWNER:= it should say OWNER=
The emphasis on “maybe”: According to man udev:
“=”
Assign a value to a key. Keys that represent a list are reset and only this single value is assigned.
“:=”
Assign a value to a key finally; disallow any later changes.