Как восстановить удаленные файлы в файловой системе ext3

Рано или поздно это происходит с каждым: через мгновение после нажатия Enter вы осознате свою ошибку, но уже слишком поздно; вы удалили очень важный файл или каталог, для которого не существует резервной копии. Или, возможно, резервная копия есть, но она месячной давности....и, пребывая в шоке, вы видите последний месяц, мгновенно пролетающий перед вашими глазами, и с болью понимаете, что вам придется делать все заново....


Оригинал: HOWTO recover deleted files on an ext3 file system
Автор: Карло Вуд (Carlo Wood)
Дата публикации: Март 2008
Перевод: Коваленко Алексей
Дата перевода: 03.09.2009 г.


Введение

К счастью, вы помните, что файлы никогда реально не удаляются, максимум - перезаписываются новым содержанием. Итак, вы как можно скорее перемонтируете диск только для чтения. Но что теперь?

Если вы попробуете найти в Google ответ на запрос "восстановление удаленных файлов в ext3", то практически любая найденная статья будет содержать вопрос пользователя о том, возможно ли это, и ответ - "нет".

Наиболее часто цитируемый абзац взят из самого ext3 FAQ:

Q: Как можно восстановить удаленные файлы или отменить их удаление в разделе диска с файловой системой ext3?

A: На самом деле, это невозможно. Вот что сказал об этом один из разработчиков, Андреас Дилгер:

Чтобы обеспечить корректное восстановление ссылок в случае неожиданного отказа системы, ext3 при удалении файла обнуляет указатели на блоки в индексном дескрипторе файла (inode), в то время как ext2 просто помечает эти блоки как неиспользуемые в битовой карте блоков, а индексный дескриптор как "удаленный", и не трогает указатели на блоки.

Ваша единственная надежда на то, чтобы найти при помощи grep все части удаленных вами файлов, и надеяться на лучшее.

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

7-го февраля 2008 года я случайно полностью удалил мой домашний каталог: более 3Гб данных были удалены командой rm -rf. На тот момент у меня была только резервная копия от июня 2007 года. Невозможность восстановления данных была неприемлема. Поэтому я проигнорировал всех, пытающихся меня в этом убедить и начал изучать, как в действительности работает файловая система ext3, и что в действительности происходит, когда файлы удаляются...

Через три недели, написав около 5000 строк кода, я восстановил на своем диске все файлы до единого.

Что вы должны знать перед тем, как начнете

Программа, которую я написал, служит для восстановления недавно удаленных файлов (незадолго до последнего размонтирования). Она НЕ РАБОТАЕТ с поврежденной файловой системой, только со случайно удаленными файлами.

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

Данная программа требует доступа к файловой системе с удаленными файлами только для чтения: она не пытается восстановить файлы. Вместо этого она позволяет вам сделать копию удаленных файлов и записать их во вновь созданное дерево каталогов в текущем каталоге (который, очевидно, должен быть на другой файловой системе). Все пути относятся к корневому разделу, то есть если вы анализируете раздел /dev/md5, который был смонтирован в каталог /home, тогда /home неизвестен разделу и программе и поэтому не является частью пути. Взамен этого, путь будет выглядеть примерно как "carlo/c++/foo.c", без слэша впереди. Корневой раздел (в примере /home) - это пустая строка, не '/'.

Имя программы ext3grep было выбрано потому, что я планировал написать высоко интеллектуальную программу, которая должна была иметь возможность реконструировать файлы путем поиска блоков, которые выглядят подобно ожидаемым блокам (базируясь на старых резервных копиях или на других правилах). grep в имени программы был добавлен в ожидании того, что цитата разработчика ext3, приведенная выше, окажется верной. Я подготовился к необходимости работы с наборами блоков, где каждый набор соответствует шаблону поиска и ранжирован по критерию похожести. Предполагалось, что затем кто-то поработает над набором блоков, используя набор операторов, предоставляемых программой, с целью снижения количества блоков, назначения их файлам, и расположения их в соответствующем порядке. Однако ничего подобного не понадобилось. Тем не менее я сохранил имя ext3grep поскольку, возможно, кто-то действительно захочет добавить программе подобную grep-функциональность (в данный момент ее grep-функциональность ограничена фиксированными строками и печатью соответствующих номеров блоков в стандартный вывод).

Как EXT3 хранит файлы?

Размеры блоков

Содержимое файлов хранится в смежных блоках по 4096 байтов каждый (действительный размер блока зависит от параметров, переданных команде mke2fs в командной строке при первом создании файловой системы и может быть 1024, 2048 или 4096 байтов). Жесткий диск это "устройство блочного ввода-вывода", что означает, что любой ввод-вывод выполняется в терминах этих блоков; за одно обращение может быть считано/записано только целое число блоков. Это не обязательно означает, что минимальный размер смежных фрагментов файла имеет такое же значение (хотя, он может быть только меньше размера блока), но на практике это именно так. Фактически, программа не будет работать, если размер фрагмента не равен размеру блока.

