linux:udev

udev ist ein Programm, mit welchem der Linux-Kernel Gerätedateien für die Datenein- und -ausgabe (Input/Output) verwaltet.

udev ersetzt seit dem Kernel 2.6 das früher genutzte devfs-Dateisystem, dessen Aufgaben es damit übernimmt. Genauso wie devfs verwaltet udev das /dev-Verzeichnis, welches die speziellen Gerätedateien enthält, um von Programmen aus auf die vom System zur Verfügung gestellten Geräte zuzugreifen.

Mit der Einführung von udev waren sowohl udev als auch devfs im Kernel enthalten. Seit Ende Juni 2006 ist nur noch udev enthalten, devfs wurde vollständig entfernt.

Quellen: aus Wikipedia, der freien Enzyklopädie. Der Rest dieser Seite ist leicht abgeändert von frappuccino.pvb.li entnommen, das dort und hier unter der CC BY-NC-SA 2.0 zur Verfügung gestellt wird.

udev rules sind sehr flexibel. Einige Beispiele, was man mit udev rules anstellen kann:

  • Ein device node vom Standardnamen in irgendwas anderes umbenennen
  • Einen alternativen / persistenten Namen für ein device node durch einen symbolischen Link zum default device node erstellen
  • Ein device node based nach der Ausgabe eines Programmes benennen
  • Berechtigungen und Besitzer eins device node ändern
  • Ein Skript starten, wenn ein device node erstellt oder gelöscht wurde (typischerweise wenn ein device angeschlossen oder abgezogen wird)
  • Netzwerk-interfaces umbenennen

Nehmen wir an, wir haben zwei USB devices: Eine Digitalkamera und einen USB-Stick. Diese devices werden normalerweise zu den device nodes /dev/sda und /dev/sdb zugewiesen, aber die exakte Zuweisung hängt davon ab, in welcher Reihenfolge die beiden original angeschlossen wurden. Das könnte zu Problemen mit Usern führen und es wäre doch schön, wenn diese Geräte permanent nach z.b /dev/camera und /dev/flashdisk gemappt würden

Desweiteren ist es bestimmt sinnvoll, wenn man das Debian-Wiki ebenfalls noch durchliest, es gibt nämlich kleine Unterschiede bei Debian (zb. udevinfo heißt udevadm info usw.). Die meisten Informationen sind von: Source-Artikel.

Wenn man sich entscheidet, wie man ein device benennen will und welche zusätzliche Aktionen beim Anhängen durchgeführt werden sollen, liest udev eine Serie von Rule-files. Diese finden sich im /etc/udev/rules.d directory. Alle Rules benötigen den .rules-Suffix.

Die default udev rules sind unter /etc/udev/rules.d/50-udev.rules zu finden. Hier finden sich auch ein paar Beispiele und einige Rules beinhalten auch ein devfs-style /dev layout. Man sollte aber nie irgendwelche eigenen Rules hier hineinschreiben.

Die Files in /etc/udev/rules.d/ werden in lexical Reihenfolge geparst und in einigen Umständen ist auch die Reihenfolge, wie die Rules abgearbeitet werden, von Bedeutung. Generell, wenn man möchte, dass die eigenen Rules vor den defaults geparst werden, könnte man ein file /etc/udev/rules.d/10-local.rules erstellen und alle eigenen Rules dort einstellen. Rules können sich übrigens nicht über mehrere Zeilen erstrecken.

Ein Device kann von mehr als einer Rule gematched werden. Das hat praktische Gründe, zum Beispiel kann man zwei Rules für dasselbe Device schreiben, wobei jede Rule dem Device einen eigenen Namen zuweist. Es werden dann beide alternativen Namen erstellt, auch wenn die Rules sich in verschiedenen Rule-files abgelegt sind. udev wendet jede Regel an, die es für eine Device findet und stoppt nicht, nachdem eine Regel auf ein Device gmatcht hat.

Jede Regel wird von Serien aus Keys-values Paaren erstellt, die durch Kommas getrennt sind. Match-Keys sind die Bedingungen, die verwendet werden umd eine Device zu identifizieren. Wenn alle match-keys in einer Rule zu einem Device passen, wird die Rule angewendet. Also macht es Sinn, dass jede Rule mindestens einen Match-Key und dazugehörende Zuweisung hat.. ;-)

Als Beispiel:

KERNEL=="hdb", NAME="my_spare_disk"

