Revised from: Bash scripts and udev rules to handle USB auto mounting / unmounting
Tested:
Uses USB insert/remove to control a headless Raspberry Pi 3 with Raspian Jessie Lite
Changes:
- Implement more functions
- Improve comments
- Modify if-fi exit handling
- Add optional flag for auto processing on insert/remove
- Optional function for USB insert - copy file (future: read config/start streaming process)
- Optional auto shutdown for USB removal (future: close process started by insert)
Goals:
- Improve bash coding and learn by implementing suggestions
- Improve my understanding of if-fi blocks and using inline commands (I'm not confident at all when it comes to streamlining code)
Current Code:
Uses udev rules to automount USB and create folder for device. Optionally can use an init constant to cause the automount/dismount process to auto start a process (in this case copy a config file) and shutdown pi on removal.
Work Flow:
uDev rules
usb-initloader.sh
Insert -> usb-automount.sh
Remove -> usb-unloader.sh
udev rules
# /etc/udev/rules.d/85-usb-loader.rules
# ADD rule:
# if USB inserted,
# and flash drive loaded as sd#
# pass on dev name and device formatting type
# run short script to fork another processing script
# run script to initiate another script (first script must finish quickly)
# to mkdir and mount, process file
#
# reload rules on PI by:
# sudo udevadm control --reload-rules
#
ACTION=="add", KERNEL=="sd*[0-9]", SUBSYSTEMS=="usb", RUN+="/home/pi/scripts/usb-initloader.sh ADD %k $env{ID_FS_TYPE}"
ACTION=="remove", KERNEL=="sd*[0-9]", SUBSYSTEMS=="usb", RUN+="/home/pi/scripts/usb-initloader.sh %k"
usb-initloader.sh
#!/bin/bash
#
# /home/pi/scripts/usb-initloader.sh
#
# this script uses udev rules
# is initiated when usb device is inserted or removed
#
# ** DEVICE INSERTED - new USB device inserted **
# ---------------------------------------------
# should be called from a udev rule like:that passes
# 1. "ADD",
# 2. kernel device (%k)
# 3. filesystem type $env(ID_FS_TYPE)
#
# ACTION=="add", KERNEL=="sd*[0-9]", SUBSYSTEMS=="usb", RUN+="/home/pi/scripts/usb-initloader.sh ADD %k $env(ID_FS_TYPE)"
#
# Mounts usb device on /media/<dev>
# Logs changes to /var/log/syslog
# use tail /var/log/syslog to look at latest events in log
#
# ** DEVICE REMOVED - USB device removed **
# ---------------------------------------------
# on remove - we only need the kernel device (%k)
# should be called from a udev rule like:
#
# ACTION=="remove", KERNEL=="sd*[0-9]", SUBSYSTEMS=="usb", RUN+="/home/pi/scripts/usb-initloader.sh %k"
#
# CONFIGURATION
#
# Location of the three scripts (** MUST match your udev rules **)
SCRIPT_DIR=/home/pi/scripts
#
# Location of Log File
LOG_DIR=/home/pi/logs
LOG_FILE="${LOG_DIR}/usb-automount.log"
#
# Mount folder (sda1 will be added underneath this)
MOUNT_DIR=/media
#
# Optional parameter to:
# - auto start a program on ADD
# - auto end program and shutdown pi on REMOVE
#
AUTO_START_FINISH=1 # Set to 0 if false; 1 if true
#
# Call speciality script and leave this one (with trailing "&")
#
if [ "$1" == "ADD" ]; then
DEVICE="$2" # USB device name (kernel passed from udev rule)
DEVTYPE="$3" # USB device formatting type
echo "==> Adding USB Device $DEVICE" >> "$LOG_FILE"
${SCRIPT_DIR}/usb-automount.sh "$LOG_FILE" "$MOUNT_DIR" "$DEVICE" "$DEVTYPE" "$AUTO_START_FINISH" >> "$LOG_FILE" 2>&1&
else
DEVICE="$1" # USB device name (kernel passed from udev rule)
echo "==> Unmounting USB Device $DEVICE" >> "$LOG_FILE"
${SCRIPT_DIR}/usb-unloader.sh "$LOG_FILE" "$MOUNT_DIR" "$DEVICE" "$AUTO_START_FINISH" >> "$LOG_FILE" 2>&1&
fi
usb-automount.sh
#!/bin/bash
#
# Script: /home/pi/scripts/usb-automount.sh
# make sure to chmod 0755 on this script
#
# USAGE: usb-automount.sh DEVICE FILESYSTEM
# LOG_FILE is the error/activity log file for shell (eg /home/pi/logs/usbloader.log)
# MOUNT_DIR is the full mount folder for device (/media/sda1)
# DEVICE is the actual device node at /dev/DEVICE (returned by udev rules %k parameter) (eg sda1)
# FILESYSTEM is the FileSystem type returned by rules (returned by udev rules %E{ID_FS_TYPE} or $env{ID_FS_TYPE} (eg vfat)
#
# In case the process of mounting takes too long for udev
# we call this script from /home/pi/scripts/usb-initloader.sh
# then fork out to speciality scripts
#
# Adapted for Raspberry Pi - Raspbian O/S
# from previous code found at:
# http://superuser.com/questions/53978/automatically-mount-external-drives-to-media-label-on-boot-without-a-user-logge
# and mount manager example at
# http://solvedforhome.com/?p=2806&v=3a52f3c22ed6
#
# Edited with many suggestions from @janos (http://codereview.stackexchange.com/users/12390/janos)
#
# Acknowledgement to:
# http://www.shellcheck.net/
# dostounix utility
LOG_FILE="$1"
MOUNT_DIR="$2"
DEVICE="$3" # USB device name (from kernel parameter passed from rule)
FILESYSTEM="$4"
AUTO_START="$5" # Do we want to auto-start a new process? 0 - No; 1 - Yes
# check for defined log file
if [ -z "$LOG_FILE" ]; then
exit 1
fi
# Define all parameters needed to auto-start program
if [ "$AUTO_START" == "1" ]; then
CONFIG_FILE="autostart-settings.cfg" # Check for this file on USB to initiate program with user-defined settings
STARTUP_FOLDER="/home/pi/autostart" # Start up folder to copy settings file into
fi
# Functions:
# #########
#
# automount function to test/make/mount USB device
#
automount() {
dt=$(date '+%Y-%m-%d/ %H:%M:%S')
echo "--- USB Auto Mount --- $dt"
#check input parameters
if [ -z "$MOUNT_DIR" ]; then
echo "Missing Parameter: MOUNT_DIR"
exit 1
fi
if [ -z "$DEVICE" ]; then
echo "Missing Parameter: DEVICE"
exit 1
fi
if [ -z "$FILESYSTEM" ]; then
echo "Missing Parameter: FILESYSTEM"
exit 1
fi
# Allow time for device to be added
sleep 2
# test for this device is already mounted
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" ]; then
echo "Error: seems /dev/$DEVICE is already mounted"
exit 1
fi
# test mountpoint - it shouldn't exist
if [ -e "$MOUNT_DIR/$DEVICE" ]; then
echo "Error: seems mountpoint $MOUNT_DIR/$DEVICE already exists"
exit 1
fi
# make the mountpoint
sudo mkdir "$MOUNT_DIR/$DEVICE"
# make sure the pi user owns this folder
sudo chown -R pi:pi "$MOUNT_DIR/$DEVICE"
# mount the device base on USB file system
case "$FILESYSTEM" in
# most common file system for USB sticks
vfat) sudo mount -t vfat -o utf8,uid=pi,gid=pi "/dev/$DEVICE" "$MOUNT_DIR/$DEVICE"
;;
# use locale setting for ntfs
ntfs) sudo mount -t auto -o uid=pi,gid=pi,locale=en_US.UTF-8 "/dev/$DEVICE" "$MOUNT_DIR/$DEVICE"
;;
# ext2/3/4 do not like uid option
ext*) sudo mount -t auto -o sync,noatime "/dev/$DEVICE" "$MOUNT_DIR/$DEVICE"
;;
esac
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" == "" ]; then
echo "Error: Failed to Mount $MOUNT_DIR/$DEVICE"
exit 1
fi
echo "SUCCESS: /dev/$DEVICE successfully mounted as $MOUNT_DIR/$DEVICE"
}
# Auto Start Funmction
autostart() {
echo "--- USB Auto Start Program ---"
look_for_cfg="$MOUNT_DIR/$DEVICE/$CONFIG_FILE"
if [ -e "$look_for_cfg" ]; then
echo "Copying Startup File to $STARTUP_FOLDER"
cp -u -p "$look_for_cfg" "$STARTUP_FOLDER"
fi
}
automount >> "$LOG_FILE" 2>&1
if [ "$AUTO_START" == "1" ]; then
autostart >> "$LOG_FILE" 2>&1
fi
usb-unloader.sh
#!/bin/bash
# /home/pi/scripts/usb-unloader.sh
#
# Called from {SCRIPT_DIR}/usb-initloader.sh
#
# USAGE: usb-automount.sh DEVICE FILESYSTEM
# LOG_FILE is the error/activity log file for shell (eg /home/pi/logs/usbloader.log)
# MOUNT_DIR is the full mount folder for device (/media)
# DEVICE is the actual device node at /dev/DEVICE (returned by udev rules %k parameter) (eg sda1)
#
# UnMounts usb device on /media/<device>
# Logs changes to /var/log/syslog and local log folder
# use tail /var/log/syslog to look at latest events in log
#
# SUPPLIED PARAMETERS
#####################
LOG_FILE="$1"
MOUNT_DIR="$2"
DEVICE="$3" # USB device name (from kernel parameter passed from rule)
AUTO_END="$4" # Set to 0 if not wanting to shutdown pi, 1 otherwise
#
# check for defined log file
if [ -z "$LOG_FILE" ]; then
exit 1
fi
#
# FUNCTIONS
###########
#
# autounload function to unmount USB device and remove mount folder
#
autounload() {
dt=$(date '+%Y-%m-%d %H:%M:%S')
echo "--- USB UnLoader --- $dt"
if [ -z "$MOUNT_DIR" ]; then
echo "Failed to supply Mount Dir parameter"
exit 1
fi
if [ -z "$DEVICE" ]; then
echo "Failed to supply DEVICE parameter"
exit 1
fi
# Unmount device
sudo umount "/dev/$DEVICE"
# Wait for a second to make sure async umount has finished
sleep 1
# Remove folder after unmount
sudo rmdir "$MOUNT_DIR/$DEVICE"
# test that this device has disappeared from mounted devices
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" ]; then
echo "/dev/$DEVICE failed to Un-Mount"
exit 1
fi
echo "/dev/$DEVICE successfully Un-Mounted"
}
autounload >> "$LOG_FILE" 2>&1
# kill auto-start process and shutdown
if [[ "$AUTO_END" == "1" ]]; then
sudo shutdown -H 0
fi