Мой юниксвейный mnt. Часть 2: флешки, диски и прочие карточки.

Начало здесь:
Часть 1: Системный диск, имена и группы.

2.1. Монтирование: как это работает.

В прошлый раз я закончил на добавлении всех съёмных дисков в группу storage. А теперь о том, как я это использую.

Идея проста – если пользователь имеет доступ к устройству, я сделаю так, чтобы он мог и монтировать. Если доступ только на чтение – монтируется на чтение. Не нужно никакой вторичной псевдоавторизации сеанса по родителю и страшных конфигов а-ля polkit. Всё, что нужно, уже есть – пользователь в группе, и юниксовые права для группы на устройство.

Устройства, оставшиеся в группе disk – те самые, которые были опознаны и поименованы первым правилом udev, считаются системными, а потому не отображаются и не монтируются. Даже если они не прописаны в fstab (а монтируются, скажем, через autofs), они всё равно не будут засорять список, в отличии от некоторых ГУИ, привязанных к udisks/polkit, к которым в таких случаях приходится применять грязные хаки, чтобы избавиться от навязчивого отображения прямо на рабочем столе разделов винчестера, не прописанных в fstab.

Все опции монтирования и прочие параметры берутся из конфига /etc/mnt.conf , который местами похож на fstab :
[pref]
#name	value
group	storage
doscp	866
#       dir,downer:	%u =USER, %i =UID, %g =GID
#dir	/run/media/%u
#mode	700
#owner	%u:root
dir	/media
#usb1	force

[fs]
#fs[.driver]  options                                       flags
*             nosuid,nodev,user
udf           mode=0644,dmode=0755,utf8,ro                  UG
iso9660       mode=0644,dmode=0755,utf8,ro,overriderockperm UG
vfat          discard,shortname=mixed,utf8,codepage=866,fmask=0133,dmask=0022 UG
hfsplus       umask=0022                                    UG
hfs           file_umask=0133,dir_umask=0022                UG
btrfs         ssd
ext4          discard
ext3
ext2
reiserfs
nilfs2        discard
f2fs          discard
xfs           discard
jfs           iocharset=utf8,discard
ntfs.ntfs3    fmask=0133,dmask=0022                         UG
#ntfs.ntfs-3g fmask=0133,dmask=0022                         UG
exfat         noatime,fmask=0133,dmask=0022                 UG

[blacklist]
#fs	label

[cd]
device	/dev/sr0
mount	cdrom
timeout	30
В строке со звёздочкой* прописаны общие опции монтирования, а дальше опции для каждой ФС отдельно. Флажки U и G означают, что ФС будет монтироваться с указанием UID и GID пользователя.

Параметр doscp задает DOS-кодировку для чтения для меток FAT, которые иногда бывают написаны русскими буквами. В ГУИ, привязанных к udisks, таких меток не видно.

Кроме всего прочего, обнаруживается подключение флешки по USB-1. Поскольку такое подключение очень медленное, и чаще всего происходит из-за плохого контакта или неисправности, об этом выводится сообщение, и по-умолчанию такие устройства НЕ монтируются, если обратное не задано в конфиге или ключом -f
Если кто помнит, Windows XP в таких случаях тоже ругалась, чем-то вроде "это устройство может работать быстрее...". Тогда я валил всё на "форточные глюки", но потом выяснил причину, проникся, и вот, реализовал эту полезную фичу :)

2.1. Монтирование: как это выглядит.

Вставляю флешку, и набираю в консоли команду mnt
$ mnt
Файловая система Тип  Размер Использовано  Дост Использовано% Cмонтировано в
/dev/sdb1        f2fs   3.7G         2.2G  1.5G           61% /media/sdb1_k4f2
Флешка смонтирована.

Когда будет надо отмонтировать, я наберу команду umt
$ umt
/dev/sdb1       3.7G    f2fs    k4f2
Флешка отмонтирована.