Das Beispiel hat also einen Match-Key (KERNEL) und einen Zuweisungs-Key (NAME). Wichtig ist, dass der match key seinen Wert durch das doppelte Gleichheitszeichen (==) erhält, der assignment key aber nur durch ein einzelnes Gleichheitszeichen (=).

Es ist darauf zu achten, dass udev keinen Support von irgendeiner Form von Zeilenfortführung unterstützt. Also keine line breaks in die Regeln einbauen, denn udev wird diese als eine weitere Rule ansehen und nicht so fortfahren, wie man es sich wahrscheinlich wünscht…
(Anm.: Bin mir jetzt nicht wirklich sicher, ob das noch aktuell ist, wenn ich mir so die bestehenden Rules anschaue…)

udev unterstützt viele match-keys, die verwendet werden können in rules. Die meist verwendeten hier.
Eine komplette Liste findet man in der udev man page.

  • KERNEL - passend zum kernel Name für ein Device
  • SUBSYSTEM - passend zum subsystem von einem Device
  • DRIVER - passend zum Namen des Treibers von einem device

Nachdem man also per match-keys ein Device 'ausfindig' gemacht hat, kann man durch assignment-keys festlegen, was damit passieren soll. Eine komplette Liste findet man in der udev man page. Die basic assignment-keys:

  • NAME - der Name, der für das device node verwendet werden soll
  • SYMLINK - eine Liste von symbolischen Links, die als alternative Namen für ein device node dienen sollen

Es gilt also festzustellen, dass udev nur einen wirklichen device node für ein device erstellt. Möchte man alternative Namen für ein device node, muss die SYMLINK Funktionalität verwendet werden. Mit dem SYMLINK assignment unterhält man eine Liste von symbolischen links, die alle auf den echten device noden zeigen. Um diese Links anzupassen, verwendet man +=. Damit lassen sich mehrere symlinks zu einem device node anhängen. Jeder Link wird durch ein Leerzeichen abgetrennt.

Ein Device, welches vom Kernel hdb benannt wurde anstatt hdb nun einen device node mit dem Namen my_spare_disk erstellen. Der device node erscheint unter /dev/my_spare_disk:

KERNEL=="hdb", NAME="my_spare_disk"

Ein Device, welches vom Kernel hdb benannt wurde UND der Treiber ide-disk ist. Der Name des Device nodes bleibt default, aber es wird ein Symbolischer Link mit dem Namen sparedisk erstellt. Hier wurde kein device node Name spezifiert, also verwendet udev den default. In Anbetracht, dass man das standard /dev-layout nicht verändern will, lässt man typischerweise den NAMEN beim default und erstellt einfach einige SYMLINKs und/oder führt weitere Assignments aus:

KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"

Das ist eine recht typische udev-rule die häufig verwendung findet. Es erstellt zwei Symbolische Links (/dev/cdrom und /dev/cdrom0) und beide zeigen nach /dev/hdc. Auch hier wurde kein NAME assignment durchgeführt, also wird der default kernel name (hdc) verwendet:

KERNEL=="hdc", SYMLINK+="cdrom cdrom0"

Mit den match-keys haben wir soweit nur eingeschränkte Möglichkeiten. In der Realität wünscht man sich oft die feinere Kontrolle: Man will devices nach erweiterten Eigenschaften wie vendor codes, exakter Produktnummer, Seriennummer, Speicherkapazität, Anzahl der Partitionen usw auslesen. Viele Treiber exportieren diese Informationen nach sysfs und udev erlaubt es diese Informationen per sysfs-matching in Regeln einzubinden, indem man den ATTR key mit einer leicht veränderten Synatx verwendet.

Eine Beispiel-Rule die ein einzelnes Attribut per sysfs ausliest:

SUBSYSTEM=="block", ATTR{size}=="234441648", SYMLINK+="my_disk"

Der Linux-Kernel repräsentiert die devices in einer baum-ähnlichen Struktur und diese wird von sysfs zur Verfügung gestellt. Als Beispiel, die device Repräsentation von einer Festplatte ist das child von einem SCSI disk device, dieses wiederum ist ein child vom Serial ATA controller device, und dieses ist wiederum ein child vom PCI bus device. Die Seriennummer von der Harddisk zum Beispiel wird nicht beim device-level sondern beim parent am SCSI disk level angegeben.

