Пользователь
0,0
рейтинг
21 мая 2014 в 05:34

Разработка → Pautomount — демон автоматического монтирования, запуска скриптов и всего такого прочего recovery mode

image

Возникла передо мной задача — автоматически выполнять действие при вставке какого-нибудь storage device в Дебиане. К примеру, просто автоматически монтировать его. А может, монтировать и синхронизировать данные, если устройство известно. А может, проверять его clamav на всякую фигню и запускать на нём что-нибудь типа USB-вакцины. Может, и включить сирену, если владельца рядом с компьютером нет =)

К примеру, есть у меня флешки, штук этак 5-7. На каждой записано что-то своё, одна — загрузочная, одна с документами, одна с программами, одна с музыкой и с фотками… И стоит дома сервер, на котором все-все копии того, что на флешках, есть. Хотелось бы мне, чтобы с одной флешкой синхронизировались документы, с другой — музыка и фотки, с третьей — загрузочные образы, и так далее. Только вот нужно это дело как-то автоматически запускать, потому как не годится каждый раз через SSH на сервер лезть и ручками всё править. Поэтому нужно что-то, где можно было бы флешки прописать и действия, нужные при подключении, задать. А там один раз скрипт синхронизации написал — и готово.

Только вот ничего готового и полностью подходящего в Гугле не нашлось. Часть решений тянут за собой кучу зависимостей и не рассчитаны на headless-установку, часть устарели, часть не имеют всего необходимого функционала, а часть позволяют это сделать, только вот слишком муторно, настраивать трудно, и вообще, NIH ;-) Поэтому написал я свой демон автоматического монтирования, ну и выкладываю его сюда, может, пригодится кому-то ещё, может, такому же владельцу домашнего сервера, который точно так же хочет синхронизировать флешки с домашним NAS. Да и хочется, чтобы кто-нибудь код покритиковал, на ошибки указал и решения некоторых проблем подсказал — смотреть в конце топика.


Был у меня как-то iPod. Иногда возникала необходимость его заряжать от компьютера с Линуксом. Но как только подключаешь его к компьютеру, он сразу включает режим синхронизации. Для того, чтобы он прекратил заряжаться, требуется либо хардварное решение (переходник папа-мама USB с по-особому замкнутыми контактами), либо выполнить eject /dev/sdx1, что выключает режим синхронизации и переводит в режим зарядки. Соответственно, нужно выполнять эту команду каждый раз — паять переходник ведь так лень… тогда и родился такой скрипт — umipod.sh: (UnMount-iPOD)

umipod.sh
#!/bin/bash
#Intended to run in background and eject device specified by UUID if plugged
# in.Check is performed every N seconds where N variable is set in script 
# header part.

N=10 #Yes, this is delay between checks, best recommended is 10 seconds 
# which is just N=10.
UUID="4C25-4DC2"
BEACON="/usr/local/bin/umipod/a.flg"

if [[ $1 = "-a" ]]
then
	touch $BEACON
	echo "Mounting temporarily allowed"
	exit 0
fi

if [[ $1 = "-d" ]]
then
	rm -f $BEACON
	echo "Device will be ejected "
	exit 0
fi
	while [  true ]
	do
		if [[ ! -e $BEACON && -e /dev/disk/by-uuid/$UUID ]]
		then
		DEVPART=`blkid -U $UUID`
		eject $DEVPART
		echo "Device ejected"
		fi
		sleep $N 
	done


Прошу сильно не плеваться на качество кода, это было написано два года назад, да и дальше в статье будет ещё много кода — берегите слюну =) Суть проста — в цикле проверяем, подключено ли устройство с конкретным UUID, если да, то находим, какое блочное устройство eject'ить и делаем это. Подключил плеер к компьютеру, через пару секунд он автоматически отключается, но продолжает заряжаться — можно дальше слушать музыку. Было достаточно удобно… Пока не потерял плеер =( С тех пор надобность в таком устройстве практически отпала.

А сейчас столкнулся с некоторыми задачами, для которых требуются автоматические действия при подключении носителя. Вот и решил это исправить, только теперь и близко не подходя к Bash =) Язык программирования долго выбирать не пришлось — да и не из чего, честно говоря, я только Python в последнее время и занимаюсь. Дальше пришлось подумать — писать ли демон, который в цикле проверяет, не появилось ли новых ещё необработанных разделов, или цепляться через udev, чтобы программа вызывалась как callback при подключении любой флешки. Второй вариант вроде потребляет меньше ресурсов — но первый не зависит от udev и проще =) Поэтому решил писать демона, которого можно спокойно сунуть в автозапуск.

Собственно, вот он. Об устройстве и возможностях легче всего будет рассказать, описывая то, как его настраивать. Но всё же вкратце опишу принцип работы:

  1. Демон каждые n секунд проверяет /dev/disk/by-uuid, затем по данным оттуда составляет список доступных разделов
  2. Этот список сравнивается со списком, сделанным за n секунд до этого, определяются разделы, которых до этого не было
  3. При наличии свежеподключенных разделов по записям из конфига определяется, какой раздел проигнорировать, а для какого выполнить какое-то действие.
  4. GOTO 1


Дальше — уже требует описания конфига =) Конфиг обычно состоит из четырёх секций, любую из которых можно безболезненно пропустить.