Действительный размер блока, так же как действительный размер фрагмента, хранится в суперблоке и может быть запрошен опцией --superblock. Например,

$ ext3grep $IMAGE --superblock | grep 'size:'
Block size: 4096 (Размер блока)
Fragment size: 4096 (Размер фрагмента)

Здесь IMAGE это переменная окружения, которая была установлена в имя устройства (раздела), содержащего файловую систему (или его копию, сделанную с помощью команды dd). Например, dev/sdd2 (в общем, любое из имен устройств, возвращаемых командой df под заголовком "Файловая система"). Обычно только root может читать устройства напрямую, но вы можете (временно) сделать их читабельными для себя, либо создать резервный образ командой dd. Заметьте, что, например, /dev/sdd это НЕ раздел (обратите внимание на отсутствующую цифру) и не содержит полезных данных для наших целей.

Весь раздел разбит на целое количество блоков, счет которых начинается с 0. Таким образом, если вы хотите сделать копию блока номер N, введите команду:

$ dd if=$IMAGE bs=4096 count=1 skip=$N of=block.$N

Где N может принимать значения от 0 до (но не включая) общего числа блоков, которое хранится в суперблоке. Например,

$ ext3grep $IMAGE --superblock | grep 'Blocks count:'
Blocks count: 2441824

Имея любой номер блока, можно вывести информацию о нем, используя опцию командной строки --block. Например:

Суперблок

Суперблок - это не настоящий блок. Его размер всегда составляет 1024 байта и первый суперблок начинается со смещения 1024. Таким образом, если размер блока 1024 байта, тогда суперблок - это блок 1, но если размер блока 2048 или 4096 байтов, тогда суперблок - это часть блока 0. На диске есть множество резервных копий суперблока, ext3grep предполагает что первый суперблок не поврежден и не пытается искать или читать резервные копии.

Можно прочитать содержимое первого суперблока командой dd, как указано ниже:

$ dd if=$IMAGE bs=1024 skip=1 count=1 of=superblock

Значения каждого байта суперблока даны в таблице 1.

Таблица 1. Суперблок.

байты тип описание
0 .. 3 __le32 Счетчик инодов
4 .. 7 __le32 Счетчик блоков
8 .. 11 __le32 Счетчик зарезервированных блоков
12 .. 15 __le32 Счетчик свободных блоков
16 .. 19 __le32 Счетчик свободных инодов
20 .. 23 __le32 Первый блок данных
24 .. 27 __le32 Размер блока
28 .. 31 __le32 Размер фрагмента
32 .. 35 __le32 Количество блоков в группе
36 .. 39 __le32 Количество фрагментов в группе
40 .. 43 __le32 Количество инодов в группе
44 .. 47 __le32 Время монтирования
48 .. 51 __le32 Время записи
52 .. 53 __le16 Счетчик количества монтирований
54 .. 55 __le16 Максимальное число монтирований
56 .. 57 __le16 "Магическое число"
58 .. 59 __le16 Состояние файловой системы
60 .. 61 __le16 Поведение при обнаружении ошибок
62 .. 63 __le16 Младшая цифра номера версии (minor revision level)
64 .. 67 __le32 Время последней проверки
68 .. 71 __le32 Максимальное время между проверками
72 .. 75 __le32 Операционная система
76 .. 79 __le32 Номер версии (Revision level)
80 .. 81 __le16 UID по умолчанию для резервных блоков
82 .. 83 __le16 GID по умолчанию для резервных блоков
84 .. 87 __le32 Первый незарезервированный инод
88 .. 89 __le16 Размер структуры инода
90 .. 91 __le16 Число групп блоков в этом суперблоке
92 .. 95 __le32 Совместимый набор функций
96 .. 99 __le32 Несовместимый набор функций
100 .. 103 __le32 Набор функций, совместимых с режимом только для чтения
104 .. 119 __u8[16] 128 битный UUID для тома
120 .. 135 char[16] Имя тома
136 .. 199 char[64] Каталог последнего монтирования
200 .. 203 __le32 Для сжатия
204 __u8 Количество блоков для попытки переназначения
205 __u8 Количество блоков для предварительного выделения для директорий
206 .. 207 __le16 Групповые описатели для увеличения в режиме реального времени
208 .. 223 __u8[16] UUID суперблока журнала
224 .. 227 __le32 Номер инода файла журнала
228 .. 231 __le32 Номер устройства файла журнала
232 .. 235 __le32 Начало списка инодов для удаления
236 .. 251 __le32[4] Случайное число для HTREE-хэша
252 __u8 Версия хэша для использования по умолчанию
253 .. 255   Зарезервировано
256 .. 259 __le32 Опции монтирования по умолчанию
260 .. 263 __le32 Первый метаблок в группе блоков
264 .. 1023   Зарезервировано

Для создания таблицы 1 была использована C-структура для суперблока, заданная в файле заголовка /usr/include/linux/ext3_fs.h. Данные (положительные целые числа) хранятся на диске в формате Little Endian (прим. переводчика: формат Little Endian, известен как "остроконечный" порядок записи байтов (порядок записи байтов от младшего к старшему), принят для записи информации в памяти персональных компьютеров архитектуры х86, иногда называется интеловский порядок байтов по названию фирмы Intel, создателя архитектуры х86). На процессорах, поддерживающих этот формат, например, Intel х86, это означает, что __le32 будет фактически uint32_t и __le16 эквивалентно uint16_t.

