Библиотека сайта rus-linux.net
Как восстановить удаленные файлы в файловой системе 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.
байты | тип | описание |
---|---|---|
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.
Журнал заполняется "Транзакциями", которые имеют всегда увеличивающийся номер последовательности. Если достигнут конец журнала, запись начинается сначала, выполняясь по кругу. Однако, если файловая система размонтирована правильно, тогда при следующем монтировании запись всегда начинается сначала (я так думаю).
Для одной транзакции существует один или более "Дескрипторов". Последний дескриптор транзакции это "Фиксирующий блок", сигнализирующий о том, что транзакция была успешно закрыта и данные в предыдущих дескрипторах транзакции записаны на диск. Существует также два других типа дескрипторов: блоки отмены и блоки, содержащие "тэги" (ярлыки). Блоки отмены - это блоки, которые должны быть (или уже) освобождены транзакцией. Тэг - это структура, которая назначает последующие блоки журнала (не блоки файловой системы!) блокам файловой системы: следующие блоки журнала содержат данные, которые должны быть (были быть) записаны в заданный блок файловой системы.
Это делает тэги особенно интересными для нас: они содержат копии данных, которые были записаны на диск ранее, включая старые иноды.