Теперь я вставлю сразу три флешки, и наберу команду mls
$ mls
/dev/sdb   3.7G
/dev/sdb1  3.7G    f2fs  k4f2
/dev/sdc   7.5G
/dev/sdc1  7G      vfat  K100N
/dev/sdc2  526.5M  f2fs  Kf2
/dev/sdd   1.8G
/dev/sdd1  1.8G    ext2  test64    
и увижу список того, что вставлено.

Я могу смонтировать всё, что есть:
$ mnt
Файловая система Тип  Размер Использовано  Дост Использовано% Cмонтировано в
/dev/sdb1        f2fs   3.7G         2.2G  1.5G           61% /media/sdb1_k4f2
/dev/sdc1        vfat   7.0G         5.5G  1.5G           79% /media/sdc1_K100N
/dev/sdc2        f2fs   526M         117M  396M           23% /media/sdc2_Kf2
/dev/sdd1        ext2   1.8G         748M  980M           44% /media/sdd1_test64
Могу отмонтировать и монтировать по одной больше:
$ umt sdd1
/dev/sdd1       1.8G    ext2    test64
$ umt sdc1 sdc2
/dev/sdc1       7G      vfat    K100N
/dev/sdc2       526.5M  f2fs    Kf2
$ mnt sdc1
Файловая система Тип  Размер Использовано  Дост Использовано% Cмонтировано в
/dev/sdc1        vfat   7.0G         5.5G  1.5G           79% /media/sdc1_K100N
могу посмотреть, что смонтировано:
$ mls
/dev/sdb   3.7G
/dev/sdb1  3.7G    f2fs  k4f2    /media/sdb1_k4f2
/dev/sdc   7.5G
/dev/sdc1  7G      vfat  K100N   /media/sdc1_K100N
/dev/sdc2  526.5M  f2fs  Kf2
/dev/sdd   1.8G
/dev/sdd1  1.8G    ext2  test64                     
и отмонтировать всё, что осталось:
$ umt
/dev/sdb1       3.7G    f2fs    k4f2
/dev/sdc1       7G      vfat    K100N

Как видите, выглядит всё просто :)
Имя для точки монтирования автоматически составляется из порядкового имени диска и метки, чтобы сразу можно было отличить флешки и по порядку подключения, и по метке.

2.3. Монтирование: как я это сделал.

Команды mnt, umt и mls – это один скрипт и два симлинка на него же, всё лежит в /usr/local/bin/

#!/bin/bash

#config defaults
GROUP=storage
CP=866
BLST=

declare -A m_label m_type m_size m_mount m_usb

get_list() {
 DEVLIST=`find -L /dev/block/ -group $GROUP -exec readlink -f {} \; | sort`
 [[ -z "$DEVLIST" ]] && return 0

 P=
 while read -r DEV SIZE TYPE LABEL
 do
  if [[ -n "$P" && $DEV == $P* ]] ; then
   [[ -n "${m_type[$P]}" ]] && unset m_type[$P] m_label[$P]
  else P="$DEV"
  fi
  if [[ -z "${m_usb[$P]}" ]] ; then
   A=`readlink -f "/sys/class/block/$P" 2> /dev/null`
   B="${A##*/usb}"
   C="${B#*/}"
   F="${A%$C}version"
   [[ -f "$F" ]] && read m_usb[$P] < "$F" || m_usb[$P]=none
  else
   m_usb[$DEV]="${m_usb[$P]}"
  fi
  m_size[$DEV]="$SIZE"
  m_type[$DEV]="$TYPE"
  printf -v m_label[$DEV] '%b' "$LABEL"
 done < <( lsblk -dnro NAME,SIZE,FSTYPE,LABEL $DEVLIST 2>/dev/null )

 while read -ra LIN
 do
  DEV="${LIN[0]##*/}"
  [[ -n $DEV && -n ${LIN[1]} ]] && printf -v m_mount["$DEV"] '%b' "${LIN[1]}"
 done < /proc/self/mounts
}