Группы

Любая файловая система ext3 делится на группы, число блоков в которых фиксировано, за исключением последней группы, содержащей оставшиеся блоки. Количество блоков в группе дано в суперблоке, то есть:

grep$ ext3grep $IMAGE --superblock | grep 'Blocks per group'
# Blocks per group: 32768

Каждая группа использует один блок как битовую карту, которая отслеживает какой из блоков внутри группы назначен (используется), таким образом, может быть максимум 4096*8 = 32768 нормальных блоков на группу.

Еще один блок используется в качестве битовой карты для записи занятых инодов. Инод - это структура данных размером 128 байт (теоретически размер может быть увеличен, действительный размер дан, опять же, в суперблоке), иноды хранятся в каждой группе в специальной таблице (4096 / 128 = по 32 инода в одном блоке). Исходя из того, что в битовой карте блоков 32768 битов, мы можем заключить, что в группе может быть максимально 32768 инодов, то есть таблица инодов в каждой группе занимает 32768 / 32 = 1024 блока. Настоящий размер таблицы инодов зависит от действительного количества инодов в группе, и это количество также хранится в суперблоке.

$ ext3grep $IMAGE --superblock | egrep 'Size of inode|inodes per group'
Number of inodes per group: 16288
Size of inode structure: 128

Число блоков для обеих битовых карт и начало таблицы инодов заданы в таблице описания группы ("group descriptor table"), которая размещается в блоке, следующем за суперблоком; то есть в блоке с номером 1 или 2 в зависимости от размера блока. Эта таблица описания группы представляет собой серию структур ext3_group_desc, также определенную в файле /usr/include/linux/ext3_fs.h, и представленную в таблице 2.

Таблица 2. Дескриптор группы

байт тип описание
0 .. 3 __le32 Блок битовой карты блоков
4 .. 7 __le32 Блок битовой карты инодов
8 .. 11 __le32 Блок таблицы инодов
12 .. 13 __le16 Счетчик свободных блоков
14 .. 15 __le16 Счетчик свободных инодов
16 .. 17 __le16 Счетчик каталогов
18 .. 31   Зарезервировано

Поскольку размер этой структуры является степенью двойки (32 байта), то в блоке может без остатка разместиться целое число дескрипторов. Поэтому таблица непрерывна, даже если занимает несколько блоков. Обратите внимание, что один блок размером 4096 байтов уже имеет возможность содержать 128 дескрипторов групп, каждая из которых может содержать 32768 блоков. Таким образом, только раздел объемом больше 16 ГБ будет использовать больше одного блока для таблицы дескрипторов групп.

Содержание таблицы выводится командой ext3grep, если какие-либо действия или группа не определены в параметрах команды явно. Например,

Иноды

Индексные дескрипторы (иноды) в таблице инодов каждой группы содержат мета-данные для каждого типа данных, которые может хранить файловая система. Этот тип может быть символической ссылкой и тогда достаточно только инода, он может быть каталогом, файлом, FIFO, сокетом UNIX и т. д. В случае файлов и каталогов реальные данные хранятся в блоках файловой системы за пределами инода. Первые 12 номеров блоков хранятся в иноде; если требуется больше блоков, тогда инод содержится ссылка на блок косвенной адресации: блок с номерами других блоков, содержащих данные. При необходимости инод может хранить указатели на блоки двойной и тройной косвенной адресации. Структура инода представлена в таблице 3.

Таблица 3. Инод

байты тип описание
0 .. 1 __le16 Режим файла
2 .. 3 __le16 Младшие 16 бит uid Владельца
4 .. 7 __le32 Размер в байтах
8 .. 11 __le32 Время доступа
12 .. 15 __le32 Время создания
16 .. 19 __le32 Время изменения
20 .. 23 __le32 Время удаления
24 .. 25 __le16 Младшие 16 бит идентификатора группы
26 .. 27 __le16 Счетчик ссылок
28 .. 31 __le32 Счетчик блоков
32 .. 35 __le32 Флаги файла
36 .. 39 linux1 Данные, зависящие от ОС 1
40 .. 99 __le32[15] Указатели на блоки
100 .. 103 __le32 Версия файла (для NFS)
104 .. 107 __le32 Список контроля доступа (ACL) к файлу
108 .. 111 __le32 Список контроля доступа к каталогу
112 .. 115 __le32 Адрес фрагмента
116 .. 127 linux2 Данные, зависящие от ОС 2

