Документация по ОС FreeBSD Понедельник, 20.05.2024, 04:47
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
Мои статьи [0]
Установка и настройка [281]
X Window [25]
Man pages [30]
Ports & Packages [26]
cvs [18]
Multimedia [20]
Нововсти в мире Unix [0]
RFC [4]
RFC (Request for Comments, Запрос на комментарии) - серия документов, публикуемая сообществом исследователей и разработчиков, руководствующихся практическими интересами, в которой описывается набор протоколов и обобщается опыт функционирования Интернет.
Безопасность [52]
Работа с железом [58]
Книги по FreeBSD [17]
Сеть [505]
Программирование [40]
FireWall [58]
Темы экзамена BSDA [14]
Официальные темы экзамена BSDA, включая подробноые описания и советы по обучению.

Главная » Статьи » Установка и настройка

Восстановление ZFS-пула с помощью подручных средств. Часть 1 [2011]

Введение

Представленный материал ни в коем случае не является инструкцией по восстановлению или чем-то похожим на инструкцию. Это просто рассказ о решении одной не тривиальной, для меня, задачи. И доказательство того факта, что ZFS вполне может быть восстановлена даже когда её драйвер утверждает, что пора доставать резервную копию т.к. все данные превратились в фарш, а фарш... О том, что нельзя сделать с фаршем читайте в эпиграфе. Текст написан в неформальном стиле, что бы подчеркнуть антинаучность представленного материала.

1. Предыстория

Был RAID-Z-пул собранный из трёх дисков по 1.5 Тб фирмы X, модели Y. Работал он работал, и изредка стали появляться в логах сообщения о том, что произошла ошибка чтения или ошибка записи на один из дисков пула. ZFS, ругаясь в логи на ошибки контрольных сумм, отлично отрабатывала такие моменты и пул продолжал нормально функционировать со статусом "ONLINE". Ошибки повторялись, но были не систематичными: различные диски, различные сектора, различное время. Назвав ошибки идиопатическими (неизвестной природы) решил, что обязательно разберусь с ними, но не сейчас. Эпик фейл подкрался незаметно, но всё же наступил. В один, не самый удачный момент, после перезагрузки, файловая система не cмонтировалась.

Листинг 1.1. Развалившийся пул с почти отказавшим диском
~# zpool import
 pool: storage
 id: 15607890160243212464
 state: FAULTED
status: The pool metadata is corrupted.
action: The pool cannot be imported due to damaged devices or data.
 The pool may be active on another system, but can be imported using
 the '-f' flag.
 see: http://www.sun.com/msg/ZFS-8000-72
config:

 storage FAULTED corrupted data
 raidz1 ONLINE
 ad2 ONLINE
 ad4 OFFLINE
 ad6 ONLINE

При попытке импорта пула, zpool(8) вообще сваливался в кору.

Листинг 1.2. Падение zpool(8) в кору
~# zpool import -o ro storage
internal error: Illegal byte sequence
Abort (core dumped)

В логах стабильно появлялись два сообщения, свидетельствующие о том, что некоторые сектора на ad4 приказали долго жить. Как выяснилось далее, в этих секторах начинались важные для ZFS структуры данных, но первые несколько килобайт этой структуры, удачным образом, не использовались и были помечены в спецификации, как "Blank space" (пустое место).

Примечание.
Здесь и далее под килобайтом понимается 1024 байта, размер сектора так же стандартный, т.е. 512 байт.

Листинг 1.3. Сбойные сектора
ad4: FAILURE - READ_DMA48 status=51<...> error=40<UNCORRECT...> LBA=2930275840
ad4: FAILURE - READ_DMA48 status=51<...> error=40<UNCORRECT...> LBA=2930276352

Попробовал прочитать эти сектора и убедился в том, что они работать не будут.

Листинг 1.4. Чтение сбойных секторов "в ручную"
~# dd if=/dev/ad4 of=/dev/null bs=512 skip=2930275840
dd: /dev/ad4: Input/output error
0+0 records in
0+0 records out
0 bytes transferred in 2.777067 secs (0 bytes/sec)

Подключил диск такой же модели и в несколько приёмов (обходя сбойные сектора) скопировал на него содержимое глючившего диска. После чего поставил новый диск вместо глючного и попытался импортировать пул. Но, к моему разочарованию, пул отказался импортироваться и с новым диском. zpool(8) продолжал падать в кору.

Листинг 1.5. Развалившийся пул, после замены диска
~# zpool import
 pool: storage
 id: 15607890160243212464
 state: FAULTED