getid() {
 DEV="${1##*/}"
 TYPE="${m_type[$DEV]}"
 LABEL="${m_label[$DEV]}"
 case "$TYPE" in
 dos|fat|vfat)
  LABEL=`echo -n "$LABEL" | iconv -f $CP`
  ;;
 esac
 SIZE="${m_size[$DEV]}"
 MOUNT="${m_mount[$DEV]}"
 USB="${m_usb[$DEV]}"
 [[ $USB =~ ^1\. ]] && USB='***USB-1***' || USB=
}

for_dev() {
 get_list
 for FILE in $DEVLIST; do
  getid $FILE
  "$@"
 done
}

dfN() {
C="df -hT $1"
if [[ -z $fN ]] ; then
 $C
 fN=1
else
 $C | tail -n 1
fi
}

gen_clabel() {
 CLABEL=$(echo -n "$LABEL"|tr '[:cntrl:]' '?')
}

mnt_dev() {
 [[ -n "$1" && "$TYPE $LABEL" =~ $1 ]] && return 1
 [[ "$TYPE" = 'swap' ]] && return 1
 if [[ $CMD == 'mnt' ]] ; then
  if [[ -n $TYPE && -z $MOUNT ]] ; then
   if [[ -n "$USB" && -z "$FORCE" ]] ; then
    list_dev
    return 1
   fi
   if [[ -n "$LABEL" ]] ; then
    L=$(echo -n "$LABEL"|tr -s '[:cntrl:][:blank:]\!"$&\47()*/;<>?\[\\\]`{|}' _)
    M="$DEV"_"$L"
   else M="$DEV"
   fi
   smount "$FILE" "$M" && dfN "$FILE"
  fi
 else
  if [[ -n $MOUNT ]] ; then
   gen_clabel
   usmount "$FILE" && echo $FILE$'\t'$SIZE$'\t'$TYPE$'\t'"$CLABEL"
  fi
 fi
}

list_dev() {
 if [[ -n $TYPE && -z "$LABEL" ]] ; then
  LABEL='\no label/'
 fi
 gen_clabel
 echo $FILE$'\t'$SIZE$'\t'$TYPE$'\t'"$CLABEL"$'\t'$MOUNT$'\t'$USB
}

sw=
while read -a C ; do
 c="${C[0]}"
 case "$c" in
 \[pref\])
  sw=pref
  ;;
 \[blacklist\])
  sw=blacklist
  ;;
 \[*)
  sw=
  ;;
 \#*|'')
  ;;
 *)
  case "$sw" in
  pref)
   o="${C[*]:1:2}"
   case "$c" in
   group)
    GROUP="$o"
    ;;
   doscp)
    CP="$o"
    ;;
   usb1)
    [[ "$o" = 'force' ]] && FORCE=1
    ;;
   esac
   ;;
  blacklist)
   BLST="$BLST$c ${C[*]:1:2}|"
   ;;
  *)
   ;;
  esac
  ;;
 esac
done < /etc/mnt.conf

CMD="${0##*/}"
case "$1" in
'-u') CMD=umt
 shift
 ;;
'-l') CMD=mls
 shift
 ;;
'-f') FORCE=1
 shift
 ;;
esac
case "$CMD" in
 'mnt'|'umt')
if [[ -z $1 ]] ; then
 if [[ -n "$BLST" ]] ; then
  b="${BLST//./\\.}"; b="${b//\*/.*}"; b="${b//\?/.}"
  BLST="^(${b%|})$"
 fi
 for_dev mnt_dev "$BLST"
