Being written...
Hotplugging, Udev, HAL and D-BUS
Ode to /dev
It is very common for people to discuss Unix and Linux as an operating system where files are used extensively as interface points even for devices connected to the system. There is a lot of truth to that statement. Historically, the /dev
directory on a *ix platform housed a static set of files for all well known devices that might be attached to a machine. Then, if a new device came out, one would typically compile a device driver into the kernel and probably also create one or more /dev
device files to act as interfaces to the device.
Device files were created at install time statically and/or by using a program script often called makedev
or MAKEDEV
. That script would create all of the commonly used static devices in /dev
using a program called mknod
. The mknod
command is a command line utility that allows you to create character and/or block special files along with a major and minor number. Whether or not a device needs to be character or blocked depends on what kind of device it is. Streaming devices, terminal input and serial devices are usually character based. Devices that need can operate on blocks of data and use random I/O are typically block devices. Good examples of block devices are things like hard disks. The major number usually indicates a class of device and the minor number is a subclass of that device. However, it could be that a major number is a device and the minor number is an instantiation of the device. Regardless, usually the major number is what registers a particular device file with a corresponding driver in the kernel.
Did you know...
One of the problems in early Linux with using major and minor numbered device files is that they were 8 bit values which meant that only a total of 255 device classes and 255 subclasses could ever be defined. This limited space meant that assigning new kernel major numbers had to be controlled. The Linux Assigned Names and Numbers Authority (LANANA, http://lanana.org) was given this responsibility. This is a huge limitation and therefore most *ix platforms no longer have limited/restricted 8 bit major/minor style device files (Linux kernel 2.6 changed to a 32 bit value for devices with a 12 bit major and 20 bit minor). But with even more potential /dev
devices the act of creating, managing and maintaining /dev
device files becomes unwieldy.
DevFS Flattery
Commercial Unix systems and BSD created an idea of a device filesystem where device files would be managed dynamically. For example, in Solaris, the command drvconfig
could find new devices that were dynamically added (e.g. via a fibre SAN) and the command disks
would create the necessary /dev
files. Alternatively, a reconfiguration reboot on Solaris would also created the /dev
files and links. Later versions of Solaris used devfsadm
instead of using the combination of drvconfig/disks
. Solaris's implementation is often called a "pseudo devfs" because it had limited scope (mostly used for just storage devices).
Hats off to DevFS
Linux DevFS was an attempt to provide dynamic /dev
management via the kernel itself for all devices. As new devices came online, the device drivers could "notify" DevFS to create the appropriate /dev
file for the device.
Unfortunately, DevFS meant some pretty extensive changes throughout the kernel and in the end, not all Linux distributions used it. Using DevFS meant that the kernel decided on how /dev
files were named which was one of the most referenced weaknesses with DevFS. However, technically, there were also race condition problems with DevFS that worried some and arguably were more important. With the vast majority of kernel developers hating the DevFS implementation, DevFS was a short lived addition to the kernel that has since been entirely removed (2.6 kernel). However, with that said, DevFS did show the need for some kind of dynamic /dev
management.
Linux Userspace Udev
The replacement for DevFS was to push device making policies out of the Linux kernel and into userspace. Now when the kernel detects a new device a message can be sent to a userspace daemon called udevd
that can do whatever is necessary to create the device file dynamically, set up permissions, symbolic links and even run programs needed for the handling of the device. Because the implementation is in userspace, the door is opened for many possibilities.
Udev Implementation
Early solutions for hotplugging involved something called udev, but it was far from the structure in newer 2.6 kernels. The old hotplug mechanism was not terribly flexible and often times created more problems than it solved. The udevd
daemon solution in new 2.6 kernels is a better framework though it is still being developed.
Hotplug (2.4, older 2.6 kernels)
Originally, udev was a utility invoked as a part of the hotplug architecture. The program defined at /proc/sys/kernel/hotplug
was invoked, usually that program was /sbin/hotplug
and it was passed information about the kernel "object" that wanted something done via environment variables. The hotplug program used scripts and configuration files found in /etc/hotplug
to determine what to do with the events... eventually even calling a userspace program called udev but with many limitations, and not at all like the new udev daemon of today.
Udev Daemon (2.6, 2.6.16+ hotplug style deprecated)
Even before looking at the udev daemon itself, we need to know how messages can make it from the kernel into userspace. In kernel 2.6, a special IPC was created called netlink. This is a multicast socket which allows potentially multiple userspace programs to receive messages from the kernel. Kernel messages are sent with information about which kernel object is making the request, what action is to be done (e.g. add, remove, mount, unmount, online, offline) and then an optional payload area for additional information.
Udev uses a filesystem to manage /dev
device files. Usually /dev
is a tmpfs. Tmpfs is an in memory/swap filesystem. It is not persistent. However, udev can actually use any kind of filesystem for /dev
. It's probably wise to use tmpfs since the whole idea of a udev managed /dev
is dynamic creation and deletion.
The udev daemon, udevd, listens to kernel event messages (UEVENT) and passes information on for udev use. Udev uses rule files and sysfs to determine how to create/remove devices and what kinds of permissions the dynamically created device should have. The rules can make use of helper programs which means that udev can actually do more than create and manipulate device file.
What is a Sysfs?
Sysfs is similar to /dev
in that it is an in memory filesystem. The kernel exports information and attributes of devices it sees into the hierarchy under /sys
. Why? Well, for one thing, it helps move this kind of information out of /proc
which is where everyone had been storing their information (arguably the wrong place, but the only place at one time).
Just before sysfs, there was a type of sysfs called ddfs (Device Driver File System) which used procfs (/proc
) to store its data. It was used primarily for debugging until it was deemed to be very useful and eventually turned into the sysfs we have today.
The udev daemon can take a configuration file (/etc/udev/udev.conf
) to determine where device files should go (/dev
) and where the udev rules files reside (/etc/udev/rules.d
) and what kind of log level to use for logging. The rule files are what determine the action that udev takes when a new device is added to the system.
2.6 Udev Rule Format
When a kernel event is processed by udev through the rules, (in general) all of the rules are applied to see what matches and what action to take. A match does not necessarily imply any action. For example:
SUBSYSTEM=="video4linux", GROUP="video"
If the SUBSYSTEM
of the kernel device event is video4linux
, then set the GROUP
name to video
. Set it on what? Well, we do not know that right now, we just know when some later rule gets done, unless another later rule overrides this one, that the permission of any resulting device file that might be created will have the group set to video.
Notice that key matches use ==
and assignments use just one =
. This can be important since not use ==
will likely mean that any rule you create will not match anything and you might create an interesting side effect that was not intended. Keys are either used in searches or in assignments and in some cases can be used in both.
Comparison Match Operators:
- ==
- True if there is a comparison match.
- !=
- True if there is no comparison match.
Simple globing style pattern matches can be used:
- *
- Matches zero, or any number of characters.
- ?
- Matches any single character.
- [range-or-set]
- Matches a single character in range (e.g. [0-9], [a-z]) or set (e.g. [2-9AJQK]). If the first character of the set is a !, then match anything that is not contained in the set.
Common keys (elements you can do a comparison match against):
- KERNEL
- match against the kernel name for the device.
- SUBSYSTEM
- match against the subsystem of the device.
- DRIVER
- match against the name of the kernel driver of the device.
Other comparison keys:
- ACTION
- match against the type of event from the kernel.
- DEVPATH
- match against the device path of the device.
- ATTR{sysfs-attribute}
- match against the value of a sysfs (/sys) attribute of the device.*
- ENV{variable-name}
- match against the value of an environment variable.*
- PROGRAM
- execute an external program and if result is true, process the rule. Program receives the environment variables of the event.
- RESULT
- matches the return result of a prior PROGRAM call.
* ATTR and ENV can also be used in assignments.
Assignment Operators:
- =
- Assign value on the right to the expression on the left.
- :=
- Assign value on the right to the expression on the left and make immutable.
- +=
- Append the value on the right to the expression (list) on the left.
Assignment Keys:
- NAME
- The name of the node to be created. Unlike some keys, only one rule can set the NAME, and once set it is good for the duration of the rest of rule processing.
- SYMLINK
- The name of a symbolic link to a node. This one can take multiple elements seperated by space and can be appended to/reset by future rules.
- OWNER, GROUP, MODE
- These 3 items set the permissions for the device node file. MODE uses
chmod
octal format.
- ATTR{sysfs-attribute}
- Set the sysfs attribute to a particular value.*
- ENV{variable-name}
- Set an environment variable to a particular value.*
- RUN
- The name of a program to run for the node information being worked on. This can be a list of programs. Warning: Long running programs may cause udev to miss other events.
- LABEL
- Declare a label that can be used as a target for GOTO.
- GOTO
- Jump to the next LABEL with a matching name.
- IMPORT{type}
- Import a set of environment variables from a source determined by type.
- program
- Execute an external program which returns VAR=VALUE tuples.
- file
- Retrieve VAR=VALUE tuples from a text file argument.
- parent
- Get environment values from a parent device by reading its database of values. The value is a matching string of the same format as a key comparison.
- WAIT_FOR_SYSFS
- Wait for the specified sysfs file to be created before continuing. Needed only in special cases where there are timing issues.
- OPTIONS
- Set this to these fixed values:
- last_rule
- Prevents further rule processing for node.
- ignore_device
- Ignore the device completely.
- ignore_remove
- Ignore future remove events for the device.
- all_partitions
- Forces the creating of all possible block device partition nodes. Use this for block devices where media changes are not detectable.
* ATTR and ENV can also be used in comparisons.
Some keys can use special substitution macros inside of them. Those keys are NAME, SYMLINK, PROGRAM, OWNER, GROUP and RUN. Substitutions are made when the rule is done with the exception of RUN, in that case the substitution is not made until all rules have been done (this allows for side effects to get done prior to invoking a program). The substitution macros look a lot like printf
macros or alternatively shell substitutions:
- $kernel, %k
- The kernel name for this device.
- $number, %n
- The kernel number for this device. For example
sda3
has a kernel number of '3'.
- $devpath, %p
- The devpath of the device.
- $id, %b
- The name of the device matched while searching the devpath upwards for SUBSYSTEMS, KERNELS, DRIVERS and ATTRS.
- $attr{file}, %s{file}
- The value of a sysfs attribute called file. If not found, it will search upwards in the sysfs hierarchy looking for a match. If file is a symbolic link, then the last part of the link is returned.
- $env{variable-name}, %E{varable-name}
- The value of an environment variable.
- $major, %M
- Kernel major number for the device.
- $minor, %m
- Kernel minor number for the device.
- $result, %c or $result{number}, %c{number}
- Returns the string returned by the PROGRAM used in the comparison for the rule. If used in the form of $c{number}, treat each space separated word of the returned string as a field selection by number. You can use number+ to specify a field and all elements following that field.
- $parent, %P
- The node name of the parent device.
- $root, %r
- The udev_root value.
- $tempnode, %N
- The name of a created temporary device node to provide access to the device from an external program before the real node is created.
- %%
- A percent sign.
- $$
- A dolar sign.
The common keys also have a plural form which says to search the sysfs starting at the device path upwards for a match (KERNELS
, SUBSYSTEMS
, DRIVERS
). For example consider:
KERNEL=="hd*[!0-9]", ATTR{removable}=="1", DRIVERS=="ide-cs|ide-floppy", GOTO="persistent_storage_end"
This matches ide device hda
, hdb
, etc. (any ide device not ending in a numeric digit) where there is a sysfs attribute of removable. However if anything in the sysfs tree from that device path upwards identifies the driver as ide-cs
or ide-floppy
, then this rule forces rule processing to continue at the label called persistent_storage_end
.
The rules files in /etc/udev/rules.d
are parsed in lexical order. The best way to add your own custom rules is with a new file. In general, your rules should precede any of the default rules on your system, however, this is not always true. A careful examination of the rules is really the best way to determine where you want your custom rules to go. And the best way to learn more about writing rules is to look at some.
Example 1, permissions on a usb printer node.
SUBSYSTEM=="usb", KERNEL=="lp*", NAME="usb/%k", SYMLINK+="usb%k", GROUP="lp"
This sets the node name to usb/%k (which for example could be lp0). Then it also ensures a symbolic link named usblp0 points to it, and that the node device file has group ownership by the group lp.
$ ls -ld /dev/usb/lp0 /dev/usblp0
lrwxrwxrwx 1 root root 7 2008-02-03 00:33 /dev/usblp0 -> usb/lp0
crw-rw---- 1 root lp 180, 0 2008-02-03 00:33 /dev/usb/lp0
Example 2, the console devices.
KERNEL=="console", MODE="600", OPTIONS="last_rule"
For the kernel device called console set the permissions so that only the owner can read/write and make sure this is the last rule to be done on the device.
Example 3, using a helper program to load firmware for some device (e.g. a wireless network adapter)
SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware.sh"
We do not have to know exactly what kind of device we are dealing with... this rule only seeks to find out if the event is requesting some kind of firmware load for the device. The helper script is at /lib/udev/firmware.sh
and is run with the environmental data for the device (e.g. DEVPATH, FIRMWARE).
Example 4, A SCSI disk (the basics, device and generic device)
SUBSYSTEM=="block", GROUP="disk", MODE="0640"
SUBSYSTEM=="scsi", ACTION=="add", ATTR{type}=="0|7|14", ATTR{timeout}="60"
SUBSYSTEM=="scsi_device", ACTION=="add", ATTRS{type}=="0|7|14", RUN+="/sbin/modprobe sd_mod"
SUBSYSTEM=="scsi_device", ACTION=="add", RUN+="/sbin/modprobe sg"
KERNEL=="sg*", GROUP="disk", MODE="640"
This will set the block device permissions to use group disk and permissions 640 (rw-r-----). The sysfs timeout for the device will get set to 60. The module sd_mod will get loaded as well as the module sg and finally, the node for the sg device will also have group disk and permissions 640.
Writing Udev Rules Via Inspection
Looking at the rules in /etc/udev/rules.d
gives some insight into how to make rules, but probably not enough information to write some rules from scratch. There are several utilities that can be used to help inspect and test rules.
udevmonitor
- Shows the live kernel events and udev messages sent to D-BUS.
udevinfo
- Shows detailed information about device nodes by path or by name. Excellent tool for discovering certain device attributes.
udevtest
- Command that does not alter anything, but merely allows you to trace what would happen rule wise if udev were invoked for a particular device node.
Using udevmonitor
In order to get a better understanding of how to write rules, we can monitor the kernel UEVENT messages and the udev messages to the D-BUS. The name of the command is udevmonitor
. If executed without options, you will get simple summary line of each message:
Examining udevmonitor
Output for my Sony Camcorder:
# udevmonitor
udevmonitor prints the received event from the kernel [UEVENT]
and the event which udev sends out after rule processing [UDEV]
UEVENT[1201927194.229522] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4
UEVENT[1201927194.229580] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/usbdev2.5_ep00
UEVENT[1201927194.230896] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0
UEVENT[1201927194.230910] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/usbdev2.5_ep81
UEVENT[1201927194.230917] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/usbdev2.5_ep02
UEVENT[1201927194.230925] add@/class/usb_device/usbdev2.5
UDEV [1201927194.269830] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4
UDEV [1201927194.271191] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/usbdev2.5_ep00
UEVENT[1201927194.323936] add@/module/usb_storage
UDEV [1201927194.324860] add@/module/usb_storage
UEVENT[1201927194.325649] add@/bus/usb/drivers/usb-storage
UEVENT[1201927194.325927] add@/class/scsi_host/host5
UDEV [1201927194.326870] add@/bus/usb/drivers/usb-storage
UDEV [1201927194.327593] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0
UDEV [1201927194.329072] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/usbdev2.5_ep81
UDEV [1201927194.329793] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/usbdev2.5_ep02
UDEV [1201927194.330761] add@/class/scsi_host/host5
UDEV [1201927194.337678] add@/class/usb_device/usbdev2.5
UEVENT[1201927195.331170] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0
UEVENT[1201927195.331212] add@/class/scsi_disk/5:0:0:0
UEVENT[1201927195.743389] add@/block/sdd
UEVENT[1201927195.743414] add@/block/sdd/sdd1
UEVENT[1201927195.743422] add@/class/scsi_device/5:0:0:0
UEVENT[1201927195.743430] add@/class/scsi_generic/sg4
UDEV [1201927195.765792] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0
UDEV [1201927195.766554] add@/class/scsi_disk/5:0:0:0
UDEV [1201927195.768189] add@/class/scsi_generic/sg4
UDEV [1201927195.776489] add@/class/scsi_device/5:0:0:0
UDEV [1201927195.784801] add@/block/sdd
UEVENT[1201927195.938454] add@/module/nls_cp437
UDEV [1201927195.939543] add@/module/nls_cp437
UEVENT[1201927195.950031] add@/module/nls_iso8859_1
UDEV [1201927195.951002] add@/module/nls_iso8859_1
UEVENT[1201927195.955314] mount@/block/sdd/sdd1
UDEV [1201927195.957249] add@/block/sdd/sdd1
UDEV [1201927195.958031] mount@/block/sdd/sdd1
The UEVENT lines are coming from the kernel when we hotplug the Sony Camcorder in. One of the first things we see is the
Sony Camcorder USB interface endpoints. The first endpoint iusbdev2.5_ep00. The _ep00 endpoint is always present and represents the control interface. In our example _ep02 and _ep81 represent the I/O sink/source(s) for the USB device. Then the kernel lets us know that the modules for usb-storage are needed and finally we start seeing more familiar SCSI related matter with regards to the block device and the partition on the device.
As nice as all of this is, it's not enough information really. Oh, you can take a look through the /sys
directory and probably gain even more insight, but there might be an easier way. Execute udevmonitor --env
. Now when we plug in our Sony Camcorder we see more information since we now get to see the environment variables in addition to the summary messages.
Examining udevmonitor --env Output for my Sony Camcorder:
# udevmonitor --env
udevmonitor prints the received event from the kernel [UEVENT]
and the event which udev sends out after rule processing [UDEV]
UEVENT[1201927350.094357] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4
SUBSYSTEM=usb
SEQNUM=1669
PHYSDEVBUS=usb
PHYSDEVDRIVER=usb
UEVENT[1201927350.094444] add@/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/usbdev2.6_ep00
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/usbdev2.6_ep00
SUBSYSTEM=usb_endpoint
SEQNUM=1670
MAJOR=442
MINOR=2053
... etc....
The latter gives much more information. Even shows some attribute level information. udevmonitor
is a great way to see the live event processing happening as the kernel sends out notifications via netlink for udev.
Using udevinfo
If you know the device node of the resulting device that has been added to the system, you can use udevinfo
to query the
device node name, or you can use the devpath (e.g. /block/sdd
) to the device and retrieve a lot of information. In the case of the Sony Camcorder, I noticed that the filesytem that was mounted was off of /dev/sdd
. You can use the udevinfo
command to directly query information about a device node by device name (for example).
# udevinfo --query=all --name=/dev/sdd
P: /block/sdd
N: sdd
S: disk/by-id/usb-Sony_Sony_Camcorder_088xxxxxxxxxx
S: disk/by-path/pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0
E: ID_VENDOR=Sony
E: ID_MODEL=Sony_Camcorder
E: ID_REVISION=0400
E: ID_SERIAL=Sony_Sony_Camcorder_088xxxxxxxxxx
E: ID_TYPE=floppy
E: ID_BUS=usb
E: ID_PATH=pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0
However, this tool can really, really help out with rule writing if allowed to walk the entire tree upwards from the device. The --attribute-walk
option can really help us to find out what we can actually query for in our rules.
# udevinfo --attribute-walk --name=/dev/sdd
Udevinfo 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 '/block/sdd':
KERNEL=="sdd"
SUBSYSTEM=="block"
DRIVER==""
ATTR{stat}==" 54 470 587 684 0 0 0 0 0 596 688"
ATTR{size}=="78126048"
ATTR{removable}=="1"
ATTR{range}=="16"
ATTR{dev}=="8:48"
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0':
KERNELS=="5:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{ioerr_cnt}=="0x2"
ATTRS{iodone_cnt}=="0x74a"
ATTRS{iorequest_cnt}=="0x74a"
ATTRS{iocounterbits}=="32"
ATTRS{timeout}=="60"
ATTRS{state}=="running"
ATTRS{rev}=="1.00"
ATTRS{model}=="Camcorder "
ATTRS{vendor}=="Sony "
ATTRS{scsi_level}=="3"
ATTRS{type}=="0"
ATTRS{queue_type}=="none"
ATTRS{queue_depth}=="1"
ATTRS{device_blocked}=="0"
ATTRS{max_sectors}=="240"
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0':
KERNELS=="target5:0:0"
SUBSYSTEMS==""
DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5':
KERNELS=="host5"
SUBSYSTEMS==""
DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0':
KERNELS=="2-5.4:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="usb-storage"
ATTRS{modalias}=="usb:v054Cp02CBd0400dc00dsc00dp00ic08isc05ip50"
ATTRS{bInterfaceProtocol}=="50"
ATTRS{bInterfaceSubClass}=="05"
ATTRS{bInterfaceClass}=="08"
ATTRS{bNumEndpoints}=="02"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceNumber}=="00"
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2/2-5/2-5.4':
KERNELS=="2-5.4"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{serial}=="088xxxxxxxxxx"
ATTRS{product}=="Sony Camcorder"
ATTRS{manufacturer}=="Sony"
ATTRS{maxchild}=="0"
ATTRS{version}==" 2.00"
ATTRS{devnum}=="6"
ATTRS{speed}=="480"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bNumConfigurations}=="1"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceClass}=="00"
ATTRS{bcdDevice}=="0400"
ATTRS{idProduct}=="02cb"
ATTRS{idVendor}=="054c"
ATTRS{bMaxPower}==" 2mA"
ATTRS{bmAttributes}=="c0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bNumInterfaces}==" 1"
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2/2-5':
KERNELS=="2-5"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{maxchild}=="4"
ATTRS{version}==" 2.00"
ATTRS{devnum}=="3"
ATTRS{speed}=="480"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bNumConfigurations}=="1"
ATTRS{bDeviceProtocol}=="02"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceClass}=="09"
ATTRS{bcdDevice}=="0009"
ATTRS{idProduct}=="6560"
ATTRS{idVendor}=="04b4"
ATTRS{bMaxPower}=="100mA"
ATTRS{bmAttributes}=="e0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bNumInterfaces}==" 1"
looking at parent device '/devices/pci0000:00/0000:00:02.1/usb2':
KERNELS=="usb2"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{serial}=="0000:00:02.1"
ATTRS{product}=="EHCI Host Controller"
ATTRS{manufacturer}=="Linux 2.6.18.8-0.7-default ehci_hcd"
ATTRS{maxchild}=="10"
ATTRS{version}==" 2.00"
ATTRS{devnum}=="1"
ATTRS{speed}=="480"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bNumConfigurations}=="1"
ATTRS{bDeviceProtocol}=="01"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceClass}=="09"
ATTRS{bcdDevice}=="0206"
ATTRS{idProduct}=="0000"
ATTRS{idVendor}=="0000"
ATTRS{bMaxPower}==" 0mA"
ATTRS{bmAttributes}=="e0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bNumInterfaces}==" 1"
looking at parent device '/devices/pci0000:00/0000:00:02.1':
KERNELS=="0000:00:02.1"
SUBSYSTEMS=="pci"
DRIVERS=="ehci_hcd"
ATTRS{broken_parity_status}=="0"
ATTRS{enable}=="1"
ATTRS{modalias}=="pci:v000010DEd0000005Bsv000010DEsd0000CB84bc0Csc03i20"
ATTRS{local_cpus}=="00000000,00000000,00000000,0000000f"
ATTRS{irq}=="225"
ATTRS{class}=="0x0c0320"
ATTRS{subsystem_device}=="0xcb84"
ATTRS{subsystem_vendor}=="0x10de"
ATTRS{device}=="0x005b"
ATTRS{vendor}=="0x10de"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
Where are the device attributes stored? In sysfs of course! But marching through sysfs can be a chore. Here's a simple shell script that makes
viewing sysfs somewhat easier. Warning: By default showsysfs
will output a lot of data.
(:source lang=bash:)
#! /bin/sh
# showsysfs: Display /sys attributes
showpathnames=""
bold=""
b=""
n=""
while [ $# -gt 0 ]; do
case "$1" in
-b)
# show attribute values in bold
b=`tput bold`
n=`tput sgr0`
;;
-p)
# Show pathnames
showpathnames=1
;;
*)
break
;;
esac
shift
done
dirs="$*"
dirs=${dirs:=/sys}
for dir in $dirs; do
find "$dir" -print0 2>/dev/null |
if [ "$showpathnames" ]; then
xargs -n1 -0 grep '.' /dev/null 2>/dev/null
else
xargs -n1 -t -0 grep '.' /dev/null 2>&1 |
sed -e 's,grep . /dev/null ,,' -e 's,[^/]*/,| ,g' \
-e 's,\([^ /|]\),\\- \1,'
fi |
sed "s/:\(.*\)/: $b\1$n/"
done
Using showsysfs
we can see some of the more distinctive attributes of the Sony Camcorder.
...
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/serial:088xxxxxxxxxx
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/product:Sony Camcorder
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/manufacturer:Sony
...
Searching sysfs can also be a long process... so if you know just a piece of information, we can make an easier more direct query about the device. In the case of the Sony Camcorder, I noticed that the filesytem that was mounted was off of /dev/sdd
. You can use the udevinfo
command to directly query information about a device node by name (for example).
# udevinfo --query=all --name=/dev/sdd
P: /block/sdd
N: sdd
S: disk/by-id/usb-Sony_Sony_Camcorder_088xxxxxxxxxx
S: disk/by-path/pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0
E: ID_VENDOR=Sony
E: ID_MODEL=Sony_Camcorder
E: ID_REVISION=0400
E: ID_SERIAL=Sony_Sony_Camcorder_088xxxxxxxxxx
E: ID_TYPE=floppy
E: ID_BUS=usb
E: ID_PATH=pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0
One of the attributes is the serial number of the device. That could be useful in creating rules that are specific to a particular Camcorder if you owned multiple ones. Let's say that you have 3 different Camcorders, all alike. Using udev
rules, we can create specific symbolic links named after the person that actually owns the device. It would also be possible to change the device node permissions to owned by a particular username.
We can inspect the rules that would be done for a given event without executing or modifying anything by using udevtest
. This command might be useful for testing out any rule modification we make.
# udevtest /block/sdd
This program is for debugging only, it does not create any node,
or run any program specified by a RUN key. It may show incorrect results,
if rules match against subsystem specfic kernel event variables.
main: looking at device '/block/sdd' from subsystem 'block'
run_program: 'usb_id -x'
run_program: '/lib/udev/usb_id' (stdout) 'ID_VENDOR=Sony'
run_program: '/lib/udev/usb_id' (stdout) 'ID_MODEL=Sony_Camcorder'
run_program: '/lib/udev/usb_id' (stdout) 'ID_REVISION=0400'
run_program: '/lib/udev/usb_id' (stdout) 'ID_SERIAL=Sony_Sony_Camcorder_088xxxxxxxxxx'
run_program: '/lib/udev/usb_id' (stdout) 'ID_TYPE=floppy'
run_program: '/lib/udev/usb_id' (stdout) 'ID_BUS=usb'
run_program: '/lib/udev/usb_id' returned with status 0
udev_rules_get_name: add symlink 'disk/by-id/usb-Sony_Sony_Camcorder_088xxxxxxxxxx'
run_program: 'path_id /block/sdd'
run_program: '/lib/udev/path_id' (stdout) 'ID_PATH=pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0'
run_program: '/lib/udev/path_id' returned with status 0
udev_rules_get_name: add symlink 'disk/by-path/pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0'
udev_rules_get_name: no node name set, will use kernel name 'sdd'
udev_device_event: device '/block/sdd' already in database, validate currently present symlinks
udev_node_add: creating device node '/dev/sdd', major = '8', minor = '48', mode = '0640', uid = '0', gid = '6'
udev_node_add: creating symlink '/dev/disk/by-id/usb-Sony_Sony_Camcorder_088xxxxxxxxxx' to '../../sdd'
udev_node_add: creating symlink '/dev/disk/by-path/pci-0000:00:02.1-usb-0:5.4:1.0-scsi-0:0:0:0' to '../../sdd'
main: run: 'socket:/org/freedesktop/hal/udev_event'
main: run: 'socket:/org/kernel/udev/monitor'
...
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/ioerr_cnt:0x2
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/iodone_cnt:0x71
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/iorequest_cnt:0x71
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/iocounterbits:32
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/timeout:60
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/state:running
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/rev:1.00
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/model:Camcorder
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/vendor:Sony
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/scsi_level:3
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/type:0
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/queue_type:none
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/queue_depth:1
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/device_blocked:0
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/max_sectors:240
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/5:0:0:0/power/state:0
/sys/devices/pci0000: 00/0000:00:02.1/usb2/2-5/2-5.4/2-5.4:1.0/host5/target5:0:0/power/state:0
...