С-структура для инода, struct ext3_inode, задана в файле заголовка /usr/include/linux/ext3_fs.h и была использована при создании таблицы 3. Тот же самый файл заголовка также определяет некоторое количество констант в форме макросов, которые должны использоваться для доступа к данным. Например, член структуры, который хранится в байтах с 40 по 99 - это i_block, его размер EXT3_N_BLOCKS 32-битных номеров блоков. i_block[EXT3_IND_BLOCK] указывает на блок косвенной адресации, если таковой существует. i_block[EXT3_DIND_BLOCK] указывает на блок двойной, а i_block[EXT3_TIND_BLOCK] - на блок тройной косвенной адресации. Обычно любая константа имеет свой макрос, более детально они представлены в файле заголовка. ext3grep использует i_reserved2 для хранения номера инода, таким образом, вывод структуры ext3_inode в gdb показывает реальный инод.

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

group = (inode_number - 1) / inodes_per_group

Это дает правильную таблицу инодов. Для поиска индекса инода в этой таблице мы вычитаем номер первого инода в таблице из нашего номера инода:

index = inode_number - (group * inodes_per_group + 1)

Обратите внимание, что этот индекс также определяет соответствующий бит в битовой карте инодов. Группы, как таковые, были сделаны прозрачными: любой инод можно адресовать номером из непрерывного диапазона [1, number_of_inodes], где число инодов определяется так:

$ ext3grep $IMAGE --superblock | grep 'Inodes count'
Inodes count: 1221600

В некоторых случаях, вы можете захотеть узнать, какой блок в файловой системе относится к таблице инодов, которая хранит определенный инод. Данную информацию можно узнать, использовав опцию командной строки --inode-to-block, например:

$ ext3grep $IMAGE --inode-to-block 2
[...]
Inode 2 resides in block 600 at offset 0x80

Инод номер 2 (макрос EXT3_ROOT_INO в файле ext3_fs.h) всегда используется для корня раздела, его тип - каталог. Из всех других специальных инодов мы используем только EXT3_JOURNAL_INO (номер 8).

Имея номер инода мы можем вывести его содержание используя ext3grep, например:

Как вы можете видеть, ext3grep сначала выводит шестнадцатеричное содержание таблицы инодов, затем интерпретирует его и печатает членов структуры, заканчивая вывод строкой Direct Block: 1109. Затем она определяет, что этот блок является каталогом (это можно увидеть также в поле "режим" инода) и поэтому в последующем данный блок в листинге показан как каталог.

Обычные файлы

Если иноды представляют обычные файлы, тогда блоки, на которые они ссылаются, просто содержат данные файла. Если размер файла не совпадает с целым числом размеров блоков, тогда оставшиеся байты в последнем блоке будут обнулены (по крайней мере, в Linux).

Символические ссылки

Значение символической ссылки - это строка: путь к файлу или каталогу, на который она ссылается. Длина строки задана в i_size. Если поле i_blocks равно нулю, то i_block не содержит номеров блоков, а используется непосредственно для хранения строки (ссылки). Если же имя цели, указанное в ссылке, длиннее и не укладывается в i_block, тогда i_blocks будет ненулевым и i_block [0] будет указывать на блок, содержащий имя цели.

Каталоги

Если инод представляет каталог, тогда его блоки - это (отдельные) связанные списки структур данных ext3_dir_entry_2. Каждый блок является замкнутым: ни одна запись каталога не указывает на объект за пределами блока. Первый блок будет всегда начинаться с записи для каталогов "." и "..".

Таблица 4. Запись каталога

байты тип описание
0 .. 3 __le32 Номер инода
4 .. 5 __le16 Длина записи каталога
6 __u8 Длина имени
7 __u8 Тип файла
8 char[] Имя файла, символьной ссылки или каталога

Используя опции --ls --inode $N, ext3grep выводит список содержимого каждого блока каталога для инода N. Например, вывод списка корневого каталога раздела:

Впоследствии можно ипользовать ext3grep --ls --inode 195457 для вывода списка содержимого каталога carlo, и так далее.

Обратите внимание, что ext3grep выводит все записи каталогов, как удаленных, так и нет. Есть два пути, чтобы определить, что каталог удален: во-первых, инод каталога будет иметь ненулевое время удаления; во-вторых, запись каталога может быть изъята из связанного списка через ее пропуск; "Длина записи каталога" (байты 4 и 5 каждой записи каталога) обычно указывает на следующую запись, или непосредственно на байт в следующем блоке, если других записей каталога в текущем блоке нет. В листинге, выдаваемом программой ext3grep, адрес записей каталога был замещен искусственным индексом (в первой колонке) и "Длина записи каталога" замещена колонкой называемой Next (Следующий), которая указывает на следующую запись каталога или содержит end, когда других записей каталога нет. В примере выше, 0 - это первая запись, 1 - следующая и последняя запись. Записи с индексом 2 и 3 пропущены. Тем не менее, видно, что запись 2 использовала указатель на запись 3. Как факт, записи 2 и 3 были удалены в одно и то же время, изменив при этом "Длину записи каталога" для записи 1 таким образом, что она больше не указывает на запись 2, а указывает на конец блока.

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

Следующие примечания помогут нам это понять.

Во-первых, эти дублирующиеся записи появляются, по большей части, из дублированных блоков каталогов, что видно из индексов записей; если бы все записи были из одного блока, то и все индексы были бы различными.. Конечно, и без направления вывода в grep видно, что каждая запись принадлежит различным блокам каталогов, но полный вывод ext3grep слишком велик, чтобы показывать его здесь.

Во-вторых, вы должны понимать, что только номер инода, тип файла в третьей колонке и имя файла - это данные из записи каталога самой по себе. Колонки "Время удаления" и "Режим" извлекаются из текущих данных в соответствующем иноде. Однако этот инод мог быть использован много раньше другим файлом, и данные которые он будет содержать, могут быть никак не связаны с записью данного каталога. Это ярко иллюстрируется приведнным выше примером, поскольку все эти файлы .viminfo определенно не были удалены в один и тот же день! В некоторых случаях можно обнаружить, что инод был переназначен (использован заново), если он все еще используется (этого не может быть, если запись каталога удалена), или когда тип файла в иноде отличается от типа файла в записи каталога. В этих случаях в пятой колонке указывается значение 'R' вместо 'D', и содержание инода не отображается. Поскольку такие записи показывают мало полезной информации, обычно они опускаются (подавляются). Если вы желаете отобразить записи с известными переназначенными инодами, вы можете добавить в командной строке опцию --reallocated. Более того, иногда сам по себе номер инода в записи каталога нулевой. Такие записи, очевидно, также мало полезны и поэтому так же подавляются. Для того, чтобы их показать, используйте опцию командной строки --zeroed-inodes.

Есть возможность применить фильтры к выводу --ls. Описание доступных фильтров дано в выводе опции --help:

Для облегчения определения целесообразных значений опций --after (после) и --before (до) было добавлено действие --histohram=dtime (время удаления). Эта опция командной строки заставляет ext3grep выводить гистограмму времени вместо количества удаленных инодов. Если вы удалили одновременно большое количество файлов, например, командой rm -rf, то легче будет определить окно времени, в пределах которого удаление имело место. Например, ниже я раскрываю мою собственную проблему, когда я удалил более пятидесяти тысяч файлов из моего домашнего каталога:

Очень важно установить правильное значение для опции --after перед восстановлением всех файлов, иначе слишком много файлов будет "восстановлено".

Журнал

Журнал - это файл, существующий в фиксированном количестве блоков. Его инод - это EXT3_JOURNAL_INO, номер которого обычно 8. Действительный номер инода также может быть найден в суперблоке:

$ ext3grep $IMAGE --superblock | grep 'Inode number of journal file'
Inode number of journal file: 8

Затем может быть найден его размер с помощью вывода инода 8:

Здесь вы можете видеть, что размер моего журнала 134217728 байтов или 32768 блоков. Первые 12 блоков перечислены прямо в иноде: блоки 1115 - 1126. Затем размещается косвенный блок 1127. Этот косвенный блок может содержать до 1024 номеров блоков, каждый из которых непосредственно указывает на свой косвенный блок, номера блоков 1128 - 2151. Затем инод указывает на двойной косвенный блок, содержащий 31 номер косвенных блоков. Общее количество двойных/тройных косвенных блоков рассчитывается и равно 34 (используя тот факт, что размер сектора равен 512 байт). Поэтому, если все данные будут храниться последовательно, то номер последнего блока журнала будет равен 1115 + 32768 + 34 - 1 = 33916. Тем не менее, журнал не помещается полностью в группу 0, таким образом, последние блоки находятся в группе 1, а заголовок группы 1 (более известный как таблица инодов) вставляется где-то между блоками журнала, в результате чего номер последнего блока будет равен 35025. Кроме этого, где-нибудь в середине могут находиться поврежденные блоки. Поэтому лучшим методом обращения к журналу является способ на основе "номеров блоков журнала".

Первый блок файла журнала (блок номер 1115 в примере выше) содержит 'суперблок журнала'. Его структура определена в /usr/include/linux/jbd.h как journal_superblock_t. Она может быть выведена на экран следующей командой:

Здесь вы можете видеть, что журнал действительно начинается с Блока Журнала Номер 1 и последний Номер Блока Журнала 32768. Это не то же самое, что и номера блоков файловой системы. Вместе с тем, в данном выводе можно найти номер реального блока файловой системы, например:

$ ext3grep $IMAGE --journal --journal-block 1
[...]
Group: 0
Block 1116 belongs to the journal.
[...]

Здесь показано, что Блок Журнала Номер 1, это блок файловой системы 1116.

Журнал заполняется "Транзакциями", которые имеют всегда увеличивающийся номер последовательности. Если достигнут конец журнала, запись начинается сначала, выполняясь по кругу. Однако, если файловая система размонтирована правильно, тогда при следующем монтировании запись всегда начинается сначала (я так думаю).

Для одной транзакции существует один или более "Дескрипторов". Последний дескриптор транзакции это "Фиксирующий блок", сигнализирующий о том, что транзакция была успешно закрыта и данные в предыдущих дескрипторах транзакции записаны на диск. Существует также два других типа дескрипторов: блоки отмены и блоки, содержащие "тэги" (ярлыки). Блоки отмены - это блоки, которые должны быть (или уже) освобождены транзакцией. Тэг - это структура, которая назначает последующие блоки журнала (не блоки файловой системы!) блокам файловой системы: следующие блоки журнала содержат данные, которые должны быть (были быть) записаны в заданный блок файловой системы.

