- Note for Ubuntu Server 11.10: This script fails on Ubuntu Server 11.10 due to the obsolete
vol_id
command. vol_id
has been superseded by blkid
. To fix the script, replace "vol_id" by "blkid -o udev" in the udev-auto-mount.sh
script.
I've been banging my head around this for a while now, and I think I've found a working solution. This is developed and tested on a Debian-based system, so it should work on Ubuntu. I'll point out the assumptions it makes so it can be adapted to other systems as well.
- It will automatically mount USB drives on plugin, and shouldn't take much to adapt for Firewire.
- It uses UDEV, so no monkeying with HAL/DeviceKit/GNOME-Anything.
- It automagically creates a
/media/LABEL
directory to mount the device to.
- However, it may interfere with other automounters; I can't test for that. I expect that, with Gnome-VFS active, both may try to do the mount ... if Gnome-VFS fails the mount, it might not configure a desktop icon. Unmounting from Gnome should be possible, but might require
gksudo
or similar.
I have not tested this on system boot, but the only reason I can see that it might not work is if it tries to mount the USB drive before the system is ready for mounts. If that's the case, you'll probably need one additional tweak to the mount script. (I'm checking with ServerFault to see if there's any advice, but not much interest in it over there.)
On to it, then.
UDEV references:
Background (UDEV? Whuzzat?)
UDEV is the kernel's hotplug system. It's what automagically configures the proper devices and device symlinks (eg /dev/disk/by-label/<LABEL>
), both at boot time and for devices added while the system is running.
D-Bus and HAL are used for sending hardware events to listeners like Desktop Environments. So when you log into GNOME and insert a CD or plug in a USB drive, that event follows this chain:
kernel -> udev -> dbus -> hal -> gnome-vfs/nautilus (mount)
And presto, your drive gets mounted. But in a headless system, we don't want to have to log in to get the benefits of automounting.
Udev Rules
Since UDEV lets us write rules and run programs on device insertion, this is an ideal choice. We're going to take advantage of Debian/Ubuntu's existing rules, let them setup the /dev/disk/by-label/<LABEL>
symlink for us, and add another rule that will mount the device for us.
UDEV's rules are kept in /etc/udev/rules.d
(and /lib/udev/rules.d
on Karmic), and are processed in numerical order. Any file not starting with a number gets processed after the numbered files. On my system, HAL rules are in a file called 90-hal.rules
, so I put my rules in 89-local.rules
so they get processed before they get to HAL. Primarily, you need to make sure these rules happen after the 60-persistent-storage.rules
. local.rules
may be good enough.
Put this in your new rules file:
# /etc/udev/rules.d/local.rules
# /etc/udev/rules.d/89-local.rules
# ADD rule: if we have a valid ID_FS_LABEL_ENC, and it's USB, mkdir and mount
ENV{ID_FS_LABEL_ENC}=="?*", ACTION=="add", SUBSYSTEMS=="usb", \
RUN+="/usr/local/sbin/udev-automounter.sh %k"
Make sure there's no spaces after the \
, just a newline
(\n
).
Change SUBSYSTEMS=="usb"
to SUBSYSTEMS=="usb|ieee1394"
for Firewire support.
If you want the device to always be owned by a particular user, add an OWNER="username"
clause. If you just need the files owned by a particular user, tweak the mount script instead.
Reading the Rule
This adds a program to run to the device's list of programs to run. It identifies USB partition devices by <LABEL>
, then passes this information to a script that performs the mount. Specifically, this rule is matching:
ENV{ID_FS_LABEL_ENC}=="?*"
-- an environment variable set by an earlier system rule. Doesn't exist for non-filesystems, so that's why we check for it. We actually want to use ID_FS_LABEL
for the mount point, but I haven't convinced UDEV to escape it for me, so we'll let the mount script handle that.
This and other environment variables are obtained by udev using the vol_id
command (deprecated). It's a handy tool to see nice quick details on a partition:
$ sudo vol_id /dev/sdc1
ID_FS_TYPE=ext2
ID_FS_UUID=a40d282a-4a24-4593-a0ab-6f2600f920dd
ID_FS_LABEL=Travel Dawgs
ID_FS_LABEL_ENC=Travel\x20Dawgs
ID_FS_LABEL_SAFE=Travel_Dawgs
ACTION=="add"
-- only match add
events...
SUBSYSTEMS=="usb"
-- only match devices that are on the USB bus. We use SUBSYSTEMS
here because this matches against our device's parents; the device we're interested in will actually be SUBSYSTEM=="scsi". Matching against a parent USB device avoids adding our program to the internal drives.
RUN+="..."
-- not a match, but an action: add this program to the list of programs to run. In the program's arguments, %k
gets expanded to the device name (eg sdc1
, not /dev/sdc1
) and $env{FOO}
gets the contents of environment variable FOO.
Testing the Rule
The first reference link (above) is an excellent UDEV tutorial, but it's slightly out of date. The programs it runs for testing your rules (udevtest
in particular) have been replaced by the catch-all udevadm
utility.
After you've added the rule, plug in your device. Give it a few seconds, then check to see what device it's been assigned to with:
$ ls -l /dev/disk/by-label/*
lrwxrwxrwx 1 root root 10 2009-10-25 07:27 label_Foo -> ../../sda1
lrwxrwxrwx 1 root root 10 2009-10-25 07:27 label_Bar -> ../../sdb1
lrwxrwxrwx 1 root root 10 2009-10-25 07:27 label_Baz -> ../../sdc1
If your removeable drive contains label_Baz
, it's on device sdc1
. Run this and look at the output towards the end:
$ sudo udevadm test /sys/block/sdc/sdc1
parse_file: reading (...) (many lines about files it reads)
import_uevent_var: import into environment: (...) (many lines about env variables)
(...) (many lines tracing rule matches & programs run)
update_link: found 1 devices with name 'disk/by-label/LABEL_BAZ'
update_link: found '/block/sdc/sdc1' for 'disk/by-label/LABEL_BAZ'
update_link: compare (our own) priority of '/block/sdc/sdc1' 0 >= 0
update_link: 'disk/by-label/LABEL_BAZ' with target 'sdc1' has the highest priority 0, create it
udevtest: run: '/usr/local/sbin/udev-automounter.sh sdc1 LABEL_BAZ'
udevtest: run: 'socket:/org/freedesktop/hal/udev_event'
udevtest: run: 'socket:@/org/kernel/udev/monitor'
Look for the script name from our RUN+=
rule in the last few lines (3rd from the bottom in this example). You can see the arguments that would be used for this device. You can run that command now to check that the arguments are sound; if it works on your commandline, it should work automatically when a device is inserted.
You can also monitor UDEV events in realtime: run sudo udevadm monitor
(see man udevadm
for details on the switches). Then just plug in a new device and watch events scroll by. (Probably overkill unless you're into really low-level details...)
Reloading the Rules
Once you've verified the rule is getting read properly, you need to tell UDEV to reload its rules so the new one takes effect. Use any of these methods (if the first doesn't work, the second should... but try the first first):
Script! Actually, 2 Scripts...
Here's the first script. Since the program we run needs to complete quickly, this just spins the second script off in the background. Put this in /usr/local/sbin/udev-automounter.sh
:
#!/bin/sh
#
# USAGE: usb-automounter.sh DEVICE
# DEVICE is the actual device node at /dev/DEVICE
/usr/local/sbin/udev-auto-mount.sh ${1} &
Here's the second script. This does a bit more input checking. Put this in /usr/local/sbin/udev-auto-mount.sh
. You may want to tweak the mount options below. This script now handles finding the partition LABEL on its own; UDEV only sends the DEVICE name.
If there's a problem mounting drives at boot-time, you can put a nice long sleep 60
in this script, to give the system time to come all the way up before the script attempts to mount the drive.
I've given a suggestion in the comments for how to check (run ps
to see if a webserver is running), but you'll want to tweak that for your system. I think most any network servers you might be using would suffice for this purpose -- nfsd, smbd, apache, etc. The risk, of course, is that the mount script will fail if the service isn't running, so maybe testing a particular file's existence would be a better solution.
#!/bin/sh
#
# USAGE: udev-auto-mount.sh DEVICE
# DEVICE is the actual device node at /dev/DEVICE
#
# This script takes a device name, looks up the partition label and
# type, creates /media/LABEL and mounts the partition. Mount options
# are hard-coded below.
DEVICE=$1
# check input
if [ -z "$DEVICE" ]; then
exit 1
fi
# test that this device isn't already mounted
device_is_mounted=`grep ${DEVICE} /etc/mtab`
if [ -n "$device_is_mounted" ]; then
echo "error: seems /dev/${DEVICE} is already mounted"
exit 1
fi
# If there's a problem at boot-time, this is where we'd put
# some test to check that we're booting, and then run
# sleep 60
# so the system is ready for the mount below.
#
# An example to experiment with:
# Assume the system is "booted enough" if the HTTPD server is running.
# If it isn't, sleep for half a minute before checking again.
#
# The risk: if the server fails for some reason, this mount script
# will just keep waiting for it to show up. A better solution would
# be to check for some file that exists after the boot process is complete.
#
# HTTPD_UP=`ps -ax | grep httpd | grep -v grep`
# while [ -z "$HTTPD_UP" ]; do
# sleep 30
# HTTPD_UP=`ps -ax | grep httpd | grep -v grep`
# done
# pull in useful variables from vol_id, quote everything Just In Case
eval `/sbin/vol_id /dev/${DEVICE} | sed 's/^/export /; s/=/="/; s/$/"/'`
if [ -z "$ID_FS_LABEL" ] || [ -z "$ID_FS_TYPE" ]; then
echo "error: ID_FS_LABEL is empty! did vol_id break? tried /dev/${DEVICE}"
exit 1
fi
# test mountpoint - it shouldn't exist
if [ ! -e "/media/${ID_FS_LABEL}" ]; then
# make the mountpoint
mkdir "/media/${ID_FS_LABEL}"
# mount the device
#
# If expecting thumbdrives, you probably want
# mount -t auto -o sync,noatime [...]
#
# If drive is VFAT/NFTS, this mounts the filesystem such that all files
# are owned by a std user instead of by root. Change to your user's UID
# (listed in /etc/passwd). You may also want "gid=1000" and/or "umask=022", eg:
# mount -t auto -o uid=1000,gid=1000 [...]
#
#
case "$ID_FS_TYPE" in
vfat) mount -t vfat -o sync,noatime,uid=1000 /dev/${DEVICE} "/media/${ID_FS_LABEL}"
;;
# I like the locale setting for ntfs
ntfs) mount -t auto -o sync,noatime,uid=1000,locale=en_US.UTF-8 /dev/${DEVICE} "/media/${ID_FS_LABEL}"
;;
# ext2/3/4 don't like uid option
ext*) mount -t auto -o sync,noatime /dev/${DEVICE} "/media/${ID_FS_LABEL}"
;;
esac
# all done here, return successful
exit 0
fi
exit 1
Super Bonus Cleanup Script!
One more script. All this does is unmount the device and remove the mountpoint directories. It assumes it has privs to do this, so you'll need to run it with sudo
. This script now takes the full mountpoint on the commandline, eg:
$ /usr/local/sbin/udev-unmounter.sh "/media/My Random Disk"
Put this in /usr/local/sbin/udev-unmounter.sh
:
#!/bin/sh
#
# USAGE: udev-unmounter.sh MOUNTPT
# MOUNTPT is a mountpoint we want to unmount and delete.
MOUNTPT="$1"
if [ -z "$MOUNTPT" ]; then
exit 1
fi
# test mountpoint - it should exist
if [ -e "${MOUNTPT}" ]; then
# very naive; just run and pray
umount -l "${MOUNTPT}" && rmdir "${MOUNTPT}" && exit 0
echo "error: ${MOUNTPT} failed to unmount."
exit 1
fi
echo "error: ${MOUNTPT} does not exist"
exit 1
Also, the solution should not conflict with the way drives are normally mounted when someone IS logged in. – endolith – 2009-10-11T23:00:35.067
I deleted my answer. It seems that the underlying infrastructure has changed since the last time I had to tweak my system's automounting behavior. – Ryan C. Thompson – 2009-10-12T23:56:25.940
Well the file still exists, but it's not in the path and doesn't seem to do anything. There must be a way to do the same thing, though. – endolith – 2009-10-16T13:55:33.903
4Great question, wish I had an answer for you, now I'm curious how this is solved. – invert – 2009-10-30T09:32:51.070
1I updated my answer. – Ryan C. Thompson – 2009-10-30T20:01:05.683
i want to do the same thing on a (mostly) headless Debian install. i absolutely do not want to install GNOME. :( – quack quixote – 2009-10-30T20:08:12.593
gnome-volume-manager is apparently based on HAL, which has been replaced with DeviceKit in Karmic, so maybe DeviceKit has something to do this? – endolith – 2009-11-02T16:09:30.877
1And I, for the record, do not have a problem with Gnome being installed, as I already do, but a solution for headless GUI-less servers would be best. – endolith – 2009-11-02T16:10:19.970
1ARRRGHH... bug in my answer. in the RUN.
/usr/local/sbin/udev-automounter.sh mount %k
should be/usr/local/sbin/udev-automounter.sh %k
. sorry. – quack quixote – 2009-11-04T04:28:55.603currently debugging the spaces-in-labels issue. what filesystems are you using? my only test device is currently FAT and working but not well; i think NTFS/ext2 will be friendlier. – quack quixote – 2009-11-04T16:28:23.513
1OK, as of Update-3, it works with spaces. it does so by using an "encoded" version of <LABEL> that converts spaces into
\x20
's. so it's not pretty, but it will work. udev isn't handling the labels-with-spaces nicely, but there is another option that uses underscores instead of\x20
's (so it at least looks nice). looks like space-handling has to go into the shellscripts. – quack quixote – 2009-11-04T17:33:52.633update 5 up, scripts v2.0. rule changed too but nothing will break if you keep the old version. – quack quixote – 2009-11-04T20:09:46.623
Right now I have an EXT3 partition, an NTFS, and three FAT32s. – endolith – 2009-11-04T20:24:37.233
you can get away with a single mount (no
case
clause) if you just want defaults. ext3 won't like the-o uid=X
stuff; it doesn't even mount on my system when i include that option. – quack quixote – 2009-11-04T21:22:43.9501Much simpler method: Set the computer to auto-login a user, and then lock the screen after login. The automatic mount points created by Gnome/Hal/whatever have real spaces in them, for the record, like
/media/EXT3\ backup/
– endolith – 2009-11-14T17:56:16.697Im glad this worked for your other question :) – invert – 2009-11-16T10:00:49.797
It worked for both! Add it as an answer here so I can vote it up. That's the solution I'm actually using. – endolith – 2009-11-17T05:02:11.870
i figured out how to do this by running halevt as a system daemon; edited my second answer with the how-to. using autologin is a clever hack-a-round but not secure enough for my taste. – quack quixote – 2009-11-21T13:04:21.577
After upgrading to Karmic, drives do not auto-mount on auto-login. >:( – endolith – 2009-12-13T17:55:17.637
@endolith you might want to review my recent answer. this used to be hard few years ago (extreme long and unnecessary accepted answer), however now it's just a matter of apt-getting a package. a short answer will help many who do not scroll down to alternate solutions and thus might miss the easy answer. – Costin Gușă – 2014-04-26T10:41:26.357