Pull to refresh

Умирающий хард Hitachi, bash и технонекрофилия

Reading time 6 min
Views 24K

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


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


Однажды знакомые попросили помочь разобраться с ноутом, на который они не смогли переустановить Windows. У ноута, судя по виду, была тяжелая жизнь: трещины корпуса, помяты углы, выломаны стойки. Понятно — проблема в повреждении жесткого диска в результате многочисленных ударов, что подтвердил и смарт: более 200 срабатываний G-sensor'а, 500 Reallocated Sector Count и еще есть в Current Pending'е. Ну людям, понятное дело, я поставил SSD, а информацию с их винта скопировал в образ командой:


dd if=/dev/sdb of=/media/hddimages/ht320.img bs=409600 conv=noerror,notrunc,sync

Параметры "conv=noerror,notrunc,sync" нужны, чтоб в случае ошибок чтения определенных секторов, в выходном файле по этим адресам записывались нули, а данные записывались на свое место без смещения.


Бывает, что при чтении большими блоками (400кб) диск не читает весь блок, а меньшими — не считывается лишь 1 сектор. Секторы здесь по 4кб, поэтому после первого прохода dd, если были ошибки чтения, я пробую повторно читать эти участки блоками по 4кб:


n=<отступ>;dd if=/dev/sdb of=/media/hddimages/ht320.img bs=4096 conv=noerror,notrunc,sync skip=$n seek=$n count=100

Параметры skip и seek нужны, чтоб чтение и запись начинались с одинаковым отступом от начала диска. Сам отступ берется из вывода первого выполнения dd, только для соответствия размеру блока, число умножаем на 100.


Иногда диски при обращении к сбойным секторам надолго зависают, да так, что помогает только переподключение к питанию и лет 5 назад был сделан, громко говоря, программно-аппаратный комплекс (с микроконтроллером даже) для автоматизированного чтения сбойных хардов с автоматическим переподключением питания в случае слишком долгого отсутствия ответа. Это было интересно и позволяло, подключив хард и введя команду, дней через 10 получить максимально полный образ. Но подопытный герой статьи не зависал намертво, поэтому необходимости доставать описанный тяжелый костыль не было.


Итак, диск считался, монтирую все разделы образа через losetup с оффсетами начала разделов из fdisk'а, умноженных на размер логического блока в мбр — 512 байт, копирую все данные людям на новый ССД. Если бы диск не монтировался или многие файлы не читались — я бы открыл образ программой R-Studio и восстанавливал бы через нее, но именно с образа.


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


Для начала пробую wipe'нуть диск (dd if=/dev/zero...) и читать после этого: скорость также нестабильна, диск подвисает и иногда выпадает input/output error, но в смарте растет количество релоков и пендингов. Через несколько циклов смарт особо меняться перестал, пендинги не релочатся, а подвисания с ошибками встречаются каждый раз в тех же местах или очень рядом. Пробую принудительно ремапить вручную командой "hdparm --make-bad-sector", но на данной модели это не работает и мне приходит осознание, что просто стирание-чтение, как и запись-чтение не сможет проявить все проблемные места. Ведь если поврежденный бит, вне зависимости от того, что именно в него пытались записать, с большей вероятностью читается как "1", то при записи в него "1", последующее чтение будет проходить без ошибок, но при записи иного паттерна — может набраться достаточно несоответствий, чтоб не справилось ECC и случилась невосстановимая ошибка чтения и через нескольких таких случаев, сектор получил статус "Bad". Кстати, записываемое значение может так накладываться на распределение поврежденных битов, что считанное неправильное значение даже удовлетворит ECC. Поэтому для максимального выявления всех плохих секторов нужно генерировать относительно случайный паттерн, записывать его на диск, считывать и сравнивать значение. Встречаются также нестабильные сектора, которые меняют свои значения постепенно за некоторое время или после обработки его соседей.


С учетом всего перечисленного, я решил реализовать в bash-скрипте следующую стратегию:


  • генерируем случайный паттерн и считаем для него контрольную сумму;
  • читаем смарт;
  • записываем диск нолями;
  • читаем диск;
  • записываем диск случайным паттерном с чтением только что записанного блока и сравнением его чек-суммы;
  • читаем диск после полной записи, проверяя чек-суммы каждого блока;
  • читаем смарт;
  • self-test;
  • goto 1.

Продолжаем так, пока не перестанут случаться неверно считанные сектора и IO-error'ы или пока винт не накроется окончательно. Кстати, как работает self test у данной модели диска, я не представляю; не знаю, чем отличается long от short'а (хотя вероятно лонг работает со всей поверхностью, а шорт — ориентируясь на ранеесобранную статистику, как при форматировании: полное и быстрое). Я надеюсь, что это подтолкнет винт принять во внимание недавний опыт и ремапить сбойные сектора.


