Как стать автором
Обновить

Замешиваем файлы в тэги. Часть 1

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров3K

Давным, давно ... искал средство организации видео-файлов.

Чтобы можно было каждой киношке назначить какие-то маркеры, тэги. Выбирать файлы по наличию тэгов. Или наоборот - по отсутствию тэгов. Например: детектив, фантастика, не-ужасы.

Чтобы всё работало в обычном проигрывателе: кликнул "Открыть файл..."; выбрал Детектив, Фантастика, не-Ужасы; получил список фильмов и выбрал нужный. Без специальных файловых менеджеров и браузеров.

И тогда такое средство мне не нашлось. А сейчас ... сейчас решил сделать его сам.

Что в результате получилось можно увидеть по ссылкам: демо-видео, код.

Часть 1: Описание задачи, Модуль ядра

Часть 2: Модуль ядра, Регистрация файловой системы

Часть 3: Inode, Lookup

Часть 4: Inode-операции: symlink, unlink

Далее будет разбор, как это делалось, какие выяснились тонкости и возникли вопросы. Так как это всё таки туториал, то будет и описание базовых вещей и помимо прямых путей будут вспомогательные описания - возможно, они будут полезными для понимания.

Общие вопросы реализации

  • Технология

Если поискать подходящую технологию, позволяющую организовывать файлы «по‑своему», то очень быстро натыкаешься на три буквы vfs: виртуальная файловая система. По описаниям и возможностям эта технология очень хорошо ложится на задачу. Кроме того, она «локальная» — для работы не нужно каких‑то внешних серверов, облаков и др.. И «легковесная» — для реализации требуется сделать модуль ядра, данные можно хранить в файле с простой структурой, никаких СУБД и т. п.. К минусам такого подхода можно причислить то, что это будет именно модуль ядра: потребуется сборка на каждом компьютере своей версии с установкой компилятора, заголовочных файлов и т. п.

Ок, попробуем сделать свою виртуальную файловую систему. И назовём её tagvfs.

  • Представление файлов

Прежде всего необходимо было определиться со способом хранения и представления видео-файлов в tagvfs (это создаваемая виртуальная файловая система). Здесь есть несколько соображений:

Во-первых, категорически не хотелось сохранять копии файлов в самой tagvfs: это занимает много-много места, создание копии потребует существенного времени (видео-файлы обычно тяжёлые). Также такой способ хранения ограничивает работу с файлами в "обычном" режиме: ведь храниться могут не только видео-файлы, но ещё какие-либо документы. Документы можно редактировать; хранить совместно с другими документами (например, исходный документ и его экспорт в pdf) и т.п. Поэтому желательно, чтобы файлы имели своё "постоянное" место хранения в обычной файловой системе, а tagvfs отображала бы эти файлы.

Во-вторых, вполне допустимо в своей виртуальной файловой системе вычитывать внешние файлы и выдавать их как свои. У такого подхода даже можно найти некоторые преимущества. Например, возможность реализовать операции из категории "ненормальное программирование": доступ к файлам в обход прав, модификация данных при чтении/записи, блокирование определённых областей и др.. Минусы у подхода тоже значительные: прежде всего это разборки с правами на файлы (кто что может читать, записывать и др.). И, конечно же, все эти операции на файлах нужно реализовать в коде.

В-третьих, остаётся вариант с символьными ссылками на файлы. Для управления видео-файлами это очень хороший вариант. Из минусов подхода - некоторое неудобство при добавлении файлов в tagvfs: приходится вместо копирования делать символьные ссылки. В остальном - одни плюсы: нет нужды самому опрашивать файлы и хранить информацию о них (как минимум наличие файлов и их размер). И обработкой прав на файлы также занимается линукс.

Ок, реализация tagvfs делается через управление символьными ссылками на файлы.

Модуль ядра (для версии 5.10)

Модули ядра - это специально оформленный код, который ядро операционной системы загружает и запускает.

  • Инициализация

С программной точки зрения модуль ядра должен содержать функцию инициализации и может иметь функцию завершения.

