ИМЯ fuse - устройство файловой системы в пользовательском пространстве (FUSE)
ОБЗОР #include <linux/fuse.h>
ОПИСАНИЕ Это устройство является основным интерфейсом между драйвером файловой системы FUSE и процессом в пользовательском пространстве, который будет предоставлять файловую систему (далее здесь называется службой файловой системы (filesystem daemon)). Данная справочная страница предназначена тому, кто заинтересован в понимании интерфейса самого ядра. Тем, кто реализует файловую систему FUSE, важнее описание библиотеки пользовательского пространства такой как libfuse, которая скрывает низкоуровневый интерфейс.
В основе FUSE лежит простой клиент-серверный протокол, в котором ядро Linux является клиентом, а служба — сервером. После получения файлового дескриптора этого устройства, служба может читать запросы с помощью read(2) из этого файлового дескриптора и писать ответы с помощью write(2). Важно отметить, что данный файловый дескриптор, связанный с файловой системой FUSE, является индивидуальным. В частности, открытие второй копии этого устройства не даст доступ к ресурсам, созданным через первый файловый дескриптор (и наоборот).
Основной протокол Каждое сообщение, читаемое службой, начинается с заголовка следующей структуры:
struct fuse_in_header { uint32_t len; /* общая длина данных, включая этот заголовок */ uint32_t opcode; /* тип операции (смотрите ниже) */ uint64_t unique; /* уникальный идентификатор этого запроса */ uint64_t nodeid; /* ID объекта файловой системы, с которым выполняется действие */ uint32_t uid; /* UID запрашивающего процесса */ uint32_t gid; /* GID запрашивающего процесса */ uint32_t pid; /* PID запрашивающего процесса */ uint32_t padding; };
После заголовка располагаются данные произвольной длины (их может и не быть), используемые вместе с запрашиваемой операцией (запрашиваемая операция указывается в opcode).
Служба обрабатывает запрос и, если нужен ответ (почти все операции требуют ответа; если нет, то это описано далее), выполняет запись с помощью write(2) в файловый дескриптор. Все ответы должны начинаться со следующего заголовка:
struct fuse_out_header { uint32_t len; /* общая длина данных, записываемых в файловый дескриптор */ int32_t error; /* возникшая ошибка (0, если нет) */ uint64_t unique; /* значение из соответствующего запроса */ };
После этого заголовка также располагаются данные произвольной длины (возможно их отсутствие), в зависимости от запроса. Однако, если ответ является ответом, FUSE_INIT
struct fuse_init_in { uint32_t major; uint32_t minor; uint32_t max_readahead; /* начиная с протокола v7.6 */ uint32_t flags; /* начиная с протокола v7.6 */ };
Это первый запрос, посылаемый ядром службе. Он используется для согласования версии протокола и других параметров файловой системы. Заметим, что версия протокола может влиять на любую структуру протокола (включая эту). Служба должна запоминать согласованную версию и флаги для каждого сеанса. На момент написания этой справочной страницы наибольшей версией поддерживаемого ядром протокола была 7.26.
Пользователи должны учесть, что описания в этой справочной странице могут быть неполны или некорректны по отношению к старым или более новым версиям протокола.
Ответ на этот запрос имеет следующий формат:
struct fuse_init_out { uint32_t major; uint32_t minor; uint32_t max_readahead; /* Начиная с v7.6 */ uint32_t flags; /* Начиная с v7.6; некоторые биты флагов были введены позднее */ uint16_t max_background; /* Начиная с v7.13 */ uint16_t congestion_threshold; /* Начиная с v7.13 */ uint32_t max_write; /* Начиная с v7.5 */ uint32_t time_gran; /* Начиная с v7.6 */ uint32_t unused[9]; };
Если главная (major) версия, поддерживаемая ядром, больше чем поддерживаемая службой, то ответ должен содержать только uint32_t major (как обычный заголовок), показывающий наибольшую главную версию, поддерживаемую службой. После этого ядро создаст новый запрос FUSE_INIT, соответствующий более старой версии. В противном случае (то есть, если версия службы новее), служба должна переключиться на работу с главной версией ядра.
Согласованной вспомогательной (minor) версией считается минимальная из вспомогательных версий, поддерживаемых службой и ядром, и обе стороны должны использовать протокол, соответствующий указанной вспомогательной версии.
FUSE_GETATTR
struct fuse_getattr_in { uint32_t getattr_flags; uint32_t dummy; uint64_t fh; /* задаётся, только если (getattr_flags & FUSE_GETATTR_FH) };
Операция запроса вычисления атрибутов, возвращаемых stat(2) и подобными
Вычисленные атрибуты и срок хранения в кэше возвращаются в следующей структуре:
struct fuse_attr_out { /* срок хранения атрибута в кэше (секунды + наносекунды) */ uint64_t attr_valid; uint32_t attr_valid_nsec; uint32_t dummy; struct fuse_attr { uint64_t ino; uint64_t size; uint64_t blocks; uint64_t atime; uint64_t mtime; uint64_t ctime; uint32_t atimensec; uint32_t mtimensec; uint32_t ctimensec; uint32_t mode; uint32_t nlink; uint32_t uid; uint32_t gid; uint32_t rdev; uint32_t blksize; uint32_t padding; } attr; };
FUSE_ACCESS
struct fuse_access_in { uint32_t mask; uint32_t padding; };
Если не используется параметр монтирования default_permissions, то этот запрос можно использовать для проверки прав доступа. Данных в ответе не ожидается, но могут возвращаться ошибки, как обычно в поле error заголовка ответа (в частности, при ошибках отказа в доступе может возвращаться -EACCES).
FUSE_OPEN и FUSE_OPENDIR struct fuse_open_in { uint32_t flags; /* флаги, передаваемые в open(2) */ uint32_t unused; };
Этот запрос открывает ноду, указанную в header->nodeid. Точна семантика будет зависеть от реализации в файловой системе. Однако, по крайней мере, файловая система должна проверить что запрашиваемые flags корректны для указанного ресурса и затем послать ответ в следующем формате:
struct fuse_open_out { uint64_t fh; uint32_t open_flags;
FOPEN_KEEP_CACHE Не делать недействительными данные кэша при открытии.
FOPEN_NONSEEKABLE Внутри файла не поддерживается смена положения.
FUSE_READ и FUSE_READDIR
struct fuse_read_in { uint64_t fh; uint64_t offset; uint32_t size; uint32_t read_flags; uint64_t lock_owner; uint32_t flags; uint32_t padding; };
По этому запросу читается до size байт файла или каталога, начиная с offset. Байты должны возвращаться сразу за обычным заголовком ответа.
FUSE_INTERRUPT struct fuse_interrupt_in { uint64_t unique; };
По этому запросу отменяется ожидающая операция, указанная в unique. По запросу не формируется ответ. Однако, само получение сообщения не отменяет указанную операцию. Ядро всё равно будет ждать ответа на указанную операцию (например, ошибку EINTR или короткое чтение). Для указанной операции будет выдано не более одно запроса FUSE_INTERRUPT. После выдачи указанной операции ядро будет непрерывно ожидать завершения указанного запроса.
FUSE_LOOKUP Непосредственно после заголовка указывается имя файла, которое будет искаться в каталоге, указанном в header->nodeid. Ожидается ответ в виде:
struct fuse_entry_out { uint64_t nodeid; /* идентификатор inode */ uint64_t generation; /* поколение inode */ uint64_t entry_valid; uint64_t attr_valid; uint32_t entry_valid_nsec; uint32_t attr_valid_nsec; struct fuse_attr attr; };
Комбинация nodeid и generation должна быть уникальной на время существования файловой системы.
Параметры сроков хранения и attr такие же как в FUSE_GETATTR.
FUSE_FLUSH struct fuse_flush_in { uint64_t fh; uint32_t unused; uint32_t padding; uint64_t lock_owner; uint32_t flags; uint32_t release_flags; uint64_t lock_owner; };
Запросы, обратные FUSE_OPEN и FUSE_OPENDIR, соответственно. Служба теперь может освободить ресурсы, связанные с файловым описателем fh, так как ядру он больше не нужен. Данных в ответе не посылается, но ответ нужно отправить после обработки запроса.
FUSE_STATFS Данная операция реализует statfs(2) для этой файловой системы. В запросе нет данных. В ответе ожидаются данные следующей структуры:
struct fuse_kstatfs { uint64_t blocks; uint64_t bfree; uint64_t bavail; uint64_t files; uint64_t ffree; uint32_t bsize; uint32_t namelen; uint32_t frsize; uint32_t padding; uint32_t spare[6]; };
struct fuse_statfs_out { struct fuse_kstatfs st; };
Информацию об этих полях смотрите в statfs(2).
ОШИБКИ EPERM Возвращается из операций над файловым дескриптором /dev/fuse, если он не был смонтирован.
EIO Возвращается из операции read(2), когда запросы ядра слишком велики для предоставляемого буфера.
Замечание: Есть несколько вариантов, в которых неправильное использование этих интерфейсов с предоставляемыми файловой системой файлами и каталогами может завершиться ошибкой EIO. Например:
* изменение mode & S_IFMT для иноды, о которой ранее было сообщено ядру; или
* выдача более коротких ответов ядру, чем оно ожидает.
EINVAL Возвращается из операции write(2), если ответ не прошёл тест на правильность. При этом не все ошибки в ответах будут пойманы. Однако обнаруживаются простые ошибки, такие как короткие ответы или некорректное значение unique.
E2BIG Возвращается из операций read(2), когда запросы ядра слишком велики для предоставляемого буфера и запросом является FUSE_SETXATTR.
FUSE_BMAP FUSE_CREATE FUSE_DESTROY FUSE_FALLOCATE FUSE_FORGET FUSE_FSYNC FUSE_FSYNCDIR FUSE_GETLK FUSE_GETXATTR FUSE_IOCTL FUSE_LINK FUSE_LISTXATTR FUSE_LSEEK FUSE_MKDIR FUSE_MKNOD FUSE_NOTIFY_REPLY FUSE_POLL FUSE_READDIRPLUS FUSE_READLINK FUSE_REMOVEXATTR FUSE_RENAME FUSE_RENAME2 FUSE_RMDIR FUSE_SETATTR FUSE_SETLK FUSE_SETLKW FUSE_SYMLINK FUSE_UNLINK FUSE_WRITE
СМОТРИТЕ ТАКЖЕ fusermount(1), mount.fuse(8)
|