Файловая система NTFS извне и изнутри. Часть 1
В этой статье мы рассмотрим основные структуры данных файловой системы NTFS, определяющие ее суть – главную файловую запись MFT, файловые записи FILE Record и последовательности обновления (update sequence или fix-ups), без знания которых осмысленная работа с редактором диска и ручное/полуавтоматическое восстановление данных просто невозможны!
Файловую систему NTFS принято описывать как сложную реляционную базу данных, обескураживающую грандиозностью своего архитектурного замысла не одно поколение начинающих исследователей. NTFS похожа на огромный, окутанный мраком лабиринт, в котором очень легко заблудиться. Хакеры давно разобрались с основными структурами данных, осветив магистральные коридоры лабиринта светом множества факелов. Боковые ответвления разведаны намного хуже и все еще находятся по власти тьмы, хранящей множество смертоносных ловушек, ждущих своих исследователей. В общем, если NTFS-«читалку» можно запрограммировать буквально за один вечер (с отладкой!), писать на NTFS-тома еще никто не рисковал.
К счастью, никто не требует от нас написания полноценного NTFS-драйвера! Наша задача значительно скромнее – вернуть разрушенный том в состояние, пригодное для восприятия операционной системой (задача-максимум) или извлечь из него все ценные файлы (задача-минимум). Вникать в структуру журналов транзакций, дескрипторов безопасности, двоичных деревьев индексаций для этого совершенно необязательно! Реально нам потребуется разобраться лишь с устройством главной файловой записи – MFT и нескольких дочерних подструктур.
Обзор NTFS с высоты птичьего полета
Основным структурным элементом всякой файловой системы является том (volume), в случае с FAT совпадающий с разделом (partition), о котором мы говорили в прошлой статье [1, 2]. NTFS поддерживает тома, состоящие из нескольких разделов. Подробнее схему отображения томов на разделы мы обсудим в следующей статье этого цикла, а пока же будем для простоты считать, что том представляет собой отформатированный раздел (т.е. раздел, содержащий служебные структуры файловой системы).
Рисунок 1. Обычный (слева) и разряженный (справа) тома
Большинство файловых систем трактуют том как совокупность файлов, свободного дискового пространства и служебных структур файловой системы, но в NTFS все служебные структуры представлены файлами, которые (как это и положено файлу) могут находиться в любом месте тома, при необходимости фрагментируя себя на несколько частей.
Самым главным служебным файлом является $MFT – Master File Table (главная файловая таблица) – своеобразная база данных, хранящая информацию обо всех файлах тома – их именах, атрибутах, способе и порядке размещения на диске (каталог также является файлом особого типа, со списком принадлежащих ему файлов и подкаталогов внутри). Важно подчеркнуть, что в MFT присутствуют все файлы, находящиеся во всех подкаталогах тома, поэтому для восстановления диска наличия $MTF-файла будет вполне достаточно.
Остальные служебные файлы (кстати говоря, называемые метафайлами или метаданными – metafile/metadata соответственно и всегда предваряются знаком доллара «$») носят сугубо вспомогательный характер, интересный только самой файловой системе. К ним в первую очередь относятся: $LogFile – файл транзакций, $Bitmap – карта свободного/занятого пространства, $BadClust – перечень плохих кластеров и т. д. (Подробнее о назначении каждого из файлов будет рассказано в следующей статье). Текущие версии Windows блокируют доступ к служебным файлам с прикладного уровня (даже с правами администратора!), и всякая попытка открытия/создания такого файла в корневом каталоге обречена на неуспех.
Классическое определение, данное в учебниках информатики, отождествляет файл с именованной записью на диске. Большинство файловых систем добавляет к этому понятие атрибута (attribute) – некоторой вспомогательной характеристики, описывающей время создания, права доступа и т. д. В NTFS имя файла, данные файла и его атрибуты полностью уравнены в правах. Можно сказать, что всякий NTFS-файл представляет собой совокупность атрибутов, каждый из которых хранится как отдельный поток (streams) байтов, поэтому во избежание путаницы атрибуты, хранящие данные файла, часто называют потоками.
Каждый атрибут состоит из тела (body) и заголовка (header). Атрибуты делятся на резидентные (resident) и нерезидентные (non-resident). Резидентные атрибуты хранятся непосредственно в $MTF, что существенно уменьшает грануляцию дискового пространства и сокращает время доступа. Нерезидентные – хранят в $MTF лишь свой заголовок, описывающий порядок размещения атрибута на диске.
Назначение атрибута определяется его типом (type) – четырехбайтовым шестнадцатеричным значением. При желании атрибуту можно дать еще и имя (name), состоящее из символов, входящих в соответствующее пространство имен. Подавляющее большинство файлов имеет по меньшей мере три атрибута: стандартная информация о файле (время создания, модификации последнего доступа, права доступа и т. д.) хранится в атрибуте типа 10h, условно обозначаемом $STANDARD_INFORMATION. Ранние версии Windows NT позволяли обращаться к атрибутам по их условным обозначениям, однако Windows 2000 и Windows XP лишены этой возможности. Полное имя файла (не путать с путем!) хранится в атрибуте типа 30h ($FILE_NAME). Если у файла есть одно или более альтернативных имен (например, MS-DOS-имя), таких атрибутов может быть несколько. Здесь же хранится ссылка (file reference) на материнский каталог, позволяющая разобраться, к какому каталогу данный файл/подкаталог принадлежит. Данные файла по умолчанию хранятся в безымянном атрибуте типа 80h ($DATA), однако при желании прикладные приложения могут создавать дополнительные потоки данных, отделяя имя атрибута от имени файла знаком двоеточия, например:
ECHO xxx > file:attr1; ECHO yyy > file:attr2; more < file:attr1; more < file:attr2
Изначально в NTFS была заложена способность индексации любых атрибутов, значительно сокращающая время поиска файла по заданному списку критериев (например, времени последнего доступа). Внутренние индексы хранятся в виде двоичных деревьев, поэтому среднее время выполнения запроса оценивается как O(lg n). Однако в текущих NTFS-драйверах реализована индексация лишь по одному атрибуту – имени файла. Как уже говорилось выше, каталог представляет собой особенный файл – файл индексов (INDEX). В отличие от FAT, где файл каталога представляет единственный источник данных об организации файлов, в NTFS он используется лишь для ускорения доступа к содержимому директории и не является обязательным, поскольку ссылка на материнский каталог всякого файла в обязательном порядке присутствует в атрибуте его имени ($FILE_NAME).
Каждый атрибут может быть зашифрован, разряжен или сжат. Однако техника работы с такими атрибутами выходит далеко за рамки первичного знакомства с организацией файловой системы и будет рассмотрена позднее. А пока же мы углубимся в изучение фундамента файловой системы – структуры $MFT.
Главная файловая запись (master file table)
В процессе форматирования логического раздела, в его начале создается так называемая MTF-зона (MFT-zone), по умолчанию занимающая 12,5% от емкости тома (а вовсе не 12%, как утверждается во многих публикациях), хотя в зависимости от значения параметра NtfsMftZoneReservation она может составлять 25%, 37% или 50%.
В этой области расположен $MFT-файл, изначально занимающий порядка 64 секторов и растущий от начала MFT-зоны к ее концу по мере создания новых пользовательских файлов/подкаталогов. Таким образом, чем больше файлов содержится на дисковом томе, тем больше размер MFT. Приблизительный размер $MFT-файла можно оценить по следующей формуле: sizeof(FILE Record) N Files, где sizeof(FILE Record) обычно равен 1 Кб, а N Files – полное количество файлов/подканалов раздела, включая недавно удаленные.
Для предотвращения фрагментации $MFT-файла MFT-зона удержится зарезервированной вплоть до полного исчерпания свободного пространства тома, затем незадействованный «хвост» MFT-зоны усекается в два раза, освобождая место для пользовательских файлов. Этот процесс может повторяться многократно, вплоть до полной отдачи всего зарезервированного пространства. Решение красивое, хотя и не новое. Многие из файловых систем восьмидесятых позволяли резервировать заданное дисковое пространство в хвосте активных файлов, тем самым сокращая их фрагментацию (причем любых файлов, а не только служебных). В частности, такая способность была у DOS 3.0, разработанной для персональных компьютеров типа Агат. Может, кто помнит такую машину?
Когда $MFT-файл достигает границ MFT-зоны, в ходе своего последующего роста он неизбежно фрагментируется, вызывая обвальное падение производительности файловой системы, причем подавляющее большинство дефрагментаторов $MFT-файл не обрабатывают! А ведь API дефрагментации, встроенное в штатный NTFS-драйвер, это в принципе позволяет! Подробности (вместе с самой утилитой дефрагментации) можно найти на сайте Марка Руссиновича. Но, как бы то ни было, заполнять дисковый том более чем на 88% его емкости категорически не рекомендуется!
При необходимости $MFT-файл может быть перемещен в любую часть диска, и тогда в начале тома его уже не окажется. Стартовый адрес $MFT-файла хранится в Boot-секторе по смещению 30h байт от его начала (см. «boot-сектор», описанный в предыдущей статье данного цикла) и в подавляющем большинстве случаев этот адрес ссылается на 4-й кластер.
Рисунок 2. Структура дискового тома под NTFS
$MFT-файл представляет собой массив записей типа FILE Record (или в терминологии UNIX – inodes), каждая из которых описывает соответствующий ей файл или подкаталог (подробнее см. раздел «Файловые записи»).
В подавляющем большинстве случаев файл/подкаталог полностью описывается одной-единственной записью FILE Record, хотя теоретически этих записей может потребоваться и несколько.
Для ссылки на одну файловую запись из другой используется ее порядковый номер (он же индекс) в $MFT-файле, отсчитываемый от нуля. Файловая ссылка (file reference) состоит из двух частей (см. таблицу 2) – 48-битного индекса и 16-битного номера последовательности (sequence number).
При удалении файла/каталога соответствующая ему файловая последовательность помечается как неиспользуемая. При создании новых файлов записи, помеченные как неиспользуемые, могут задействоваться вновь, при этом счетчик номера последовательности, хранящийся внутри файловой записи, увеличивается на единицу. Этот механизм позволяет отслеживать «мертвые» ссылки на уже удаленные файлы – очевидно, sequence number внутри file reference будет отличаться от номера последовательности соответствующей файловой записи (этой проверкой занимается утилита chkdsk и автоматически, насколько мне известно, она не выполняется).