status: The pool metadata is corrupted.
action: The pool cannot be imported due to damaged devices or data.
 The pool may be active on another system, but can be imported using
 the '-f' flag.
 see: http://www.sun.com/msg/ZFS-8000-72
config:

 storage FAULTED corrupted data
 raidz1 ONLINE
 ad2 ONLINE
 ad4 ONLINE
 ad6 ONLINE

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

2. Восстановление

2.1. Рекогносцировка местности

Внутреннее устройство ZFS я знал плохо - пришлось изучать его на форсаже, с прицелом на возможность восстановления данных. После недолгих поисков нагуглил[1] сообщение некоего Nathan Hand, который утверждал, что смог восстановить работоспособность пула. Так же в сообщении обнаружилась ссылка на черновой вариант спецификации[2] ZFS. Настроение стало улучшаться.

2.2. Некоторые сведения об устройстве ZFS

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

Структура данных, с которой начинается разбор бинарного месива диска перед тем как это месиво станет навороченной файловой системой, называется метка (англ. Label). На диске хранится несколько меток в строго определённых позициях.

Схема 2.2.1. Расположение меток на диске
 +----+----+--------------------+----+----+
 | L0 | L1 | | L2 | L3 |
 +----+----+--------------------+----+----+

L0, L1, L2, L3 - метки ZFS. L0, L1 - располагаются друг за другом без пропусков в начале диска (с нулевого смещения). L2, L3 - располагаются в конце диска, но не обязательно в самых последних секторах, после них может оставаться свободное место (скорее всего из-за выравнивания на 128 Кб). Пространство между L1 и L2 занято вспомогательными структурами и самими данными, но их позиции строго не определены. Метки на одном диске должны быть строго одинаковыми. О том, почему выбрано такое количество меток и каким образом они обновляются хорошо написано в документации[2].

Структура метки достаточно проста, она состоит из 8Кб пустого пространства (англ. Blank Space), 8Кб загрузочного заголовка (англ. Boot Header), 112Кб конфигурационных данных в формате имя - значение (англ. Name/Value Pair List) и 128Кб массива структур Uberblock. Итого получается 256Кб на одну метку.

Схема 2.2.2. Структура метки
 Blank Boot Name/Value Uberblock
 Space Header Pair List Array
 +-------+--------+----------------+---------------------------------+
 | | | | | | | | | | | | | | | ... | | |
 +-------+--------+----------------+---------------------------------+
 0 8K 16K 128K 256K

Список имя-значение, содержит следующие конфигурационные данные (перечислены самые интересные, на мой взгляд):
version - версия формата хранения;
name - имя пула;
state - состояние пула (активен/экспортирован/удалён);
txg - номер транзакции в которой выполнялась запись (будет описан далее);
pool_guid - уникальный идентификатор пула;
guid - уникальный идентификатор виртуального устройства;
vdev_tree - дерево, описывающее всю конфигурацию входящих в пул виртуальных устройств, само дерево состоит из пар имя-значение, основные поля описывающие отдельное устройство:
  - type - тип устройства (файл/блочное устройство/зеркало/raidz/корень);
  - path - имя устройства (только для файлов и блочных устройств);
  - guid - уникальный идентификатор описываемого виртуального устройства;
  - children - массив подчинённых виртуальных устройств.

Более подробное описание самих данных и ссылки на формат их хранения доступны в спецификации.

Примечание.
Следует отметить, что ZFS активно оперирует таким понятием, как виртуальное устройство (англ. vdev), под которым может пониматься, как вполне реальный диск, так и абсолютно абстрактный RAID-Z массив или корень иерархии устройств.

Uberblock (перевода не придумал) по своей природе похож на Superblock UFS - он является началом всей структуры данных на диске. Почему используется целый массив блоков вместо одного? Всё просто: ZFS никогда не пишет данные поверх уже существующих, вместо этого она записывает новую структуру в новую позицию, и только потом допускает возможность модификации уже имеющихся данных (их перезапись). При чтении структуры диска (напр. после перезагрузки) просто находится запись сделанная последней и используется, остальные записи считаются неактуальными. Сам Uberblock в начале содержит пять 64-битных полей:
ub_magic - "магическое число" идентифицирующее (сигнатура) блок;
ub_version - версия формата хранения;
ub_txg - номер транзакции в которой записана данная структура;
ub_guid_sum - сумма идентификаторов устройств;
ub_timestamp - UTC метка времени.

Далее следуют указатели на подчинённые структуры. Суммарный объём структуры равен 1Кб. С учётом того, что массив имеет объём 128Кб получаем, что в каждой метке может храниться 128 Uberblock'ов, созданных в разное время.

"Магическое число" всегда равно 0x00bab10c (oo-ba-block) на диск данное значение будет записано следующей последовательностью байт.

Таблица 2.2.1. Запись "Магического числа"
 +----------------+-------------------------+
 | Edians | Bytes |
 +----------------+-------------------------+
 | Big (x86) | 0c b1 ba 00 00 00 00 00 |
 | Little (Sparc) | 00 00 00 00 00 ba b1 0c |
 +----------------+-------------------------+

Особого внимания заслуживает поле транзакция, в котором хранится номер транзакции, по завершении которой и был записан данный Uberblock. Так же, как было отмечено ранее, номер транзакции отдельно указывается в метке. На основании этого номера и принимается решение, какой Uberblock считается активными. Активным считается Uberblock с максимальным номером транзакции, при этом номер транзакции блока должен быть больше и равен номеру транзакции метки. Если это условие не выполняется, то производится поиск блока с меньшим номером. Т.о. если при выполнении транзакции произошла ошибка и Uberblock записан не был, то ZFS сможет корректно откатиться к предыдущей транзакции.

Для просмотра содержимого меток диска используется опция "-l" утилиты zdb(8).

Дальше начинаются дебри из структур, описывающих расположение блоков на диске. Кому интересно, тот может ознакомиться с ними в документации[2].

2.3. Детальное обследование пациента

Начать было решено с простейших манипуляций - проверить читаемость и корректность меток, с помощью zdb(8).

Листинг 2.3.1. Чтение меток с дисков (показано только начало дампа)
~# zdb -l /dev/ad2
--------------------------------------------
LABEL 0
--------------------------------------------
 version=14
 name='storage'
 state=0
 txg=253277
 pool_guid=15607890160243212464
...
~# zdb -l /dev/ad4
--------------------------------------------
LABEL 0
--------------------------------------------
 version=14
 name='storage'
 state=0
 txg=247242
 pool_guid=15607890160243212464
...
~# zdb -l /dev/ad6
--------------------------------------------
LABEL 0
--------------------------------------------
 version=14
 name='storage'
 state=0
 txg=253277
 pool_guid=15607890160243212464
...

Никакого криминала метки считались - придётся вводить в действие тяжёлую артиллерию.

2.4. Тяжёлая артиллерия

Для того, что бы было удобнее копаться в бинарном содержимом меток их необходимо было скопировать с дисков в отдельные файлы. Для этого нужно знать начала меток и их размер на диске. С размером вопросов не было - размер меток постоянен и равен 256Кб. Смещения L0 и L1 равны 0 и 256Кб соответственно. Оставалось выяснить смещения L2 и L3. Объём диска равен 1465138584Кб, вычитая размер метки, я должен был получить смещение метки L3: 1465138328Кб. Но не тут то было. Сделав дамп 256Кб от полученного смещения я решил его проверить, найдя смещение первого Uberblock'а от начала дампа. Это должно было быть ровно 128Кб или 020000h. Uberblock легко определить в куче байт по "Магическому числу", с которого он начинается, но необходимо делать поправку на порядок байт в записи числа.

Листинг 2.4.1. Неправильный выбор смещения
~# dd if=/dev/ad2 of=tmp.dump bs=1024 count=256 skip=1465138328
256+0 records in
256+0 records out
262144 bytes transferred in 0.057129 secs (4588621 bytes/sec)
~# od -t xC -A x tmp.dump | grep "0c b1 ba 00" | head -n 1
0000000 0c b1 ba 00 00 00 00 00 0e 00 00 00 00 00 00 00

Промазал. Первый Uberblock в полученном дампе располагался на нулевом смещении, а это значит, что ошибка составляет минимум 128Кб. Т.к. первый Uberblock должен располагаться на смещении 128Кб от начала метки, а в дампе уже на нулевом смещении я видел один из блоков. Для определения корректного смещения было необходимо определить, сколько Uberbock'ов попало в дамп и сместиться на соответствующее количество блоков + 128Кб.

Листинг 2.4.2. Определение количества Uberblock'ов, попавших в дамп
~# dd if=/dev/ad2 of=tmp.dump bs=1024 count=256 skip=1465138328
256+0 records in
256+0 records out
262144 bytes transferred in 0.057163 secs (4585903 bytes/sec)
~# od -t xC -A x tmp.dump | grep "0c b1 ba 00" | wc -l
 104

В дампе 104 блока, должно быть 128 - нам надо сместиться влево (к младшим адресам) на: (128 - 104) + 128 = 152 (Кб). Новое смещение: 1465138328 - 152 = 1465138176 (Кб). Делаем дамп и проверяем количество блоков и смещение первого из них.

Листинг 2.4.3. Проверка нового смещения
~# dd if=/dev/ad2 of=tmp.dump bs=1024 count=256 skip=1465138176
256+0 records in
256+0 records out
262144 bytes transferred in 0.057357 secs (4570387 bytes/sec)
~# od -t xC -A x tmp.dump | grep "0c b1 ba 00" | head -n 1
0020000 0c b1 ba 00 00 00 00 00 0e 00 00 00 00 00 00 00
~# od -t xC -A x tmp.dump | grep "0c b1 ba 00" | wc -l
 128

Ага, угадал. Смещение L2 получить элементарно - необходимо вычесть 256Кб из полученного смещения L3: 1465138176 - 256 = 1465137920 (Кб). Получилась небольшая таблица смещений. Т.к. диски одинаковой модели, то и смещения для них будут одинаковыми.

Таблица 2.4.1. Смещения меток
 +----+-------------+
 | L# | Offset (Kb) |
 +----+-------------+
 | L0 | 0 |
 | L1 | 256 |
 | L2 | 1465137920 |
 | L3 | 1465138176 |
 +----+-------------+

Т.к. рутинную работу лучше выполняет машина, я набросал простенький скрипт для дампа всех меток в отдельные файлы.

Листинг 2.4.4. Скрипт для дампа
#!/bin/sh

L0_OFF=0
L1_OFF=256
L2_OFF=1465137920
L3_OFF=1465138176

DISKS="ad2 ad4 ad6"

for DISK in $DISKS; do
 echo "Dump labels from: $DISK"
 l=0
 for OFF in $L0_OFF $L1_OFF $L2_OFF $L3_OFF; do
 fdump="$DISK-label$l.dump"
 echo " Label: $l (out: $fdump, offset: $OFF)"

 # Create output file
 touch "$fdump"

 # Dumping
 dd if="/dev/$DISK" of="$fdump" bs=1024 count=256 skip=$OFF 2> /dev/null

 l=`expr $l + 1`
 done
done

После его выполнения метки были сохранены в файлы adN-labelX.dump (N - номера дисков: 2, 4, 6; X - номер метки: 0, 1, 2, 3). Разбираться стало немного удобнее.

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

2.5. Поиск ответов в коде реализации

Немного порывшись по коду zpool и libzfs, обнаружил следующие строки в libzfs_status.c, которые и устанавливают статус пула "The pool metadata is corrupted".

Листинг 2.5.1. Код определяющий состояние ZFS-пула
 /*
 * Corrupted pool metadata
 */
 if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
 vs->vs_aux == VDEV_AUX_CORRUPT_DATA)
 return (ZPOOL_STATUS_CORRUPT_POOL);

Проверяемые поля устанавливаются в модуле ядра - пришлось перебираться в ядро. После недолгих поисков был найден файл spa.c, ответственный за открытие/закрытие, а так же импорт пула. В данном файле было несколько участков кода, в которых поля устанавливались в интересующее состояние. Надо было как-то выяснить что именно не нравиться алгоритму импорта. Для этого насовал кучу отладочного вывода в модуль, пересобрал его и загрузил вместо стандартного, разрешив вывод отладочной информации. Теперь, в момент импорта пула, в логи сыпалось множество отладочной информации, показывающей ход выполнения алгоритма импорта/открытия пула. Среди прочего был найден код проверки активного Uberblock'а.

Листинг 2.5.2. Проверка активного Uberblock'а
 /*
 * If we weren't able to find a single valid uberblock, return failure.
 */
 if (ub->ub_txg == 0) {
 vdev_set_state(rvd, B_TRUE, VDEV_STATE_CANT_OPEN,
 VDEV_AUX_CORRUPT_DATA);

Примечание.
У zpool(8) есть специальная недокументированная опция "-F", которая позволяет импортировать даже сбойный пул. Работать после этого он конечно не будет, но для отладки и восстановления данных опция полезная.

Добавив перед проверкой вывод номера транзакции активного блока, получил номер транзакции, которую ZFS считает последней удачной. Это была 253277 транзакция.

Немного порывшись в коде, пришёл к выводу, что ошибка где-то глубже чем неправильно записанная метка. Т.к. восстанавливать всю структуру блок за блоком в мои планы не входило (пул содержал несколько сотен гигабайт не такой уж и ценной информации), я решил прибегнуть к грубой силе - заставить ZFS откатиться к предыдущей транзакции.


Часть 2



Источник: http://www.lissyara.su/articles/freebsd/file_system/zfs_recovery/
Категория: Установка и настройка | Добавил: oleg (05.02.2011) | Автор: BlackCat
Просмотров: 1036 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024