Updates: 1. Replacing hdparm --security-erase with hdparm --security-erase-enhanced allowed hexdump to read the entire drive on the next run, despite smartctl reporting no increase in reallocated sectors. 2. See §7 for >2TB HDDs and §8 for another approach using ddrescue.
# fdisk -l /dev/sdX
Disk /dev/sdX: 1.8 TiB, 2000398934016 bytes, 3907029168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
# hdparm --user-master u --security-erase p /dev/sdX
security_password: "p"
/dev/sdX:
Issuing SECURITY_ERASE command, password="p", user=user
# hexdump /dev/sdX
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
hexdump: /dev/sdX: Input/output error
4f5b1000
hexdump reports the byte offset in hexadecimal. There are 512 bytes per sector on this drive as seen in step 0 above. Convert 4f5b1000 to decimal and divide by 512 to pinpoint the errant sector:
#echo $((0x4f5b1000))1331367936 #echo $((1331367936/512))2600328
# hexdump -s 1331367936 -n 1 /dev/sdX
hexdump: /dev/sdX: Input/output error
4f5b1000
(UPDATE: Using hexdump to display one of the last (simulated) sectors on an SSD took almost 13 minutes, while the dd and hdparm methods below were virtually instantaneous.)
# dd if=/dev/sdX bs=512 skip=2600328 count=1 | hexdump
dd: error reading '/dev/sdX': Input/output error
...
# hdparm --read-sector 2600328 /dev/sdX
/dev/sdX:
reading sector 2600328: succeeded
0000 0000 0000 0000 0000 0000 0000 0000
...
The hdparm man page states in part,
hdparm will issue a low-level read (completely bypassing the usual block layer read/write mechanisms) for the specified sector. This can be used to definitively check whether a given sector is bad (media error) or not (doing so through the usual mechanisms can sometimes give false positives).
However, these long-standing bugs may cause inaccurate results:
# hdparm --repair-sector 2600328 --yes-i-know-what-i-am-doing /dev/sdX
/dev/sdX:
re-writing sector 2600328: succeeded
Despite this seeming success, hexdump and dd still failed to read sector 2600328; skipping that sector only led to additional read errors farther along the drive.
# for ((i=0;i<=3907029167;i++)) ; do hdparm --read-sector $i /dev/sdX 2>&1 | grep FAILED ; done
reading sector 6305325: FAILED: Input/output error
...
That is: 1) iterate through all 3907029168 sectors, 2) read each sector with hdparm, 3) combine stderr and stdout (to keep the failed sector number and its error message together), and 4) look for lines containing "FAILED". Optionally, save output to fail.log while maintaining terminal output by adding |& tee -a fail.log after the grep command.
While this returns all unreadable sectors, it does not verify that all readable sectors contain zeros, which we can check by replacing grep with awk,
# for (( i=0; i<=3907029167; i++ )) ; do hdparm --read-sector $i /dev/sdX 2>&1 ; done | awk '!/0000 0000 0000 0000 0000 0000 0000 0000/ && !/succeeded/ && NF && !/\/dev\/sdX:/ || $4~/FAILED/'
excluding lines containing 32 zeros, "succeeded", nothing, and "/dev/sdX:" (in order to return only any non-zero data), and printing lines containing "FAILED" in the fourth column.
Alas, after several days the process was still around two orders of magnitude away from completion.
# badblocks -b 512 -c 16384 -sv -t 0 -o bb.log /dev/sdX
Checking blocks 0 to 3907029167
Checking for bad blocks in read-only mode
Testing with pattern 0x00: done
Pass completed, 615 bad blocks found. (615/0/0 errors)
# while read i ; do hdparm --repair-sector $i --yes-i-know-what-i-am-doing /dev/sdX ; done <bb.log
All 615 sectors reported to have been rewritten successfully.
# while read i ; do hdparm --read-sector $i /dev/sdX ; done <bb.log
A handful of unreadable sectors were still reported, but repairing then reading them individually (as many as 5 or 6 times in a few cases) succeeded at last.
badblocks uses 32-bit block numbers, so scanning past 2TB requires a block size larger than 512 bytes. With larger blocks, each number in bb.log represents a group of logical sectors rather than one sector. You must repair every sector in the group.
For drives with 512-byte logical sectors:
| Drive size | badblocks block size |
Sectors per logged block |
|---|---|---|
| up to 2TB | -b 512 |
1 |
| up to 16TB | -b 4096 |
8 |
| up to 32TB | -b 8192 |
16 |
| up to 64TB | -b 16384 |
32 |
hdparm uses the drive's logical sector numbers. For 512-byte logical-sector drives, convert each badblocks number by multiplying it by N, where N = badblocks block size / 512.
For example, on a 4TB drive using -b 4096, first set:
# N=8
then scan with the corresponding block size:
# badblocks -b 4096 -c 16384 -sv -t 0 -o bb.log /dev/sdX
followed by repairing every sector in every logged group:
# if [[ -z "$N" || "$N" -eq 0 ]]; then
echo "Error: N is unset!" >&2
else
while read -r block; do
for ((offset = 0; offset < N; offset++)); do
hdparm --repair-sector "$((block * N + offset))" \
--yes-i-know-what-i-am-doing /dev/sdX
done
done < bb.log
fi
and finally reading them back:
# if [[ -z "$N" || "$N" -eq 0 ]]; then
echo "Error: N is unset!" >&2
else
while read -r block; do
for ((offset = 0; offset < N; offset++)); do
hdparm --read-sector "$((block * N + offset))" /dev/sdX >/dev/null ||
echo "Sector $((block * N + offset)) still failing" >&2
done
done < bb.log
fi
Verify with the same block size used for the scan:
# badblocks -b 4096 -c 16384 -sv -t 0 /dev/sdX
Some enterprise drives expose true 4096-byte logical sectors:
# blockdev --getss /dev/sdX
4096
Do not use -b 512 on a 4Kn drive. Use -b 4096, so each badblocks block matches one 4096-byte logical sector and each logged number matches one drive LBA.
Scan:
# badblocks -b 4096 -c 16384 -sv -t 0 -o bb.log /dev/sdX
Then use the normal repair and read-back commands from Section 6.
For 4Kn drives larger than 16TB, use -b 8192. Each logged block then covers two 4096-byte sectors, so use the loop method from §7.1 with:
# N=2
In general, for 4Kn drives, N = badblocks block size / 4096.
Use hdparm 9.57 or later, which includes improvements "to better cope with non-512 byte sectors".
ddrescue's -S (sparse) option skips allocation of all-zero blocks (the output filesystem must support sparse files, e.g., ext4, XFS, Btrfs, NTFS), so if the resulting image consumes ~0 bytes of actual disk space, every readable sector was zero. The mapfile quantifies exactly how much couldn't be read and therefore couldn't be verified.
# ddrescue -d -b "$(blockdev --getss /dev/sdX)" -S /dev/sdX zerocheck.img zerocheck.map
-d — direct I/O; bypasses the kernel page cache for more reliable error reporting-b "$(blockdev --getss /dev/sdX)" — sets block size to the drive's logical sector size (typically 512; 4096 for 4Kn drives)-S — sparse output: zero blocks consume no space in zerocheck.imgzerocheck.map — records every unreadable region; ddrescue's default multi-pass strategy skips bad sectors and retries them later-n to skip the scraping phase for a faster initial run on severely degraded drivesdu -h --apparent-size zerocheck.img# logical size (≈ disk capacity)du -h zerocheck.img# actual allocated spaceddrescuelog -t zerocheck.map# rescued / bad-sector / non-tried counts
If ddrescuelog -t zerocheck.map reports any bad sectors, list the affected sectors and test each one individually:
# ddrescuelog -l- -b "$(blockdev --getss /dev/sdX)" zerocheck.map
430109848
438692731
438692734
-l- list blocks with status - (bad-sector) only-b report positions in units of the drive's logical sector size (typically 512), so each number is an LBA that can be passed directly to hdparm. The mapfile itself stores byte offsets in hex, and -b converts them.ddrescuelog -t (e.g., 3 × 512 B = 1536 B)# hdparm --read-sector 430109848 /dev/sdX
0000 means the sector is zero. Repeat for each listed LBA.hdparm --repair-sector, then check again.Verification is complete when every readable sector is sparse (0 bytes allocated) and every listed bad sector reads back as zero. This covers the host-addressable LBA range only. Check for HPA/DCO with hdparm -N and hdparm --dco-identify.
How to get md RAID array to rebuild even if read errors for the hdparm for loop idea
user000001's answer to Brace expansion with variable? for the improved c-style for loop idea
slhck's answer to Send all error messages to a text file? for the STDERR to STDOUT redirect explanation
Byte Commander's answer to How do I save terminal output to a file? for the redirect table
repairbadblocks.sh for the badblocks and hdparm pairing idea
Bruno De Fraine's answer to Looping through the content of a file in Bash for the while loop idea
Is it possible to recover data from a drive overwritten with zeros once?
dd can optionally skip read errors via conv=noerror,sync
created: 2018.06.02, updated: 2026.09.23