Die vier Haupt-Match-Keys (KERNEL/SUBSYSTEM/DRIVER/ATTR) matchen nur auf Werte beim device, nicht aber auf Werte vom Parent-Device. udev bietet Varianten von match-keys die auch nach oben im device-tree matchen:

  • KERNELS - passend zum kernel Namen eines device, oder zum kernel Name eines parent devices
  • SUBSYSTEMS - passend zum subsystem eines device, oder zum subsystem eines parent devices
  • DRIVERS - passend zum Namen eines Treibers des device, oder zum Namen eines Treibers des parent devices
  • ATTRS - passend zum sysfs Attribut des device, oder zum sysfs Attribut eines parent devices

In Betrachtung der Hierarchie scheinen die Rules schon ein kleines bisschen komplizierter

Wenn man Rules schreibt wird man eventuell mehrere ähnliche Devices vorfinden. udev's printf-ähnliches String-Replacment ist da sehr hilfreicht. Man kann einfach diese Operatoren in Zuweisungen der Rules aufnehmen und udev wird diese auswerten.

Die meist verwendeten Operatoren sind %k und %n.
%k ermittelt den kernel Namen für ein device, zB. „sda3“ für ein device, das (by default) auf /dev/sda3 erscheint.
%n ermittelt die Kernel Nummer für ein device (Die Partitionsnummer eines Speichergerätes), zB. „3“ für /dev/sda3.

udev unterstützt natürlich weitere Operatoren für erweiterte Abfragen. Diese findet man in der udev man page. Es gibt auch eine alternative Synatx - $kernel und $number für die obigen Beispiele. Möchte man auf ein % matchen, muss %% verwendet werden, möchte man auf $ matchen, analog dazu $$.

Um das zu veranschaulichen, hier ein Beispiel:

KERNEL=="mice", NAME="input/%k"
KERNEL=="loop0", NAME="loop/%n", SYMLINK+="%k"

Die erste Rule checkt, dass der Maus-Device node exklusiv im /dev/input erscheint (by default würde es /dev/mice sein). Die zweite Rule checkt, dass der device node mit dem Namen loop0 unter /dev/loop/0 erstellt wird und erstellt ebenso einen symbolischen link unter /dev/loop0.

Die Verwendung im obigen beispiel ist jedoch fraglich, denn diese Regeln hätten auch ohne Substitutions gemacht werden können. Besseren Einsatz dieser weiter unten….

Genauso wie genaues String-matching erlaubt udev auch shell-style pattern matching. Diese 3 patterns werden unterstützt:

  • * - passend zu jedem Zeichen, das null oder mehrere mal vorkommt
  • ? - passend zu jedem Zeichen das nur einmal vorkommt
  • [] - passend zu einem einzelnen Zeichen (spezifiziert in Klammer), ranges sind erlaubt

Beispiele zu den obigen Zeichen. Es werden auch substitution Operatoren verwendet:

KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"

Die erste Rule matched auf alle Floppy-Disk drivers und stellt sicher, dass die devices nodes unter /dev/floppy erstellt werden und ein Symbolischer Link zum default Namen erstellt wird.
Die zweite Rule stellt sicher, dass hiddev devices nur im /dev/usb directory vorhanden sind.

Um rules basierend auf sysfs Informationen schreiben zu können, muss man zuerst die Namen der Attribute und deren Werte kennen.

Sysfs hat eigentlich eine sehr simple Struktur. Es ist logisch aufgeteilt in Verzeichnisse. Jedes Verzeichnis beinhaltet eine Anzahl von Dateien (Attribute) wobei jede typischerweise nur einen Wert beinhaltet. Einige symbolische Links sind vorhanden, die auf die device parents verweisen (siehe weiter oben)

Einige Verzeichnisse verweisen zu Top-Level device Pfaden. Diese Verzeichnisse repräsentierien aktuelle Devices mit entsprechenden device nodes. Top-level device Pfade können als sysfs Verzeichnisse klassifiziert werden, die ein dev file beinhalten. Mit diesem Kommando kann man diese auflisten:

find /sys -name dev

Als Beipsiel, das /sys/block/sda Verzeichnis ist der device Pfad für meine Harddisk. Er ist durch einen symbolischen Link /sys/block/sda/device zu seinem Parent verlinkt, dem SCSI disk device.

Wenn man also rules basierend auf den sysfs Informationen schreibt, matcht man einfach die Attribute des Inhalts von einigen Files in einen Teil von der Kette. Zum Beispiel lese ich die Grüsse meiner Harddisk wie folgt:

cat /sys/block/sda/size
234441648