else
 get_list
 while [[ -n $1 ]] ; do
  [[ "$1" = /* ]] && FILE="$1" || FILE="/dev/$1"
  getid "$FILE"
  mnt_dev
  shift
 done
fi
;;
 'mls')
for_dev list_dev | column -t -s $'\t'
;;
esac
Скрипт работает под пользователем. Непосредственно для операции монтирования раньше из этого скрипта вызывался pmount, но потом он перестал меня устраивать из-за излишней хардкодности и древности – он не знал некоторых новых ФС и поддерживал не все нужные мне опции.

В результате я написал вместо pmount ещё один скрипт, который имеет нормальный (НЕ хардкодный) конфиг и сам перезапускается под рутом через sudo. Скрипт называется smount, и на него ссылаются ещё два симлинка: usmount и /usr/bin/umount.smount – последний нужен для отмонтирования под пользователем из сторонних программ, таких как eject, благодаря параметру uhelper=smount.
#!/bin/bash

#config defaults
MDIR=/media
DMODE=755
DOWNER=

HL1="helper=smount"
HL2="u$HL1"

NAME="${0##*/}"

[[ "$NAME" = "umount.smount" ]] && NAME='usmount'

uid=`id -u`
if ((uid>0))
then
 FILE=`readlink -f $0`
 DIR="${FILE%/*}"
 exec sudo $DIR/$NAME "$@"
fi

err() {
 echo "$@" >&2
 exit 1
}

addopt() {
 [[ -n "$O" && -n "$1" ]] && d=',' || d=
 O="$O$d$1"
}

ugparse() {
 if [[ "$o" =~ %(u|i|g) ]] ; then
  [[ -n "$SUDO_USER" ]] && u="$SUDO_USER" || u=root
  o="${o//%u/$u}"
  [[ -n "$SUDO_UID" ]] && id="$SUDO_UID" || id=0
  o="${o//%i/$id}"
  [[ -n "$SUDO_GID" ]] && g="$SUDO_GID" || g=0
  o="${o//%g/$g}"
 fi
}

RO=
i=1
for p in "$@"; do
 case "$p" in
 -r|--read-only)
  RO=1
  ;;
 -*)
  ;;
 *)
  par[$i]="$p"
  ((i++))
  ;;
 esac
done

declare -A OPT FLAG DRIVER

sw=
while read -a C ; do
 c="${C[0]}"
 case "$c" in
 \[fs\]|\[FS\])
  sw=fs
  ;;
 \[pref\]|\[PREF\])
  sw=pref
  ;;
 \[*)
  sw=
  ;;
 \#*|'')
  ;;
 *)
  case "$sw" in
  fs)
   [[ "$c" = '*' ]] && c=default
   t="${c%%.*}"
   d="${c#*.}"
   [[ "$d" != "$t" ]] && DRIVER["$t"]="$d"
   o="${C[1]}"
   [[ "$o" =~ ^\# ]] && continue
   [[ "$o" = '*' ]] && o=
   OPT["$t"]="$o"
   o="${C[2]}"
   f=0
   [[ "$o" =~ ^\# ]] && continue
   [[ "$o" =~ [uU] ]] && f=1
   [[ "$o" =~ [gG] ]] && ((f|=2))
   FLAG["$t"]="$f"
   ;;
  pref)
   o="${C[1]}"
   [[ "$o" =~ ^\# ]] && o=
   case "$c" in
   dir)
    if [[ -n "$o" ]] ; then
     ugparse
     MDIR="$o"
    fi
    ;;
   mode)
    [[ -n "$o" ]] && DMODE="$o"
    ;;
   owner)
    if [[ -n "$o" ]] ; then
     ugparse
     DOWNER="$o"
    fi
    ;;
   esac
   ;;
  *)
   ;;
  esac
  ;;
 esac
done < /etc/mnt.conf

case "$NAME" in
smount)
 DEV="${par[1]}"
 MNT="${par[2]}"
 [[ -z "$MNT" ]] && err "No selected mountpoint"
 MNT="$MDIR"/$(echo -n "$MNT"|tr -s '[:cntrl:][:blank:]\!"$&\47()*/;<>?\[\\\]`{|}' _)
 grep -qF " $MNT " /proc/mounts && err "$MNT already mounted"
 ;;
usmount)
 DEV=
 MNT=
 P="${par[1]}"
 [[ -z "$P" ]] && err "No argument"
 if [[ -d "$P" ]] ; then
  MNT="$P"
  L=`grep -F " $MNT " /proc/mounts`
  DEV="${L%% *}"
 else
  DEV="$P"
 fi
 ;;
*)
 err "Unknown command '$NAME'"
 ;;
esac

read DEV < <(readlink -f "$DEV")
[[ -b "$DEV" ]] || err "No usable source"
if [[ -n "$SUDO_USER" ]] ; then
 sudo -u "$SUDO_USER" [ -r "$DEV" ] || err "No access to source"
 sudo -u "$SUDO_USER" [ -w "$DEV" ] || RO=1
fi

case "$NAME" in
smount)
 R=
 [[ -n "$RO" ]] && R='-r'

 TYPE=`lsblk -dnro FSTYPE "$DEV" 2>&-`
 [[ -z "$TYPE" ]] && err "Unknown FS type"

 T=
 d=${DRIVER["$TYPE"]}
 [[ -n "$d" ]] && T="-t $d"

 O=${OPT[default]}
 addopt ${OPT["$TYPE"]}

 Fd=${FLAG[default]}
 F=${FLAG["$TYPE"]}
 ((F|=Fd))

 if [[ -n "$SUDO_UID" ]] ; then
  ((F&1)) && addopt "uid=$SUDO_UID"
  ((F&2)) && addopt "gid=$SUDO_GID"
 fi

 [[ -z "$O" ]] && O=defaults

 if [[ ! -d "$MDIR" ]] ; then
  mkdir -m "$DMODE" -p "$MDIR" || err "Can't create $MDIR"
  chown "$DOWNER" "$MDIR"  || err "Can't change owner of $MDIR to $DOWNER"
 fi
 mkdir -p "$MNT" || err "Can't create $MNT"
 mount $R $T -o "$O,$HL1,$HL2" "$DEV" "$MNT" || rmdir "$MNT"
 ;;
usmount)
 if [[ -z "$MNT" ]] ; then
  L=`grep -F "$DEV " /proc/mounts`
  L="${L#* }"
  MNT="${L%% *}"
 fi
 [[ -n "$MNT" && -d "$MNT" ]] || err "Mountpoint not found"
 [[ "$MNT" =~ ^"$MDIR"/.*$ ]] && F=1 || F=0
 if [[ -n "$SUDO_USER" ]] || ((F==1)) ; then
  ((F==0)) && err "$MNT not in $MDIR"
  umount -i "$MNT" && rmdir "$MNT"
 else
  UTAB=/run/mount/utab
  [[ -f "$UTAB" ]] && while read line; do
   l=" $line "
   o="${l##* OPTS=}"; o=",${o%% *},"
   if [[ $l = *\ SRC="$DEV"\ * && $l = *\ TARGET="$MNT"\ * && $o = *,$HL1,* ]] ; then
    F=1
    break
   fi
  done < "$UTAB"
  umount -i "$MNT" || exit 1
  ((F==0)) || rmdir "$MNT"
 fi
 ;;
esac
Запись в /etc/sudoers :
%users hostname=NOPASSWD:/usr/local/bin/smount *,/usr/local/bin/usmount *
где hostname это имя хоста.

Кроме группы storage, обычно присутствует ещё одно специфическое устройство, которое в неё не входит – это CD/DVD дисковод. О монтировании сидишников я расскажу в следующей части.

Обновление от 11.02.2023:
Обновлён скрипт smount, в него добавлена возможность указывать в конфиге "тип ФС" в формате ФС.Драйвер, например ntfs.ntfs3, это позволяет задействовать новый драйвер NTFS уровня ядра.