Задавать функции можно двумя способами:

  1. Через макросы:

static int tagvfs_init(void) { ... ; return res; }

static void tagvfs_exit(void) { ... }

module_init(tagvfs_init);
module_exit(tagvfs_exit);
  1. Явное объявление функций:

int init_module(void) { ... ; return res; }
void cleanup_module(void) { ... }

Вообще, первый способ (через макросы) является рекомендуемым, т.к. упрощает встраивание модуля в ядро линукса (если ваш модуль когда-нибудь попадёт в исходники ядра). В нашем случае модуль будет динамически подгружаться и разница между двумя способами несущественна. Но делаем так, как рекомендуют.

Функции инициализации должна быть одна на модуль. Конечно, если её вообще не указать, то модуль скомпилируется, но что с таким модулем делать - непонятно. Функции завершения также должна быть одна на модуль; либо её может не быть, если операции по очистке ресурсов не требуются. Более того, если модуль вкомпилирован в ядро, то функция завершения вообще не вызовется, даже если она есть.

  • Возвращаемые значения

Функция инициализации возвращает результат своей работы через int. В данном случае всё довольно очевидно: значение 0 означает успешную инициализацию, отрицательное значение указывает на ошибку. Так исторически сложилось, что во многих функциях возвращаемое значение кроме результата (который стараются делать неотрицательным) может содержать ещё информацию об ошибке. Всё немного усложняется, если функция возвращает адрес. Адрес тоже можно привести к знаковому числу и проверить на отрицательность, однако даже валидный адрес после преобразования может выдать отрицательное значение. Поэтому за ошибку принимается не любое отрицательное число (и в случает адреса и в случае значения int), а только от -4095 до -1. (ВНИМАНИЕ: Это для ядра 5.10).

Конечно же, для работы с такими значениями есть макросы:

IS_ERR_VALUE, IS_ERR - для проверки целых чисел и адресов;

ERR_PTR, PTR_ERR - для преобразования кода ошибки в адрес и наоборот.

Подробнее про макросы можно посмотреть здесь: https://elixir.bootlin.com/linux/v5.10/source/include/linux/err.h .

Для случая, когда при успехе возвращается 0 (и таких функций много), используется, наверное, самая простая проверка на ошибки:

if (res) { ... обработка ошибок ... }

Что касается самого возвращаемого значения в случае ошибок, то рекомендуется пользоваться штатными константами ядра, соответствующими случившейся ошибке, и не выдумывать своих значений. Связано это с тем, что линукс помимо кода ошибки будет ещё выводить текстовую расшифровку и если, например, программист на любую ошибку будет возвращать -1, то пользователь будет искать более мощную утилиту sudo, т.к. увидит что-то наподобие:

insmod: ERROR: could not insert module tagvfs.ko: Operation not permitted

Часто используемые константы (со знаком минус): -ENOMEM, -EINVAL. И список можно посмотреть здесь: https://elixir.bootlin.com/linux/v5.10/source/include/uapi/asm-generic/errno-base.h .

  • Информация о модуле

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("Description information");

Особенность этих макросов в том, что их можно указывать по несколько для одного модуля: даже лицензий можно указать несколько (и какая будет использоваться в ядре?). Более того, это рекомендуемый способ для указания нескольких авторов: каждый указывается в отдельном макросе.

Указанную информацию можно потом прочитать через утилиту modinfo и получить что-то наподобие:

description:    VFS module to organize files by tags
author:         Evgeny Kislov <dev@evgenykislov.com>
license:        GPL
name:           tagvfs
vermagic:       5.10.0-23-amd64 SMP mod_unload modversions 

Помимо заданных в коде данных есть ещё одна очень важная информационная строка: vermagic. В ней содержится версия ядра, под которую собран модуль.

Продолжаем разбираться ...


Заставка является изображением, сгенерированным нейросетью Kandinsky.

Теги:
Хабы:
Всего голосов 5: ↑4 и ↓1+3
Комментарии19

Публикации

Истории

Работа

Программист С
32 вакансии

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань