Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Файловые системы Linux

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

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

Как 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.

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

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

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