Продолжение:
Часть 3: укрощение сидишника и немного GUI.
Будем реализовывать,честно говоря пока что вручную монтирую устройства что подключаю,lsusb и lsblk очень хорошие вещи,разделы на вениках монтирую через самописный банальный скрипт с mount /dev/sd** /media/name* (лень было каждый раз писать mount) Не нравится мне дефолтное монтирование в /run/media/name_of_user/name_volume (или как там).
Хорошим ты делом занялся,спасибо,посмотрим что же еще можно натворить будет.
Спасибо на добром слове :)

А lsblk действительно хорошая утилита, её гораздо удобнее использовать в скриптах, чем blkid и некоторые другие.

Что касается разделов "на вениках", то когда я гружу левый комп со своей флешки, "системным диском" считается именно она, а собственные винчестеры этой машины успешно попадают в группу storage, и монтируются так же, как и флешки.
Спасибо за статью и отдельно за lsblk :)
Чем-то напоминает mnttools ( https://aur.archlinux.org/packages/mnttools/ ), только добавлены настройки мотирования по типу ФС.
А разве без sudo работать не будет, если пользователь состоит в группе storage?
Кстати, /media больше нету. Теперь предлагают все монтировать в /mnt.
Natrio
Спасибо на добром слове :)

А lsblk действительно хорошая утилита, её гораздо удобнее использовать в скриптах, чем blkid и некоторые другие.

Что касается разделов "на вениках", то когда я гружу левый комп со своей флешки, "системным диском" считается именно она, а собственные винчестеры этой машины успешно попадают в группу storage, и монтируются так же, как и флешки.
Я свой арч на флехе привязал по UUID'у в fstab'е (ИМХО единственное место где UUID полезен). Надо будет там реализовать ваш метод, ибо каждый раз ручками монтировать диски машин куда я подключаюсь уже поднадоело =)
farwayer
Кстати, /media больше нету. Теперь предлагают все монтировать в /mnt.
Если /media/ нету, ничто не мешает его создать.
А в /mnt/ у меня статические точки монтирования.

А разве без sudo работать не будет, если пользователь состоит в группе storage?
Не будет, разве что только FUSE и только в принадлежащий пользователю каталог.
Во всех остальных случаях для монтирования нужны права рута, которые обычно получаются через бит SUID на бинарнике, а в скриптах на баше я обычно использую самоперезапуск через sudo.

RAMZAY
Я свой арч на флехе привязал по UUID'у в fstab'е (ИМХО единственное место где UUID полезен). Надо будет там реализовать ваш метод, ибо каждый раз ручками монтировать диски машин куда я подключаюсь уже поднадоело =)
UUID полезен для автоматических установщиков, а так же тем, что сохраняется при клонировании раздела.
Его минусы – лишь обратная сторона его плюсов: что удобно для роботов, неудобно для людей; а привязка к разделу не даёт простого способа отделить в udev весь диск целиком.
Продолжение:
Часть 3: укрощение сидишника и немного GUI.
Natrio, огромное спасибо,очень клевая задумка,всё,теперь никаких mount,только mnt и umt))
Non progredi - est regredi
Решил попробовать на форточных дисках с некоторых пор интегрированный в ядро Linux драйвер ntfs3, как альтернативу юзерспейному FUSE-драйверу NTFS-3G.
Ситуация, когда при монтировании требуется прямо указывать драйвер

mount -t ntfs3 ...
весьма необычна, так что мне пришлось впервые с 2014 года править скрипт smount

Добавлять в конфиг ещё один столбец с указанием драйвера я не стал, так что теперь в первом столбце "тип ФС" теперь может быть вписан в виде ФС.Драйвер, например ntfs.ntfs3

Обновлённый скрипт и конфиг в посте вверху.
 
Зарегистрироваться или войдите чтобы оставить сообщение.