Наши партнеры

UnixForum





Библиотека сайта 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

Документация модуля fusepy является не совсем полной, поэтому при любой необходимости получения дополнительной информации о той или иной операции следует обращаться к документации основной версии FUSE, расположенной по адресу fuse.sourceforge.net.

Файловые системы в пространстве пользователя

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

  1. SSHFS позволяет монтировать удаленные файловые системы с использованием исключительно протокола SSH без необходимости установки какого-либо дополнительного программного обеспечения на стороне сервера.
  2. EncFS позволяет создавать зашифрованные файловые системы для безопасного хранения ваших данных.
  3. Archivemount позволяет работать со сжатыми архивами таким же образом, как и с обычными директориями без необходимости их полной распаковки.

Вы можете найти на нашем сайте еще 2 статьи того же автора из серии "Code Ninja":