Имеем сборку armbian для сервера, то есть без какой-либо графической оболочки. Или же такую же сборку от Xunlong (которая основана на том же armbian).
Пусть в наш компьютер пользователь периодически вставляет самые обычные флешки (с файловой системой FAT32). При вставке такой флешки никакого нового локального диска E не появляется. То есть автоматическое монтирование не происходит. Однако нашей программе на компьютере необходимо с этой флешкой работать. Как же её примонтировать?
Ручной вариант
Связан с использованием команды mount и описан везде. Однако я постараюсь привести довольно занятный пример использования этого метода. Предположим, что порт, куда пользователь может вставить флешку, только один. Это облегчает задачу, потому что появляется возможность красиво обойти постоянное переименование устройств. Да-да, в Linux ваша флешка будет то /dev/sda то /dev/sdb то ещё как-то. Так вот, если порт USB для флешки всегда один и тот же, то
1) Получаем уникальное имя устройства, привязанное к порту. Другими словами, получаем название флешки (HDD, ...), когда она воткнута. Для этого не вставляем флешку, делаем
ls -l /dev/disk/by-path/
Затем вставляем флешку и делаем ту же команду. В результате видим, что первый раз не было, а второй раз появилось имя. У меня второй раз было добавилось имя "platform-xhci-hcd.10.auto-usb-0:1:1.0-scsi-0:0:0:0".
Пробуем монтировать. Монтирование - это когда вы отображаете содержимое флешки в папку компьютера, то есть для программы (да и у пользователя), работающей с флешкой, будет ощущение, что они работают с папкой на компьютере. Заранее в домашнем каталоге (именуется ~) создадим папку-точку-монтирования, назовем её usb, то есть:
cd ~ && mkdir usb
2) Собственно монтируем:
sudo mount /dev/disk/by-path/platform-xhci-hcd.10.auto-usb-0:1:1.0-scsi-0:0:0:0 ~/usb -t vfat
3) Проверяем, заходим в папку:
cd ~/usb
4) Смотрим какие есть файлы в ней:
ls
Надеюсь вы заранее положили в вашу FAT32 флешку какие-нибудь файлы с русскоязычными названиями и русским текстом, чтобы уже на этом этапе проверить и убедиться, что вам не надо пересобирать ядро Linux для поддержки vfat.
Для OrangePi 4B с ядром 4, см /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
Автоматическое монтирование
Выше было описано ручное монтирование, теперь автоматизируем монтирование. Готовые решения уже существуют (кто знает ещё - пишите в коменты), но мы потренируемся изобретать свой велосипед и узнаем как это работает. Знание как это работает позволит нам кастомизировать монтирование. Например, сделать так, чтобы монтирование всегда проходило в одну и ту же папку (предполагается, что есть только 1 порт, куда пользователь может вставить флешку) или наоборот, сделать так, чтобы папка содержала LABEL монтируемой флешки, а если LABEL отсутствует, то имя точки монтирования было бы каким-то определенным. Теперь, когда я оправдал изобретение велосипеда, приступим.
Мы будем использовать правила udev в составе монстра systemd. Итак, создадим свой файл-правило 99-local.rules (имя правила - какое мне в голову пришло, но расширение суффикс обязательно .rules) по пути /etc/udev/rules.d/ и наполним его содержимым:
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
Теперь создадим так называемый systemd unit. Символ Собака позволит передавать имя флешки как аргумент.
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
Итак, подготовительный этап пройден. Теперь при подключении и отключении флешки будет вызываться скрипт usb-mount.sh Давайте его реализуем. Для того, чтобы печатать в файл через команду cat символ $ я буду экранировать обратным слешем \, чтобы во время печати скрипта в файл вместо текста не происходила подстановка.
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
Собственно в этом скрипте можно заняться недюжей кастомизацией. Например, если известно, что пользователь может вставить только одну флешку, то мы можем монтировать эту одну флешку всегда в папку /media/usb слегка изменив скрипт
Скрипт usb-mount.sh с точкой монтирования всегда /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
После того, как скрипт создан, необходимо дать права этому скрипту на выполнение:
chmod +x /usr/local/bin/usb-mount.sh #chmod 777 /usr/local/bin/usb-mount.sh
Для тестирование необходимо или перезагрузить компьютер или же перезагрузить и udev и systemd
udevadm control --reload-rules && systemctl daemon-reload
Описанные выше 4 шага могут быть осуществлены при сборке armbian. Для этого эти шаги необходимо вставить в файл userpatches/customize-image.sh Пример.
При возникновении неполадок можно изучить отладочный вывод
sudo udevadm control --log-priority=debug && journalctl -f
Было бы занятно попробовать сделать то же самое автоматическое монтирование без монстра systemd, через eudev, если уважаемый читатель попробует, напишите в коментах.