In einer udev rule könnte ich also ATTR{size}==„234441648“ verwenden, um die Disk zu identifizieren. Da sich aber udev durch die ganze device Kette wiederholt, könnte ich alternativ auch in einem anderen Teil der Kette mit ATTRS suchen (zb. Attribute in /sys/class/block/sda/device/). Es gibt jedoch ein paar Stolperstellen mit solchen verschiedenen Stellen auf die später eingegangen wird.

Obwohl dies nützlich und interessant ist, ist ein manuelles durchforsten durch sysfs zeitaufwändig und unnötig.

Achtung: <color red>Bei Debian verwedet man udevadm !</color>

Mit udevinfo hat man wahrscheinlich das „straightforward tool“, welches man verwenden kann um rules zu erstellen. Alles was man wissen muss ist der sysfs device Pfad vom gewünschten device. Hier ein zusammengestutztes Beispiel:

udevinfo -a -p /sys/block/sda

dasselbe für Debian lautet:

udevadm info --name /dev/sda

<color green> looking at device '/block/sda':
<tab>KERNEL==„sda“
<tab>SUBSYSTEM==„block“
<tab>ATTR{stat}==„ 128535 2246 2788977 766188 73998 317300 3132216 5735004 0 516516 6503316“
<tab>ATTR{size}==„234441648“
<tab>ATTR{removable}==„0“
<tab>ATTR{range}==„16“
<tab>ATTR{dev}==„8:0“</color>

<color blue> looking at parent device '/devices/pci0000:00/0000:00:07.0/host0/target0:0:0/0:0:0:0':
<tab>KERNELS==„0:0:0:0“
<tab>SUBSYSTEMS==„scsi“
<tab>DRIVERS==„sd“
<tab>ATTRS{ioerr_cnt}==„0x0“
<tab>ATTRS{iodone_cnt}==„0x31737“
<tab>ATTRS{iorequest_cnt}==„0x31737“
<tab>ATTRS{iocounterbits}==„32“
<tab>ATTRS{timeout}==„30“
<tab>ATTRS{state}==„running“
<tab>ATTRS{rev}==„3.42“
<tab>ATTRS{model}==„ST3120827AS<tab>„
<tab>ATTRS{vendor}==„ATA „
<tab>ATTRS{scsi_level}==„6“
<tab>ATTRS{type}==„0“
<tab>ATTRS{queue_type}==„none“
<tab>ATTRS{queue_depth}==„1“
<tab>ATTRS{device_blocked}==„0“</color>

<color red> looking at parent device '/devices/pci0000:00/0000:00:07.0':
<tab>KERNELS==„0000:00:07.0“
<tab>SUBSYSTEMS==„pci“
<tab>DRIVERS==„sata_nv“
<tab>ATTRS{vendor}==„0x10de“
<tab>ATTRS{device}==„0x037f“</color>

udevinfo generiert einfach eine Liste der Attribute, welche man „as-is“ verwenden kann in den match-keys der udev rules. Vom obigen Beispiel könnte man also zB. eine der folgenden Rules erstellen:

<tab>SUBSYSTEM==„block“, ATTR{size}==„234441648“, NAME=„my_hard_disk“
<tab>SUBSYSTEM==„block“, <color blue>SUBSYSTEMS==„scsi“, ATTRS{model}==„ST3120827AS“</color>, NAME=„my_hard_disk“

Anhand der Farben im obigen Beispiel wird veranschaulicht, wie Attribute kombiniert werden können. Ein Device, wie das obige mit einem einzigen Parent device kann nicht vermischt mit match-attributen von verschiedenen Parent devices werden - eine solche Regel würde nicht funktionieren.

Als Beispiel also eine „kaputte“ Regel, die so nicht funktionieren würde, weil sie versucht, auf die Attribute zweier Parent devices zu matchen:

<tab>SUBSYSTEM==„block“, <color blue>ATTRS{model}==„ST3120827AS“</color>, <color red>DRIVERS==„sata_nv“</color>, NAME=„my_hard_disk“

Gewöhnlicherweise erhält man eine grosse Anzahl von Attributen, von denen man eines auswählt, mit dem die Regel konstruiert wird. Normalerweise wird ein Attribut gewählt, welches das device in einem von „Mensch erkennbaren“ und persistenten Zustand zu erkennen gibt. Im obigen Beispiel wurde die Diskgrösse und die Modellnummer verwendet, und nicht etwa „bedeutungslose“ Nummern wie etwa ATTRS{iodone_cnt}==„0x31737“…

Beachten wir nochmals die Effekte von der Hierarchie im udevinfo output:
Die <color green>grüne Sektion</color> entspricht, bezüglich der udev-Abfrage, die Standard-match-keys wie KERNEL und ATTR.
Die <color blue>blaue</color> und die <color red>rote Sektion</color> entsprechen den Parent devices, welche abgefragt werden mit Varianten wie SUBSYSTEMS and ATTRS. Somit kann mit der Komplexität der hierarchischen Struktur nun eigentlich doch wieder ganz einfach umgegangen werden, man muss nur sicherstellen, dass man die exakten Werte verwendet, die einem udevinfo vorschlägt.

Ein weiterer Punkt, den man beachten sollte ist, dass Text-Attribute im udevinfo-output eingebettet in Leerzeichen erscheinen (siehe zb. ST3120827AS oben). In eigenen rules kann man den extra Leerschlag spezifizieren oder man kann ihn einfach wegschneiden, wie es im Besipiel oben gemacht wurde.

Die einzige Komplikation in Verwendung mit udevinfo ist, dass man den top-level device Pfad kennen muss (/sys/block/sda im Beispiel oben). Das ist nicht immer offensichtlich. Trotzdem, wenn man im Grossen und Ganzen rules schreibt für ein device, das bereits existiert, kann man udevinfo verwenden um den device-Pfad ausfindig zu machen:

udevinfo -a -p $(udevinfo -q path -n /dev/sda)

Obwohl udevinfo beinahe der am meisten straightforward way zum Listen der exakten Attribute ist, um rules zu erstellen, gibt es Alternativen, mit denen einiger User glücklicher werden. Utilities wie usbview zeigen eine ähnliche Anzahl Informationen an, die für die rules verwendet können.

udev erlaubt weitere Assignments in Rules, mit denen man Besitzer und Berechtigungen auf jedes device zuweisen kann.

Mit dem GROUP Assignment hat man auch die Möglichkeit, den Besitzer eines device-nodes als Unix-Gruppe anzugeben. Als Beispiel definieren wird die Gruppe „video“ als Besitzer von framebuffer devices:

KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k", GROUP="video"

Mit dem OWNER Assignment, wahrscheinlich weniger nützlich, kann man auch festlegen, welcher Unix-User der Besitzer des device-nodes sein soll. Im Beispiel hier gehören John alle floppy-devices:

KERNEL=="fd[0-9]*", OWNER="john"

udev selbst erstellt grundsätzlich nodes mit der Unix-Berechtigung von 0660 (lesen/schreiben für Besitzer und Gruppe). Sollte es nötig sein, dieses defaults zu überschreiben auf gewissen devices, kann man das MODE Assignment in rules verwenden.

Im Beispiel hier definieren wir dem inotify-node lese- und schreibzugriff für alle:

KERNEL=="inotify", NAME="misc/%k", SYMLINK+="%k", MODE="0666"

Unter gewissen Umständen möchte man ein bisschen mehr Flexibilität als die Standard udev-rules bieten. In dieser Situation kann udev ein Programm starten und den Standard-Output dieses Programmes für den device-namen verwenden.

Um diese Funktionalität zu verwenden, trägt man einfach den absoluten Pfad des zu startenden Programmes (und dessen Paramter) in das PROGRAM-Assignment ein. Jetzt kann man eine Variante von der %c Substitution im NAME/SYMLINK Assignment verwenden.

In den folgenden Beispielen wird ein fiktionales Programm (/bin/device_namer) verwendet. device_namer verwendet ein commandline Argument (den Kernelname für das Device). Basierend auf dem Kernelnamen erstellt dann device_namer irgendeinen Output, aufgesplittet in Teile, nach stout. Jeder dieser Teile ist einfach ein einziges Wort welches durch eine Leertaste getrennt wird.

Im ersten Beispiel nehmen wir an, dass device_namer einen Output mit einer Nummer von Teilen ausgibt. Jeder dieser Teile dient dazu einen Symlink (alternativen Namen) für das gefragt device zu erstellen.

KERNEL=="hda", PROGRAM="/bin/device_namer %k", SYMLINK+="%c"

Im nächsten Beispiel nehmen wir an, dass device_namer zwei Teile ausgibt. Der erste Teil ist der device name, der zweite ist der Name für einen zusätzlichen symbolischen Link. Jetzt verwenden wir die %c{N} Substitution, wobei sich der Teil N auf die Ausgabe bezieht:

KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2}"

In nächsten Beispiel nehmen wir an, dass device_namer einen Teil für den device-namen ausgibt, gefolgt von irgendeiner Nummer von Teilen, welche zusätzliche Symlinks erstellen. Jetzt wird die %c{N+} substitution verwendet, welche die Teile N, N+1, N+2, … auswertet bis zum Ende des Outputs:

KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2+}"

Die Output-Parts können in jedem Assignment-Key verwendet werden, nicht nur in NAME and SYMLINK. Das nächste Beispiel verwendet das fiktionale Programm zum auslesen der Unixgruppe, welcher das Device gehören soll:

KERNEL=="hda", PROGRAM="/bin/who_owns_device %k", GROUP="%c"

Ein weiterer Grund um udev-Rules zu schreiben ist ein bestimmtes Programm zu starten, sobald das Device verbunden oder disconnected wird. Zum Beispiel ein Skript, welches automatisch alle Fotos von der Digitalkamera herunterlädt, sobald die Kamera angeschlossen wird.

Man darf diese Aktion nicht mit der PROGRAM Funktionalität von oben verwechseln. PROGRAM wird verwendet, um device Namen zu generieren (diese Programmes sollten für nichts anderes benutzt werden). Wenn diese Programme ausgeführt werden, wurde das device node noch gar nicht genreriert, somit ist eine Aktion aufgrund des Devices noch gar nicht möglich.

Die Funktionalität die hier gezeigt wird erlaubt es aber, ein Programm zu starten, wenn das device-node vorhanden ist. Dieses Programm kann auf dem device agieren, jedoch sollte es nicht sehr lange laufen, denn solange das Programm läuft ist udev auf Pause… Ein Workaround für diese Limitation könnte sein, dass sich das Programm sofort ablöst.

Ein Beispiel, die das RUN list assignment verwendet:

KERNEL=="sdb", RUN+="/usr/bin/my_program"

Wenn /usr/bin/my_program ausgeführt wird, sind bereits Teile vom udev-Environment verfügbar als Environment Variabeln, inklusive Key-values wie SUBSYSTEM. Man kann auch die ACTION Variable verwenden um zu erkennen, ob das device verbunden oder getrennt wurde - ACTION fügt „add“ oder „remove“ hinzu.

udev startet diese Programme nicht in einem aktiven Terminal oder im context einer Shell. Man sollte natürlich sicherstellen, dass das Programm ausführbar ist und mit einem Shebang startet

udev stellt einen ENV Key für Environment Variabeln zur Verfügung, welcher für matching und assignment rules verwendet werden kann.

Im Assignment-Fall kann man eine Environment Variable setzen welche dann später matcht. Man kann auch Environment Variablen setzen, die von externen Programmen verwendet werden

Eine fiktionale Beispielrule setzt eine Environment Variable:

KERNEL=="fd0", SYMLINK+="floppy", ENV{some_var}="value"

Im Matching-Fall kann man sicherstellen, dass eine Rule nur dann startet, wenn eine Environment Variable zutrifft. Environment Variabeln in udev sind nicht dieseleben, die man in einer Konsole sieht!

Eine fiktionale Beispielrule bezieht sich auf einen match.
Diese Rule erstellt den /dev/floppy Link nur dann, wenn $an_env_var ist auf „ja“ gesetzt im udev Environment:

KERNEL=="fd0", ENV{an_env_var}=="yes", SYMLINK+="floppy"

Ein weiteres, nützliches Assignment ist die OPTIONS Liste. Davon sind folgende verfügbar:

  • all_partitions - create all possible partitions for a block device, rather than only those that were initially detected
  • ignore_device - ignore the event completely
  • last_rule - ensure that no later rules have any effect

Die nächste Rule, als Beispiel, setzt das Gruppenrecht auf dem harddisk-node und stellt sicher, dass keine spätere Rule mehr angewendet wird:

KERNEL=="sda", GROUP="disk", OPTIONS+="last_rule"

Wenn der Printer eingeschalten wird, wird im wahrscheinlich etwas wie /dev/lp0 zugewiesen. Sagt nicht sehr viel aus, also warum nicht etwas aussagekräftigeres verwenden und einen alternativen Namen zuweisen.

# udevinfo -a -p $(udevinfo -q path -n /dev/lp0)
looking at device '/class/usb/lp0':
     KERNEL=="lp0"
     SUBSYSTEM=="usb"
     DRIVER==""
     ATTR{dev}=="180:0"

looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb1/1-1':
     SUBSYSTEMS=="usb"
     ATTRS{manufacturer}=="EPSON"
     ATTRS{product}=="USB Printer"
     ATTRS{serial}=="L72010011070626380"

Die Regel schaut so aus:

SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", SYMLINK+="epson_680"

