Wednesday, December 30, 2009

Creating udev rules on Linux

I spent some time figuring out how to customize behaviour when hot pluging devices on linux, so I might as well post my notes in case I forget. :)

You can use udev to trigger events, to write custom linux scripts for hot plugged device.

Here's one of the more clever ideas with custom hotplug scripts using a standard usb stick as a key to unlock the computer. This will secure a workstation to only the person with the key:

http://raftaman.net/?p=300

Here's a simple example of writing a custom image processor for a camera (most cameras are already supported, but this is an example from the ground up).

Basically what is needed is:

a. figuring out the name of the device on the system
b. figuring out the full signature of the device
c. writing a rule to match the signature
d. writing a program that will be triggered

First you have to figure out the name of the device on the system. When the device is unplugged, take a snapshot of all the device on the system. IMO an easy way is to look at the /sys/ directory. It will contain nodes for all hotplugged devices. First take a snapshot when the device is unplugged.

find /sys > /tmp/dev_out.txt

Then plug in the device, and take another snapshot:

find /sys > /tmp/dev_in.txt

Now you can compare the differences in the two snapshots. Note that one physical device can actually be several devices (for example if it has multiple partitions). You will want a short name.

diff /tmp/dev_out.txt /tmp/dev_in.txt

An alternate way to find the name of device nodes, run this, and plug in device.

udevadm monitor

The name might look like /sys/path/to/device. Then get complete signature of the device:

udevadm info -a -p /sys/path/to/device

Udevadm info starts with the device specified by the devpath and then walks up the chain of parent devices... The complete signature of the device looks like a bunch of gibberish, but it's just key/value pairs:


looking at device '/devices/pci0000:00/0000:00:1f.4/usb2/2-2/2-2:1.0/host13/target13:0:0/13:0:0:0/block/sdc':
KERNEL=="sdc"
SUBSYSTEM=="block"
DRIVER==""
ATTR{range}=="16"
ATTR{ext_range}=="256"
ATTR{removable}=="1"
ATTR{ro}=="0"
ATTR{size}=="429569"
ATTR{capability}=="53"
ATTR{stat}==" 87 996 1083 1820 0 0 0 0 0 1016 1820"

looking at parent device '/devices/pci0000:00/0000:00:1f.4/usb2/2-2/2-2:1.0/host13/target13:0:0/13:0:0:0':
KERNELS=="13:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{device_blocked}=="0"
ATTRS{type}=="0"
ATTRS{scsi_level}=="0"
ATTRS{vendor}=="VTech "
ATTRS{model}=="Kidizoom "
ATTRS{rev}=="1.21"
ATTRS{state}=="running"
ATTRS{timeout}=="30"
ATTRS{iocounterbits}=="32"
ATTRS{iorequest_cnt}=="0x28a"
ATTRS{iodone_cnt}=="0x28a"
ATTRS{ioerr_cnt}=="0x8"
ATTRS{modalias}=="scsi:t-0x00"
ATTRS{evt_media_change}=="0"
ATTRS{queue_depth}=="1"
ATTRS{queue_type}=="none"
ATTRS{max_sectors}=="240"

looking at parent device '/devices/pci0000:00/0000:00:1f.4/usb2/2-2/2-2:1.0/host13/target13:0:0':
KERNELS=="target13:0:0"
SUBSYSTEMS=="scsi"
DRIVERS==""

...

These two lines should basically describe the device:

ATTRS{vendor}=="VTech "
ATTRS{model}=="Kidizoom "

Forming a rule is basically a matter of picking out a few lines from the complete signature and putting them all on one line seperated with commas. Files go under

/etc/udev/rules.d/

The name of the rule starts with a number (order executed) and ends with .rules
There can only be at most one parent in matching rules.
RUN will trigger a program to execute after device is mounted
ACTION can be "add" or "remove"

# EXAMPLE:
# FILE /etc/udev/rules.d/99-camera-copy.rules
# This will trigger /path/to/your/program
# when the device is plugged in
ACTION=="add", SUBSYSTEM=="block", ATTRS{vendor}=="VTech ", ATTRS{model}=="Kidizoom ", ATTRS{rev}=="1.21", RUN+="/path/to/your/program"

Also, udev will pause until the program called is finished. You can also run the program in the background to return immediately (recommended for longer processes). To detach bash script, enclose it in braces and end with &:


#!/bin/bash
{
echo "hello there" >> /tmp/udev_test.log
} &

All programs will be run as root user (full permissions). To lauch graphical programs as a regular user:

#!/bin/bash
{
# what program to run?
run=picasa
user=`who grep :0\) cut -f 1 -d ' '`
export DISPLAY=:0.0
su $user -c "xhost local:$user; $run"
} &


For more information, here's an older writeup (the tool "udevinfo" has changed to "udevadm info")

http://www.reactivated.net/writing_udev_rules.html


Also, thank you Rcspam for an example of using the kidizoom on Ubuntu 10.04+:
http://rcspamsblogen.blogspot.com/2011/01/kidizoom-and-ubuntu-maverick.html
(English)

17 comments:

rcspam said...