Это делает тэги особенно интересными для нас: они содержат копии данных, которые были записаны на диск ранее, включая старые иноды.

Пример восстановления вручную

В следующем примере мы будем вручную восстанавливать маленький файл. В листинге приведена только часть вывода команды, для экономии места и для того, чтобы сделать пример более читаемым.

Используя ext3grep $IMAGE --ls --inode мы находим имя файла, который хотим восстановить:

Очевидно, что инод 309631 стерт и мы не имеем номеров блоков для этого файла:

$ ext3grep $IMAGE --print --inode 309631
[...]
Inode is Unallocated
Group: 19
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 0
num of links: 0
sectors: 0 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1202351093 = Thu Feb  7 03:24:53 2008
Inode Modified: 1202351093 = Thu Feb  7 03:24:53 2008
Deletion time:  1202351093 = Thu Feb  7 03:24:53 2008

Direct Blocks:

Поэтому мы будем искать его старую копию в журнале. Сначала мы найдем блок файловой системы, который содержит этот инод:

$ ext3grep $IMAGE --inode-to-block 309631 | grep resides
Inode 309631 resides in block 622598 at offset 0xf00.

Затем мы найдем все дескрипторы журнала, ссылающиеся на блок 622598:

$ ext3grep $IMAGE --journal --block 622598
[...]
Journal descriptors referencing block 622598:
4381294 26582
4381311 28693
4381313 28809
4381314 28814
4381321 29308
4381348 30676
4381349 30986
4381350 31299
4381374 32718
4381707 1465
4381709 2132
4381755 2945
4381961 4606
4382098 6073
4382137 6672
4382138 7536
4382139 7984
4382140 8931

Это означает, что транзакция с номером последовательности 4381294 имеет копию блока 622598 в блоке 26582 и так далее. Наибольший номер последовательности, находящийся внизу, должен быть последними данными записанными на диск и, таким образом, блок 8931 должен быть точно таким же, как текущий блок 622598. Для поиска последней, не удаленной копии, необходимо начинать снизу списка, и двигаться вверх.

Если вы попытаетесь вывести такой блок, ext3grep распознает, что это блок из таблицы инодов и будет выводить содержимое всех 32 инодов, находящихся в ней. Однако мы хотим видеть информацию только об иноде 309631, поэтому используем grep:

$ ext3grep $IMAGE --print --block 8931 | grep -A15 'Inode 309631'
--------------Inode 309631-----------------------
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 0
num of links: 0
sectors: 0 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1202351093 = Thu Feb  7 03:24:53 2008
Inode Modified: 1202351093 = Thu Feb  7 03:24:53 2008
Deletion time:  1202351093 = Thu Feb  7 03:24:53 2008

Direct Blocks:

Это действительно то же самое, что мы видели в блоке 622598. Затем смотрим на меньшие порядковые номера, пока не находим инод с временем удаления (Deletion time), равным нулю. Первый, начиная с конца, который мы обнаруживаем, это блок 6073:

$ ext3grep $IMAGE --print --block 6073 | grep -A15 'Inode 309631'
--------------Inode 309631-----------------------
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 40
num of links: 1
sectors: 8 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1189688692 = Thu Sep 13 15:04:52 2007
Inode Modified: 1189688692 = Thu Sep 13 15:04:52 2007
Deletion time:  0

Direct Blocks: 645627

Процессы, описанные выше, автоматизированы и могут быть выполнены гораздо быстрее при помощи опции командной строки --show-journal-inodes. Эта опция будет искать блок, к которому принадлежит инод, затем все копии блока в журнале, и последовательно выводить только запрошенные иноды из каждого из этих блоков (каждый из которых содержит 32 инода, как вы знаете), исключая дублирование:

Файл действительно мал, только один блок. Мы копируем этот блок командой dd, как показано ранее:

$ dd if=$IMAGE bs=4096 count=1 skip=645627 of=block.645627
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.0166104 seconds, 247 kB/s

и затем редактируем файл, удаляя добавленные нули, или копируем первые 40 байт (заданный размер файла).

$ dd if=block.645627 bs=1 count=40 of=start_azureus
40+0 records in
40+0 records out
40 bytes (40 B) copied, 0.000105397 seconds, 380 kB/s

$ cat start_azureus
cd /usr/src/azureus/azureus
./azureus 

Файл восстановлен!

Обратите внимание, что можно просмотреть все дескрипторы заданной транзакции. Транзакция, которую мы использовали для восстановления этого файла, была 4382098. Полностью транзакцию можно увидеть, выполнив команду:

Здесь, как вы видите, например, TAG 6072=393218 означает, что блок 6072 содержит старую копию блока 393218. Я не знаю, почему вывод команды говорит о том, что транзакция не была завершена (это выглядит очень маловероятным). Возможно, завершающий блок был перезаписан и эта старая транзакция журнала больше не завершится.

