Automount USB drives with systemd
We're updating our servers from a very out-of-date distro to a modern Debian Jessie based system, including lightdm / xfce, and of course systemd (and udisks2). One sticking point is automounting USB drives. We used to accomplish this with some udev rules. The old rules almost still work - the mount point gets created and the drive is mounted fine, but after a few seconds systemd is doing something that breaks the mount, so subsequent access attempts result in "Transport endpoint is not connected" errors.
Manually mounting the drive via the command line works fine. So does letting a file manager (thunar and thunar-volman, which in turn uses udisks2). But those are not viable options - these systems mostly run headless, so thunar isn't normally running. We need to be able to plug in disk drives for unattended cron-based backups.
I thought that modifying the udev script to spawn a detached job which waits a few seconds before performing the mount might do the trick, but systemd seems to go out of its way to prevent this - it somehow still waits for the detached job to finish before continuing.
Perhaps having the udev script tickle udisks2 somehow is the right approach? I'm at a lose, so any advice greatly appreciated.
After several false starts I figured this out. The key is to add a systemd unit service between udev and a mounting script.
(For the record, I was not able to get this working using udisks2 (via something like udisksctl mount -b /dev/sdb1
) called either directly from a udev rule or from a systemd unit file. There seems to be a race condition and the device node isn't quite ready, resulting in Error looking up object for device /dev/sdb1
. Unfortunate, since udisks2 could take care of all the mount point messyness...)
The heavy lifting is done by a shell script, which takes care of creating and removing mount points, and mounting and unmounting the drives.
/usr/local/bin/usb-mount.sh
#!/bin/bash
# This script is called from our systemd unit file to mount or unmount
# a USB drive.
usage()
{
echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
exit 1
}
if [[ $# -ne 2 ]]; then
usage
fi
ACTION=$1
DEVBASE=$2
DEVICE="/dev/${DEVBASE}"
# See if this drive is already mounted, and if so where
MOUNT_POINT=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }')
do_mount()
{
if [[ -n ${MOUNT_POINT} ]]; then
echo "Warning: ${DEVICE} is already mounted at ${MOUNT_POINT}"
exit 1
fi
# Get info for this drive: $ID_FS_LABEL, $ID_FS_UUID, and $ID_FS_TYPE
eval $(/sbin/blkid -o udev ${DEVICE})
# Figure out a mount point to use
LABEL=${ID_FS_LABEL}
if [[ -z "${LABEL}" ]]; then
LABEL=${DEVBASE}
elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then
# Already in use, make a unique one
LABEL+="-${DEVBASE}"
fi
MOUNT_POINT="/media/${LABEL}"
echo "Mount point: ${MOUNT_POINT}"
/bin/mkdir -p ${MOUNT_POINT}
# Global mount options
OPTS="rw,relatime"
# File system type specific mount options
if [[ ${ID_FS_TYPE} == "vfat" ]]; then
OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
fi
if ! /bin/mount -o ${OPTS} ${DEVICE} ${MOUNT_POINT}; then
echo "Error mounting ${DEVICE} (status = $?)"
/bin/rmdir ${MOUNT_POINT}
exit 1
fi
echo "**** Mounted ${DEVICE} at ${MOUNT_POINT} ****"
}
do_unmount()
{
if [[ -z ${MOUNT_POINT} ]]; then
echo "Warning: ${DEVICE} is not mounted"
else
/bin/umount -l ${DEVICE}
echo "**** Unmounted ${DEVICE}"
fi
# Delete all empty dirs in /media that aren't being used as mount
# points. This is kind of overkill, but if the drive was unmounted
# prior to removal we no longer know its mount point, and we don't
# want to leave it orphaned...
for f in /media/* ; do
if [[ -n $(/usr/bin/find "$f" -maxdepth 0 -type d -empty) ]]; then
if ! /bin/grep -q " $f " /etc/mtab; then
echo "**** Removing mount point $f"
/bin/rmdir "$f"
fi
fi
done
}
case "${ACTION}" in
add)
do_mount
;;
remove)
do_unmount
;;
*)
usage
;;
esac
The script, in turn, is called by a systemd unit file. We use the "@" filename syntax so we can pass the device name as an argument.
/etc/systemd/system/[email protected]
[Unit]
Description=Mount USB Drive on %i
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/local/bin/usb-mount.sh add %i
ExecStop=/usr/local/bin/usb-mount.sh remove %i
Finally, some udev rules start and stop the systemd unit service on hotplug/unplug:
/etc/udev/rules.d/99-local.rules
KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service"
KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service"
This seems to do the trick! A couple of useful commands for debugging stuff like this:
-
udevadm control -l debug
turns on verbose logging to/var/log/syslog
so you can see what's happening. -
udevadm control --reload-rules
after you modify files in the rules.d dir (may not be necessary, but can't hurt...). -
systemctl daemon-reload
after you modify systemd unit files.
there is a new, succinct systemd
auto-mount option which can be used with fstab
which allows you to use all the standardized mount permission options, and it looks like this:
x-systemd.automount
an example of it in an fstab
line:
/dev/sdd1 /mnt/hitachi-one auto noauto,x-systemd.automount 0 2
the noauto
option will mean it will not attempt to be mounted at boot, as with older software autofs
.
after adding a new x-systemd.automount
line to fstab
you then need to run:
sudo systemctl daemon-reload
and then both, or one, of the following:
sudo systemctl restart remote-fs.target
sudo systemctl restart local-fs.target
for more infomation about it:
https://wiki.archlinux.org/index.php/Fstab#Automount_with_systemd
Using pmount, systemd and Mike Blackwell's approach, you can simplify the whole thing:
/etc/systemd/system/[email protected]
[Unit]
Description=Mount USB Drive on %i
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/pmount --umask 000 /dev/%i /media/%i
ExecStop=/usr/bin/pumount /dev/%i
/etc/udev/rules.d/99-usb-mount.rules
ACTION=="add",KERNEL=="sd[a-z][0-9]*",SUBSYSTEMS=="usb",RUN+="/bin/systemctl start usb-mount@%k.service"
ACTION=="remove",KERNEL=="sd[a-z][0-9]*",SUBSYSTEMS=="usb",RUN+="/bin/systemctl stop usb-mount@%k.service"
HTH and thank you Mike.
I have modified the script from @MikeBlackwell to:
- recognize device names that span multiple characters, not just
/dev/sd[a-z]
but/dev/sd[a-z]*
; often the case with servers that have larger number of spindles. - track the list of automounted drives at
/var/log/usb-mount.track
- log the actions to
/var/log/messages
with tag usb-mount.sh - prefix device name with the device label for the mount point to not run in to problems with drives that haven't been assigned a label(empty?):
/media/sdd2_usbtest
,/media/sdd2_
- included wrapper scripts to place the files appropriately and undo if required
Since @MikeBlackwell has already done most of the heavy lifting, I chose not to rewrite it; just made the necessary changes. I have acknowledged his work sighting his name and URI of the original answer.
Find it at https://github.com/raamsri/automount-usb