Hi
It is very interesting...
I just buy a kidizoom for my children.
I dont know udev very well and the Vtech Kidizoom without SD card dont mount. Apparently , the cause is the fat16 format of the interne flash memory. Can you give me the solution to automate the mount with udev.
Perhaps, with something like that:
ACTION=="add", SUBSYSTEM=="scsi", ATTRS{vendor}=="VTech ", ATTRS{model}=="Kidizoom ", NAME="KIDIZOOM%1", SYMLINK+="vtech_kidizoom%n", RUN+="mount /dev/vtech_kidizoom%n /media/KIDIZOOM%1"
What do you think about that
thanx

rcspam

Kevin said...

That might work. It's hard to say without actually running a test with the device. I'd have to see the signature of the device with udevadm.

I tested mine at first by running a command like RUN+="echo 1 >> /tmp/test" just to see if the command was firing.

I'm using our kidizoom camera without an internal SD card and it mounts automatically (like a usb device).

rcspam said...

Mine is ubuntu 10.10
What is your system ?

Kevin said...

Ah, good point. I'm connecting the camera to a computer running and older version 9.04.

I just tested with my computer running 10.04, and it created an icon but I wasn't able to open it.

So it looks like support may have been affected in the upgrade. Perhaps with gnome volume manager?

A custom udev rule should work as a fix. If it's the same camera as mine, the rule I posted may work (as the trigger). The hardest thing with udev IMO is finding a device signature that fires. The "mount" commands can be tested independently on the command line.

Kevin said...

Also, in order to mount it the drive, you may need a -t flag.

I just tested the following and it works:

sudo -i
mkdir /tmp/test
mount -t vfat /dev/sdb1 /tmp/test/

Then the mount point is browsable.

Otherwise, it does look like support of the device has been affected by the last upgrade. running dmesg I see:

[ 561.522246] Buffer I/O error on device sdb, logical block 429568
[ 561.539206] sd 2:0:0:0: [sdb] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
[ 561.539222] sd 2:0:0:0: [sdb] Sense Key : Illegal Request [current]
[ 561.539232] sd 2:0:0:0: [sdb] Add. Sense: Logical block address out of range
[ 561.539244] sd 2:0:0:0: [sdb] CDB: Read(10): 28 00 00 06 8e 00 00 00 01 00
[ 561.539259] end_request: I/O error, dev sdb, sector 429568

rcspam said...

in fact after several tries... ex:

SUBSYSTEMS=="scsi", ACTION=="add", ATTRS{vendor}=="VTech ", ATTRS{model}=="Kidizoom ", SYMLINK+="kidizoom%n", RUN+="scrpit/with/pmount %k"
SUBSYSTEMS=="scsi", ACTION=="remove", ATTRS{vendor}=="VTech ", ATTRS{model}=="Kidizoom ", SYMLINK+="kidizoom%n", RUN+=script/with/pumount %k"

The scripts run your script with a pmount or pumount command. And it is very unstable. It was ok to mount 1 hour ago but now i need run pmount bye hand to mount and unmount kidizoom. the "RUN" script with pumount command has never worked, an echo command neither.

Perhaps a bug in maverick udev !?

rcspam said...

pmount script:

#!/bin/bash
{
echo $1 pmount > /tmp/echo-pmount
sleep 3
user=`who | grep :0\) | cut -f 1 -d ' '`
su $user -c "pmount -t vfat /dev/$1 KIDIZOOM"
} &

pumount script (which have never works)

#!/bin/bash
{
echo $1 pumount > /tmp/echo-pumount
sleep 3
user=`who | grep :0\) | cut -f 1 -d ' '`
su $user -c "pumount /dev/$1"
} &

rcspam said...

here is the rules:
http://dl.dropbox.com/u/2677320/90-kidizoom.rules

Kevin said...

I see two possible issues. One the {} & commands will background the process, so that they may run in parallel to a second process that follows. The & backgrounds the block of code and returns immediately. If you need to run multiple commands, it would either all need to be inside a {}& block, or the & will need to be removed.

The other is I would put a sleep after the pmount since it might take a second or so for everything to register.

Also, could you automatically run a un mount before trying a mount? Maybe that would force a clean mount.

rcspam said...

I have find the solution.
In fact i have create a function run by "su" for the mount script.
To unmount kidizoom we need to use the rules ENV and not ATTRS.

the rules are here:
http://dl.dropbox.com/u/2677320/90-kidizoom.rules

...and the scripts here:
http://dl.dropbox.com/u/2677320/mount-script
http://dl.dropbox.com/u/2677320/umount-script

Kevin said...

Thank you for posting your solution. I will test it out.

rcspam said...

I start a blog on http://rcspamsblog.blogspot.com/ with a note on kidizoom and take a link to yours

Kevin said...

Thanks, I added a link to your post as well. :)

rcspam said...

hi sevkeifert, you can now replace your english translate link because i have create un translation of my blog in english, i think it will be more readable than google traslate:
http://rcspamsblogen.blogspot.com/
Cheers
Rcspam

rcspam said...

Ooop ! i forgot...

HAPPY NEW YEAR 2011 !

Kevin said...

Thank you rc, I updated the link.

Peer said...

Also, thank you Rcspam for an example of using the kidizoom on Ubuntu 10.04+: ... kiddyzoom.blogspot.de