Deconstructing Linux ``udev`` Rules =================================== ``udev`` is one of those pieces of ``Linux`` that is fairly well documented and not very well understood. This note isn't intended as a general introduction to writing ``udev`` rules, but, rather, a brief introduction to the topic by way of specific example. .. NOTE:: The examples and descriptions below assume you are running as ``root``. Most of the commands described will either return nothing or will not work at all unless you are ``root``. Why Bother With ``udev``? ========================= There are many clever uses for ``udev`` documented on the Web, but the most common use is to ensure that when you connect a device - disk, tape, usb thumbdrive, camera... whatever - to a ``Linux`` system, that device shows up with the same name every time. Original ``Unix`` derivatives had a static tree of devices the system could support. This was encoded in the ``/dev`` file tree hierarchy. This was pretty inflexible in the face of devices being added- and removed from the system as it ran. For this reason, modern device handling in ``Linux`` and most other ``Unix`` derivatives is *dynamic* - the content of ``/dev`` changes to reflect the actual state of the system as things get connected or disconnected. (Exactly how this is done is outside the purpose of this document, but if you care, investigate how the ``Linux /sys`` filesystem works.) Our Example Problem =================== While the example below is "cooked", it is very much rooted in real world ``udev`` applications. We want to do the following things: - Identify a specific disk no matter what name it was assigned name under ``/dev``. - Create a symbolic link to that disk so that - no matter what it's name under ``/dev/`` might be at the moment - the symbolic link is always the same. - Change the user and group ownership of that disk to something other than the default (``root:disk``). - Set specific permissions for the disk. - Create a corresponding "raw" character device under ``/dev/raw`` associated with our disk above. Where Do ``udev`` Rules Live? ============================= User created rules - well, created by ``root``, actually - are found in ``/dev/udev/rules.d``. If you look there, you'll see that the files there begin with numbers like ``50`` or ``60``. ``udev`` reads rules in *lexical order*. That means it reads the ``50...`` file before the ``60...`` file before the ``70...`` file and so on. This is important because you have to be careful to insert your rule in early enough in the lexical order so that it can override any subsequent defaults. Unfortunately, because of the way ``udev`` works, rules read later in the lexical order can also *override earlier rules* if we're not careful. We'll see an example of this below, and how to fix it. In our case, we'll create our rules in the file ``15-ExampleRules.rules`` which should pretty much guarantee that our rules will be the first ones read. How Does ``udev`` Read Rules ============================ When ``udev`` first starts, or any time it is informed that rules have been changed, it first reads a set of system-wide default rules in ``/lib/udev/rules.d/``. Then it reads the rules in ``/etc/udev/rules.d``. If you name your own rule file the same as one of system-wide rules, yours will take precedence. There is also a way to install "temporary" rules, but the location for such rules is distro-specific. Ordinarily, the running ``udev`` daemon is automatically informed that a rule file has changed and it will reread them all again when this happens. You can also force a rule reload with:: udevadm control --reload-rules Another way to do this is to restart the ``udev`` daemon or reboot to get the latest rules read in. Note that the daemon restart procedure is also distro-specific, so you'll have to figure out what works on your system. Our Example Rules ================= We need two rules to achieve our goals above. Notice that the first rule below is broken across multiple lines to make it more readable, but it is all on one line in the actual rules file. It is possible to break rules across lines but you have to ensure that you follow the syntax that ``udev`` expects. To keep things from mysteriously breaking, I typically put the entire rule one one line:: KERNEL=="sd*", PROGRAM=="/sbin/scsi_id --whitelisted /dev/$name", RESULT=="1ATA_VBOX_HARDDISK_VB5f712327-2bb4be0c", SYMLINK+="my_fine-disk01", OWNER:="3009", GROUP:="421", MODE:="0600", RUN=="/bin/raw /dev/raw/raw1 /dev/$name" KERNEL=="raw1", SYMLINK+="rmy_fine-disk01", OWNER:="3009", GROUP:="421", MODE:="0600" What Does All This Mean? ======================== Rules are made up of key-value pairs separated by an operator. These key-value pairs are separated by commas. Key-value pairs either *match* or *assign*. Match key-value pairs check to see if a particular thing "matches" what we're looking for. Think of them as ``if`` statements in a programming language. Assignment key-value statements take some sort of action * usually on the thing that was previously matched*. But, you're not restricted to this. It's entirely possible to write a rule that operates on something completely unrelated to the matched condition. For instance, you could write a rule that says, *reboot the computer everytime my little brother plugs in his favorite thumbdrive*. (This is, however, considered very bad manners and may get you sent to your room without dinner.) Let's take each rule apart, one key-value pair at a time: - ``KERNEL=="sd*"`` This matches any time the kernel emits a message with the string ``sd`` followed by anything. For example, the kernel sending messages about ``sda``, ``sdb``, ``sdc`` and so on would all match. Why are we doing this? - ``PROGRAM=="/sbin/scsi_id --whitelisted /dev/$name"`` - ``RESULT=="1ATA_VBOX_HARDDISK_VB5f712327-2bb4be0c"`` - ``SYMLINK+="my_fine-disk01"`` - ``OWNER:="3009", GROUP:="421", MODE:="0600,`` - ``RUN=="/bin/raw /dev/raw/raw1 /dev/$name"`` Now, let's look at the second rule: - ``KERNEL=="raw1"`` - ``SYMLINK+="rmy_fine-disk01"`` - ``OWNER:="3009", GROUP:="421", MODE:="0600`` Final Thoughts ============== Obviously, you'd have to have another pair of rules for each additional disk you want to manage this way. Adding another disk would be a matter of using ``scsi_id`` to get its ``wwid`` for the ``RESULT`` field of the first rule. You'd also have to change any references to ``my_fine_disk01`` and ``raw1``. For reasons that are not entirely clear (to me anyway), the ``raw`` command only knows how to create raw devices whose names begin with ``raw``, go figure. Another way to get a unique ID for a device is to tail your system log (``tail -f /var/log/messages`` or ``tail -f /var/log/syslog``) and watch what happens when you plug your device into, say, a USB port. If you want to know all the attributes ``udev`` knows about a particular device, use this, substituting your device for ``/dev/sdd`` :: udevadm info --query=all --name /dev/sdd 2>&1| less The output of this command can be helful in figuring out just which attributes and values you need to get to a running rule. Finally, you can test your rules to see what is matching, again substituting for ``/block/sdd``:: udevadm test /block/sdd 2>&1| less Author ====== Tim Daneliuk - tundra@tundraware.com Comments, corrections, clarifications, and/or improvements welcome! Document Revision Information ============================= ``$Id: Deconstructing_Linux_udev_Rules.rst,v 1.109 2013/11/01 00:08:30 tundra Exp $`` You can find the latest version of this document at: http://www.tundraware.com/TechnicalNotes/Deconstructing-Linux-udev-Rules