Pull to refresh

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

Reading time 4 min
Views 34K

Разработчикам часто приходится иметь дело с файлами, представляющими из себя древовидную структуру: 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, каждый из которых соответствует какому-либо системному вызову.


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


Orgfuse


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


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


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


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()

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


... монтируем файл как файловую систему ...
> 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.


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

Tags:
Hubs:
+49
Comments 50
Comments Comments 50

Articles