Восстановление данных и лечение HDD с помощью dd во FreeBSD

Если начал сбоить диск, если на нём обнаружились не читаемые блоки, если его содержимое имеет значение, значит эта статья должна помочь для решения этих проблем.

Для начала нужно немедленно отмонтировать все разделы диска во избежание потери данных. Впрочем, у вас наверняка есть их резервная копия. Ведь есть, правда? ;) Помните, до тех пор пока данные не хранятся в трёх разных местах, их вообще не существует! Нравоучения закончились, теперь можно приступать к диагностике и лечению. Перво-наперво посмотрим SMART подозрительного диска на предмет подтверждения своих подозрений. А лучше сразу запустить само-диагностику HDD, выполнив команду smartctl -t long /dev/ad6, а потом уже смотреть информацию смарта. Для этого воспользуемся утилитой smartctl. Она выдаёт много интересного, но вот самое интересное:

  1. gva# smartctl -A /dev/ad6
  2. ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE       UPDATED   WHEN_FAILED RAW_VALUE
  3.   5 Reallocated_Sector_Ct   0x0033   100   100   036    Pre-fail   Always        -       0
  4. 197 Current_Pending_Sector  0x0012   100   096   000    Old_age    Always        -       169
  5. 198 Offline_Uncorrectable   0x0010   100   096   000    Old_age    Offline       -       169

Как видим, на диске пока не появилось бэд-блоков, однако появились подозрительные блоки в количестве 169 штук, которые были выявлены в ходе самодиагностики винта. Это блоки диска, при чтении которых произошёл какой-то сбой, о котором и в самом смарте написано и можно почитать в логах. Вот отрывок /var/log/messages:

  1. Jan 14 22:03:17 gva kernel: (ada1:ahcich1:0:0:0): READ_DMA48. ACB: 25 00 2f d1 c1 40 9f 00 00 00 20 00
  2. Jan 14 22:03:17 gva kernel: (ada1:ahcich1:0:0:0): CAM status: ATA Status Error
  3. Jan 14 22:03:17 gva kernel: (ada1:ahcich1:0:0:0): ATA status: 51 (DRDY SERV ERR), error: 40 (UNC )
  4. Jan 14 22:03:17 gva kernel: (ada1:ahcich1:0:0:0): RES: 51 40 38 d1 c1 00 9f 00 00 00 00
  5. Jan 14 22:03:17 gva kernel: (ada1:ahcich1:0:0:0): Retrying command
  6. Jan 14 22:03:21 gva kernel: (ada1:ahcich1:0:0:0): READ_DMA48. ACB: 25 00 2f d1 c1 40 9f 00 00 00 20 00
  7. Jan 14 22:03:21 gva kernel: (ada1:ahcich1:0:0:0): CAM status: ATA Status Error
  8. Jan 14 22:03:21 gva kernel: (ada1:ahcich1:0:0:0): ATA status: 51 (DRDY SERV ERR), error: 40 (UNC )
  9. Jan 14 22:03:21 gva kernel: (ada1:ahcich1:0:0:0): RES: 51 40 38 d1 c1 00 9f 00 00 00 00
  10. Jan 14 22:03:21 gva kernel: (ada1:ahcich1:0:0:0): Retrying command
  11. Jan 14 22:03:25 gva kernel: (ada1:ahcich1:0:0:0): READ_DMA48. ACB: 25 00 2f d1 c1 40 9f 00 00 00 20 00
  12. Jan 14 22:03:25 gva kernel: (ada1:ahcich1:0:0:0): CAM status: ATA Status Error
  13. Jan 14 22:03:25 gva kernel: (ada1:ahcich1:0:0:0): ATA status: 51 (DRDY SERV ERR), error: 40 (UNC )
  14. Jan 14 22:03:25 gva kernel: (ada1:ahcich1:0:0:0): RES: 51 40 38 d1 c1 00 9f 00 00 00 00
  15. Jan 14 22:03:25 gva kernel: (ada1:ahcich1:0:0:0): Retrying command
  16. Jan 14 22:03:29 gva kernel: (ada1:ahcich1:0:0:0): READ_DMA48. ACB: 25 00 2f d1 c1 40 9f 00 00 00 20 00
  17. Jan 14 22:03:29 gva kernel: (ada1:ahcich1:0:0:0): CAM status: ATA Status Error
  18. Jan 14 22:03:29 gva kernel: (ada1:ahcich1:0:0:0): ATA status: 51 (DRDY SERV ERR), error: 40 (UNC )
  19. Jan 14 22:03:29 gva kernel: (ada1:ahcich1:0:0:0): RES: 51 40 38 d1 c1 00 9f 00 00 00 00
  20. Jan 14 22:03:29 gva kernel: (ada1:ahcich1:0:0:0): Retrying command
  21. Jan 14 22:03:33 gva kernel: (ada1:ahcich1:0:0:0): READ_DMA48. ACB: 25 00 2f d1 c1 40 9f 00 00 00 20 00
  22. Jan 14 22:03:33 gva kernel: (ada1:ahcich1:0:0:0): CAM status: ATA Status Error
  23. Jan 14 22:03:33 gva kernel: (ada1:ahcich1:0:0:0): ATA status: 51 (DRDY SERV ERR), error: 40 (UNC )
  24. Jan 14 22:03:33 gva kernel: (ada1:ahcich1:0:0:0): RES: 51 40 38 d1 c1 00 9f 00 00 00 00
  25. Jan 14 22:03:33 gva kernel: (ada1:ahcich1:0:0:0): Error 5, Retries exhausted
  26. Jan 14 22:03:33 gva kernel: g_vfs_done():ada1s1a[READ(offset=1372302983168, length=16384)]error = 5

Паниковать не стоит, есть миллион причин почему это могло случиться. Это ещё не означает, что диск имеет физические повреждения и подлежит замене. Но всякое может быть, поэтому поищем ещё не выявленные ошибки. Следует проверить диск полностью на предмет ещё не выявленный сбойных секторов. Диск это может сделать собственными силами, но есть ещё один, более удобный способ, — утилитой dd. Здесь и далее приведён листинг лишь для двух ошибок, поскольку листинг всех ошибок будет занимать неприлично много места:

  1. gva# dd if=/dev/ad6 of=/dev/null bs=65536 conv=noerror
  2. dd: /dev/ad6: Input/output error
  3. 20939144+0 records in
  4. 20939144+0 records out
  5. 1372267741184 bytes transferred in 13768.709737 secs (99665674 bytes/sec)
  6. dd: /dev/ad6: Input/output error
  7. dd: /dev/ad6: Input/output error
  8. 20939630+0 records in
  9. 20939630+0 records out
  10. 1372301295616 bytes transferred in 14014.402646 secs (97920784 bytes/sec)
  11. dd: /dev/ad6: Input/output error
  12. 22892776+1 records in
  13. 22892776+1 records out
  14. 1500300992512 bytes transferred in 15608.577596 secs (96120289 bytes/sec)

Как видно из листинга, с помощью утилиты dd мы спровоцировали не читаемые блоки заявить о себе. Параметр conv=noerror говорит программе, что следует продолжать работу не смотря на встреченную ошибку. Хотя размер блоков на диске 512 байт, для ускорения работы программы был задан размер блока 65536 байт (bs=65536). Можно задать и другой размер, который покажет наибольшую скорость в вашем случае. Обычно оптимальный размер блока подбирается экспериментальным путём. Таким образом мы выявим не сам сбойный 512-байтовый блок, а группу блоков, среди которых и находится сбойный. При необходимости можно уточнить поиск с меньшим размером блоков, благо, где искать мы уже знаем, так что много времени это не займёт. Но пока такой необходимости нет. Зато есть необходимость выявить номер 512-байтового блока, а точнее его LBA-адрес — именно он понадобится в дальнейшем. Будем вычислять его по формуле:


бн = Бн * Бр / бр - 1
бн = 20939144 * 65536 / 512 - 1 = 2680210431