Die meisten Kameras identifizieren sich selbst als externe Harddisk. Um die Fotos zu kopieren, mountet man das device und kopiert die Bilder. Aber nicht alle Kameras arbeiten auf diese Weise, einige verwenden ein anderes Protokoll (wie zb. Kameras, die von gphoto2 unterstützt werden). In diesem Fall möchte ich nicht alle Rules neu schreiben, da diese ausschliesslich durch den userspace kontrolliert werden (anstatt eines spezifischen Kerneltreibers).

USB-Kameras identifieren sich gewöhnlich als Disk mit einer einzigen Partition, zum Beispiel als /dev/sdb mit /dev/sdb1. Der sdb node ist hier aber uninteressant, aber sdb1 nicht - diesen wollen wir mounten. Da gibt es aber ein Problem, denn sysfs ist verkettet, die brauchbaren Attribute, die uns udevinfo liefert für /dev/sdb1 sind identisch mit denen von /dev/sdb. Diese ergibt, dass die rule aber potentiell auf beide matcht (die raw disk und die eigentlich Partition). Das wollen wir natürlich nicht, die rule muss also spezifiziert werden.

Im dieses Problem zu umgehen, muss man sich also überlegen, was denn sdb und sdb1 unterscheidet. Überraschenderweise ist es ganz einfach: der Name selbst! Also matchen wir doch einfach auf das NAME-Feld:

# udevinfo -a -p $(udevinfo -q path -n /dev/sdb1)
looking at device '/block/sdb/sdb1':
     KERNEL=="sdb1"
     SUBSYSTEM=="block"

looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0':
     KERNELS=="6:0:0:0"
     SUBSYSTEMS=="scsi"
     DRIVERS=="sd"
     ATTRS{rev}=="1.00"
     ATTRS{model}=="X250,D560Z,C350Z"
     ATTRS{vendor}=="OLYMPUS "
     ATTRS{scsi_level}=="3"
     ATTRS{type}=="0"

und die Rule schaut so aus:

KERNEL=="sd?1", SUBSYSTEMS=="scsi", ATTRS{model}=="X250,D560Z,C350Z", SYMLINK+="camera"

Eine USB-Harddisk ist vergleichbare mit einer USB-Kamera. Trotzdem verwenden wir hier andere Patterns. Im Kamera-Beispiel war sdb uninteressant für uns, das ist ja nur Partitionieren usw. interessant. Wer partitioniert denn schon seine Kamera? Bei einer Harddisk, zb. einer 100GB USB Harddisk, ist es aber verständlich, dass man diese unter Umständen partitionieren möchte. Hier können wir die Vorteile von udev's string substitutions anwenden:

KERNEL=="sd*", SUBSYSTEMS=="scsi", ATTRS{model}=="USB 2.0 Storage Device", SYMLINK+="usbhd%n"

Diese Rule erstellt symbolische Links wie:

  • /dev/usbhd - den fdiskable node
  • /dev/usbhd1 - die erste Partition (mountable)
  • /dev/usbhd2 - die zweite Partition (mountable)

USB Card Readers (CompactFlash, SmartMedia, etc) sind aber immer noch eine andere Range von USB Speichergeräten mit nochmals anderen Requirements.

Diese Geräte informieren typischerweise den Hostcomputer nicht, wenn ein Medium gewechselt wurde. Wenn man also einen leeren Cardreader einstekct und erst dann die Speicherkarte einlegt, wird der Computer es nicht mitbekommen und man hat keine mountbare sdb1 Partition für das Medium.

Eine mögliche Lösung ist, sich der all_partitions Option zu bedienen. Dieses erstellt 16 Partition-nodes für jedes Blockdevice für welche die rule matcht:

KERNEL="sd*", SUBSYSTEMS=="scsi", ATTRS{model}=="USB 2.0 CompactFlash Reader", SYMLINK+="cfrdr%n", OPTIONS+="all_partitions"

Jetzt wird man nodes mit den Namen: cfrdr, cfrdr1, cfrdr2, cfrdr3, …, cfrdr15 erhalten

Diese Geräte werden als USB-Seriell-Devices erkannt, bekommen also standardmässig den ttyUSB1 device-node Die Palm utilities beziehen sich aber auf /dev/pilot, Palm-User möchten also eine Rule, die das sicherstellt:

SUBSYSTEMS=="usb", ATTRS{product}=="Palm Handheld", KERNEL=="ttyUSB*", SYMLINK+="pilot"

