Fuse - это проект, предоставляющий программисту функционал для написания драйвера для произвольной файловой системы. Fuse (в Linux) включает в себя модуль ядра, используемый различными программами для связи с драйвером ФС (который является, по существу, исполняемым файлом).
Стратегия разработки драйвера проста - необходимо реализовать некоторый набор функций с определенными прототипами, указатели на которых передать модулю ядра (с помощью вызова некоторой функции - т.н. fuse_main) - после чего при обращении к ФС будут вызываться именно те функции из драйвера, указатели на которые были переданы в fuse_main. Нетрудно догадаться, что диапазон применения fuse очень и очень широк - с ее помощью можно разработать самые "неожиданные" ФС :-)
Плюсы fuse:
- Код драйвера ФС выполняется в user-space - сразу исчезают многие проблемы, возникающие при отладке модуля ядра
- У fuse есть много "привязок" к разным ЯП (кроме C, это еще и Python, например)
- Простые принципы разработки драйверов (простое API)
- Стабильность, надежность и защищенность
Подробности разработки драйверов ФС на C/C++ для fuse можно узнать в статье: "Разработка собственной файловой системы с помощью FUSE" (ibm.com Россия).
Однако в этой статье я расскажу, как написать драйвер, используя python.
2. Установка "привязок" к fuse для python
Все просто. Для начала получим исходный код "привязок":
Проследуйте на страницу FUSEWiki - FusePython и выберите понравившийся вам способ загрузки.
Я использовал Mercurial:
hg clone http://mercurial.creo.hu/repos/fuse-python-hg
После загрузки перейдите в каталог с исходниками fuse-python и сделайте:
python setup.py build
su -c 'python setup.py install'
Вы получите модуль fuse, в котором содержаться вся необходимые классы.
3. Небольшой пример
Давайте напишем драйвер для "игрушечной" виртуальной ФС.
В начале - каркас драйвера:
- Первым делом мы импортируем нужные модули:
import os,stat
import fuse - Теперь необходимо сообщить fuse-python какую ее версию мы используем:
fuse.fuse_python_api = (0, 2)
- Описываем базовый для нашего драйвера класс (он будет наследовать класс fuse.Fuse):
class simpleFS(fuse.Fuse):
#функции
#TODO - Описываем функцию, которая будет "запускать" наш драйвер:
def runSimpleFS():
usage='Simple FS ' + fuse.Fuse.fusage
fs = simpleFS(version="%prog " + fuse.__version__,usage=usage,dash_s_do='setsingle')
fs.parse(errex=1)
fs.main()
Как вы могли заметить, принципиально важными являются вызов конструктора fuse.Fuse (с правильным списком аргументов), вызов fs.parse() и вызов fs.main(). fs.main() это и есть как раз та самая fuse_main. Указатели на функцию в fuse-python подменяются функциями-членами_класса fuse.Fuse.
Важное замечание: после вызова fs.main() драйверу перестает быть доступной консоль, из которой, собственно, драйвер и был запущен. В частности, теряется связь с STDIN, STDOUT, STDERR - т.е. мы сможем догадаться об исключенях только по результатам работы драйвера. - Вызываем runSimpleFS():
runSimpleFS()
Каркас готов.
Теперь давайте определимся с тем, что будет представлять из себя наша ФС:
- 2 каталога - '/' и '/simple' - оба с правами 0555 и root.root в качестве пользователя.группы_пользователя
- 1 файл - '/README' (0444 root.root)
Реализация нашего класса:
- Конструктор. В нем принципиально важно вызвать конструктор базового класса со всеми переданными в конструктор simpleFS аргументами (а self.README - содержимое файла '/README'):
def __init__(self, *args, **kw):
fuse.Fuse.__init__(self, *args, **kw)
self.README = 'This is simple FS\n' - Пользовательские действия с ФС (системные вызовы и прочее). На каждой такой функции в fuse.Fuse стоит "заглушка". Нам остается только переопределить нужные нам функции:
# getattr вызывается при получении информации об объекте ФС. Например, при использовании команды ls
def getattr(self, path):
# В объекте fuse.Stat() вернем интересующую информацию
st = fuse.Stat()
# "Режим" - права доступа, тип объекта
st.st_mode = 0
# Номер inode
st.st_ino = 0
st.st_dev = 0
# Количество ссылок на объект
st.st_nlink = 0
# ID владельца объекта
st.st_uid = 0
# ID группы владельца объекта
st.st_gid = 0
# Размер объекта
st.st_size = 0
# Временные штампы
st.st_atime = 0
st.st_mtime = 0
st.st_ctime = 0
if path == '/' or path == '/simple':
# Каталоги
st.st_mode = stat.S_IFDIR | 0755
st.st_nlink = 3
elif path == '/README':
# Файлы
st.st_mode = stat.S_IFREG | 0444
st.st_nlink = 1
st.st_size = len(self.README)
else:
# path не существует
return -errno.ENOENT
return st
# readdir вызывается при попытке просмотра содержимого каталога. Например, при использовании ls
def readdir(self, path, offset):
# В каждом каталоге есть '.' и '..'
yield fuse.Direntry('.')
yield fuse.Direntry('..')
if path == '/':
# Кроме того, в '/' есть еще и 'README' и 'simple'
yield fuse.Direntry('README')
yield fuse.Direntry('simple')
# open вызывается при попытки открыть файл. Мы должны проверить флаги доступа - наш единственный файл '/README' доступен только на чтение
def open(self, path, flags):
if path != '/README':
return -errno.ENOENT
accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
if (flags & accmode) != os.O_RDONLY:
# Ошибка доступа
return -errno.EACCES
# read вызывается при попытки прочитать данные из файла
# offset - смещение в читаемом файле
# size - размер считываемого ("запрощенного") блока
# read возвращает считанные символы
def read(self, path, size, offset):
if path != '/README':
return -errno.ENOENT
slen = len(self.README)
if offset < slen:
if offset + size > slen:
size = slen - offset
buf = self.README[offset:offset+size]
else:
buf = ''
return buf
# statfs вызывается в ответ на запрос информации о ФС
def statfs(self):
# Вернем информацию в объекте класса fuse.StatVfs
st = fuse.StatVfs()
# Размер блока
st.f_bsize = 1024
st.f_frsize = 1024
st.f_bfree = 0
st.f_bavail = 0
# Количество файлов
st.f_files = 2
# Количество блоков
# Если f_blocks == 0, то 'df' не включит ФС в свой список - понадобится сделать 'df -a'
st.f_blocks = 4
st.f_ffree = 0
st.f_favail = 0
st.f_namelen = 255
return st
Вот и все. Драйвер готов (исходный код драйвера лежит здесь).
Проверка:
mkdir smpfs
python simplefs.py smpfs
ls -la smpfs/
ls -la smpfs/simple
cat smpfs/README
df
su -c 'umount smpfs'
Заметьте, что первым параметром в скрипт передается точка монтирования (каталог 'smpfs' в нашем случае).
В случае, если этого примера вам показалось мало - посмотрите в каталог 'example' дистрибутива fuse-python. Там есть кое-что интересное.
4. Почитать
- В первую очередь: Статья о fuse в Википедии
- Обязательно:
pydoc fuse
- Статья о fuse на ibm.com Россия
- Сайт fuse (там встречаются нерабочие примеры)
5. BashOrgFS
Здесь лежит моя поделка на заданную тему. bash.org.ru представлен в виде файловой системы.
Монтировать так:
./bashorg ТОЧКА_МОНТИРОВАНИЯ
.Заранее - это просто пример. Используйте на свой страх и риск. Предварительно читайте README ;-)
6. За сим все. Успехов!
4 комментария:
Полезная информация. Спасибо. Статья стала стимулом написать для себя ФС на базе FUSE. :)
хотелось бы найти что-нибудь про настройки и т.д. находящиеся в монтируемой папке типа autorun.inf, Trash, Trash-1000 и т.д. У смих авторов Fuse не найти толком доков, приходится самому ручками изучать чего хочет система по ошибкам в логе
Привет.
Можете еще раз выложить исходный код?
Я пютаюсь скачать тот, что Вы выложили:
Несуществующая страница
Страница, которую вы читаете, не существует.
5. BashOrgFS
Здесь лежит моя поделка на заданную тему. bash.org.ru представлен в виде файловой системы.
Тоже не могу скачать.
Отправить комментарий