Автомонтирование флешек в armbian +1


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




Комментарии (4):

  1. apro
    /#24466764 / +4

    Готовые решения уже существуют

    Здесь ожидал услышать об udisks2 и надстройках для него.

  2. 13werwolf13
    /#24467510 / +2

    необходимо дать права этому скрипту на выполнение

    chmod 777

    я даже коментировать это не буду, потому что цензурно выразить не получается!

  3. Zhbert
    /#24468016

    После того, как скрипт создан, необходимо дать права этому скрипту на выполнение:

    chmod 777

    сборку armbian для сервера

    Автор, не позорься :)

    • DungeonLords
      /#24468992

      Критикуя предлагай. @13werwolf13придётся набраться мужества.

      По поводу терминологии. Для меня, как я уже оговорил в 1м абзаце, сборка armbian для сервера есть сборка без какой-либо графической оболочки (читай без Xorg). Это вовсе не означает, что устройство будет или не будет подключено к интернету. И даже не говорит о том, что на устройстве не будут запускаться графические приложения.