{
  "globals": {
    "interval":3,
    "debug":false,
    "noexecute":true,
    "comment":"Everything that is in this section will be exported as a global variable in the script, replacing if there's something to replace."
  },
  "exceptions": [
    {"uuid":"ceb62844-7cc8-4dcc-8127-105253a081fc", "comment":"System boot partition"},
    {"uuid":"6d1a8448-10c2-4d42-b8f6-ee790a849228", "comment":"System root partition"},
    {"uuid":"9b0bb1fc-8720-4793-ab35-8a028a475d1e", "comment":"System swap partition"}
  ],
  "rules": [
    {"uuid":"E02C8F0E2C8EDEC2", "mount":{"mountpoint":"/media/16G-DT100G2"}},
    {"uuid":"7F22-AD64", "mount":{"mountpoint":"/media/16G-DT100G3"}},
    {"uuid":"406C9EEE6C9EDE4A", "mount":{"mountpoint":"/media/80G-Music"}},
    {"uuid":"52663FC01BD35EA4", "mount":{"mountpoint":"/media/32G-Data"}}
  ],
  "default": {
    "mount":true,
    "comment":"Configuration section for the actions that are taken if drive isn't in either exception or rule list."
  }
}


1) Секция «globals»


Тут всё просто — любая переменная в этой секции экспортируется в global namespace демона такой незатейливой функцией:

def export_globals():
def export_globals():
    log("Exporting globals from config file")
    for variable in config["globals"].keys():
        if debug:
            log("Exporting variable "+variable+" from config")
        globals()[variable] = config["globals"][variable]

Просто и быстро, не нужно составлять всякие списки переменных к экспорту или запихивать все переменные в словарь.

Полезные переменные:
main_mount_dir — основная директория для монтирования, когда путь для монтирования неизвестен либо указан в виде относительного пути. По умолчанию — "/media".
default_mount_option — опции при монтировании по умолчанию, когда другие не указаны. Я оставляю там «rw», хоть и не уверен, не прописано ли это уже по умолчанию в драйверах для файловых систем =)
logfile — путь к лог-файлу. Если надо поменять, лучше указывать абсолютный — чёрт его знает, куда попадут логи, если указать относительный =) Скорее всего — в \.
interval — интервал между проверками на предмет появления новых разделов. Как по мне — даже интервал в одну секунду не загружает процессор сколь-либо значительно, даже на одноядерном Celeron 900MHz =)

Не столь полезные переменные:
debug — тут всё понятно. Значительно убыстряет увеличение размера лог-файла, в реальной жизни вряд ли понадобится =)
super_debug — убыстряет расширение лог-файла ещё больше. Вероятность того, что понадобится, ещё меньше.
noexecute — опция, которая включена по умолчанию и при которой демон только имитирует вызовы внешних команд — таким образом, с момента первого запуска и до тех пор, пока не будет убрана опция, ничего монтироваться/выполняться не будет. Сделана для того, чтобы до настройки демон не монтировал разделы, которые уже монтируются, используя fstab.

2) Секция «exceptions»


Любой раздел, чей UUID/Label указан в этой секции, будет жестоко проигнорирован. Собственно, основной use case — разделы, которые автоматически монтируются при загрузке системы из fstab. То есть обычные записи в этой секции будут выглядеть так:
  "exceptions": [
    {"uuid":"ceb62844-7cc8-4dcc-8127-105253a081fc", "comment":"System boot partition"},
    {"uuid":"6d1a8448-10c2-4d42-b8f6-ee790a849228", "comment":"System root partition"},
    {"uuid":"9b0bb1fc-8720-4793-ab35-8a028a475d1e", "comment":"System swap partition"}
  ]

Кстати — всякие там ключи типа «comment», естественно, игнорируются. Если не считать трюк с повторным использованием ключей в словаре, это единственная возможность комментировать конфиг-файл в формате JSON =)

3) Секция «rules»


Тут описываются правила для особых случаев. К примеру, здесь можно указать правило типа:

    {"uuid":"E02C8F0E2C8EDEC2", "mount":{"mountpoint":"/media/16G-DT100G2", "options":"rw,uid=1002,gid=1002"}},


При наличии такого правила раздел с UUID E02C8F0E2C8EDEC2 будет всегда монтироваться по пути "/media/16G-DT100G2", используя опции «rw,uid=1002,gid=1002».

4) Секция «default»


Тут всё просто — если раздел не соответствует записям в двух предыдущих секциях, то именно эта секция отвечает за действие по умолчанию. Обычно содержит просто «mount»:true, ну или «mount»:false.

Правила


Как можно видеть, во последних трёх секциях в правилах есть два типа переменных.
Первый тип — переменные для идентификации какого-то конкретного раздела. Их пока три типа — «uuid», «label» и «label_regex».
  • «uuid» — тут всё понятно.
  • «label» — позволяет задать метку раздела, при совпадении которой будет принято действие.
  • «label_regex» — сопоставляет метку диска с указанным регулярным выражением, используя re.match(). На входе, естественно, ожидается Perl-like регулярное выражение.

Второй тип — переменные для обозначения действия. Их тоже три:
  • «mount» может иметь следующие значения: true (автоматически монтировать), false ( не монтировать) или словарь с дополнительными аргументами для монтирования (подразумевается true). Пока что допустимые ключи для словаря — «mountpoint» (задание точки монтирования) и «options» (дополнительные опции для монтирования).
  • «command» — строка, содержащая путь к команде, которую следует выполнить. Тут всё ясно =)
  • «script» — какой-нибудь кастомный скрипт, который вызывается с аргументами «DEVICE_PATH UUID MOUNTPOINT LABEL». Вместо последних двух, если нет точки монтирования или метки раздела, будет None.

Естественно, при обработке секции «exceptions» никакие действия, даже если будут указаны, приняты не будут, а в секции «default» не будут обрабатываться переменные идентификации — смысла нет =)

Добавлю пару примеров правил в конфиге:

{
  "globals": {
    "interval":1,
    "debug":false,
    "default_mount_option":"noexec,noatime,rw"
  },
  "exceptions": [
    {"label":"Root", "comment":"System boot partition"},
    {"uuid":"6d1a8448-10c2-4d42-b8f6-ee790a849228", "comment":"System root partition"},
    {"uuid":"9b0bb1fc-8720-4793-ab35-8a028a475d1e", "comment":"System swap partition"}
  ],
  "rules": [
    {"label":"MULTISYSTEM", "mount":{"mountpoint":"/media/MULTISYSTEM", "comment":"Будет конфликтовать, если будет два раздела с одинаковой меткой =( "}},
    {"uuid":"7F22-AD64", "mount":{"mountpoint":"/media/16G-DT100G3"}, "command":"/usr/local/bin/sync_dt100g3.sh"},
    {"uuid":"406C9EEE6C9EDE4A", "mount":{"mountpoint":"/media/80G-Music"}, "command":"mocp --server; mocp -P", "comment":"Автоматическое проигрывание музыки при "},
    {"label_regex":".*iPhone.*",  "mount":true, "script":"/usr/loca/bin/iphone_factory_restore.sh", "comment":"Учимся делать бекапы ;-) "}
  ],
  "default": {
    "mount":false,
    "comment":"Ты куда монтируешься, а ну пропуск покажи"
  }
}


Зачем?


В итоге — что позволяет этот демон? Кроме того, что он помогает автоматически монтировать разделы на подключаемых устройствах, он может быть использован для:
  1. Синхронизации файлов на съёмных носителях с, к примеру, домашним сервером на Linux
  2. Своеобразного usb-modeswitch, вспомнить ту же ситуацию с iPod
  3. Проверки носителей на вирусы
  4. Сливания данных с чьих-нибудь iPhone/Android/флешек/фотоаппаратов втихаря =)
  5. Создания Samba-шар для доступа к носителям по сети


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

  1. Можно ли постоянно держать этот демон под рутом? Проблема в том, что нужно иметь привилегии для запуска всех команд — как монтирования, так и исполнения внешних скриптов, среди которых может быть тот же rsync, к примеру. Безопасность файла конфигурации — из той же оперы. Если скрипт запускается под рутом, а в конфиге прописана команда, которая случайно стирает MBR&MFT, будет весело.
  2. Для выполнения скриптов и команд, определённо, в ближайшее время нужно будет сделать обязательное выполнение в фоне, в отдельном потоке — а не то любая команда, которая будет работать дольше пары секунд, застопорит весь демон. В принципе, такая же ситуация может возникнуть при монтировании. Но тут дилемма — если выполнять монтирование в фоне, то команда или скрипт могут быть выполнены раньше монтирования, такой вот race condition =( Думаю выделить в отдельный поток обработку каждого раздела, так не будет ни race condition, ни повисания, если mount виснет из-за того, что нужно ещё пофиксить ФС.
  3. Нужно ли демону уходить в фоновый режим самому или это необязательно? Пока что устроено так — демон сам в фоновый режим уходить не умеет, за него это делает start-stop-daemon в init-скрипте.
  4. Как по пути к блочному устройству (/dev/sdxZ) проще и быстрее всего проверить, примонтировано ли оно? Желательно используя стандартные модули Python, ну, или в крайнем случае — используя внешние команды. Тогда можно будет избавиться от проблем, связанных с двукратным монтированием одного и того же раздела. Парсить mtab и сопоставлять UUID с путям к блочным устройствам — задача ещё та ;-)
  5. Стоит ли генерировать из fstab список исключений до первого запуска или можно положиться на пользователя, который один раз вобьёт это ручками?
  6. При запуске внешних скриптов нужно иметь в виду возможные спецсимволы в partition label, типа случайно попавших туда "&&rm -rf /" (сработает при запуске внешнего скрипта) и "../../etc/" (может смонтировать раздел вместо "/etc") ;-) Вопрос — какие спецсимволы нужно фильтровать для полной защиты этой дыры? На ум сразу приходят "&", "/" и ";", но может быть больше.


GitHub
Кстати, эту статью можно считать продолжением мной обещанного, но на уже почти год как остановленного курса статей по настройке своего переносного сервера, используя Debian. Собирался сделать что-то такое в рамках реализации своего файлового сервера, только вот тогда не смог найти готового решения… А сейчас написал своё =) В ближайшее время думаю написать ещё пару статей, которые подойдут под тематику этого курса.
Арсений Пичугин @CRImier
карма
7,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (39)

  • +11
    А чем не подошел udev? Он может делать тоже самое.
    • –1
      1) Не хотелось зависимости от udev. Да, вроде как он в ближайшее время никуда пропадать не собирается, но чем меньше зависимостей, тем лучше.
      2) Не совсем то же самое. К примеру, я уверен, что настраивать там монтирование по UUID — та ещё морока. К тому же — сервис легче отключить, быстрее время отклика (если понизить интервал между опросами до половины секунды, к примеру). Короче, такой же гибкости, просто используя пару правил, не выйдет.

      Получается, что если делать по уму, как минимум вешать какой-то похожий на мой скрипт коллбэком сразу на все storage devices, и уже в нём точно так же по уму разбирать UUID, исключения/правила/прочее… А потом смотришь и думаешь — «С одной стороны — скрипт. С другой стороны — скрипт, приделанный к udev, но с той же функциональностью. И какая тогда разница, что писать?»
      • +2
        1) Мне кажется, что лучше все таки использовать стандартное средство. Unix-way'но.
        2) Монтирование по UUID настраивается легко, отключить правило udev так-же просто. Про время отклика вы ошибаетесь, ваш скрипт отработает когда устройство появиться в /dev, а появится оно там тогда, когда отработает udev. Ну и зачем нагружать проц постоянным циклом (хоть он почти ничего и не потребляет).
        Опять же имхо, выйдет гораздо гибче. Можно фильтровать не только по uuid, но и по моделям, вендору и тд.
        • 0
          1) Насчёт Unix-way согласен. С другой стороны, даже систем, заменяющих init, оказывается, что-то около 3-4. Так что — принцип есть, а вот применим он, оказывается, не везде =) А так, да, признаю, моя работа заменима udev… С похожим вспомогательным скриптом. С одной стороны, я написал в каком-то смысле велосипед. С другой — он выполняет задачу, имеет некоторые преимущества перед аналогом, ну и, в конце концов, свой, родной ;-)
          2) Отключение правила udev подразумевает — отредактировать файл или хотя бы держать его название в памяти. А тут — service pautomount stop. Да и время отклика другое, поскольку демон инициализируется один раз — а скрипт каждый раз, когда запускается. А постоянный цикл — это прочитать один раз содержимое директории и сравнить пару переменных. Не так и трудно =)
          3) Трудно представить ситуацию, где понадобится фильтровать по модели/вендору, честно. Если даже label_regex как-то себя оправдывает, то use case для этого… Разве что в мастерской, где ремонтируют флешки и для каждой модели флешки есть свой пакет для восстановления данных через сервисный режим контроллера, по счастливой случайности реализованный и включаемый через USB =)
          • 0
            >Трудно представить ситуацию, где понадобится фильтровать по модели/вендору, честно.
            Только вчера с помощью udev поборол прошивку arduino по модели/вендору :)
          • +2
            А постоянный цикл — это прочитать один раз содержимое директории и сравнить пару переменных. Не так и трудно =)

            использовали бы как минимум inotify: pyinotify или watchdog(на хабре: линк)
            • 0
              Кстати, подобная функциональность есть в pyuv¹ и PySide/PyQt: оба, конечно, не специализированные решения, как watchdog и, тем более, pyinotify, но о них полезно знать на случай, если понадобится какая‐то ещё их возможность.

              ¹ привязка Python к libuv, которая, в свою очередь, предоставляет платформонезависимые абстракции для ряда платформо‐специфичных вещей для nodejs.
    • 0
      Да, мне тоже показалось, что использовать для этой задачи udev как-то естественнее. Вот пример реализации автоматического монтирования и отмонтирования (при выдёргивании, безопасно для vfat и ext4) флешек — может кому пригодится.

      Файл сохранить в /etc/udev/rules.d/, отредактировав в нём первые две строки (имя юзера и путь монтирования):
      99-usb-automount.rules
      # Allow user mount/umount (set device owner and use 'owner' option in fstab).
      # Create mount point directories for vfat/ext2/ext3/ext4/ntfs partitions.
      # Create symlinks using partition label (overwrite duplicates).
      # Auto-mount vfat with 'flush' option.
      # Auto-mount ext4 with 'sync' option.
      # No auto-mount for other filesystems (because there is NO SAFE auto-unmount).
      # Auto-unmount vfat/ext2/ext3/ext4/ntfs and cleanup directories/symlinks.
      
      ENV{mount_user}="powerman"
      ENV{mount_path}="/mnt"
      
      KERNEL=="sd*", DRIVERS=="usb-storage", ENV{ID_FS_TYPE}!="", \
          OWNER="%E{mount_user}", \
          ENV{mount_point}="usb.%k"
      
      ACTION=="add",    ENV{mount_point}!="", ENV{ID_FS_TYPE}=="vfat", \
          ENV{mount_fs}="vfat", \
          ENV{mount_opts}="flush,owner,umask=007,showexec,quiet"
      ACTION=="add",    ENV{mount_point}!="", ENV{ID_FS_TYPE}=="ext2|ext3", \
          ENV{mount_fs}="%E{ID_FS_TYPE}", \
          ENV{mount_opts}="owner"
      ACTION=="add",    ENV{mount_point}!="", ENV{ID_FS_TYPE}=="ext4", \
          ENV{mount_fs}="ext4", \
          ENV{mount_opts}="sync,owner"
      ACTION=="add",    ENV{mount_point}!="", ENV{ID_FS_TYPE}=="ntfs", \
          ENV{mount_fs}="ntfs-3g", \
          ENV{mount_opts}="owner,umask=007,fmask=111"
      
      ACTION=="add",    ENV{mount_point}!="", \
          RUN+="/bin/mkdir -p %E{mount_path}/%E{mount_point}", \
          RUN+="/bin/sed -i.bak '$a/dev/%k %E{mount_path}/%E{mount_point} %E{mount_fs} %E{mount_opts} 0 0' /etc/fstab"
      ACTION=="add",    ENV{mount_point}!="", ENV{ID_FS_TYPE}=="vfat|ext4", \
          RUN+="/usr/bin/sudo -u %E{mount_user} /bin/mount %E{mount_path}/%E{mount_point}", \
          RUN+="/usr/bin/sudo -u %E{mount_user} /usr/bin/env DISPLAY=:0 /usr/bin/notify-send -t 2500 -u normal -i /usr/share/icons/oxygen/48x48/devices/drive-removable-media-usb-pendrive.png 'USB Flash automounted' '%E{mount_point}\n%E{ID_FS_LABEL}'"
      ACTION=="remove", ENV{mount_point}!="", \
          RUN+="/bin/umount -l %E{mount_path}/%E{mount_point}", \
          RUN+="/bin/sed -i.bak '/^\/dev\/%k \S\+\/%E{mount_point} /d' /etc/fstab", \
          RUN+="/bin/rmdir %E{mount_path}/%E{mount_point}", \
          RUN+="/usr/bin/sudo -u %E{mount_user} /usr/bin/env DISPLAY=:0 /usr/bin/notify-send -t 1000 -u normal -i /usr/share/icons/oxygen/48x48/devices/drive-removable-media-usb-pendrive.png 'USB Flash disconnected' '%E{mount_point}\n%E{ID_FS_LABEL}'"
      
      ACTION=="add",    ENV{mount_point}!="", ENV{ID_FS_LABEL}!="", \
          RUN+="/bin/ln -nsf %E{mount_point} %E{mount_path}/%E{ID_FS_LABEL}"
      ACTION=="remove", ENV{mount_point}!="", ENV{ID_FS_LABEL}!="", \
          RUN+="/bin/rm %E{mount_path}/%E{ID_FS_LABEL}"
      

      • +1
        1. А Вы, простите, КАК до этого скрипта додумались, какие мануалы читали (ну, кроме man udevadm). У меня как-то не получилось совладать с запуском ppp при вставке 3g-модема… udev никакой отладки не предоставляет…
        2. Почему RUN+= а не RUN=?
        3. Почему в начале скрипта ENV{mount_user}=«powerman» а в скрипте %E{mount_user}
        • +1
          1. Какие мануалы, не смешите. Не поймите неправильно, я доку читать люблю, причём много и вдумчиво, но не в ситуации когда нужно сделать одноразовый скрипт на птичьем языке очередной тулзы. Погуглил, нашёл похожий пример реализующий базовую функциональность (условную установку переменных, обращение к переменным, и выполнение команды при подключении/отключении флешки) — для нормального программиста этого более чем достаточно, чтобы написать своё решение просто «по аналогии». Что касается отладки — вывод udevadm monitor -u -p достаточно информативен вроде бы.
          2. Надо полагать, = устанавливает (заменяет) значение, а += добавляет к текущему значению. Я использовал += для того, чтобы можно было проще видеть отдельные команды, отключать/заменять их — т.е. просто для лучшей читабельности скрипта.
          3. Потому, что именно таким образом устанавливались и считывались значения переменных окружения в нагугленном мной примере — есть ли какие-то ещё способы это делать в синтаксисе udev я не вникал.
        • 0
          Хотя нет, вру. Какую-то доку я всё-таки тогда попытался почитать, как минимум помню что читал Writing udev rules — но всё-равно, от примера было больше толку, чем от доки.
      • 0
        По-взрослому надо mount_user не константой забивать, а вычислять, чьи X-ы сейчас запущены и находятся на активном VT.
        • 0
          Не поверите, я тоже об этом думал. Но решил, что чем меньше кода работает внутри udev под root-ом, и чем он проще — тем лучше. И что этот фактор важнее, чем писать излишне универсально и по-взрослому.
          • 0
            Помнится, в каком-то дистрибутиве я видел готовое решение. Правда, оно было гномозависимыми или еще какими-то недостатками обладало, так что я не попытался перенести его и адаптировать для себя.
  • +1
    При запуске внешних скриптов нужно иметь в виду возможные спецсимволы в partition label, типа случайно попавших туда "&&rm -rf /" (сработает при запуске внешнего скрипта) и "../../etc/" (может смонтировать раздел вместо "/etc") ;-) Вопрос — какие спецсимволы нужно фильтровать для полной защиты этой дыры? На ум сразу приходят "&", "/" и ";", но может быть больше.
    Почему бы не воспользоваться либо списками (при использовании списков в Python запуск внешних программ обходится без shell), либо экранированием строки (правда я знаю всего один способ экранирования на Python: замените её на "$_ARGUMENT_0001" и предоставьте соответствующую переменную окружения). При правильном подходе единственное, что надо будет учитывать — это /, который надо на что‐то заменить (можете проверить как это сделано системой в /dev/disk/by-label).

    Внутри скриптов тоже придётся это учитывать, поэтому перед тем, как запустить команду, сконструируйте полный путь и не забудьте про кавычки (;rm -rf / не доставит вам никаких проблем в команде mount $MOUNTPOINT $DEVICE, кроме того, что из‐за пробелов mount получит в аргументах /mnt/;rm, -rf, / и /dev/…).

    Полный путь нужен, чтобы избавиться от потенциальных проблем с - в начале имени. Для того, чтобы при наличии в переменной ;rm -rf /, его запустить вам нужен eval либо что‐то эквивалентное вроде запуска sh -c, а это присутствует в скриптах крайне редко. Фильтрация только покажет, что вы не разбираетесь в проблеме.
    • 0
      Пока что — да, используется shell режим. В режиме shell=False списки, если я правильно помню, обязательны. Честно скажу — я пробовал использовать списки. Есть одна проблема… Это неэстетично выглядит =) Если вместо каждой строки в конфиге вбивать список — конфиг становится ещё более нечитаемым =( В ближайшее время собираюсь покопаться в os.popen и аналогах, может, там найдётся что-то, что non-shell и принимает на вход строку, а то, действительно, зря я пока subprocess ограничился. Спасибо!
      • +1
        Вы ищете ответ не в той плоскости. Строка без shell нарушает однородность аргументов функций и требует другого кода для обработки, поэтому вы, скорее всего, ничего не найдёте. А вот преобразование строки в список есть и не ограничивается простым unicode.split() или даже re.split(): посмотрите модуль shlex. os.popen*, кстати, если мне не изменяет память, deprecated.

        Правда без shell вы очевидно теряете вещи вроде command1;command2.
        • 0
          >>>однородность аргументов функций
          Можно объяснить, что это за зверь?

          Кстати, спасибо за shlex! Я раньше про него читал, но потом, когда искал ещё-раз, перепутал его с ast, ну и решил, что ошибся и такого инструмента нет. Теперь вспомнил.

          Прикол в том, что если ставить shell=False, то теряется возможность использовать "&", "|", ";", "&&". С одной стороны — затыкается дыра. С другой — нужно на каждую команду, где может использоваться один из спецсимволов, писать скрипт и вызывать уже сам скрипт. Короче, либо удобство, либо безопасность =) Поэтому я и думаю фильтровать label, как единственное место, где есть возможность для атаки со стороны обычного юзера. Конфиг не считаю за возможность атаки, так как владелец конфига — рут, если кто-то изменил конфиг, то это не проблема демона, так как скомпрометирована вся система прав.
          • 0
            Однородность? Ни разу не слышали про consistency? Суть в том, что программист, помня некоторый (неполный) набор функций должен быть способен предсказать, какие аргументы принимают функции из того набора, что он не помнит, и какие возвращают значения. Иначе программисту надо часто заглядывать в документацию и он говорит, что язык — говно.
            • 0
              Не могли бы Вы подкинуть какую-нибудь литературу, где поподробнее описан принцип этой самой однородности?

              И всё же — если я решу очищать LABEL от всяких спецсимволов, оставляя только ASCII, при этом оставлю shell=True ради того, чтобы удобно было использовать shell extensions — как это покажет мою некомпетентность и непонимание проблемы? Кстати, label всё равно надо фильтровать. Опыт показывает, что русские символы и пробелы в label («Зарезервировано системой», это камень в твою сторону!) довольно сильно усложняют процесс автомонтирования, привнося необходимость избыточного экранирования. Уж не знаю, как кто-то решает подобные вещи при монтировании через udev =)
              • 0
                Тем, что это не является необходимым. Программисты любят универсальные решения и конструктивную параною. Если вы избавитесь от русского языка в метках, то получите абсолютно бесполезные имена: если там есть русский язык, то 99% метка написана полностью по-русски. Это не универсально. mount'у абсолютно без разницы, какие байты есть в имени каталога; при использовании простых двойных кавычек и переменных окружения проблемы могут возникнуть только с / и дефисоминусом в начале аргумента. Это не конструктивно.

                Фильтруя символы, вы показываете свою неспособность придумать универсальное и корректное решение. Советую просто использовать мой вариант экранировки из первого комментария из Python (+ os.path.abspath и замена /) и двойные кавычки в скриптах.

                //

                А литературу по этому поводу я не знаю (хотя она должна быть). Но при совместной разработке чего-либо крайне сложно избежать ситуации, когда вам говорят, что это надо переделать «because it is inconsistent with (some previously added code)». Так же откройте некоторые холивары на программистскую тему, там будут постоянно швыряться аргументами про отсутствие однородности: этим грешат противники PHP в PHP vs что-то, эти аргументы можно найти в войнах git vs что-то… Короче, читайте тематические интернеты и понимание этой концепции само придёт к вам.
                • 0
                  Ну так в любом случае после фильтрации нужно проверять — не остались ли от LABEL рожки да ножки. Собственно, это нужно бы проверять и без фильтрации =) Потом, я пишу решение, которое не должно создавать лишних проблем. Если я оставлю русские и прочие Unicode символы, то есть один нюанс — для того, чтобы из консоли без установленной соответствующей раскладки добраться до папки, в которой нет английских символов, нужно использовать mc или что-то типа того. Моё мнение — нафиг так жить. Я уж лучше сделаю один ключ в конфиге, который включает фильтрацию некорректных символов, и один, который включает фильтрацию не-английских раскладок, ну и включу их по дефолту, чтобы улучшить user experience.

                  Теперь вопрос — можете провести аналогию между Вашим определением однородности и фильтрованием? Моя функция execute(), которая является деривативом от supbrocess.check_output (который является, ЕМНИП, деривативом от os.popen ;-) ), не должна иметь такие же аргументы — она не в том же модуле, не является заменой, а лишь упрощённым вариантом. Более того, я её использую именно потому, что мне просто легче вбить команду строкой, нежели списком аргументов, ну и получать список из (exit_code, output) после каждого вызова, а не ловить эксепшен только из-за того, что код выхода команды — не 0. Зачем однородность в аргументах, если одна из целей — упрощение аргументов?

                  Кстати, не совсем понимаю, почему Питон требует именно список аргументов. У Вас случаем нет ссылки на PEP или что-то вроде того, где обсуждается эта деталь? Я не нашёл =(
                  • 0
                    Можно использовать Tab. Можно даже помочь пользователю, добавив предсказуемый ASCII-only префикс.

                    Однородность относится к библиотекам языка. Я просто объяснил, почему вы вряд ли что-либо найдёте. Есть, конечно, и исключения: см. unicode.translate и bytes.translate (тут исключение, вероятно, связана с оптимизацией) или интерфейсы для работы с архивами и потоковыми форматами сжатия (тут исключение, вероятно, связано с разными разработчиками).

                    Относительно списка: а что бы вы предложили? Учтите, что никому не нужно тратить время на парсинг (если я формирую команду от и до, а не беру из конфига, то у меня нет никаких проблем со списками: наоборот, строки были бы неудобны). Также в *nix есть семейство функций exec*. Они принимают списки: либо в виде char**, либо в виде аргументов: знаете int main(int argc, char **argv) (иногда ещё и с char **env)? Python использует execve, которая использует именно char ** для списка аргументов. Так что хотите-не хотите, а в *nix список вам нужен.
                    • +1
                      Tab не работает в таких случаях. Префикс — это уже обработка label и проверка. К тому же префикс никак не поможет Таб-ом перейти на нужную папку в консоли, если папок с одинаковым префиксом больше одной =) Да и глупо будет выглядеть. Лучше уж сразу тогда монтировать по UUID, если label некрасивый — как у меня сделано в случае, когда label не существует =)

                      Про библиотеки из C, которые используются как основы для Питоновских библиотек, понятно. Но если уж Питон предоставляет обёртки над функциями из C — можно сделать эти обёртки удобными, как это, собственно, и сделано в большинстве Питоновских библиотек.
                      Про формировку — тоже понятно, впрочем, можно добавить строчку с " ".join(argument_list), если функция требует строку, делов-то =) Может, мне url для urllib2 удобнее массивом отдавать — удобство формировки аргументов для всех, кто формирует команды от и до, не должно заставлять всех остальных делать так же. Я имею в виду то, что если это было бы единственным аргументом в пользу списков, то списки точно не были бы нужны =)

                      Насчёт однородности — нашёл os.system. Принимает на вход строку. stdout не выдаёт, правда, как и subprocess.call =( Более того, однородность между теми же check_output и popen тоже неполная — так почему между моей функцией и вышеперечисленными она должна быть полной?

                      То же разное поведение subprocess.check_output при shell=True и shell=False — это ли не вопиющий пример неоднородности? Вон, человек тоже недоволен — asvetlov.blogspot.com/2011/03/subprocess.html, плюс там пример вообще неадекватного поведения.
                      И баг есть в багтрекере — bugs.python.org/issue7839. Там тоже это поведение описано.
                      Собственно, я свою функцию execute() написал, когда с этой фигнёй столкнулся. В скором времени добавлю в неё поддержку shell=False при помощи shlex — будет нормальной =)
                      • 0
                        Tab не работает в таких случаях.
                        Да ну? Переходите на zsh, всё работает. В fish тоже всё работает. Даже bash можно настроить, чтобы всё работало. Под «всё» я имею ввиду, что повторное нажатие Tab вызывает циклическое переключение между вариантами автодополнения. Zsh дополнительно позволяет управлять переключением стрелками, что очень помогает (как ни странно, у меня нет русской раскладки и все русские тексты я пишу либо со смартфона (транслитом, но используя ruKeyboard), либо транслитом в Vim). Так что я знаю, о чём говорю.

                        К тому же префикс никак не поможет Таб-ом перейти на нужную папку в консоли, если папок с одинаковым префиксом больше одной =)
                        Это целиком и полностью вопрос выбора способа создания префикса.

                        Про библиотеки из C, которые используются как основы для Питоновских библиотек, понятно. Но если уж Питон предоставляет обёртки над функциями из C — можно сделать эти обёртки удобными, как это, собственно, и сделано в большинстве Питоновских библиотек.
                        Они удобные. Во‐первых, только shell оперирует сплошной строкой: и exec*, и main(), и вещи вроде sys.argv — это всё списки.

                        Сплошная строка в main()/sys.argv никому на хрен не сдалась и совершенна неудобна: попытайтесь обработать список файлов, полученный в виде сплошной строки и поймёте, почему. Соответственно нелогично иметь сплошную строку на другом конце: это нарушает принцип «явное лучше неявного»: получается «сплошная строка» → «магия» → «список». Вряд ли вы найдёте здесь много человек, способных полностью описать, как работает «магия» в случае с shell: а ведь там это одна из основополагающих вещей.

                        Собственно, здесь вы нарушаете сразу два принципа: «явное лучше неявного» и «простое лучше сложного». Это не дело.

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

                        Сплошная строка неудобна для генерации списка аргументов из списка вещей (к примеру, файлов) — из‐за экранирования простым ' '.join(argument_list) не обойдётся.

                        Список удобен для генерации аргументов от и до, и, чёрт возьми, я ни разу, ни в одной своей программе не хотел использовать shell=True или использовать строку. И, по‐моему, нигде и не использую.

                        Сплошная строка неэкономична: чтобы её создать вам придётся пройти процесс «генерация аргументов» — «экранирование» — «парсинг строки» — «генерация char ** массива». В случае списка половина пунктов из середины выкидывается.

                        И ещё, если у вас есть функция со списком, то вы можете взять её и прикрутить наверх функцию со строкой, обратное тоже верно. Так зачем тащить в стандартную библиотеку всякую дрянь? Ruby уже имел проблемы с безопасностью от того, что никто не помнит всю документацию (вспомните, к примеру, $ против \Z).
                        Конечно, вы не можете сделать язык, на котором все программы будут безопасны. Но вы можете уменьшить нагрузку на программиста, уменьшив пространство выбора, оставив только более безопасные решения. В идеологии языка никогда не было TMTOWTDI.

                        В Python было бы логичнее сделать Popen с *args в качестве списка, чтобы не отличаться от прочих функций, а никак не принимать на вход строку. И отдельный модуль, который позволяет засунуть в *args запуск $SHELL с заданной строкой.

                        Насчёт однородности — нашёл os.system.
                        В модуле os много практически прямых привязок к C’шным функциям. И этот вариант использует оболочку. Кроме того, «The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function.» и «Note that POSIX does not specify the meaning of the return value of the C system() function, so the return value of the Python function is system-dependent

                        То есть вы нашли deprecated функцию. Не удивительно, что тут не соблюдается условие однородности: такие функции существуют только для обратной совместимости. По‐моему, её не собираются удалять только потому, что из‐за простоты os.system слишком часто используется.

                        Более того, однородность между теми же check_output и popen тоже неполная — так почему между моей функцией и вышеперечисленными она должна быть полной?
                        Мне не жалко повторить во второй раз, что однородность должна быть в библиотеках языка, и что она не идеальна. И, кстати, вы не нашли «что-то, что non-shell и принимает на вход строку». Ну и os.popen* тоже deprecated. Более того, в документации на os.system стоит просто рекомендация использовать subprocess. А в документации на os.popen стоит очень заметное розовое предупреждение о том, что popen «Deprecated since version 2.6». Жаловаться на неоднородность deprecated функций имеет не больше смысла, чем жаловаться на то, что человеческий эмбрион не выглядит как человек: такие неоднородности обусловлены историческими причинами.

                        Замечу, что в Python 3 ситуация иная: там нет ни deprecation warning при os.popen (но есть рекомендация использовать subprocess), ни информации о том, что с subprocess нужен для замены os.popen в первых абзацах документации этого модуля: здесь написано только то, что subprocess — это замена os.system и os.spawn*. os.popen с цифрами, впрочем, нет вообще.

                        То же разное поведение subprocess.check_output при shell=True и shell=False — это ли не вопиющий пример неоднородности? Вон, человек тоже недоволен — asvetlov.blogspot.com/2011/03/subprocess.html, плюс там пример вообще неадекватного поведения.
                        Я с ним согласен. Как я сказал выше, я бы вообще зарубил Popen возможность использовать shell, но добавил модуль или функцию в subprocess, которая сформирует список аргументов для Popen так, чтобы вызывался shell со строкой. Ну и Popen('cat', filename, filename2) выглядит лучше, чем Popen(['cat', filename, filename2]). Но всё это не меняет того факта, что функции в subprocess принимают практически одни и те же аргументы.
                      • 0
                        Кстати, в той issue, что вы привели здесь, есть ссылка на shell-command.readthedocs.org/en/latest/index.html. Если вам нужно использовать оболочку, то этот модуль выглядит весьма интересно (он занимается, в том числе, автоматическим экранированием аргументов).
  • +2
    «label_regex»:"*iPhone*"
    Обычно при слове regex ожидают один из диалектов Perl’оподобных регулярок, либо что‐то вроде ERE/BRE. А это обычно называется glob либо более общим pattern (которое также может относится и к regex). Если вы подаёте код Python’овскому re.*, то выражение должно выглядеть как .*iPhone.*. Если это выражение правильно, то поле лучше переименовать.
    • 0
      >>>«comment»:«За правильность регулярки вообще не ручаюсь»
      Я просто очень редко сталкиваюсь с регулярными выражениями, уже подзабыл, как и что конкретно в Питоне. Естественно, что ожидается Perl-like регулярное выражение =) Сейчас добавлю, спасибо!
  • +2
    К черту Поттеринга, я напишу свой systemd с питоном и скриптами!
    • 0
      Как будто systemd — это что-то плохое. Само его существование никого не заставляет на него переходить, если не ошибаюсь, даже в новом Дебиане есть возможность его безболезненно отключить. Да и, насколько понимаю, его не зря выбрали новой init system:
      The Debian Technical Committee voted to make systemd the default system management daemon for Linux in the «jessie» release.
      lists.debian.org/debian-ctte/2014/02/msg00405.html — думаю в ближайшее время прочитать эту дискуссию сначала. Всё же ну очень интересно — почему =)
      • 0
        Само его существование никого не заставляет на него переходить.
        Только теоретически. По факту, вам придётся отказаться от популярных дистрибутивов, если вы не сторонник systemd.
        Да и, насколько понимаю, его не зря выбрали новой init system.
        По поводу адекватности этого выбора ведутся жаркие баталии (на ЛОРе — при выходе любой новости про systemd). Даже на хабре не так давно статья была: habrahabr.ru/post/223351/ если у вас есть желание подискутировать на эту тему, то я рекомендую делать это в том топике.
  • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Cхемой кабеля для зарядки ipod'а не поделитесь?
    • 0
      pinouts.ru/PortableDevices/ipod_pinout.shtml
      Это для Nano:
      >>>To charge iPod Nano, USB D+ & D- should be tied together and then connected to a 10K ohm resistor, and the other side of this resistors then needs to be connected to 5v power.
      То есть — от источника питания отходит два провода — земля и питание, на кабеле со стороны айпода пины USB D+ и D- закорачиваются, и 10К резистором подтягиваются к питанию. У меня работало с iPod Nano 6G.
      Со всеми остальными устройствами от Apple — только выставлять напряжения резисторными делителями. Я просто беру два маленьких потенциометра, которые регулируются отвёрткой, подсоединяю у обоих один крайний контакт к земле, один к питанию, а центральные идут на D- и D+. Естественно, для начальной регулировки нужен вольтметр — зато не нужно искать какие-то резисторы по 77к =)
      Собственно, у меня в черновиках есть уже одна статья насчёт зарядок, Apple-овских и не очень, да вот только Хабр решил, что хаб DIY — это для меня роскошь =) Вообще, странно, почему DIY — непрофильный хаб. Один из самых интересных и популярных, если посмотреть.
  • +2
    Если уж делать с минимум зависимостей и для Linux — то только на чистом C.
  • +1
    Да уж, раз в секунду проверять наличие файла в ФС… Конечно загрузка процессора будет ничтожна, но всё-таки это — неправильно!
  • 0
    Совершенно не вижу смысла в этом велосипеде.
    Для организации автоматических бэкапов по UUID ФС на флешке можно простой скрипт на баше записать, который будет вызываться udev'ом.
    Но я бы так не делал. Надежнее вручную запустить — вдруг какие ошибки возникнут? Да и непонятно, как сигнализировать об окончании процесса, если он в фоне идет — музыку сыграть? А вдруг ты резервирование ночью делаешь, когда все родные спят? И тут на всю квартиру «тадам!».
    • 0
      notify-send
    • 0
      1) Считайте это простым скриптом на Bash, только на Python =) Я не люблю Bash, честно. Причины заслуживают отдельной статьи — и включают в себя определённые неудобства в использовании, возможно, правда, только лично для меня, куча телодвижений для нормальной обработки имён файлов в 100& случаев. Тот же JSON на Баше разбирать — довольно неочевидная штука. Да, BASH определённо собрал бы больше плюсов, но для этой цели Python лучше. К тому же мне лично не нравится каждый раз редактировать правила в udev =)

      2) Голосовое/звуковое оповещение — одна из тем, которую я собираюсь реализовать в ближайшее время, и, естественно, периоды оповещения — актуальная тема. Я уже один раз решал это с помощью скрипта-обёртки над Festival, правда, написан он по-нубски и сейчас особой ценности не представляет =) Если вкратце — для festival были созданы файлы, в каждом из которых был короткий сценарий с фразой. festival для обработки таких файлов запускается с опцией -b file (--batch). Мой скрипт становился вместо festival, принимал эту опцию и проверял текущее время, ну и вызывал festival, если время позволяло =) Но, думаю, Насчёт ошибок — тоже решается сигнализацией, которая, к слову, может быть не только звуковой.
      В ближайшем времени буду искать простое решение, вроде демона, который бы централизованно управлял бы всеми оповещениями на сервере и пропускал/не пропускал бы их в зависимости от времени/важности/типа/чего-нибудь ещё. Не найду — напишу сам =)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.