Automount flash drives in armbian
We have an assembly armbian for the server, that is, without any graphical shell. Or the same assembly from Xunlong (which is based on the same armbian).
Let the user periodically insert the most common flash drives (with the FAT32 file system) into our computer. When you insert such a flash drive, no new local drive E appears. That is, automatic mounting does not occur. However, our program on the computer needs to work with this flash drive. How to mount it?
Manual option
Related to using the mount command and described everywhere. However, I will try to give a rather amusing example of the use of this method. Let’s assume that there is only one port where the user can insert a USB flash drive. This makes things easier because it gives you the ability to nicely get around the constant renaming of devices. Yes, yes, in Linux your flash drive will be either /dev/sda or /dev/sdb or something else. So, if the USB port for a flash drive is always the same, then
1) We get a unique device name associated with the port. In other words, we get the name of the flash drive (HDD, …) when it is plugged in. To do this, do not insert a USB flash drive, do
ls -l /dev/disk/by-path/
Then we insert the flash drive and do the same command. As a result, we see that the first time was not, and the second time the name appeared. The second time I added the name “platform-xhci-hcd.10.auto-usb-0:1:1.0-scsi-0:0:0:0”:
Let’s try to install. Mounting is when you map the contents of a flash drive to a computer folder, that is, for a program (and for a user) that works with a flash drive, it will feel like they are working with a folder on the computer. In advance, in the home directory (named ~), we will create a mount-point folder, let’s call it usb, that is
cd ~ && mkdir usb
4) Actually we mount
sudo mount /dev/disk/by-path/platform-xhci-hcd.10.auto-usb-0:1:1.0-scsi-0:0:0:0 ~/usb -t vfat
5) Check, go to the folder
cd ~/usb
6) We look at what files are in it
ls
I hope you put some files with Russian-language names and Russian text into your FAT32 flash drive in advance, so that at this stage you can check and make sure that you do not need to rebuild the Linux kernel to support vfat
For OrangePi 4B with kernel 4, see /external/config/kernel/linux-rk3399-legacy.config
#
# DOS/FAT/NT Filesystems
#
CONFIG_FAT_FS=y
CONFIG_MSDOS_FS=y
CONFIG_VFAT_FS=y
CONFIG_FAT_DEFAULT_CODEPAGE=866
CONFIG_FAT_DEFAULT_IOCHARSET="utf8"
# CONFIG_NTFS_FS is not set
Pseudo filesystems
CONFIG_PROC_FS=y
CONFIG_PROC_KCORE is not set
CONFIG_PROC_SYSCTL=y
CONFIG_PROC_PAGE_MONITOR=y
CONFIG_PROC_CHILDREN is not set
CONFIG_PROC_UID=y
CONFIG_KERNFS=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y
CONFIG_TMPFS_XATTR=y
CONFIG_HUGETLBFS is not set
CONFIG_HUGETLB_PAGE is not set
CONFIG_CONFIGFS_FS=y
CONFIG_MISC_FILESYSTEMS=y
CONFIG_ADFS_FS is not set
CONFIG_AFFS_FS is not set
CONFIG_ECRYPT_FS is not set
CONFIG_SDCARD_FS is not set
CONFIG_HFS_FS is not set
CONFIG_HFSPLUS_FS is not set
CONFIG_BEFS_FS is not set
CONFIG_BFS_FS is not set
CONFIG_EFS_FS is not set
CONFIG_LOGFS is not set
CONFIG_CRAMFS is not set
CONFIG_SQUASHFS=y
CONFIG_SQUASHFS_DECOMP_SINGLE=y
CONFIG_SQUASHFS_DECOMP_MULTI is not set
CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU is not set
CONFIG_SQUASHFS_XATTR is not set
CONFIG_SQUASHFS_ZLIB=y
CONFIG_SQUASHFS_LZ4 is not set
CONFIG_SQUASHFS_LZO is not set
CONFIG_SQUASHFS_XZ is not set
CONFIG_SQUASHFS_ZSTD is not set
CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set
CONFIG_SQUASHFS_EMBEDDED is not set
CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3
CONFIG_VXFS_FS is not set
CONFIG_MINIX_FS is not set
CONFIG_OMFS_FS is not set
CONFIG_HPFS_FS is not set
CONFIG_QNX4FS_FS is not set
CONFIG_QNX6FS_FS is not set
CONFIG_ROMFS_FS is not set
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG is not set
CONFIG_PSTORE_FTRACE is not set
CONFIG_PSTORE_RAM=y
CONFIG_SYSV_FS is not set
CONFIG_UFS_FS is not set
CONFIG_NETWORK_FILESYSTEMS=y
CONFIG_NFS_FS=y
CONFIG_NFS_V2=y
CONFIG_NFS_V3=y
CONFIG_NFS_V3_ACL=y
CONFIG_NFS_V4=y
CONFIG_NFS_SWAP=y
CONFIG_NFS_V4_1 is not set
CONFIG_NFS_USE_LEGACY_DNS is not set
CONFIG_NFS_USE_KERNEL_DNS=y
CONFIG_NFSD is not set
CONFIG_GRACE_PERIOD=y
CONFIG_LOCKD=y
CONFIG_LOCKD_V4=y
CONFIG_NFS_ACL_SUPPORT=y
CONFIG_NFS_COMMON=y
CONFIG_SUNRPC=y
CONFIG_SUNRPC_GSS=y
CONFIG_SUNRPC_SWAP=y
CONFIG_SUNRPC_DEBUG is not set
CONFIG_CEPH_FS is not set
CONFIG_CIFS is not set
CONFIG_NCP_FS is not set
CONFIG_CODA_FS is not set
CONFIG_AFS_FS is not set
CONFIG_NLS=y
CONFIG_NLS_DEFAULT="utf8"
CONFIG_NLS_CODEPAGE_437=n
CONFIG_NLS_CODEPAGE_737 is not set
CONFIG_NLS_CODEPAGE_775 is not set
CONFIG_NLS_CODEPAGE_850 is not set
CONFIG_NLS_CODEPAGE_852 is not set
CONFIG_NLS_CODEPAGE_855 is not set
CONFIG_NLS_CODEPAGE_857 is not set
CONFIG_NLS_CODEPAGE_860 is not set
CONFIG_NLS_CODEPAGE_861 is not set
CONFIG_NLS_CODEPAGE_862 is not set
CONFIG_NLS_CODEPAGE_863 is not set
CONFIG_NLS_CODEPAGE_864 is not set
CONFIG_NLS_CODEPAGE_865 is not set
CONFIG_NLS_CODEPAGE_866=y
CONFIG_NLS_CODEPAGE_869 is not set
CONFIG_NLS_CODEPAGE_936=n
CONFIG_NLS_CODEPAGE_950 is not set
CONFIG_NLS_CODEPAGE_932 is not set
CONFIG_NLS_CODEPAGE_949 is not set
CONFIG_NLS_CODEPAGE_874 is not set
CONFIG_NLS_ISO8859_8 is not set
CONFIG_NLS_CODEPAGE_1250 is not set
CONFIG_NLS_CODEPAGE_1251 is not set
CONFIG_NLS_ASCII=y
CONFIG_NLS_ISO8859_1=n
CONFIG_NLS_ISO8859_2 is not set
CONFIG_NLS_ISO8859_3 is not set
CONFIG_NLS_ISO8859_4 is not set
CONFIG_NLS_ISO8859_5 is not set
CONFIG_NLS_ISO8859_6 is not set
CONFIG_NLS_ISO8859_7 is not set
CONFIG_NLS_ISO8859_9 is not set
CONFIG_NLS_ISO8859_13 is not set
CONFIG_NLS_ISO8859_14 is not set
CONFIG_NLS_ISO8859_15 is not set
CONFIG_NLS_KOI8_R is not set
CONFIG_NLS_KOI8_U is not set
CONFIG_NLS_MAC_ROMAN is not set
CONFIG_NLS_MAC_CELTIC is not set
CONFIG_NLS_MAC_CENTEURO is not set
CONFIG_NLS_MAC_CROATIAN is not set
CONFIG_NLS_MAC_CYRILLIC is not set
CONFIG_NLS_MAC_GAELIC is not set
CONFIG_NLS_MAC_GREEK is not set
CONFIG_NLS_MAC_ICELAND is not set
CONFIG_NLS_MAC_INUIT is not set
CONFIG_NLS_MAC_ROMANIAN is not set
CONFIG_NLS_MAC_TURKISH is not set
CONFIG_NLS_UTF8=y
CONFIG_DLM is not set
CONFIG_VIRTUALIZATION is not set
Automatic mount
Manual mounting was described above, now we automate mounting. Ready solutions already exist (who else knows – write in the comments), but we will practice reinventing our own wheel and find out how it works. Knowing how this works will allow us to customize the mount. For example, make sure that mounting always goes to the same folder (it is assumed that there is only 1 port where the user can insert a flash drive) or vice versa, make the folder contain the LABEL of the mounted flash drive, and if there is no LABEL, then the name mount points would be somehow defined. Now that I’ve justified the invention of the bicycle, let’s get started.
We will be using the udev rules as part of the systemd monster. So, let’s create our own rule file 99-local.rules (the name of the rule is what came to my mind, but extension suffix is required .rules) along the path /etc/udev/rules.d/ and fill it with the contents:
cat <<EOF > /etc/udev/rules.d/99-local.rules
KERNEL=="sd*[!0-9]|sr*", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service"
KERNEL=="sd*[!0-9]|sr*", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service"
EOF
Now let’s create the so-called systemd unit. The Dog symbol will allow you to pass the name of the flash drive as an argument.
cat <<EOF > /etc/systemd/system/usb-mount@.service
[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
EOF
So, the preparatory stage is over. Now, when connecting and disconnecting a flash drive, the usb-mount.sh script will be called Let’s implement it
cat <<EOF > /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
EOF
Actually in this script, you can do some great customization. For example, if it is known that the user can only insert one flash drive, then we can mount this one flash drive always in the / media / usb folder by slightly changing the script
usb-mount.sh script with mount point always /media/usb
cat <<EOF > /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="usb"
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
EOF
For testing, you must either restart the computer or restart both udev and systemd
udevadm control --reload-rules && systemctl daemon-reload
If you encounter problems, you can examine the debug output
sudo udevadm control --log-priority=debug && journalctl -f
It would be interesting to try to do the same automatic mounting without the systemd monster, through eudev, if the dear reader tries, write in the comments.