где

  • бн — номер искомого 512-байтового блока диска (2680210431),
  • Бн — номер 65536-байтового блока, который выдала dd (20939144),
  • Бр — размер блока, который выдала dd (65536),
  • бр — размер блока, номер которого мы ищем (512).
  • - 1 — смещение количества блоков, относительно нумерации LBA.

Таким образом мы вычисляем номера физических блоков для всех сбойных блоков, номера которых нам указал dd. Я не знаю номера это именно не прочитанных блоков или последних прочитанных, поэтому, на всякий случай, к вычисленным номерам добавлю ещё набор этих же номеров, увеличенных на единицу. Кроме того самый последний номер блока в выдаче dd указывает не на сбойный блок, а на последний, поэтому его мы в расчёт не берём.

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

  1. gva# bsdlabel /dev/ad6s1
  2. # /dev/ad6s1:
  3. 8 partitions:
  4. #          size     offset    fstype   [fsize bsize bps/cpg]
  5.   a: 2930277089         16    unused        0     0
  6.   c: 2930277105          0    unused        0     0     # "raw" part, don't edit

Чтобы понять, в каком разделе находится блок, нужно в колонке offset найти строку с меньшим наиболее близким к номеру блока числом. К примеру, если блок имеет номер 2680210431, значит наиболее близкое к нему число в колонке offset — 16, что означает, что блок находится в разделе a. И это не удивительно, поскольку в примере всего один раздел. :) Полный путь к разделу — /dev/ad6s1a, его мы и монтируем. Сделаем это в режиме чтения во избежание всяческих неожиданностей:

  1. gva# mount -o ro -t ufs /dev/ad6s1a /mnt

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

  1. gva# fsdb -r /dev/ad6s1a
  2. ** /dev/ad6s1a (NO WRITE)
  3. Examining file system `/dev/ad6s1a'
  4. Last Mounted on /usr/home/disk
  5. current inode: directory
  6. I=2 MODE=40755 SIZE=512
  7.         BTIME=Jan 8 00:52:13 2011 [0 nsec]
  8.         MTIME=Jul 16 23:39:29 2013 [0 nsec]
  9.         CTIME=Jul 16 23:39:29 2013 [0 nsec]
  10.         ATIME=Jan 12 03:13:30 2014 [0 nsec]
  11. OWNER=gva GRP=gva LINKCNT=10 FLAGS=0 BLKCNT=4 GEN=3e80ec28
  12. fsdb (inum: 2)> findblk 2680210432
  13. 2680210432: data block of inode 165523470
  14. fsdb (inum: 2)> findblk 2680210560
  15. 2680210560: data block of inode 165523470
  16. fsdb (inum: 2)> findblk 2680272640
  17. 2680211968: data block of inode 165523470
  18. fsdb (inum: 2)> findblk 2680272768
  19. 2680212096: data block of inode 165523470
  20. fsdb (inum: 2)> q

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

Теперь, когда известен инод повреждённого файла, можно найти путь к файлу с помощью программы find:

  1. gva# find /mnt -inum 165523470
  2. /mnt/private/xxx/porno/video.file

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

  1. gva# dd if=/mnt/private/xxx/porno/video.file of=/usr/home/gva/video.file bs=512 conv=noerror,sync
  2. dd: /mnt/private/xxx/porno/video.file: Input/output error
  3. 4938784+0 records in
  4. 4938784+0 records out
  5. 2528657408 bytes transferred in 255.684458 secs (9889758 bytes/sec)
  6. dd: /mnt/private/xxx/porno/video.file: Input/output error
  7. dd: /mnt/private/xxx/porno/video.file: Input/output error
  8. 4938785+0 records in
  9. 4938785+0 records out
  10. 2528657920 bytes transferred in 316.122989 secs (7998969 bytes/sec)
  11. dd: /mnt/private/xxx/porno/video.file: Input/output error
  12. 70658472+0 records in
  13. 70658472+0 records out
  14. 36177137664 bytes transferred in 15758.371732 secs (2295741 bytes/sec)

Как понятно из листинга, мы скопировали повреждённый файл в другое место, игнорируя ошибки чтения (noerror) и заполняя непрочитанные места нулями (sync). Так же был выбран самый маленький размер блока в 512 байт (bs=512) для более точного выявления сбойных секторов и более полной копии.

Если вдруг при копировании не возникло ошибок, значит был неправильно рассчитан сбойный физический блок. Требуется более подробный поиск. Его снова осуществим утилитой dd, но теперь укажем ей конкретные блоки для чтения. Для этого воспользуемся такой формулой:


бк = Бр / бр
бк = 65536 / 512 = 128


где

  • бк — количество 512-байтовых блоков, в которых следует уточнить поиск (128),
  • Бр — размер блока, который выдала dd при грубом поиске (65536),
  • бр — размер блока, количество которых мы ищем (512).

Теперь можно уточнить поиск вот такой командой:

  1. gva# dd if=/dev/ad6 of=/dev/null bs=65536 conv=noerror count=128

Опция count подскажет dd сколько блоков прочитать. В результате мы получим уже точные номера физических блоков, в которых определим иноды, по которым определим имена файлов, которые скопируем в безопасное место.

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

Нужно затереть сбойные блоки, чтобы спровоцировать контроллер винчестера их перераспределить, либо, чтобы он убедился, что они в порядке. Прежде, чем заняться лечением диска, его нужно перемонтировать в режиме записи, но FreeBSD не даст этого сделать, так-как на диске выявлены проблемы, и потребует его проверить. Так и поступим. Ведь все нужные данные забекаплены и бояться нечего.

  1. gva# umount /mnt
  2. gva# fsck -y -f -t ufs /dev/ad6s1a
  3. gva# mount -t ufs /dev/ad6s1a /mnt

Так-как затирать не читаемые блоки по номерам лениво, а файл с ними всего один, будем затирать повреждённый файл. dd сама разберётся что к чему:

  1. gva# dd if=/dev/zero of=/mnt/private/xxx/porno/video.file bs=512
  2. /mnt: write failed, filesystem is full
  3. dd: /mnt/private/xxx/porno/video.file: No space left on device
  4. 644384545+0 records in
  5. 644384544+0 records out
  6. 329924886528 bytes transferred in 5154.050639 secs (64012737 bytes/sec)

Судя по листингам, восстанавливаемый файл при копировании имел размер 36177137664 байт, а после заполнения нулями распух в десять раз — до 329924886528 байт… Это вовсе не потому, что я забыл указать опцию count. Дело в том, что при записи в файл совсем не обязательно данные должны писаться в те же блоки, где была предыдущая версия файла. Так что, плохие блоки могли бы и не быть затёртыми. Поэтому нужно перезаписать нулями и свободное место в профилактических целях. Благо у меня его не много. Если свободного места много, следует всё же заморочиться с затиранием конкретных блоков по номерам, быстрее будет. Устройство /dev/zero — файл бесконечного размера и его копирование в другой файл приведёт к тому, что целевой файл будет расти бесконечно, пока не исчерпается свободное место на диске. Это и произошло. Профилактические работы прошли успешно. :)

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

  1. gva# dd if=/mnt/private/xxx/porno/video.file of=/dev/null bs=65536 conv=noerror
  2. 5034254+1 records in
  3. 5034254+1 records out
  4. 329924886528 bytes transferred in 3758.004039 secs (87792584 bytes/sec)

Как видим, ошибок больше не возникает. Это означает, что лечение помогло. Заглянем в SMART.

  1. gva# smartctl -A /dev/ad6
  2. ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE       UPDATED   WHEN_FAILED RAW_VALUE
  3.   5 Reallocated_Sector_Ct   0x0033   100   100   036    Pre-fail   Always        -       0
  4. 197 Current_Pending_Sector  0x0012   100   096   000    Old_age    Always        -       0
  5. 198 Offline_Uncorrectable   0x0010   100   096   000    Old_age    Offline       -       0

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

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

Примечания

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

Чтобы не выполнять предыдущую рекомендацию, напишите скрипт, который всё это автоматизирует. ;)

Если вас что-то смутило в процессе восстановления, не поленитесь перепроверить диск ещё раз.

Название восстанавливаемого файла в статье изменено с целью поисковой оптимизации. :)

Комментарии

Комментировать