December 08, 2005

udev: eliminating scripts, setting GROUP, understanding variables

In response to a few queries, here's some more information about udev.

%p or $devpath or DEVPATH is the path under /sys, but without the leading "/sys." E.g., the device path will be /sys/block/sdc, and DEVPATH will be "/block/sdc." $kernel or %k is the path *under* dev, e.g., bus/usb/002/017, so /dev/%k is the actual path to the device name.

Using Program. PROGRAM runs *before* the anything is done in the rule. If PROGRAM fails, the entire rule fails. When I was making errors in PROGRAM -- for example, when I was using the wrong SUBSYTEM, or I didn't give a full path name to umount -- then the SYMLINK wasn't created because PROGRAM returned an error.

So, let's say I want to mount a flash drive. I don't need an external script; I can use:

SUBSYSTEM=="block", ACTION=="add", SYSFS{product}=="Flash Disk", SYMLINK="ums", ENV{flashdrive}="%p", RUN+="/bin/mount -t vfat -o user,sync,umask=0000 /dev/%k /ums"

Note that you must give the full path name to "mount," otherwise RUN will assume it's a udev script and try to find it in /lib/udev or some other udev-specific location.

If I'd used "PROGRAM" instead of run, then udev would attempt to execute mount before adding the /dev entry, PROGRAM would return a non-zero exit code, and the rule would not execute and the /dev entry would not be made. (Well certainly the SYMLINK would not be made; it's possible the /dev entry is made, but I don't think it is.)

The man page mentions the ENV, but what are the contents of this mysterious variable? They can be viewed by using "udevmonitor --env" -- it gives a printout of each ENV variable as it's used/set. I suspect that there are more than the ones we see...

Aras Vaichas explains that "SUBSYSTEMs are what you find in /sys/class/ /sys/block/ /sys/bus/, e.g. usb, scsi, serio, etc." You can see the SUBSYSTEMs by using "udevmonitor --evn" and looking for SUBSYTEM. The PHYSDEVDRIVER variable is the associated kernel module.

Finally, setting GROUP for a device you've hotplugged. Instead of using an external script as was the case with hotplug, you can use a simple rule instead. This is how I set GROUP when I mount my USB-enabled camera:

SUBSYSTEM=="usb_device", SYSFS{idVendor}=="04a9", SYSFS{idProduct}=="3051", GROUP="camera"

The trick is knowing that it's the "usb_device" SUBSYSTEM that creates the USB device entry that gtkam and libgphoto2 use, e.g., /dev/usb/002/017. Finding out what SUBSYSTEM to use is a trail-and-error process, but udevmonitor --env does display the /dev entry when it's created, and that's a very good start.

Posted by Moshe Yudkowsky at 08:42 AM

December 01, 2005

Automounting Flash Drives and MP3 Players with udev

In my previous entry, I showed how to use hotplug to automount a flash drive. But that was under devfs; with udev, hotplug is no longer available. Here's some information on how to get UMS and MP3s to automount on udev. It's not an optimal solution by any means, but it's the one I find useful.

First of all, I suggest you read Daniel Drake's introduction to udev rules. As he suggested, I used udevinfo to find an appropriate SYSFS. Please note that udevinfo is a bit buggy: you have to use "udevinfo -q path -a" and not "udevinfo -a -q path" if you want full information.

One you're finished, here's some additional information. Kay Sievers explained to me that 'Sysfs values are not readable at remove, cause the device directory is already gone. You need to match against properties which you can see in udevmonitor --env or you need to store a custom key with ENV{key}="value" in the database, which is imported on remove.'

In order to add and remove the flash drive, I use the following udev rules (modified 20051207 to add SUBSYSTEM):


SUBSYSTEM=="block", ACTION=="add", SYSFS{product}=="Flash Disk", SYMLINK="ums", RUN+="/etc/hotplug/usb/ums", ENV{flashdrive}="%p"


SUBSYSTEM=="block", ACTION=="remove", ENV{flashdrive}=="%p", RUN+="/etc/hotplug/usb/ums"

The first rule sets, on an "add," the custom environment variable "flashdrive" to the value of DEVPATH (%p). The 2nd rule checks, on a "remove," to see if the devpath that's being removed is the one for the flash drive -- if so, the designated script runs.

Which is great. Something similar works for the iRiver mp3 player too, expect that I use a different SYSFS. Now the problem becomes getting the ums script to work.

Here's what I use. This isn't a perfect solution, because udev calls the ums sript multiple times (once for usb and once for scsi_generic events). I could eliminate these multiple calls, but I've decided not to bother. Note added 20051207: By adding SUBSYTEM, I call the script just once. The trick is finding the right SUBSYSTEM; several are called during the plugin event. Eventually, I discovered that the "block" is the best one to use; it seems to be the one that handles all devices that need to be block-accessible, i.e., to act as if they were drives.

To summarize what the script does: if I receive an add event, and the expected /dev/ums is available, I mount the device to the /ums mount point. On a remove, I umount if it's mounted.

By the way, I've been reluctant to remove the legacy devfs code from the script until this script and udev have been in service for a while; when using udev, /dev/ums is either there or not, and there's no need to check other paths, which makes the "else" path for defining NEWDEV unnecessary.

To make this script work for an iriver, change "ums" to "iriver" in appropriate places.

Script:

#!/bin/bash
##############################################
# iRiver hotplug script
# Written by Stefan MÄnsby (stefan@nis.nu)
# Version: 0.05
##############################################
# modified MY 2005-01-09
# modified for udev by Moshe Yudkowsky 2005-11-30
MOUNTDIR="/ums"
LOG=/var/log/usb-hotplug.log

ERRCODE=0 # for debugging, do not report errors

lap=$(date --rfc-3339=ns)

# defvs: DEVICE seems pretty useless
# need to search for "host*" in DEVPATH
# udev: no need for host* at all
plug() {

echo "...$lap: $DEVICE, $DEVPATH requested" >> $LOG

NEWDEV=""
((count=10))

if [ -b /dev/ums ]
then
NEWDEV=/dev/ums
else


while [ -z $NEWDEV ]
do
# look for host* on DEVPATH
NEWDEV=$(find /sys${DEVPATH} -name "host*") # find scsi host name
[[ ! -z $NEWDEV ]] &&
NEWDEV=$(basename $NEWDEV) # find at $(date)" that host
[[ $count -le 0 ]] &&
{
echo "...$lap: unable to find $NEWDEV in /sys${DEVPATH}, exiting" >> $LOG
exit $ERRCODE # stop if too many attempts
}
((count -=1))
echo "...$lap: $DEVPATH was not ready, try again" >> $LOG
sleep 1
done

NEWDEV=/dev/scsi/${NEWDEV}/bus0/target0/lun0/disc

fi

echo "...$lap: We wil use device $NEWDEV" >> $LOG

((count=10))

while [ ! -b $NEWDEV ]
do
echo "...$lap: wait for $NEWDEV to show as device, $count attempts left" >> $LOG
sleep 1
((count -= 1))
[[ $count -le 0 ]] &&
{
echo "...$lap: cannot get device, exiting" >> $LOG
exit $ERRCODE
}
done

echo "...device $NEWDEV is ready" >> $LOG

if [ -b $NEWDEV ]
then
{
echo "...$lap: $(date) mounting $NEWDEV" >> $LOG

# mount when plugged in
if (echo "..."; mount -t vfat -o user,sync,umask=0000 $NEWDEV $MOUNTDIR) >>$LOG 2>&1
then
echo "...$lap: mount succeeded" >> $LOG
else
echo "...$lap: mount failed" >> $LOG
fi
}
else
echo "...$lap: $DEVICE requested, unable to mount or find $NEWDEV" >> $LOG
reason=$(file $NEWDEV)
echo "...$lap: Reason: $reason" >> $LOG
fi

# create REMOVER script to umount when it unplugs
# if [ ! -b /dev/ums ]
# then
#
# :> $REMOVER
# echo "umount -f $MOUNTDIR >> $LOG" >> $REMOVER
# echo "echo \"$(date) $(time) unmounting /ums\" >> $LOG" >> $REMOVER
#
# chmod +x $REMOVER
# fi
#
}

unplug() {

if [ ! -b /dev/ums ]
then
mount | grep --quiet ums && umount $MOUNTDIR && echo "...$lap: umount suceeded"
fi
}

# start script

# lap=$(date --rfc-3339=seconds)

echo "ums/$lap: udev requested $ACTION" >> $LOG

case $ACTION in
"add") plug ;;
"remove") unplug ;;
*) echo "...No action taken" >> $LOG ;;
esac

exit 0

Posted by Moshe Yudkowsky at 07:49 AM

Migration from devfs to udev

I recently migrated from devfs to udev; although many users simply make the transition without any problems, I could't boot up without using devfs. Here's a mini "how to" that explains how to fix the basic migration from devfs to udev if you find that you can't boot.

Here's a summary of the problem I encountered and how I resolved it.

Problem: as I booted up, I got the usual deluge of reports; then I got three error messages:

/sbin/init: 432 cannot create /dev/null
/sbin/init: 433 cannot open dev/console
Kernel panic -- not syncing: Attempted to kill init!

After a bit of research and some asking around, I found that the answer was as follows.

There's a difference between devfs and udev. Devfs makes the
"console" and "null" files available by the time the files are needed
during the boot. udev starts later in the boot cycle, so you must supply your own copy of /dev/console and /dev/null. (There's a facility in udev that's supposed to create these files in advance (/etc/udev/links.conf in the Debian package), but it didn't work in my case.)

Therefore, before trying to boot using udev only, make certain you have a "console" and "null" file in /dev. Of course, if your system is running, you have these files now -- but the question is, were they part of the /dev directory, or did devfs put it there? Here's how to check.

* First, mount the root directory someplace other than /, so that the /dev
directory can be examined as a standalone -- so it can be seen as it looks without stuff that devfs puts there. As root, issue the command:

# mount -o bind / /mnt

(where /mnt is some random mouting point).


* Next, look at /mnt/dev. You need to have two special files there, console and null:

crw------- root root 5, 1 Nov 28 16:50 console
crw-rw-rw- root root 1, 3 Nov 28 16:51 null

If you have them already, you're all set. On my system, I had "null" and it was *not* a "c" (special character) file; it was an ordinary file. I got rid of that file and issued the following commands to create console and null:


# mknod -m 660 console c 5 1
# mknod -m 660 null c 1 3


* Unmount the / file system:

# umount /mnt.

* Make certain you have udev! Linux 2.6.14-2 requires udev to boot, but you are responsible for actually installing udev.

* Once /dev/console and /dev/null exist, you can boot using udev only. (For example, on my system, a boot using udev only is done by removing the "devfs=mount" command from lilo.) There's other stuff to do to convert completely to udev, but this gets you started.

Posted by Moshe Yudkowsky at 06:54 AM