Библиотека сайта rus-linux.net
Code Ninja: Создание файловой системы с помощью FUSE
Оригинал: Code Ninja: Make a filesystem with FUSE
Автор: Ben Everard
Дата публикации: 19 сентября 2016 г.
Перевод: А.Панин
Дата перевода: 23 октября 2016 г.
Комбинируем Python и FUSE для создания нового дерева директорий в вашем дистрибутиве.
Для чего это нужно?
- Вы поймете принцип создания файловых систем с помощью FUSE.
- Вы сможете интегрировать данные своего приложения с операционной системой на фундаментальном уровне.
- Вы пополните свой арсенал знаний, связанных с использованием языка программирования Python.
В большинстве случаев файловые системы являются структурами данных, хранящимися на каком-либо физическом носителе (таком, как жесткий диск), позволяющими сохранять наши данные на этом носителе и извлекать их с него. На самом деле, подобное описание не является единственно верным, так как файлы и директории являются лишь сущностями, которые используются компьютерами для максимально удобного упорядочивания информации с точки зрения пользователя. Наряду с обычными дисковыми файловыми системами, мы также можем создавать собственные файловые системы, которые будут возвращать данные любого типа.
Обычно файловые системы создаются силами ядра операционной системы, в нашем же случае мы будем использовать технологию файловых систем пространства пользователя ("Filesystems in USErspace" или "FUSE"), позволяющую разрабатывать приложения, создающие файловые системы вне ядра операционной системы. В данной статье мы будем использовать язык программирования Python для создания нашей файловой системы. Эта простейшая файловая система будет содержать всего лишь один файл с именем date
, в котором, в свою очередь, будет содержаться информация о текущей дате.
В первую очередь следует убедиться в том, что вы установили компоненты FUSE в вашу систему. В Ubuntu для этого следует использовать команду:
sudo apt-get install fuse
После этого нужно установить модуль Python, который будет использоваться для создания файловой системы:
sudo pip install fusepy
Теперь у нас есть все что нужно и мы можем приступить к разработке кода. Большая часть этого кода будет представлять собой реализацию класса нашей файловой системы. Структура этого класса выглядит следующим образом:
class Context(LoggingMixIn, Operations): def getattr(self, path, fh=None): #код def read(self, path, size, offset, fh): #код def readdir(self, path, fh): #код access = None flush = None getxattr = None listxattr = None open = None opendir = None release = None releasedir = None statfs = None
Как несложно заметить, существует 12 операций, которые пользователи могут выполнить по отношению к любому элементу файловой системы, при этом лишь три из них будут использоваться в нашей простейшей программе. Мы присвоили всем остальным методам класса значение None
для того, чтобы избежать проблем в случае их задействования пользователем. Тремя интересующими нас операциями являются операции получения атрибутов файла, чтения файла и чтения содержимого директории. Нам придется реализовать каждый из этих методов для того, чтобы возвращать корректный результат при его вызове.
Наша файловая система корректно функционирует и позволяет получить информацию о текущей дате.
Наши атрибуты
В первую очередь я предлагаю рассмотреть метод getattr
. Данный метод будет вызываться тогда, когда реализации файловой системы понадобится получить атрибуты файла. В него будут передаваться два параметра: путь к файлу и его хэндл (мы будем использовать лишь путь к файлу). Операционная система ожидает, что эта функция возвратит словарь со всеми актуальными значениями атрибутов файла. В рамках нашей файловой системы будут доступны лишь два пути: /
, соответствующий корневой директории файловой системы, и /date
, соответствующий файлу с информацией о текущей дате. Исходя из этого, наш код для формирования словаря с значениями атрибутов файлов будет выглядеть следующим образом:
def getattr(self, path, fh=None): if path == '/': attr = dict(st_mode=(S_IFDIR | 0755), st_nlink=2) elif path == '/date': attr = dict(st_mode=(S_IFREG | 0444), st_size=30) attr['st_ctime'] = attr['st_mtime'] = attr['st_atime'] = time() return attr
Так как по пути /
расположена директория, а по пути /data
— файл, при передаче этих путей требуется возвращать значительно отличающиеся друг от друга атрибуты. При этом в обоих случаях нужно возвращать значение прав доступа, которое формируется на основе флагов, импортированных из модуля stat
, и числового значения, соответствующего правам доступа к файлу в Linux. Также в обоих случаях необходимо добавлять в словарь метки времени модификации и последнего доступа. В нашем случае эти метки времени не имеют особого значения, поэтому при установке их значений мы будем использовать текущее время.
Директория также должна содержать атрибут с количеством указывающих не нее жестких ссылок. В нашем случае у директории нет поддиректорий, поэтому их количество будет равно 2. Для файла также нужен атрибут с значением его размера в байтах. Здесь мы немного схитрим и установим неизменное значение 30, хотя и очевидно, что размер файла может изменяться и напрямую зависит от текущей даты.
Вторым необходимым нам методом является метод read
. Он будет вызываться всегда, когда операционная система захочет прочитать содержимое файла. В случае нашей файловой системы можно будет прочитать содержимое единственного файла, поэтому нам придется лишь убедиться в том, что осуществляется чтение именно этого файла и вернуть строку с информацией о текущей дате:
def read(self, path, size, offset, fh): if path == '/date': return datetime.datetime.now().strftime("%B %d, %Y") + '\n'
Последним интересующим нас методом является метод, который задействуется каждый раз, когда операционной системе требуется получить информацию о содержимом директории. И снова следует помнить о том, что в нашем распоряжении имеется лишь одна директория, поэтому нам достаточно просто вернуть список содержимого этой директории:
def readdir(self, path, fh): return ['.', '..', 'date']
На этом реализацию нашего основного класса можно считать оконченной. Теперь мы можем приступить к созданию программы, которая будет задействовать этот класс, активируя тем самым нашу новую файловую систему:
from stat import S_IFDIR, S_IFREG from sys import argv, exit from time import time from fuse import FUSE, Operations, LoggingMixIn import datetime class Context(LoggingMixIn, Operations): #код, приведенный выше if __name__ == '__main__': if len(argv) != 2: print('использование: %s <точка монтирования>' % argv[0]) exit(1) fuse = FUSE(Context(), argv[1], foreground=True, ro=True)
В первом блоке осуществляется импорт всех необходимых модулей. Строка if __name == '__main__:'
выглядит немного странно, но на самом деле это полезная синтаксическая конструкция языка Python для выделения кода, который может исполняться как при запуске приложения из командной строки, так и при вызове из других точек кода. Данное выражение возвращает значение True в том случае, если код был исполнен в результате запуска приложения. В данном случае оно позволяет осуществить активацию файловой системы в пространстве пользователя и тогда, когда мы осуществляем запуск программы, и тогда, когда мы подключаем данный файл исходного кода Python в качестве модуля к другому приложению. В последней строке кода для активации файловой системы используется импортированная функция FUSE
. Первые два аргумента этой функции являются нашим новым классом и путем к точке монтирования файловой системы соответственно (этот путь передается с помощью аргумента интерфейса командной строки приложения и используется в момент активации файловой системы). Все остальные аргументы позволяют передать стандартные параметры файловой системы.
При наличии данного кода вы можете активировать созданную файловую систему, просто запустив приложение с интерфейсом командной строки. Привилегии, необходимые для ее активации могут отличаться в зависимости от используемого дистрибутива. Самым надежным способом тестирования приложения является его запуск от лица пользователя root. Вам придется открыть две терминальные сессии. В рамках первой терминальной сессии следует подготовить файловую систему с помощью следующих команд:
mkdir fuse-test sudo python fusedate.py fuse-test
После этого вы сможете перейти в новую файловую систему и прочитать из ее файла информацию о текущей дате в рамках второй терминальной сессии:
sudo bash cd fuse-test cat date
Этой информации вполне достаточно для реализации ваших собственных файловых систем. Очевидно, что наша файловая система является слишком ограниченной в плане возможностей, но базовый подход к реализации файловых систем в пространстве пользователя остается неизменным вне зависимости от количества их файлов и директорий.
Документация модуля fusepy является не совсем полной, поэтому при любой необходимости получения дополнительной информации о той или иной операции следует обращаться к документации основной версии FUSE, расположенной по адресу fuse.sourceforge.net.
Файловые системы в пространстве пользователя
Технология файловых систем в пространстве пользователя не предназначена для создания исключительно "игрушечных" файловых систем. Она на самом деле очень полезна, так как снижает барьер вхождения в процесс разработки и позволяет создавать свои собственные новые файловые системы даже не-разработчикам ядра Linux. Она также значительно упрощает распространение программных компонентов для поддержки новых файловых систем, так как разработчикам больше не приходится компилировать их в виде модулей ядра операционной системы. А это несколько реализаций файловых систем пространства пользователя, которые я предпочитаю использовать:
-
SSHFS
позволяет монтировать удаленные файловые системы с использованием исключительно протокола SSH без необходимости установки какого-либо дополнительного программного обеспечения на стороне сервера. -
EncFS
позволяет создавать зашифрованные файловые системы для безопасного хранения ваших данных. -
Archivemount
позволяет работать со сжатыми архивами таким же образом, как и с обычными директориями без необходимости их полной распаковки.
Вы можете найти на нашем сайте еще 2 статьи того же автора из серии "Code Ninja":