Когда я закончил писать bash-скрипт, запустил его и на следующий день проверил результаты — увидел, что очень медленно работает верификация, при этом загрузка процессора не доходит и до 60% ни на одном ядре. Это сподвигло меня поиграться с размером блока, протестировать разные алгоритмы хэша для контрольных сумм, попробовать прямую верификацию diff'ом, а не сравнивание контрольных сумм, но скорости обработки выше 12 мегабайт в секунду достигнуть я так и не смог. В итоге остановился на сравнении diff'ом блоков по 400кб, а контрольные суммы вычисляю только в случае несовпадения лишь для последующего анализа лога.


Скрипт получился такой:
#!/bin/bash
# формат строки запуска hddtest.sh diskdev logfile [blocksize] 

diskdev="$1";
test_log="$2"; #"~/work/hdd/test.log"
blsz="${409600:-$3}";
n="1";
sizebyte=`fdisk -l "$diskdev"|grep "Disk $diskdev:"|cut -d" " -f5`;
let sizebl=$sizebyte/$blsz; #"781428" for 320GB

while true;do
 echo "starting iteration $n";
 dd if=/dev/urandom of=fil bs="$blsz" count=1;
 md5ok=`md5sum fil|cut -d" " -f1`;
 cp fil fil_"$n";
 echo "random pattern md5sum $md5ok">>"$test_log";
 smartctl -A "$diskdev">>"$test_log";
 echo "filling disk with zeroes">>"$test_log";
 dd if=/dev/zero of="$diskdev" bs="$blsz"; #count="$sizebl";
 echo "disk is wiped fully">>"$test_log";
 dd of=/dev/null if="$diskdev" bs="$blsz"; # count="$sizebl";
 echo "writing disk with fil-pattern">>"$test_log";
 i="0";
 while [ "$i" -le "$sizebl" ]; 
 do 
  #echo "writing fil: $i ">>"$test_log";
  dd if=fil of="$diskdev" bs="$blsz" seek="$i";
  dd if=/dev/null of=tst;
  dd if="$diskdev" bs="$blsz" of=tst skip="$i" count=1 conv=notrunc,noerror,sync;
  #md5tst=`md5sum tst|cut -d" " -f1`;
  verf=`diff -s fil tst|sed 's/.* //g'`;
  if [ "$verf" != "identical" ];
  #if [ "$md5ok" != "$md5tst" ]; 
  then 
   md5tst=`md5sum tst|cut -d" " -f1`;
   echo "$i : md5 $md5tst is not ok">>"$test_log"; 
   cp tst tst_"$n"_"$i";
  fi;
  let i="$i"+1;
 done;
 echo "test of full writed with fil-pattern disk">>"$test_log";
 i="0";
 while [ "$i" -le "$sizebl" ]; 
 do
  #echo "after writing test: $i">>"$test_log";
  dd if=/dev/null of=tst;
  dd if="$diskdev" bs="$blsz" of=tst skip="$i" count=1 conv=notrunc,noerror,sync;
  #md5tst=`md5sum tst|cut -d" " -f1`;
  verf=`diff -s fil tst|sed 's/.* //g'`;
  if [ "$verf" != "identical" ];
  #if [ "$md5ok" != "$md5tst" ]; 
  then 
   md5tst=`md5sum tst|cut -d" " -f1`;
   echo "$i : md5 $md5tst is not ok">>"$test_log"; 
   cp tst tst_"$n"_"$i";
  fi;
  let i="$i"+1;
 done;
 smartctl -A "$diskdev" >>"$test_log";
 smartctl -t long "$diskdev">>"$test_log";
 sleep 5000;
 #smartctl -t short "$diskdev">>"$test_log";
 #sleep 240;
 let n="$n"+1;
done

Как после многократного выполнения скрипта показали логи, все сбойные сектора находились в первых 13 ГБ диска, там было несколько "очагов" поражения (вероятно, при ударе головкой побило-поцарапало поверхность). Последние 15 прогонов диск не видел подозрительных (pending) секторов, всё уже было как бы ремаплено, но где-то посредине 13-го Гигабайта периодически неверно считывался один блок или блоки недалеко от него по разным адресам. Причем, один блок мог 2 цикла подряд считаться неверно, затем 2 раза верно и снова раз неверно. Так что отловить последние 10 сбойных секторов — была долгая операция. Всего было ремаплено 1268 секторов! И в конце меня ждал сюрприз: когда всё уже работало стабильно, после очередного self-test'а параметр Reallocated Sector Count стал "0" и о проблемах напоминал только счетчик событий Reallocated Event Count и записи о последних 5 ошибках (с адресом и временем от начала работы), хранимых в журнале.


Несмотря на стабильную работу, я все же решил минимизировать взаимодействие с поврежденной областью, чтоб не травмировать головку о возможные неровности в местах с поврежденной поверхностью пластин, да и местным секторам в долгосрочной перспективе доверять не хотелось. Я просто отступил немного с запасом и создал раздел, начиная с 15-го Гигабайта. И, как показало время, диск чувствует себя довольно хорошо и стабильно работает в носимом ноутбуке уже 10 месяцев.


Хотя полностью доверять восстановленному диску нельзя и экономическая целесообразность затеи сомнительна, но иногда результат — лишь приятное дополнение к хорошему пути.

Tags:
Hubs:
+37
Comments 48
Comments Comments 48

Articles