Восстановление файлов

Конечно, было бы весьма трудно восстанавливать этим путем большие файлы, состоящие из множества блоков, а представьте ручное восстановление тысяч файлов! Поэтому все, что представлено выше, может быть автоматизировано. Однако если Вы восстанавливаете 50 тысяч файлов, в сущности, невозможно впоследствии проверить, как прошло восстановление, особенно когда в результате получается файлов БОЛЬШЕ, чем вы в действительности хотели - слишком трудно найти все ненужные файлы. В действительности, вам необходимо бережно и аккуратно, насколько это возможно, подходить к восстановлению файлов.

Этого не требуется для восстановления единственного файла, вы можете просто передать путь к этому файлу ext3grep:

Обратите внимание, что в данном случае создается каталог RESTORED_FILES/carlo/bin в текущем каталоге, для того, чтобы иметь возможность восстановить этот файл. Также обратите внимание, что если RESTORED_FILES/carlo/bin/start_kvm уже существует в этом каталоге, то он не будет перезаписан!

Для выполнения работы по восстановлению, в первую очередь, вам необходимо пройти стадию 1 и стадию 2 анализа диска, которые так же выполняются программой ext3grep (смотрите ниже).

Имеется возможность выгрузить все имена файлов, которые ext3grep сможет найти, используя для этого опцию командной строки --dump-names:

Список файлов будет заканчиваться каталогом lost+found, это файлы, для которых не может быть найден каталог (однако, для них все еще есть копия инода в журнале). Наиболее вероятно, что эти файлы были удалены очень давно, и в любом случае могут быть исключены из рассмотрения.

После того, как вы получили удовлетворительный вывод --dump-names, вы можете заменить --dump-names опцией --restore-all, чье действие, по-сути, это вызов --restore-file для каждого имени файла, выведенного опцией --dump-names. Как упоминалось выше, очень рекомендуется использовать правильную опцию командной строки --after, для того, чтобы избежать попыток ext3grep восстановления очень старых файлов. Обратите внимание, что на данный момент вывод --dump-names не фильтруется, и только --restore-file (--restore-all) обеспечивается опцией командной строки --after.

Например,

где carlo/bin/startx только один файл, который был восстановлен. Этот файл был удален последним, и я установил значение --after на одну секунду раньше того, как это было сделано. Обратите внимание на логику того, почему этот файл был последним: поскольку я запускаю X этим скриптом, соответственно он использовался до того момента, пока я не перезагрузил систему.

Обращаю ваше внимание на тот факт, что проверка более чем 50000 файлов на разделе общим объемом 10 ГБ заняла 3,1 секунды, это очень быстро. Это вызвано несколькими факторами: 1) когда ext3grep запускается в первый раз, она выполняет полный анализ раздела и записывает результаты в файл кэша (за два шага; первый - стадия 1, и затем стадия 2). Эти стадии необходимы только один раз; 2) поскольку только один файл восстанавливался, доступ к диску больше не требовался (помимо того, я имею 4ГБ оперативной памяти в системе, - таким образом, все, что требовалось, уже было сохранено в кэше); 3) у моей системы очень быстрый процессор. Тем не менее, он использовался на 100% в эти 3,1 секунды. Время восстановления большого количества файлов, в основном, будет зависеть от скорости доступа к жесткому диску, однако и в этом случае работа программы остается относительно быстрой (вы можете просто сидеть и ждать результат).

Стадия 1

Стадия 1 создания кэш-файла - это запись в DEVICE.EXT3GREP.STAGE1, где DEVICE замещается именем устройства (т. е., если $IMAGE это /dev/hda2, то DEVICE это hda2). Мало что может быть сделано неправильно на стадии 1, - это просто сканирование целого диска и поиск всех блоков, которые выглядят как содержащие каталоги.

Формат кэш файла на стадии 1 выглядит следующим образом:

В первой части первая колонка - это иноды, после которых идет пробел, затем двоеточие и еще один пробел, разделяющие список номеров блоков, которые используют этот инод для записи каталога с именем ".". Очевидно, что может быть только один каталог, который использует этот инод, таким образом ext3grep определяет, который, из этих номеров блоков, является последним реальным номером. Вторая часть списка - это все номера блоков, которые содержат расширенные блоки каталогов, т. е. блоки каталогов, которые не являются первым блоком и не содержат записи каталога с именем ".". Какому каталогу они принадлежат неизвестно до тех пор, пока неизвестен номер оригинального инода. На стадии 2 ext3grep будет пытаться найти, какому каталогу принадлежат эти блоки.

Стадия 2

