Automatically mount USB drives in Linux without a GUI

The helloNULL web server is a headless system, i.e. it does not run a windowing/desktop environment. One feature that is therefore lacking is the automatic mounting of USB flash drives upon plugging them in. For example, in the Debian/XFCE environment of my mobile desktop, a flash drive will be mounted to /media/label upon plugging in to the port, where ‘label’ is the drive label. This post will show you how to duplicate that functionality on a terminal-only system using udev rules and a simple script.

When a USB drive is plugged in udev handles initiating the device and by using udev rules, it is possible to specify a script to be run whenever this event occurs. In Debian, udev rules are written in text files under /etc/udev/rules.d/. Each file in that directory is parsed by order of filename. Rules that run scripts should begin with a  number in the 80’s. Our script can be called /etc/udev/rules.d/81-custom.rules and should contain the following code:

# Custom udev rules. Note that udev rules are run in order based
# on the filename. Rules in filenames that start with numbers
# are run before this one.
# This rule detects USB drives that are added to the system so
# that they can be mounted automagically by the script.
# It is based off of the replies to a question on
ENV{ID_FS_USAGE}=="filesystem", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/root/ mount %k"
ENV{ID_FS_USAGE}=="filesystem", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/root/ cleanup %k"

The first rule will trigger whenever a usb device (SUBSYSTEMS) is added (ACTION) and the device contains a filesystem (ENV{ID_FS_USAGE}). The second rule is similar but triggers upon a ‘remove’ event. In either case, udev will add the script (RUN) specified to the list of scripts to be run for that event. Our script has two functions, mount and cleanup, depending on the event. In the former case, our script will need to know the kernel name of the device (e.g. sda1) to use for the mount command. The script looks like this:

# This script is called by a udev rule whenever a usb drive is
# plugged into the system or removed from the system.
# Usage:
# where MODE is either mount or cleanup
# and DEVICE is the device basename, i.e. /dev/DEVICE

if [ "$1" = "mount" ]; then
    # The ID_FS_LABEL enviroment variable is only available
    # When this script is caleld by udev
    mkdir -p "/media/$ID_FS_LABEL"
    $(mount | grep -q "/media/$ID_FS_LABEL") || mount /dev/$2 "/media/$ID_FS_LABEL"
elif [ "$1" = "cleanup" ]; then
    rmdir "/media/$ID_FS_LABEL"
    echo "ERROR: Mode $1 should be 'mount' or 'cleanup'."

This is pretty straight-forward. In the case of mounting a usb drive, the device name is created from the passed argument while the mount point is generated from the disk label taken from an environment variable. The same environment variable is used to remove the mount point when the disk is removed. The proper way to remove the drive is with the eject command, like this:

That’s it! This is perhaps not the most robust solution but should work for most cases. Put the 81-custom.rules file in your /etc/udev/rules.d/ directory and the script in your /root directory (or wherever you want). Don’t forget to give the script execute permissions.

Edit 06/03/2013: I changed my mount line to give read-write permissions to the plugdev group. The plugdev group is for users who are allowed to mount and unmount removable devices. On my system, all people-users (as opposed to system accounts) are members of the plugdev group. This means that any real person logged in can access the mounted USB drive. The new mount line looks like this:

$(mount | grep -q "/medial/$ID_FS_LABEL") || mount -o gid=plugdev,dmask=007,fmask=117 /dev/$2 "/media/$ID_FS_LABEL"

Edit 12/16/2013: I discovered that my mount command from the 06/03/2013 change only works right for file systems of type vfat and would fail if the usb drive connected was formatted as ext3, for example. The problem is the options specified (gid, etc.) which aren’t valid for ext3 and ext4 filesystems which preserve permissions. The solution I decided on was to specify two different udev rules, one for each type of file system. Different mount options will be set in an environment variable depending on the file system detected. Then in my script, this environment variable is put directly into the mount call.

New udev lines:

# the mount options are different depending on the file system type
ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_TYPE}=="vfat", SUBSYSTEMS=="usb", ACTION=="add", ENV{MOUNT_OPTIONS}="-o gid=plugdev,dmask=007,fmask=117", RUN+="/root/ mount %k"
ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_TYPE}=="ext?", SUBSYSTEMS=="usb", ACTION=="add", ENV{MOUNT_OPTIONS}="", RUN+="/root/ mount %k"

New lines:

$(mount | grep -q "/media/$ID_FS_LABEL") || mount $MOUNT_OPTIONS /dev/$2 "/media/$ID_FS_LABEL"