Как использовать hdparm, чтобы исправить ожидающий сектор?
SMART указывает на один ожидающий сектор жесткого диска моего сервера. Я прочитал много статей, рекомендующих использовать hdparm для "легкого" принудительного перемещения диска на поврежденный сектор, но я не могу найти правильный способ его использования.
Некоторая информация от моего "smartctl":
Error 95 occurred at disk power-on lifetime: 20184 hours (841 days + 0 hours)
When the command that caused the error occurred, the device was active or idle.
After command completion occurred, registers were:
ER ST SC SN CL CH DH
-- -- -- -- -- -- --
40 51 00 d7 55 dd 02 Error: UNC at LBA = 0x02dd55d7 = 48059863
Commands leading to the command that caused the error were:
CR FR SC SN CL CH DH DC Powered_Up_Time Command/Feature_Name
-- -- -- -- -- -- -- -- ---------------- --------------------
c8 00 08 d6 55 dd e2 00 18d+05:13:42.421 READ DMA
27 00 00 00 00 00 e0 00 18d+05:13:42.392 READ NATIVE MAX ADDRESS EXT
ec 00 00 00 00 00 a0 02 18d+05:13:42.378 IDENTIFY DEVICE
ef 03 46 00 00 00 a0 02 18d+05:13:42.355 SET FEATURES [Set transfer mode]
27 00 00 00 00 00 e0 00 18d+05:13:42.327 READ NATIVE MAX ADDRESS EXT
SMART Self-test log structure revision number 1
Num Test_Description Status Remaining LifeTime(hours) LBA_of_first_error
# 1 Extended offline Completed: read failure 90% 20194 48059863
# 2 Short offline Completed without error 00% 15161 -
С этим "плохим LBA" (48059863) в руках, как я могу использовать hdparm? Какой тип адреса должны иметь параметры "--read-sector" и "--write-sector"?
Если я ввожу команду hdparm --read-sector 48095863 /dev/sda, она считывает и сбрасывает данные. Если эта команда была правильной, я должен ожидать ошибку ввода-вывода, верно?
Вместо этого он сбрасывает данные:
$ ./hdparm --read-sector 48059863 /dev/sda
/dev/sda:
reading sector 48059863: succeeded
4b50 5d1b 7563 a932 618d 1f81 4514 2343
8a16 3342 5e36 2591 3b4e 762a 4dd7 037f
6a32 6996 816f 573f eee1 bc24 eed4 206e
(...)
2 ответа
Если по какой-либо причине вы предпочитаете пытаться очистить эти поврежденные сектора и не заботитесь о существующем содержимом диска, может помочь приведенный ниже фрагмент оболочки. Я проверил это на старом диске Seagate Barracuda, который в любом случае не имеет гарантии. Он может не работать с другими моделями или производителями дисков, но он должен поставить вас на правильный путь, если вам нужно что-то написать. Это уничтожит любой контент, который у вас есть на диске.
Вы можете предпочесть просто запустить badblocks, hdparm Secure Erase (SE) ( https://wiki.archlinux.org/index.php/Securely_wipe_disk) или какой-то другой инструмент, который действительно предназначен для этого. Или даже производитель предоставил такие инструменты, как SeaTools (есть 32-битная версия Linux для предприятий, Google).
Перед тем, как сделать это, убедитесь, что диск полностью не используется / размонтирован. Кроме того, я знаю, пока цикл, никаких оправданий. Это взломать, вы можете сделать это лучше...
baddrive=/dev/sdb
badsect=1
while true; do
echo Testing from LBA $badsect
smartctl -t select,${badsect}-max ${baddrive} 2>&1 >> /dev/null
echo "Waiting for test to stop (each dot is 5 sec)"
while [ "$(smartctl -l selective ${baddrive} | awk '/^ *1/{print substr($4,1,9)}')" != "Completed" ]; do
echo -n .
sleep 5
done
echo
badsect=$(smartctl -l selective ${baddrive} | awk '/# 1 Selective offline Completed: read failure/ {print $10}')
[ $badsect = "-" ] && exit 0
echo Attempting to fix sector $badsect on $baddrive
hdparm --repair-sector ${badsect} --yes-i-know-what-i-am-doing $baddrive
echo Continuning test
done
Одним из преимуществ использования метода "самотестирования" является то, что нагрузка обрабатывается микропрограммой привода, поэтому ПК, к которому он подключен, не загружается, как это было бы при использовании dd или badblocks.
ПРИМЕЧАНИЕ: извините, я допустил ошибку, правильное условие условия:
while [ "$(smartctl -l selective ${baddrive} | awk '/^ *1/{print $4}')" = "Self_test_in_progess" ]; do
И условие выхода скрипта становится:
[ $badsect = "-" ] || [ "$badsect" = "" ] && exit 0
Я думаю, что он мог читать без ошибок, потому что этот сектор не плохой, но другие инструменты не читают сектор из-за какого-то другого поведения. (читать дальше, что достигает фактически нечитаемого сектора?)
Я обнаружил несколько плохих секторов, и если я исправлю только один нечитаемый с помощью "hdparm --read-sector", другие "плохие" сектора внезапно перестанут не читаться такими вещами, как dd. И что интересно, при просмотре вывода "dmesg" сообщается только о нечитаемых hdparm.
например. У меня были сектора с 36589320 по 36589327 и с 36589344 по 36589351, не читаемые с помощью dd, но только 36589326 и 36589345 были нечитаемыми с помощью hdparm --read-sector. Затем я использовал hdparm --write-sector для этих 2, а затем все 16 секторов снова стали читаемыми.
Вот небольшая часть вывода dmesg:
[30152036.527940] end_request: I/O error, dev sda, sector 36589326
[30152077.363710] end_request: I/O error, dev sda, sector 36589345
И информация о диске:
# smartctl -i /dev/sda
...
=== START OF INFORMATION SECTION ===
Device Model: TOSHIBA MK2002TSKB
...
Firmware Version: MT2A
User Capacity: 2,000,398,934,016 bytes [2.00 TB]
Sector Size: 512 bytes logical/physical
...
И, видимо, прошивка этого диска либо неправильно записывает перераспределенные секторы, либо они не были действительно перераспределены, а просто повреждены (как неисправимая ошибка ECC, но поверхность все еще работает, как будто она была вызвана битой гнилью, а не неисправной электроникой или плохие СМИ)
# smartctl -A /dev/sda | egrep "Reallocated|Pending|Uncorrectable"
5 Reallocated_Sector_Ct 0x0033 100 100 050 Pre-fail Always - 0
196 Reallocated_Event_Count 0x0032 100 100 000 Old_age Always - 0
197 Current_Pending_Sector 0x0032 100 100 000 Old_age Always - 0
198 Offline_Uncorrectable 0x0030 100 100 000 Old_age Offline - 0
# smartctl -l error /dev/sda
...
SMART Error Log Version: 1
No Errors Logged
Обратите внимание, я запустил --read-сектор и --write-сектор. Для правильного перераспределения сектора может потребоваться чтение, а не просто запись. Если вы не читаете сначала, он может не знать, что сектор плохой.
На основе ответа @Glenn вы найдете скрипт fixbad по адресу
http://wiki.bitplan.com/index.php/Bad_Block_Howto
по состоянию на 10.09.2020 содержание сценария:
#!/bin/bash
# see http://wiki.bitplan.com/index.php/Bad_Block_Howto
# see https://github.com/hradec/fix_smart_last_bad_sector/blob/master/fix_smart_last_bad_sector.sh
# see https://www.thomas-krenn.com/de/wiki/Analyse_einer_fehlerhaften_Festplatte_mit_smartctl
# WF 2020-10-04
disk=/dev/sdb
mode=short
# verbose
verbose=false
# should commands only be shown?
dry=false
# should write fixes be performed?
fix=false
# range of sectors to modify after bad sector
range=8
# set to sudo if sudo is needed
sudo=sudo
# serial number
serial="-?-"
#ansi colors
#http://www.csc.uvic.ca/~sae/seng265/fall04/tips/s265s047-tips/bash-using-colors.html
blue='\033[0;34m'
red='\033[0;31m'
green='\033[0;32m' # '\e[1;32m' is too bright for white bg.
endColor='\033[0m'
#
# a colored message
# params:
# 1: l_color - the color of the message
# 2: l_msg - the message to display
#
color_msg() {
local l_color="$1"
local l_msg="$2"
echo -e "${l_color}$l_msg${endColor}"
}
#
# error
#
# show an error message and exit
#
# params:
# 1: l_msg - the message to display
error() {
local l_msg="$1"
# use ansi red for error
color_msg $red "Error: $l_msg" 1>&2
exit 1
}
#
# show the usage
#
usage() {
echo "usage: $0 [disk]"
echo " [-c|--check]"
echo " [-d|--dry]"
echo " [-h|--help]"
echo " [-i|--info]"
echo " [[-m|--mode] mode]"
echo " [[-r|--range] range]"
echo " [[-s|--serial [serial]]"
echo " [-t|--test]"
echo " [[-w|--wait [type]]"
echo " [-v|--verbose]"
echo
echo " -h|--help: show this usage"
echo " -c|--check: check the disk"
echo " -d|--dry: dry run - show commands only"
echo " -i|--info: show info about the given disk"
echo " -m|--mode: set mode: default=short"
echo " -r|--range: range of sectors to modify after bad sector"
echo " -s|--serial: get serial number of confirm serial number"
echo " -t|--test: run test for the given type e.g. selective selftest"
echo " -w|--wait: wait for the result of the given testype e.g. selective selftest"
echo " -v|--verbose: set verbose mode"
echo ""
echo "example:"
echo " $0 /dev/sdb -i"
echo ""
echo "for any write operation you need to confirm the serial number"
echo "to get serial number: "
echo " $0 disk -s "
exit 1
}
#
# get a number range from 0 to the given n-1
#
# params
# 1: n
function getRange() {
local l_n="$1"
range=$(python -c "for i in range($l_n): print i,")
echo $range
}
#
# read the result of the smartctl test for the given disk
#
# params
# 1: l_disk: the disk under test e.g. /dev/sdb
# 2: l_type: the type of the test e.g. selective
function readResult() {
local l_disk="$1"
local l_type="$2"
$sudo smartctl -l $l_type $l_disk | egrep "^#?[[:space:]]*[0-9]"
}
#
# show the Result
#
function showResult() {
local l_logline="$1"
local l_logstatus="$2"
if [ "$verbose" == "true" ]
then
echo $l_logstatus:$l_logline
else
echo $l_logline | gawk '
/#/ {
print $0; exit
}
{
status=substr($4,1,9)
progress=$5;
gsub("\\[","",progress);
range=$7
printf("\r%s",progress);
}'
fi
}
#
# wait for the result of a running selftest
#
# param 1: l_disk: the disk under test e.g. /dev/sdb
# param 2: l_type: the type of the test e.g. selective
# param 3: l_wait: number of seconds to wait
#
function waitForResult() {
# example
#=== START OF READ SMART DATA SECTION ===
#SMART Selective self-test log data structure revision number 1
#SPAN MIN_LBA MAX_LBA CURRENT_TEST_STATUS
# 1 7814037167 Self_test_in_progress [90% left] (2564632-2630167)
local l_disk="$1"
local l_type="$2"
local l_wait="$3"
local l_logline=""
local l_logstatus=""
color_msg $blue "Waiting for $l_type test of $l_disk to stop (each dot is $l_wait sec)"
while [ "$l_logstatus" != "Completed" ]; do
l_logline=$(readResult "$l_disk" "$l_type" | egrep "^#?[[:space:]]*1")
l_logstatus=$(echo $l_logline | gawk ' /Completed/ { print "Completed"; }')
showResult "$l_logline" "$l_logstatus"
sleep $l_wait
done
}
#
# get the serial number of the device
#
function getSerialNumber() {
local l_disk="$1"
serial=$($sudo smartctl -i $l_disk | grep "Serial Number" | cut -f 2 -d':')
echo $serial
}
#
# get the blocksize of the given file system
#
function getBlockSize() {
local l_fs="$1"
blocksize=$($sudo tune2fs -l $l_fs | grep "Block size:" | cut -f2 -d':')
echo $blocksize
}
#
# get the partition for the given disk
#
function getPartition() {
local l_disk="$1"
fs=$(mount | grep $l_disk | cut -f1 -d' ')
echo $fs
}
#
# get the start sector for the given disk
#
function getStartSector() {
local l_disk="$1"
local l_fs="$2"
startsector=$($sudo fdisk -l $l_disk | grep $l_fs | cut -f4 -d' ')
echo $startsector
}
#
# get Info about the given disk
#
function getInfo() {
local l_disk="$1"
$sudo smartctl -i $l_disk | egrep "(Model|Serial|Rotation|Sector|Capacity)"
$sudo hdparm -I $l_disk | egrep "(Serial Number|Model)"
fs=$(getPartition $l_disk)
if [ "$fs" != "" ]
then
color_msg $blue "Partition: $fs"
blocksize=$(getBlockSize $fs)
color_msg $blue "Blocksize: $blocksize"
else
color_msg $red "couldn't find mounted partition for $l_disk"
fi
}
#
# geh the current pending sector for the given disk
#
function getCurrentPendingSector() {
local l_disk="$1"
# if msg is empty don't show message but only return the current pending sector count
local l_msg="$2"
psectorline=$($sudo smartctl -A $l_disk | grep Current_Pending_Sector)
psector=0
if [ $? -eq 0 ]
then
if [ "$l_msg" != "" ]; then color_msg $green "$psectorline"; fi
psector=$(echo $psectorline | cut -f 10 -d ' ')
if [ $psector -gt 0 ]
then
if [ "$l_msg" != "" ]; then color_msg $red "Current_Pending_Sector is not zero but $psector"; fi
else
if [ "$l_msg" != "" ]; then color_msg $green "Current_Pending_Sector is zero!"; fi
fi
else
if [ "$l_msg" != "" ]; then color_msg $red "smartctl -A did not output Current_Pending_Sector"; fi
psector=-1
fi
if [ "$l_msg" == "" ]; then echo $psector; fi
}
#
# fix the given bad sector on the given disk with the given range of sectors to fix
#
# param 1: disk e.g. /dev/sdb1
# param 2: defect sector to repair
# param 3: range - range of sectors to repair e.g. 8
#
fixBad() {
local l_disk="$1"
local l_sector="$2"
local l_range="$3"
color_msg $blue "repairing sector $l_sector to $l_sector+$l_range on $l_disk ..."
r=$(getRange $l_range)
for i in $r ; do
let b1=$l_sector+$i
if [ "$dry" == "true" ]
then
echo hdparm --repair-sector $b1 --yes-i-know-what-i-am-doing $l_disk
else
$sudo hdparm --repair-sector $b1 --yes-i-know-what-i-am-doing $disk >> /tmp/smart_repaired.log
fi
done
#tail -n 60 /tmp/smart_repaired.log | grep writing | tail -n 20
#grep '#' /tmp/smart | head -5
#hdparm -I $disk > /tmp/hdparm
}
#
# check the needed software
#
checkSoftware() {
for sw in gawk debugfs fdisk hdparm smartctl tune2fs python $sudo
do
bin=$(which $sw)
if [ $? -eq 0 ]
then
if [ "$verbose" == "true" ]
then
color_msg $green "will use $bin as $sw"
fi
else
error "$0 needs $sw to work please install it"
fi
done
}
#
# run a test for the given disk in the given mode
#
# params
# 1: l_disk: the disk under test e.g. /dev/sdb
# 2: l_mode: the mode of the self test e.g. short/long
function runTest() {
local l_disk="$1"
local l_mode="$2"
color_msg $blue "running $l_mode smartctl test for $l_disk ..."
$sudo smartctl -t $l_mode $l_disk > /tmp/null
}
#
# check the given disk in the given mode
#
function checkDisk() {
local l_disk="$1"
local l_mode="$2"
local l_serial="$3"
fs=$(getPartition $l_disk)
blocksize=$(getBlockSize $fs)
startsector=$(getStartSector $l_disk $fs)
color_msg $blue "checking Current_Pending_Sector count for $l_disk partition $fs blocksize $blocksize startsector $startsector"
getCurrentPendingSector "$l_disk" show
psector=$(getCurrentPendingSector "$l_disk")
if [ $psector -gt 0 ]
then
runTest $l_disk $l_mode
fi
}
#
# check the lba block
#
function lbaCheck() {
local l_disk="$1"
fs=$(getPartition $l_disk)
blocksize=$(getBlockSize $fs)
startsector=$(getStartSector $l_disk $fs)
diskserial=$(getSerialNumber $l_disk)
readResult "$l_disk" selftest | while read line
do
echo $line | grep "read failure" > /dev/null
if [ $? -eq 0 ]
then
if [ "$verbose" == "true" ]
then
echo $line
fi
index=$(echo $line | cut -f2 -d' ')
state=$(echo $line | cut -f3-4 -d ' ')
progress=$(echo $line | cut -f8 -d ' ')
lba=$(echo $line | cut -f10 -d ' ')
if [ "$lba" == "" ]
then
lba=0
fi
if [ "$lba" -gt 0 ]
then
echo $index $state
echo "progress: $progress"
echo "lba: $lba"
# calculate the file system block
fsb=$(gawk -v L=$lba -v S=$startsector -v B=$blocksize 'BEGIN {printf ("%.0f",((L-S)*512/B))}')
echo "file system block: $fsb"
if [ "$fix" == "true" ]
then
if [ "$serial" != "$diskserial" ]
then
color_msg $red "you need to provide the serial number of $l_disk to perform fix operations"
else
fixBad $l_disk $lba $range
fi
fi
fi
fi
done
}
#
# try Fixing bad sectors
#
function tryFix() {
local l_disk="$1"
badsect=$($sudo smartctl -l selective ${baddrive} | gawk '/# 1 Selective offline Completed: read failure/ {print $10}')
[ $badsect = "-" ] && exit 0
echo Attempting to fix sector $badsect on $baddrive
echo hdparm --repair-sector ${badsect} --yes-i-know-what-i-am-doing $baddrive
}
#
# start a check loop on the given drive
#
function checkLoop() {
local baddrive="$1"
badsect=1
while true; do
color_msg $blue "Testing $baddrive from LBA $badsect"
$sudo smartctl -t select,${badsect}-max ${baddrive} 2>&1 >> /dev/null
waitForResult $baddrive selective 5
tryFix $baddrive
color_msg $blue "running next test"
done
}
# make sure the needed software is available
checkSoftware
# commandline option
while [ "$1" != "" ]
do
option=$1
shift
case $option in
-h|--help)
usage
;;
-i|--info)
getInfo $disk
;;
-m|--mode)
if [ $# -lt 1 ]
then
usage
else
mode=$1
shift
fi
;;
-c|--check)
checkDisk $disk $mode $serial
;;
-d|--dry)
dry=true
;;
-l|--loop)
checkLoop $disk
;;
-f|--fix)
fix=true
;;
-r|--range)
if [ $# -lt 1 ]
then
usage
else
range=$1
shift
fi
;;
-s|--serial)
if [ $# -lt 1 ]
then
getSerialNumber $disk
exit 1
else
serial=$1
shift
fi
;;
-t|--test)
runTest $disk $mode
;;
-v|--verbose)
verbose=true
;;
-w|--wait)
if [ $# -lt 1 ]
then
usage
else
type=$1
shift
waitForResult $disk $type 5
fi
;;
-x)
lbaCheck $disk $serial;;
*)
disk=$option
;;
esac
done