Погружение в Dokan. Как сделать свою файловую систему для Windows без FUSE

В Linux, как известно, многие вещи реализованы как файлы в файловой системе. А если и не реализованы, то их можно реализовать самому при помощи FUSE. В Windows это менее принято, но если все же очень хочется смонтировать что-то как ФС, то это возможно. Я покажу, как этого добиться, используя C# и библиотеку Dokan.

Если знаком с утилитой CyberSafe Top Secret, то ты, наверное, тоже столкнулся с тем, что добавлять файлы в контейнер неудобно. Совсем другое дело — VeraCrypt: монтируешь локальный диск, и файлы шифруются на лету. Именно так будет работать наш проект.

Теория

Каждый раз, когда ты открываешь папку «Компьютер», файловый менеджер отправляет запрос ядру с просьбой сказать, какие есть диски. Как происходит общение с драйвером? Через диспетчер ввода-вывода. Любое приложение может отправить ему пакет с запросом (IRP, I/O Request Packet) и информацией, кому он предназначен. Диспетчер принимает этот запрос и передает его нужному драйверу.


Получив список занятых букв, файловый менеджер последовательно опрашивает систему о том, какие метки тома у этих дисков, сколько на них осталось места, какая файловая система и так далее. Каждый такой запрос передается первому драйверу в цепочке. Если тот не знает, что ответить, он спрашивает следующий, тот в свою очередь — дальше. И так, пока какой-либо из них не ответит, что нужная инфа найдена (или не найдена). В реальности эта схема несколько сложнее, но для общего понимания этого достаточно.

INFO

Передача данных между драйверами по цепочке позволяет существовать руткитам и прочей нечисти.

Любой драйвер средствами все того же диспетчера ввода-вывода может что-нибудь спросить у любого приложения, работающего в user-mode, что и используется в драйвере FUSE.

FUSE

Хоть мы и не будем использовать FUSE, в двух словах расскажу, что это такое. FUSE работает одним из драйверов в цепочке и позволяет быстро создать свою ФС без возни с драйвером, а еще такую ФС могут монтировать пользователи без прав рута.

Результат создания своего драйвера ФС

FUSE не является драйвером ФС и не отвечает на запросы самостоятельно, а передает их пользовательскому приложению, которое и отвечает на запрос. Ответ приложения отправляется обратно в ядро, а оттуда — приложению, которое запросило информацию.

Dokan

В теории существует версия FUSE для Windows, однако заставить ее работать мне не удалось. Возможно, это было бы само по себе интересным опытом, но я избрал другой путь.

Есть такой проект — Dokan. По сути, это тот же FUSE, но с кучей приятных дополнений. Во-первых, он ни разу за время его использования у меня не выдал ни одного синего экрана смерти. Во-вторых, есть библиотеки, которые позволяют работать с ним из самых разных языков, включая Delphi, Ruby, C# и Java (их ты найдешь на GitHub по ссылке выше). И в-третьих, разобраться с ним почти так же просто, как и с FUSE. Так что будем использовать его, библиотеку под C# и немного фантазии.

От изначального проекта Dokan сейчас осталось очень мало. После версии 0.6.0 появился серьезно доработанный форк под названием Dokany. Теперь жив только Dokany, и, соответственно, мы будем использовать его. В дальнейшем, говоря о Dokan, я буду подразумевать именно Dokany.

Подготовка

Чтобы использовать Dokan, нам понадобится драйвер. К нашему счастью, есть уже готовые собранные драйверы, которые нужно только установить. Тут есть три варианта. Первый — воспользоваться автоматическим установщиком. Второй — скачать собранные бинарники (они уже подписаны) и встроить их в свой установщик. Ну и третий — скачать исходный код, благо он открыт (часть проекта распространяется по лицензии LGPLv3, часть — по MIT), и собрать все самостоятельно.

Плюс такого подхода в том, что мы можем подписать готовый драйвер своей подписью, но на этом плюсы заканчиваются. Я выбрал первый вариант. Скачать установщик можно тут. Мастер в конце попросит перезагрузить компьютер, что мы и сделаем. Если после перезагрузки мы видим драйвер dokan1.sys, то все сделано правильно. Если нет — можно попробовать поставить вручную.

