Disaggregate

Disaggregate Technical Web Log

Technical information and commentary on a variety of topics.
Category:

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 December 1, 2005 07:49 AM