Эта стадия выполняется через функцию init_directories(), содержащую, в большинстве своем, эвристический код. Во-первых, она определяет, какой блок является реальным стартовым блоком каталога, и затем назначает каждый расширенный блок каталога одному из этих каталогов (смотрите также раздел "Что еще сделать?" ниже). Как результат, появляется возможность назначить имя пути к каждому (каталогу) иноду и назначить им список блоков каталога. Наконец, этот результат записывается в файл кэша (DEVICE.EXT3GREP.STAGE2). В случае, если здесь что-либо пойдет неправильно, вы имеете возможность внести исправления, отредактировав этот файл, удалив некорректные или добавив корректные номера блоков. Однако, не удаляйте и не добавляйте комментарии, - ext3grep будет работать некорректно, если вы слишком сильно измените файл.

База данных locate

Каюсь, для того, чтобы восстановить файлы, я воспользовался ещё кое-чем, кроме данных в моем разделе: у меня все еще был раздел /var, и поэтому все еще была база данных locate (в /var/cache/locate/locatedb). Я использовал ее для составления списка имен всех файлов, которые были удалены, и записал их в файл locate_output в следующем формате:

carlo
carlo/.Trash
carlo/.Xauthority
[...]

Другими словами, в том же формате, что и вывод --dump-names. Этот файл открывался и использовался через функцию load_locate_date, в исходном файле locate.cc, которая заполняет карту filename_to_locatepath_map. Эта карта впоследствии используется функцией parent_directory для того, чтобы строить предположения о том, какому родительскому каталогу отнести заданное имя файла.

Даже тогда, поскольку моя база данных locate не была полной, мне потребовалось вручную вносить поправки. В частности, можно добавить в файл locate.cc жестко запрограммированный список регулярных выражений, которые будут вызывать возврат в определенный родительский каталог. Дополнительно я так же жестко закодировал перевод трех номеров блоков (каталогов) в "lost+found". Возможно вам придется настроить/исправить эту часть кода, если вы хотите достичь успеха в правильном восстановлении ваших данных в корректный каталог.

Если вы видите сообщения типа указанного ниже:

Could not find an inode for extended directory at BLOCKNR, disregarding it's contents. 
(Невозможно найти инод для расширенного каталога в BLOCKNR, игнорирую его содержание.)

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

Излишние жесткие ссылки

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

Для того чтобы упростить очистку от этих ссылок в ext3grep существует опция командной строки --show-hardlinks.

Здесь жесткие ссылки для инода 309562 - корректные. Жесткая ссылка для инода 702474 неправильная, и один из этих файлов должен быть удален. После того как будет выполнено ручное определение, какой из этих файлов неправильный, и его удаление, он не будет отображаться в выводе команды при ее повторном запуске. Будет показан отчет только о тех жестких ссылках, которые все еще существуют в выходном каталоге. Вы можете использовать --show-hardlinks только после выполнения программы с опцией --restore-all, иначе вы не получите результата, поскольку не существует выходных файлов.

Что еще нужно сделать?

Программа была написана во время изучения того, как работает ext3. Ее ранняя функциональность поэтому, не зависит от того, что я написал позднее. Достоинство этой функциональности в том, что она работает быстрее и будет работать, даже если отбросить более поздний код. Она также более практичная, поэтому вы можете использовать ее для проверки того, что происходит в реальности, без зависимости от более сложного (и эвристического) кода, который был добавлен позднее. Тем не менее, существуют так же и недостатки: фильтрация кода, который я написал для --ls не используется в позже написанном коде, который оперирует опциями --dump-names и --restore-all. Также стадия 2 была реализована без использования журнала, как было бы возможно, используя код написанный позже. Это не просто изменить, поскольку этот код использует результаты стадии 2. Я думаю, что лучшим алгоритмом для поиска корректных блоков для последней копии каталога будет точно такой же, который я использовал для окончательного восстановления файлов: используя поиск последнего не удаленного инода этого каталога в журнале. Однако, в настоящее время программа работает не так.

Опции командной строки

Все опции командной строки перечисляются при добавлении к команде ext3grep опции --help:

Новые функциональные возможности добавлялись методом "top-down ("сверху вниз"), таким образом, можно увидеть исторический очерк того, как создавалась программа.

Пожертвования

Если кто-то пожелает пожертвовать что-то, чтобы сделать мою жизнь легче, пожалуйста щелкните на кнопке внизу, которая направит вас на безопасный сайт PayPal (https://www.paypal.com/...). Спасибо за вашу поддержку!

Где скачать программу

Пожалуйста следуйте по этой ссылке на сайт проекта ext3grep: http://groups.google.com/group/ext3grep/web/ext3grep-source-code-and-overview

Очень рекомендую вам присоединиться к этой группе и прочитать архивы рассылки.

Если у вас есть учетная запись gmail и вы не возражаете против получения рассылки (обратите внимание, что вы можете перенаправить почту gmail на любой другой почтовый адрес), тогда вы можете пройти по этой ссылке:

http://groups.google.com/group/ext3grep/subscribe

Если вы не хотите использовать gmail, тогда, пожалуйста, подпишите сами себя, отправив сообщение на адрес ext3grep-subscribe@googlegroups.com. Недостаток такого метода подписки в том, что вы не станете действительным членом группы и веб-сайт будет для вас менее полезен. Например, у вас не будет возможности установить рассылку типа "Дайжест".

Copyright © 2008 Carlo Wood