Загруженный драйвер dokan1.sys

Чтобы установить вручную, придется скачать более объемный файл. Кроме драйверов, он содержит и нужные тебе библиотеки (если ты знаешь C++), так что не спеши удалять его после установки.

Нас же сейчас интересует папка x64 (у тебя ведь 64 бита?). В ней — набор папок, как на картинке.

Содержимое папки x64

У меня Windows 8.1, так что иду в соответствующую папку (рекомендую Release) и, ткнув по inf-файлу правой кнопкой мышки, выбираю «Установить». Подтверждаю запрос UAC и жду окончания процесса, после чего перезагружаю машину.

Теперь установка должна пройти успешно. Если что-то не получилось — убедись, что ставишь ту версию драйвера.

WWW

В этом может помочь утилита DriverView.

Кроме Dokan, нам еще понадобится Visual Studio. Недавно вышла версия 2019, так что, даже если у тебя уже установлена, советую обновиться. С приготовлениями все, переходим к кодингу.

WARNING

Любые вмешательства в файловую систему, в том числе создание своей ФС, могут повредить или уничтожить твои данные. Все описанное в статье ты повторяешь на свой страх и риск. Ни автор, ни редакция «Хакера» не несут ответственности за твои действия. Все операции рекомендуем предварительно выполнять в виртуальной машине.

Кодинг

Открываем Visual Studio и создаем новый проект типа Console App (.NET Framework). На скриншоте видно, что целевой фреймворк — 4.5.2, но минимально поддерживаемый — 4.0. Так что, если твоя машина не поддерживает 4.5.2, ты знаешь, что делать.


Проект создали, и теперь нашему взору предстала заглушка метода Main. Ты ведь установил NuGet вместе со «Студией»? Если нет, устанавливай. Оттуда мы ставим пакет DokanNet (Tools → NuGet Package Manager → Manage NuGet Packages for Solution). Любители командной строки могут открыть PowerShell-консоль NuGet (Tools → NuGet Package Manager → Package Manager Console) и выполнить Install-Package DokanNet.


Чтобы создать свою ФС, нам нужен класс, реализующий IDokanOperations. Создаем новый класс (Ctrl + Shift + A) и добавляем туда using DokanDet;. Наш класс должен реализовывать интерфейс IDokanOperations, так что исправляем class XakepFSClass на class XakepFSClass : IDokanOperations.


Как ты видишь, в 10-й строке ошибка. Конечно, мы же унаследовали кучу методов от интерфейса, но не реализовали их. Я знаю, ты не хочешь объявлять каждый метод вручную, поэтому поставь курсор на неугодное выражение (IDokanOperations в 10-й строке) и нажми Alt + Enter. В появившемся меню выбери Implement interface.


Теперь порядок! Но все методы выкидывают исключение NotImplementedException, что нам никак не подходит. Давай-ка реализуем Hello World, а затем — ФС, хранящую все данные в JSON.

HelloWorldFS

Поскольку это просто Hello World, я не хочу изменять файл, который мы только что создали. Сделаем его копию, переименуем для лучшего восприятия (для переименования выбери файл в правой панели и нажми F2). Теперь откроем наш новый класс и переименуем и его, а то компилятор не поймет наши фокусы. У тебя должно получиться как на скриншоте.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


"Hackcat : ."

Комментарии (3)

  • Позволю себе пару замечаний.

    > вызываем сборщик с шансом 1/9.
    Опыт всего дотнет-сообщества показывает, что любые попытки "помочь" дотнетовскому сборщику мусора приводят к деградации производительности.

    > new Random().Next(10)
    Это очень неудачная конструкция, результат которой однозначно определяется значением системного таймера, взятым с точностью до миллисекунды. То есть, если вызвать конструкцию несколько раз в течение миллисекунды, получим одинаковые числа.

    • Про первое отчасти согласен, но про неудачность конструкции во втором замечании - покажи мне адекватно работающую сложную функцию, которую можно вызвать несколько раз в течение миллисекунды. Я уже не говорю про то, что сам рандом отрабатывает вовсе не моментально

      • А в данном случае точно нужна сложная? Почему бы не написать просто:

        static int xxx;
        ...
        {
        if (Interlocked.Increment(ref xxx) % 10 == 0) ...
        }