Файловая система, дешево и быстро

от автора

Разработчикам часто приходится иметь дело с файлами, представляющими из себя
древовидную структуру: XML, JSON, YAML, всякого рода языки разметки вроде
Markdown или Org-mode. Облегчая в общем и целом нашу жизнь, такие файлы имеют
склонность к бесконтрольному росту, в какой-то момент из решения превращаясь в
проблему.

Стандартное решение этой проблемы — разбиение на меньшие файлы. Это, конечно,
работает, но не всегда удобно.

Но существует и альтернатива, о которой — ниже.

org-mode и его разметка.

Пожалуй, стоит сначала изложить мою проблему. Я использую Емакс и — как многие
пользователи Емакса — для написания почти всех моих документов, заметок,
рабочего дневника и списков задач использую язык разметки org-mode. Выглядит документ в этой разметке примерно следующим образом:

... простой пример файла из репозитория ... > cat tests/simple.org document section * headline 1 headline section 1 ** inner headline 1 some inner section 1 some inner section 1-2 ** inner headline 2 inner section 2 ** inner headline 3 *** inner inner headline 1 * headline 2 section text 2

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

И тогда мне в голову пришло, что было бы здорово ходить по моему файлу как по
директориям, при помощи, скажем, стандартных в Юниксах cd headline1 или cd ..,
ls -l и cat section.

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

Конечно, писать полноценную файловую систему для Линукса — дело долгое,
неблагодарное и уж точно не стоит оно того в такого рода редких случаях.

FUSE

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

С помощью FUSE было написано множество самых разных файловых систем, от
игрушечных ФС, монтирующих, например, статьи с Википедии, до вполне серьезных
частей современных Линуксов вроде того же Gnome. Таким образом, FUSE стал
обязательным элементом популярных дистрибутивов.

Еще приятней работу с FUSE делает тот факт, что в наши дни доступны совсем уж
тривиальные в использовании обертки на высокоуровневых языках вроде Python,
Ruby, Java и многих других, т.е. собственную файловую систему можно сделать
буквально за два-три часа.

fusepy

Конкретно на Питоне оберток вокруг libfuse (клиентской части FUSE) даже
несколько, но больше всего мне понравился проект fusepy: код проекта очень
простой и понятный, кроме примеров на Гитхабе и исходного кода мне так ничего
и не понадобилось.

Файловая система на базе fusepy сводится к переопределению методов класса
fuse.Operations, каждый из которых соответствует какому-либо системному
вызову:

class FuseOperations(Operations):      def __init__(self, tree):         self.tree = tree         self.fd = 0      def open(self, path, flags):         self.fd += 1         return self.fd      def read(self, path, size, offset, fh):         node = self.tree.find_path(path)         if node is None:             raise FuseOSError(EIO)         return node.content[offset:offset + size]      def readdir(self, path, fh):         node = self.tree.find_path(path)         if node is None:             raise FuseOSError(EROFS)         return ['.', '..'] + [child for child in node.children]      def getattr(self, path, fh=None):         node = self.tree.find_path(path)         if node is None:             raise FuseOSError(ENOENT)         return node.get_attrs()

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

Orgfuse

Собственно, конкретный формат файла, который хочется представить в виде дерева
директорий и файлов, не так важен. В случае с разметкой org-mode мне не
понравился ни один из доступных парсеров для Питона, и я просто написал
собственный. Парсер проходит по указанному файлу, создавая дерево, отражающее
структуру документа.

Дерево разбора (parse tree) файла разметки дальше преобразуется в другое
дерево
, отражающее файлы и директории, которые будет видеть пользователь
файловой системы.

Чтобы работать с последним деревом было достаточно реализовать четыре
системных вызовов (open, read, readdir, getattr), каждый из которых занимал
буквально несколько строк кода на Питоне.

Итоговый скрипт работает примерно следующим образом:

... монтируем файл как файловую систему ... > mkdir mount > python orgfuse.py tests/simple.org mount/ ... открываем другой терминал и наслаждаемся ... > tree mount mount/ ├── headline 1 │   ├── inner headline 1 │   │   └── section │   ├── inner headline 2 │   │   └── section │   ├── inner headline 3 │   │   └── inner inner headline 1 │   └── section ├── headline 2 │   └── section └── section  6 directories, 5 files

Все это чудо занимает порядка двух сотен строк или 3-4 часа моей ленивой
вечерней работы, с моей маленькой задачей справляется замечательно.

Инструкции по установке и код, как водится, можно найти Github.

Если кому интересно преращение прототипа во что-то удобоваримое, с
возможностью редактирования файлов и поддержкой большего количества форматов — буду рад пообщаться.

ссылка на оригинал статьи https://habrahabr.ru/post/315654/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *