Мы — долго запрягаем, быстро ездим, и сильно тормозим.
www.lissyara.su —> статьи —> FreeBSD —> Файловая система —> Восстановление ZFS

Восстановление ZFS-пула с помощью подручных средств

Автор: BlackCat.


Оглавление

  • Введение
  • 1. Предыстория
  • 2. Восстановление
  • 2.1. Рекогносцировка местности
  • 2.2. Некоторые сведения об устройстве ZFS
  • 2.3. Детальное обследование пациента
  • 2.4. Тяжёлая артиллерия
  • 2.5. Поиск ответов в коде реализации
  • 2.6. Решение грубой силой
  • Год спустя
  • Мораль
  • Список литературы

                                                    Фарш нельзя провернуть назад.
                                                          Слоган радиопередачи
                                                                   "Литрофарш".

    Введение

    Представленный материал ни в коем случае не является инструкцией по восстановлению или чем-то похожим на инструкцию. Это просто рассказ о решении одной не тривиальной, для меня, задачи. И доказательство того факта, что 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                   |
     +----------------+-------------------------+
     | Little (x86)   | 0c b1 ba 00 00 00 00 00 |
     | Big (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.6. Решение грубой силой

    Для того, что бы заставить ZFS откатиться к предыдущему состоянию, было необходимо стереть Uberblock последней удачной транзакции (а это была 253277 транзакция). Но прежде, немного переделав предыдущий скрипт, я получил скрипт извлекающий номера транзакций из всех Uberbock'ов всех дампов. Другой вариант поиска необходимого Uberblock'а - найти его по значению в дампе (253277 = 03DD5Dh).

    Примечание.
    Данный скрипт будет работать только на пулах созданных на узлах с big-endian архитектурой (x86, etc). Для little-endian архитектур необходимо в функции преобразования массива в число убрать цикл, инвертирующий массив.

    Примечание.
    На платформе i386, функция преобразования массива в число, при больших номерах транзакций, может работать с ошибками. Это вызвано тем, что для представления номера транзакции используется 64-битное число, а expr(1) работает со стандартными типами и может произойти целочисленное переполнение. Для 64-битной платформы (AMD64) такого ограничения быть не должно (но не проверялось).

    Листинг 2.6.1. Скрипт извлекающий номера транзакций Uberblock'ов
    #!/bin/sh
    
    DISKS="ad2 ad4 ad6"
    LABELS="0 1 2 3"
    
    FIELD_OFF=16
    FIELD_LEN=8
    
    arr2int()
    {
      arr=""
      res=0
    
      # Invert octet order
      for oct in $1; do
        arr="$oct $arr"
      done
    
      # Convert
      for oct in $arr; do
        oct=`printf "%d" 0x$oct`
        res=`expr -e $res \* 256 + $oct`
      done
    
      echo $res
    }
    
    fin=`expr 256 \* 1024`
    prev=""
    for DISK in $DISKS; do
      for L in $LABELS; do
        fdump="$DISK-label$L.dump"
        # First block pos
        off=`expr 128 \* 1024 + $FIELD_OFF`
        n=0
        while [ $off -lt $fin ]; do
          # Get octets array
          str=`od -t xC -A n -v -j $off -N $FIELD_LEN $fdump`
          str=`echo $str | sed 's/^ *\([^ ].*[^ ]\) *$/\1/'`
          # Convert to integer
          txg=`arr2int "$str"`
          printf "txg %10u disk %s label %s n %3u\n" $txg $DISK $L $n
          off=`expr $off + 1024`
          n=`expr $n + 1`
        done
      done
    done
    


    После запуска скрипта и перенаправления вывода в файл, получил список из 1536 записей (3 диска с 4 метками по 128 блоков в каждой). Далее сортируем его по номеру транзакции и определяем наибольший.

    Листинг 2.6.2. Поиск Uberblock'а с наибольшим номером
    ~# ./extract-ub-txg.sh > txg.lst
    ~# sort txg.lst | tail -n 10
    txg     253276 disk ad6 label 2 n  92
    txg     253276 disk ad6 label 3 n  92
    txg     253277 disk ad2 label 0 n  93
    txg     253277 disk ad2 label 1 n  93
    txg     253277 disk ad2 label 2 n  93
    txg     253277 disk ad2 label 3 n  93
    txg     253277 disk ad6 label 0 n  93
    txg     253277 disk ad6 label 1 n  93
    txg     253277 disk ad6 label 2 n  93
    txg     253277 disk ad6 label 3 n  93
    


    Ага. Транзакция 253277 есть только на ad2 и ad4 видимо на ней ad4 и стало плохо. Теперь определим, на каких дисках есть запись о предыдущей транзакции и с каким номером были записаны метки на дисках.

    Листинг 2.6.3. Определение номеров транзакций меток
    ~# zdb -l /dev/ad2 | grep "txg"
        txg=253277
        txg=253277
        txg=253277
        txg=253277
    ~# zdb -l /dev/ad4 | grep "txg"
        txg=247242
        txg=247242
        txg=247242
        txg=247242
    ~# zdb -l /dev/ad6 | grep "txg"
        txg=253277
        txg=253277
        txg=253277
        txg=253277
    


    Листинг 2.6.4. Поиск Uberblock'ов последней и предпоследней транзакций
    ~# grep "25327[67]" txg.lst | sort
    txg     253276 disk ad2 label 0 n  92
    txg     253276 disk ad2 label 1 n  92
    txg     253276 disk ad2 label 2 n  92
    txg     253276 disk ad2 label 3 n  92
    txg     253276 disk ad4 label 0 n  92
    txg     253276 disk ad4 label 1 n  92
    txg     253276 disk ad4 label 2 n  92
    txg     253276 disk ad4 label 3 n  92
    txg     253276 disk ad6 label 0 n  92
    txg     253276 disk ad6 label 1 n  92
    txg     253276 disk ad6 label 2 n  92
    txg     253276 disk ad6 label 3 n  92
    txg     253277 disk ad2 label 0 n  93
    txg     253277 disk ad2 label 1 n  93
    txg     253277 disk ad2 label 2 n  93
    txg     253277 disk ad2 label 3 n  93
    txg     253277 disk ad6 label 0 n  93
    txg     253277 disk ad6 label 1 n  93
    txg     253277 disk ad6 label 2 n  93
    txg     253277 disk ad6 label 3 n  93
    


    Итак, текущий активный Uberblock располагается в 93 позиции массива Uberblock'ов на дисках ad2 и ad6. Uberblock предыдущей транзакции расположен в 92 позиции на всех дисках. Остаётся рассчитать смещение в дампе 93 позиции массива: 128 + 93 = 221 (Кб). Проверим расчёт, найдя блок по номеру транзакции: 253277 = 03dd5dh, с учётом порядка байт, нужно найти последовательность: 5d dd 03 00.

    Листинг 2.6.5. Поиск Uberblock'а по номеру транзакции
    ~# od -t xC -A x ad2-label0.dump | grep -B 1 "5d  dd  03  00" | head -n 2
    0037400    0c  b1  ba  00  00  00  00  00  0e  00  00  00  00  00  00  00
    0037410    5d  dd  03  00  00  00  00  00  71  78  02  5c  2e  cc  05  2d
    


    Искомая последовательность найдена по смещению 037410h, блок начинается по смещению 037400h, т.к. поле txg расположено по смещению 10h = 16 байт от начала блока. Переведём в килобайты: 037400h / 400h = ddh = 221 - предыдущий расчёт смещения был сделан верно. Остаётся затереть текущие активные блоки и записать метки назад на диск. Но перед этим стоит сохранить оригинальные метки.

    Листинг 2.6.6. Удаление Uberblock'ов
    ~# mkdir orig
    ~# cp ad*.dump ./orig/
    ~# dd if=/dev/zero of=ad2-label0.dump bs=1024 count=1 seek=221 conv=notrunc
    1+0 records in
    1+0 records out
    1024 bytes transferred in 0.000078 secs (13134457 bytes/sec)
    ~# dd if=/dev/zero of=ad2-label1.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    ~# dd if=/dev/zero of=ad2-label2.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    ~# dd if=/dev/zero of=ad2-label3.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    ~# dd if=/dev/zero of=ad6-label0.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    ~# dd if=/dev/zero of=ad6-label1.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    ~# dd if=/dev/zero of=ad6-label2.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    ~# dd if=/dev/zero of=ad6-label3.dump bs=1024 count=1 seek=221 conv=notrunc
    ...
    


    Было бы неплохо проверить, те ли блоки были затёрты.

    Листинг 2.6.7. Проверка содержимого блоков
    ~# od -t xC -A x -v ad2-label0.dump | grep -A 1 "^0037410"
    0037410    00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
    0037420    00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
    


    Так, блоки затёрты - остаётся вернуть метки на диск.

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

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

    Листинг 2.6.8. Скрипт записи меток на диски
    #!/bin/sh
    
    L0_OFF=0
    L1_OFF=256
    L2_OFF=1465137920
    L3_OFF=1465138176
    
    DISKS="ad2 ad4 ad6"
    
    echo "Script disabled. Exit."
    exit
    
    for DISK in $DISKS; do
      echo "Write labels to: $DISK"
      l=0
      for OFF in $L0_OFF $L1_OFF $L2_OFF $L3_OFF; do
        fdump="$DISK-label$l.dump"
        echo "  Label: $l (in: $fdump, offset: $OFF)"
    
        # Writing
        dd if="$fdump" of="/dev/$DISK" bs=1024 count=256 seek=$OFF 2> /dev/null
    
        l=`expr $l + 1`
      done
    done
    


    Скрестив пальцы, импортирую пул в режиме только для чтения.

    Листинг 2.6.9. Импорт пула в режиме только чтение
    ~# zpool import -o ro storage
    


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

    Листинг 2.6.10. Состояние пула после импорта
    ~# zpool status
      pool: storage
     state: ONLINE
    status: The pool is formatted using an older on-disk format.  The pool can
            still be used, but some features are unavailable.
    action: Upgrade the pool using 'zpool upgrade'.  Once this is done, the
            pool will no longer be accessible on older software versions.
     scrub: resilver completed after 0h0m with 0 errors on Wed Dec 28 16:08:41 2010
    config:
    
            NAME        STATE     READ WRITE CKSUM
            storage     ONLINE       0     0     0
              raidz1    ONLINE       0     0     0
                ad2     ONLINE       0     0     0
                ad4     ONLINE       0     0     0  23K resilvered
                ad6     ONLINE       0     0     0
    
    errors: No known data errors
    


    Быстренько достав винт подходящего объема, запустил полное копирование всех данных с пула на него, т.к. продолжать работать с пулом, побывавшим в состоянии "FAULTED" - не наш метод. И вот пока бегут в обратную сторону часы, оставшиеся до завершения копирования я и решил записать эту небольшую историю.

    Год спустя

    Ещё весной задал на форуме вопрос о поддержке "отмотки" транзакций. И был направлен тов. Anon Y Mous на путь истинный, т.е. в последние (на тот момент) правки HEAD-ветки. Немного покопавшись в репозитории, нашёл нужную правку[3], в которой была добавлена поддержка той самой "отмотки" транзакций. Но это всё предисловие.

    После обновления, в правке за номером 219089 [3], ZFS до версии 28, в zpool(8) появилась возможность откатываться на несколько транзакций назад при передаче параметра -F. Так что описанный в статье приём восстановления стал обычной рутинной задачей. За подробностями по ссылке на правку или в подсказку, выводимую zpool(8).

    Мораль

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

    Список литературы

    1. Need Help Invalidating Uberblock // http://mail.opensolaris.org/pipermail/zfs-discuss/2008-December/024427.html
    2. ZFS On-Disk Specification // http://opensolaris.org/os/community/zfs/docs/ondiskformat0822.pdf
    3. FreeBSD repo revision 219089 // http://svnweb.freebsd.org/base?view=revision&revision=219089



    размещено: 2011-02-04,
    последнее обновление: 2012-02-02,
    автор: BlackCat


    kazak, 2011-01-22 в 12:15:07

    Статья хорошая, а мог бы ты описать сценарий действий штатными средствами?

    BlackCat, 2011-01-23 в 21:53:26

    Штатными средствами ZFS сама себя восстанавливает без внешнего вмешательства. Если только диск не вышел из строя. В этом случае меняем диск.

    Black Cat, 2011-02-04 в 13:25:17

    Слуяайно у нас одинаковые ники
    Статья, мега
    Проделана серьезнаю работа

    Очень было интересно почитатать

    Аноним, 2011-02-04 в 14:25:18

    Вот и зачем тащить весь этот Ынтырпрайз, к тому же ещё нестабильный, в сохо-сектор? Не понимаю.

    BlackCat, 2011-02-04 в 15:07:11

    Тов. Аноним есть такое подозрение, что вы меня не совсем правильно поняли. Сама статейка - это доказательство (prof of concept) возможности восстановления руками. О конкретных показателях надёжности я даже не пытался сделать никаких выводов - это сложная тема, требующая большого объёма исследований и выходящая за рамки моей компетенции. В данном конкретном случае ZFS начала сбоить из-за глючного железа и сбоя питания, а не ошибок в коде реализации и просчётов главного архитектора ФС. После замены диска и пересоздания пула и до настоящего момента - ни одной ошибки, но это то же не показатель.
    Если же говорить о теоритической возможности выхода из строя, то да, она существует. Но с таким же успехом можно раскритиковать устройство вселенной, т.к. существует теоритическая возможность прекращения её существования.
    А если серьёзно. Есть желание убедить сообщество в том, что время ZFS ещё не пришло или ей не место в том или ином классе систем хранения, интересно самому разобраться - создайте тему на форуме, привидите аргументы, там и подискутируем.

    GrossHo, 2011-02-04 в 16:36:01

    После прочтения я стал чувствовать свою кожу более сухой и шелковистой, что стало дополнительным плюсом чтобы использовать ZFS на своих серверах. Есть одно замечание не касающееся проделанной работы, а одного малоинтересного факта: для x86 и amd64 характерен Little Endian, тогда как для Sparc - Big Endian. Еще раз спасибо за интересную статью.

    BAZ Megodriver, 2011-02-04 в 18:34:09

    Очень правильная мораль....
    Она всегда верна.

    MarvinFS, 2011-02-04 в 19:04:54

    Мега респект!!!! просто душа поёт за наших отечественных специалистов которые еще могут победить "систему"! Спасибо!

    Hubbitus, 2011-02-04 в 19:36:51

    Мега-статья!

    Еще бы на основе наработок и опыта написать бы какую-то утилитку для более-менее автоматического использования другими. Было бы вообще супер.

    Morty, 2011-02-07 в 10:33:58

    статья отличная !! понравилось
    Автору респект

    Bx, 2011-02-25 в 21:18:36

    А чем плохи номера транзакций последних, выдаваемых "zdb -l"? Чисто теоретически - мог бы сработать откат на меньшую из, вероятно.

    sage444, 2011-03-04 в 9:43:20

    Где-то в рассылке встречал упоминания о чем-то похожем, но тут все очень детально и понятно.
    автору респект!

    BlackCat, 2011-03-11 в 23:30:07

    Bx, zdb(8) с параметром -l выдаёт номер транзакции в которой была записана метка (label), а не номер последней транзакции, произведённой в пуле.
    Насчёт отката к предыдущей транзакции: так о нём и шла речь.

    winterheart, 2011-03-14 в 8:27:09

    Статья хорошая, но IMHO, ситуация - суета вокруг рояля. Надо было делать дампы/бекапы изначально. Вспоминаем старую поговорку сисадминов.

    playnet, 2011-04-05 в 0:46:04

    Выпал 1 диск из 5 рейда (по сути) и смерть всех данных? Как-то оно вообще ненадежно тогда. Смерть при потере 1 диска допустима исключительно для страйпа!
    Остальные диски были нормальные или тоже с бэдами?

    BlackCat, 2011-04-05 в 0:51:31

    playnet, обратите внимание на мой коментарий от 2011-02-04.

    Мфынф, 2011-07-09 в 8:53:20

    Ахуеть , дайте две.
    Тока нах он такое нужен zfs если один диск выпал и куку.

    BlackCat, 2011-11-23 в 22:55:57

    Обновил статью, добавив раздел "Год спустя". В zpool(8) добавили параметр, позволяющий выполнять откат на несколько транзакций назад.

    Пострадавший, 2011-11-25 в 17:39:59

    Вы бы оставили свои правки для spa.c :)

    BlackCat, 2011-11-26 в 1:44:21

    Какие?!! Если вы имеете ввиду отладочный вывод - то в нём нет ничего необычного: перед ключевыми точками ветвления ставиться функция отладочного вывода, через которую выводим значения параметров участвующих в условии перехода.

    BlackCat, 2012-02-02 в 12:19:27

    Наконец-то понял, какую ошибку имел ввиду GrossHo: были неправильно указаны названия для порядка байт. Правильные названия, как уже было указано: для x86 и amd64 характерен Little Endian, тогда как для Sparc - Big Endian.
    GrossHo, спасибо.

    chpoqxie, 2012-03-04 в 22:58:17

    Я в свое время, когда убедился в бесплодности попыткой поднять упавший raidz на четырех винтах под фряхой, загрузился с инсталл-цд солярки x86, и она прекрасно подцепила пул, пофиксила его, и после этого фряха нормально с ним общается.

    имхо, не стоит юзать zfs под fbsd для критичной инфы. по крайней мере, полгода назад точно не стоило. а в конце 2010-начале 2011 - ууу. автор - камикадзе.

    Karharot, 2013-09-01 в 0:13:48

    http://www.linux.org.ru/forum/general/9530626



  •  

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

    © lissyara 2006-10-24 08:47 MSK

    Время генерации страницы 0.1194 секунд
    Из них PHP: 60%; SQL: 40%; Число SQL-запросов: 77 шт.
    У Вас отключено GZIP-сжатие в браузере. Размер страницы 100259