пятница, 19 июня 2009 г.

Теперь устройство может быть безопасно извлечено :) linux

Постановка задачи

В прошлый раз мы посмотрели как можно вывести информацию о том подмонтирована ли флешка или нет на экран при помощи системного монитора conky. Как известно, если есть какой-нибудь процесс, который занимает какой-нибудь файл на флешке, то umount ругнётся:

umount: /media/usb: device is busy
umount: /media/usb: device is busy
Таким образом, для начала надо узнать что это за процесс, а затем убить его.

В этой заметке я приведу скрипт, который использует для автоматического отключения занимающих процессов программу fuser и выводит сообщения на экран посредством программы gmessage.

Кому это надо? Ну, скорее всего, не пользователям DE, где это и так реализовано уже, наверное (хотя в последний раз, когда я использовал DE, мне приходилось самому искать держащие процессы). А вот пользователям лёгких оконных менеджеров, может и пригодится.

Решение

fuser - для отключения мешающих процессов

Программа fuser идентифицирет процессы, использующие данный файл и позволяет их убить. Допустим, мы открыли какой-то файл на флешке в программе evince и ещё лазим в терминала по файловой системе. Характерный вывод такой:

~> fuser -vm /media/usb/
                     USER        PID ACCESS COMMAND
/media/usb/:         maxim      6369 ..c.. bash
                     maxim      7404 f.... evince
-v означает более полный вывод, а -m /media/usb указывает на точку монтирования. В википедии это хорошо описано, а в конце приведён пример как убить махом все процессы, занимающие требуемую файловую систему. В моём случае для отрубания всего (от точки монтирования /media/usb) будет служить команда:
fuser -km /media/usb

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

lsof - для информативного вывода

Для более информативного вывода, на мой взгляд, больше подойдёт программа lsof. Вот типичный вывод:


~> lsof | grep /media/usb/
bash       6369       maxim  cwd       DIR       8,17    32768    1663 /media/usb/Experiment
evince     7404       maxim   14r      REG       8,17   873474    1684 /media/usb/ABX3_preprint.pdf
Колонки означают следующее:
~> lsof | head -1
COMMAND     PID        USER   FD      TYPE     DEVICE     SIZE    NODE NAME
Более удобный вывод для меня (PID COMMAND NAME, разделённые табуляцией) я получаю с помощью awk:
lsof | grep /media/usb/ | awk '{print $2"\t"$1"\t"$9}'
Здесь мы режем строку на части, разделённые пробелом (в случае непробельного разделителя надо использовать -F "разделитель") и выводим соответствующие части в желаемом порядке.

Таким образом, получим:

~> lsof | grep /media/usb/ | awk '{print $2"\t"$1"\t"$9}'
6369    bash    /media/usb/Experiment
7404    evince  /media/usb/ABX3_preprint.pdf
По-моему, это весьма информативно. Я не соображу сходу что значит процесс 7404, но уж точно пойму /media/usb/ABX3_preprint.pdf. А это мне и надо.

Gmessage - для графического отображения информации

Хочется, когда отмонтирование прошло удачно, получить сообщение, что всё удачно. В обратном случае хотелось бы, чтобы появилось окошко, показывающее, что такие-то процессы мешаются, и предлагающее их убить.

Для этого можно использовать старую добрую программу xmessage или её более приятный глазу аналог, gmessage (оно же gxmessage).

Для отображения текста из файла text-file в окне с кнопками "Ла" и "Нет" достаточно скомандовать:

gxmessage -name "Заголовок окна" -buttons "Да:0,Нет:1" -file text-file
При нажатии на кнопку "Да" будет генериться сигнал 0, при нажатии на "Нет", соответственно - 1.

Таким образом, если мы хотим, чтобы при нажатии на "Да" что-то происходило, мы будем использовать такую конструкцию:

if gxmessage -name "Заголовок окна" -buttons "Да:0,Нет:1" \
    -file text-file;
then
    *что-то выполняем*
fi

Пишем скрипт

Итак, предлагаю такой ход действий:

  • Введём переменную точки монтирования и лога сообщений:
MOUNT_POINT="/media/usb"
LOG=/tmp/mount_usb_log
  • Узнаём подмонтирована ли флешка (если $if_mounted непусто, то выполняем то, что внутри if:
if_mounted=`grep $MOUNT_POINT /etc/mtab`
if [[ -n `echo $if_mounted` ]]; then

...
fi
  • Если флешка подмонтирована, пытаемся её отмонтировать, а все возможные сообщения пишем в лог:
umount $MOUNT_POINT &> $LOG

  • Затем, если лог непустой, то есть что-то пошло не так, выведем сообщение какие процессы нам мешают и предложим их убить.
  • В случае успеха, напишем, что всё получилось.

У меня получилось следующее:

#!/bin/sh
MOUNT_POINT="/media/usb"

if_mounted=`grep $MOUNT_POINT /etc/mtab`
LOG=/tmp/mount_usb_log

function my_umount {
    # если подмонтирована, пытаемся отмонтировать
    if [[ -n `echo $if_mounted` ]]; then

        umount $MOUNT_POINT &> $LOG
    fi
}

my_umount

if [[ -s $LOG ]]; then

    # если лог непустой, то
    # пишем в него какой процесс занимает (попутно приводим к приличному виду с помощью awk)

    lsof | grep /media/usb/ | awk '{print $2"\t"$1"\t"$9}' > $LOG

    # если есть лог, то пишем, что флешка занята

    if gxmessage -file $LOG -name "Флешка занята!" \
        -buttons "Force:0,Cancel:1";
    then

        while [[ -n `echo $if_mounted` ]]; do
            # при нажатии на Force убиваем процессы, которые занимают флешку

            fuser -km $MOUNT_POINT
            # и пытаемся снова отмонтировать
            my_umount
            if_mounted=`grep $MOUNT_POINT /etc/mtab`
            # пока не отмонтируем (пока /etc/mtab не будет содержать "/media/usb")

        done
        gxmessage "Флешка успешно отмонтирована"
    fi

else
        gxmessage "Флешка отмонтирована"

fi

Цикл я использовать поначалу не планировал, думал просто скомандовать

fuser -km $MOUNT_POINT
my_umount

но оказалось, что команды не выполныются последовательно. Почему-то срабатывает вторая, пока ещё не отработала первая (стало быть ничего не отмонтировалось, потому что всё ещё были живы процессы). && не помогало. А использовать sleep перед второй командой не совсем честно. Поэтому и организовал цикл, который не закончится, пока точно флешке не отмонтируется. Только после этого выведется сообщение об успешном усходе нашего предприятия.

Итак, скриншоты.

Когда флешка занята, видим кто мешает:

После нажатия на «Force» процессы убиты, флешка отмонтирована и мы видим:

Ура! :)

Дело за малым:

  • обзываем скрипт umount_usb
  • делаем его исполняемым (chmod u+x umount_usb)
  • кладём куда-нибудь в $PATH (например, в ~/bin, при этом добавить в ~/.bashrc export PATH="$PATH:$HOME/bin")
  • настраиваем sudo, чтобы запускать umount без запроса пароля (смотри здесь на предмет NOPASSWD, тогда в скрипте mount меняем на sudo mount), либо смотрим на мои опции в /etc/fstab (user позволяет от обычного пользователя монтировать): /dev/sdb1 /media/usb auto user,noauto,iocharset=utf8,showexec 0 0 (для кого это - китайская грамота следует прочитать это)
  • назначаем горячую клавишу. Например, я использую xbindkeys. В ~/.xbindkeys: пишем
  "umount_usb"
  Mod4+u

Заключение

Данное решение может пригодиться пользователям оконных менеджеров. Разумеется, основной недостаток заключается в том, что оно применимо лишь для файловых систем, имеющих один раздел. У меня других и нет. Но для кого-то это критично. На первый взгляд не должно возникнуть больших проблем в плане распространения данного способа на случай более сложной таблицы разделов.

Копируете статью - поставьте ссылку на оригинал!

Комментариев нет:

Отправить комментарий