Filesystem in Userspace (FUSE) («Файловая система в пользовательском пространстве») — это модуль для ядер UNIX-подобных операционных систем, с открытым исходным кодом и относящийся к свободному программному обеспечению.
В операционных системах обычно существует несколько уровней привилегий, в частности, уровень ядра и пользовательский уровень. Код на уровне ядра имеет абсолютную власть. Системное программное обеспечение, такое как файловые системы, обычно является частью ядра, и не может быть изменено обычным пользователем.
FUSE позволяет легко создавать файловые системы:
Виртуальные файловые системы не хранят данные непосредственно. Они действуют как представление, трансляция существующей файловой системы или устройства хранения.
Реализовать с использованием FUSE файловую систему HTTPFS. Ей при монтировании указывается веб-страница, ссылки которой будут представлены в виде каталогов, а файлы будут содержать html-страницы или файлы.
Нам понадобится последняя версия FUSE. Установка очень простая, после распаковки выполним:
Для написания файловой системы будем использовать язык Python 2.7.
Нам понадобится модуль fuse-python.
Для его установки нужен пакет python2.7-dev.
После распаковки fuse-python выполним:
Чтобы проверить, что необходимые компоненты работают правильно, запустим стандартный пример, прилагающийся к fuse-python. Заодно научимся монтировать и отмонтировать файловые системы FUSE. В папке, из которой производилась установка fuse-python, выполним:
Файловая система с единственным файлом "hello" была смонтирована в папку "/tmp/fuse/". Чтобы отмонтировать файловую систему, выполним:
Документацию к модулю fuse-python можно найти в файле fuse.py (docstrings) из папки с fuse-python, и по ссылке.
Импортируем нужные модули.
Модуль pointer, написанный мной, содержит класс PointerHTTPFS, выполняющий операции по загрузке веб-страниц из сети, их анализу и др. Я вынес эти операции в отдельные файлы, чтобы не отвлекать внимание от FUSE в этом отчете. Полный исходный код файловой системы HTTPFS доступен по ссылке.
import errno
import os
import stat
import sys
import fuse
from fuse import Fuse
from pointer import PointerHTTPFS
Сообщим fuse-python какую ее версию мы используем.
fuse.fuse_python_api = (0, 2)
Опишем структуру StatHTTPFS. В конструкторе все ее поля инициализируются нулями. Структура StatHTTPFS хранит все атрибуты файловых объектов. Подробнее о том, для чего используется каждое поле и какие значения они могут принимать можно прочитать в man fstat. Я объясню предназначение некоторых полей, когда потребуется их использовать.
class StatHTTPFS(fuse.Stat):
def __init__(self):
fuse.Stat.__init__(self)
self.st_mode = 0
self.st_ino = 0
self.st_dev = 0
self.st_nlink = 0
self.st_uid = 0
self.st_gid = 0
self.st_size = 0
self.st_atime = 0
self.st_mtime = 0
self.st_ctime = 0
Опишем основной для нашей файловой системы класс HTTPFS. От класса-предка Fuse он получает определение по умолчанию всех файловых операций. Большинство операций по умолчанию возвращают код ошибки -errno.ENOSYS, означающий «операция не определена».
class HTTPFS(Fuse):
В конструкторе класса HTTPFS вызывается конструктор базового класса, которому передаются именованные аргументы в словаре **kw, и инициализируется переменная-член self.root. Ей присваивается переданный адрес корневой веб-страницы файловой системы.
def __init__(self, root, *args, **kw):
Fuse.__init__(self, *args, **kw)
self.root = root
Первой определим функцию getattr (точнее, метод класса HTTPFS). Она вызывается перед каждой операцией в файловой системе. Ей передается путь до файлового объекта (файла, папки, сокета и др.). Если такого объекта не существует, то возвращается код ошибки -errno.ENOENT, означающий «не найден файл или папка». Если же файловый объект существует, она возвращает заполненную структуру StatHTTPFS.
Поле st_mode содержит информацию о типе файлового объекта и правах доступа. Константы S_IFDIR и S_IFDIR указывают, что файловый объект соответственно является папкой или обычным (regular) файлом. Правила числовой записи прав доступа можно посмотреть здесь.
Поле st_nlink задает количество жестких ссылок на файловый объект. Для файла это число должно быть равно 1. Для каталога равно 2 или 3.
Поле st_size для файла содержит его размер в байтах.
В этой функции я первый раз использовал объект моего класса PointerHTTPFS. Он инициализируется адресом корневой веб-страницы и путем до файлового объекта. Предназначение методов isDir, isFile и getSize ясно из названия.
def getattr(self, path):
st = StatHTTPFS()
pt = PointerHTTPFS(self.root, path)
if pt.isDir():
st.st_mode = stat.S_IFDIR | 0444
st.st_nlink = 2
elif pt.isFile():
st.st_mode = stat.S_IFREG | 0444
st.st_nlink = 1
st.st_size = pt.getSize()
else:
return -errno.ENOENT
return st
Функция readdir вызывается при попытке просмотра содержимого каталога. Например, при использовании ls. Ей передается путь до каталога и смещение. Возвращает она список объектов переноса данных каталога (fuse.Direntry), инициализируемых именами объектов, содержащихся в каталоге. В режиме, который я использовал, смещение игнорируется. Про другой режим работы функции readdir можно прочитать в документации (там декларация функции приведена на C).
def readdir(self, path, offset):
pt = PointerHTTPFS(self.root, path)
for entry in pt.getEntries():
yield fuse.Direntry(entry)
Функция open вызывается при попытке открыть файл. Ей передается путь до файла и флаги. Функция проверяет разрешена ли операция для данных флагов доступа. Наша файловая система доступна только для чтения, поэтому при попытке открытия файла с возможностью записи возвращается код ошибки -errno.EACCES, означающий «доступ запрещен». Подробнее про флаги доступа можно прочитать по ссылке.
def open(self, path, flags):
accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
if (flags & accmode) != os.O_RDONLY:
return -errno.EACCES
Функция read вызывается для чтения данных из открытого файла. Ее аргументами является путь до файла, размер запрошенного блока в байтах и смещение этого блока от начала файла. Гарантируется, что перед вызовом read будет обязательно вызван open, поэтому в функции read не нужно проверять права доступа. Функция read должна вернуть ровно столько байт, сколько было запрошено, или ошибку. В противном случае либо лишние байты будут отсечены, либо недостающие заполнены нулями. Ошибка с кодом -errno.EIO означает, что произошла ошибка ввода/вывода.
def read(self, path, size, offset):
pt = PointerHTTPFS(self.root, path)
try:
return pt.read(size, offset)
except RuntimeError, e:
return -errno.EIO
Функция release служит для закрытия открытого файла. Она вызывается, когда нет больше ссылок на открытый файл: все дескрипторы файлов закрыты и вся память, на которую был отображен файл, освобождена. На каждый вызов функции open должен приходиться точно один вызов release и с теми же самыми флагами.
Метод release класса PointerHTTPFS удаляет некоторую связанную с файлом информацию из кэша.
def release(self, path, flags):
pt = PointerHTTPFS(self.root, path)
pt.release()
В классе HTTPFS я переопределил не все файловые операции. Для большинства действует реализация по умолчанию. Названия операций в fuse-python и их аргументы перечислены в FUSE Python Reference. Несколько более подробное описание операций, но для языка C, доступно по ссылке. То, что написано для C, в какой-то мере можно перенести и на python.
Опишем функцию, которая будет запускать нашу файловую систему.
Проверяется, что переданный URL-адрес корневой веб-страницы правильный и страница по нему доступна.
def main():
baseurl = ''
if len(sys.argv) < 3:
sys.argv += ['--help']
else:
baseurl = 'http://' + sys.argv[1]
pt = PointerHTTPFS(baseurl, '/')
if not pt.isDir():
print 'Error: bad BaseURL\n'
sys.argv += ['--help']
Создается переменная основного класса файловой системы.
Именованный аргумент dash_s_do='setsingle' указывает, что наша файловая система будет работать в однопоточном режиме. Подробнее об этом аргументе по ссылке.
Команда server.parse() анализирует остальные параметры командной строки. Параметр errex=1 указывает какой код нужно вернуть, если во время работы парсера произошла ошибка.
Команда server.main() запускает файловую систему.
usage = """%prog [BaseURL] [mountpoint] [options]"""
server = HTTPFS(baseurl, version="%prog " + fuse.__version__,
usage=usage,
dash_s_do='setsingle')
server.parse(errex=1)
server.main()
if __name__ == '__main__':
main()
После вызова server.main() приложение теряет доступ к консоли, из которой оно было запущено. Поэтому для того, чтобы проводить отладку и получать какую-либо информацию о работе запущенного приложения, требуется вести лог-файл.
Полный исходный код файловой системы HTTPFS доступен по ссылке.