Имеем сборку 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, если уважаемый читатель попробует, напишите в коментах.