Der Produkt-String variiert von Produkt zu Produkt, dies sollte erst also noch mit udev-info geprüft werden.

Nehmen wir an, wir haben zwei optische Devices im Rechner, einen DVD-Reader (hdc) und einen DVD-Brenner (hdd). Ich erwarte jetzt nicht, dass sich diese beiden Namen jemals ändern (solange ich nicht an den Kabeln rumfummle…) Nichtsdestotrotz gibt es User, die lieber ein /dev/dvd haben möchten.

da wir die KERNEL Namen für diese Geräte kennen, ist das ja ganz simpel:

SUBSYSTEM=="block", KERNEL=="hdc", SYMLINK+="dvd", GROUP="cdrom"
SUBSYSTEM=="block", KERNEL=="hdd", SYMLINK+="dvdrw", GROUP="cdrom"

Obwohl Netzwerkinterfaces referenziert sind mit Namen, haben sie gewöhnlicherweise keinen device node verknüpft damit. Trotzdem ist das rule schreiben beinahe identisch. Es macht Sinn, sich bei NICs auf die MAC-Adresse zu konzentrieren…

# udevinfo -a -p /sys/class/net/eth0
looking at class device '/sys/class/net/eth0':
    KERNEL=="eth0"
    ATTR{address}=="00:52:8b:d5:04:48"

Und hier die Rule…:

KERNEL=="eth*", ATTR{address}=="00:52:8b:d5:04:48", NAME="lan"

Natürlich muss der Netzwerktreiber neu gestartet werden damit die Rule „zieht“. Entweder unloaded / loaded man das modul oder man macht einen Reboot. Natürlich muss das System dann auch umkonfiguriert werden, dass nun „lan“ und nicht mehr „eth0“ verwendet wird Es kann zu Problemen führen (zB. das interface wird nicht umbenannt), bis nicht auch wirklich alle Referenzen zu eth0 bereinigt wurden.

Ich denke mir, dass man das ganze aber auch mit der bestehenden udev-Rule in /etc/udev/rules.d/70-persistent-net.rules anpassen kann. Da passiert genau dasselbe.

Eine Ausgabe aktueller udev-Ereignisse bietet udevadm:

udevadm monitor

Verwendet man einen aktuellen Kernel mit inotify support, dann monitort udev automatisch das rules-Verzeichnis und lädt alle Modifikationen, die gemacht wurden automatisch.

Trotzdem wird udev nicht automatisch alle Rules neu durchführen, um eine neue Rule anzuwenden. Hat man zum Beispiel eine rule erstellt, die einen zusätzlichen Symlink für die Kamera erstellen soll während diese eingesteckt ist, muss man nicht erwarten, dass dieser Symlink sofort erscheint. Um diesen Symlink anzuzeigen, muss die Kamera entweder aus- und wieder eingesteckt werden oder alternativ, zb. bei nicht-auswechselbaren Devices, kann man udevtrigger starten. (ist bei Lenny nicht in den Rep's, nehme also an, dass das hier nicht mehr vonnöten ist…)

Wenn der Kernel keinen inotify Support beinhaltet, werden neue rules nicht automatisch erkannt. In diesem Fall muss udevcontrol reload_rules durchgeführt werden, damit die neuen Regeln erkannt werden.

Wenn man den top-level device Pfad in sysfs kennt, kann man udevtest verwenden, um die Aktionen anzuzeigen, die udev durchführen würde. Auch hier gilt bei Debian wieder, dass man udevadm verwendet, diesmal einfach mit dem Parameter test. Dies ist hilfreich zum debuggen von rules. Möchte man zb. eine Rule debuggen, die auf /sys/class/sound/dsp verweist:

# udevtest /class/sound/dsp
main: looking at device '/class/sound/dsp' from subsystem 'sound'
udev_rules_get_name: add symlink 'dsp'
udev_rules_get_name: rule applied, 'dsp' becomes 'sound/dsp'
udev_device_event: device '/class/sound/dsp' already known, remove possible symlinks
udev_node_add: creating device node '/dev/sound/dsp', major = '14', minor = '3', mode = '0660', uid = '0', gid = '18'
udev_node_add: creating symlink '/dev/dsp' to 'sound/dsp'

…bei Debian gibt man also einfach udevadm test /class/sound/dsp ein…

Zu beachten ist, dass der /sys-Präfix vom udevtest Kommando entfernt wurde. Das ist, weil udevtest auf den device-Pfaden arbeitet. udevtest ist nur zum testen/debuggen, es erstellt keine device-nodes, egal was im Output steht!