When you see kernel messages like these indicating sector read errors:
[ 5733.186033] sd 4:0:0:0: [sdb] Unhandled sense code
[ 5733.186038] sd 4:0:0:0: [sdb] Result: hostbyte=invalid driverbyte=DRIVER_SENSE
[ 5733.186042] sd 4:0:0:0: [sdb] Sense Key : Medium Error [current]
[ 5733.186048] sd 4:0:0:0: [sdb] Add. Sense: Unrecovered read error
[ 5733.186053] sd 4:0:0:0: [sdb] CDB: Read(10): 28 00 05 b7 2e 40 00 00 08 00
[ 5733.186064] end_request: critical target error, dev sdb, sector 95891008
The critical information is the sector number (95891008 in this case) and device (sdb). We need to map this back to actual files.
The most efficient way is using ext filesystem's debug tool:
# First find the block group containing our sector
block_size=$(blockdev --getbsz /dev/sdb)
sector=95891008
block=$((sector * 512 / block_size))
# Then query debugfs
sudo debugfs -R "icheck $block" /dev/sdb
Example output:
Block Inode number
123456 7890
Then find the file path:
sudo debugfs -R "ncheck 7890" /dev/sdb
For cases where debugfs isn't available, here's a more thorough scanner:
#!/bin/bash
target_sector=95891008
device=/dev/sdb
block_size=$(blockdev --getbsz "$device")
target_block=$((target_sector * 512 / block_size))
find /mnt/sdb -type f -print0 | while IFS= read -r -d $'\0' file; do
blocks=$(debugfs -R "stat \"$file\"" "$device" 2>/dev/null | grep "^BLOCKS:" | cut -d: -f2)
[[ "$blocks" == *"$target_block"* ]] && echo "Found in: $file"
done
On newer Linux versions with filefrag support:
# First convert sector to filesystem block
sector=95891008
block_size=4096 # typical for ext4
fs_block=$((sector * 512 / block_size))
# Then scan all files
find /mnt/sdb -type f -exec filefrag -v {} + | grep "block $fs_block"
For bulk errors, modify the script to process multiple sectors:
error_sectors="95891008 95891009 95891100"
for sector in $error_sectors; do
echo "Processing sector $sector"
fs_block=$((sector * 512 / 4096))
debugfs -R "icheck $fs_block" /dev/sdb
done | sort -u | while read block inode; do
[ "$inode" -ne 0 ] && debugfs -R "ncheck $inode" /dev/sdb
done
For advanced verification before filesystem repair:
sudo hdparm --read-sector 95891008 /dev/sdb
Combine with dd for reading multiple sectors:
sudo dd if=/dev/sdb bs=512 skip=95891008 count=8 | hexdump -C
When dealing with disk errors like the one shown in the kernel log:
[ 5733.186033] sd 4:0:0:0: [sdb] Unhandled sense code
[ 5733.186064] end_request: critical target error, dev sdb, sector 95891008
We need an efficient way to map the problematic sector (95891008 in this case) back to the actual file(s) affected, rather than scanning the entire filesystem.
The most efficient method is using debugfs
, which comes with e2fsprogs:
# First get block size and calculate block number
block_size=$(blockdev --getbsz /dev/sdb)
block_number=$((95891008 * 512 / block_size))
# Then use debugfs to find the inode
debugfs -R "icheck $block_number" /dev/sdb
This will output the inode number. Then get file details:
debugfs -R "ncheck $inode_number" /dev/sdb
For a more low-level approach, we can use hdparm
to read the raw sector:
sudo hdparm --read-sector 95891008 /dev/sdb
This helps verify if the sector is truly unreadable, as some errors might be transient.
For a more comprehensive scan of problematic sectors:
sudo badblocks -v -s /dev/sdb > badsectors.txt
Then cross-reference with files:
for sector in $(cat badsectors.txt); do
block=$((sector * 512 / $(blockdev --getbsz /dev/sdb)))
debugfs -R "icheck $block" /dev/sdb | grep -v "Block not found"
done
Here's a script to automate finding all affected files from kernel logs:
#!/bin/bash
device="/dev/sdb"
logfile="/var/log/kern.log"
# Extract all bad sectors from logs
grep "critical target error.*sector" $logfile | \
awk '{print $NF}' | sort -u > bad_sectors.txt
# Process each bad sector
while read sector; do
block_size=$(blockdev --getbsz $device)
block=$((sector * 512 / block_size))
echo "Processing sector $sector (block $block)"
inodes=$(debugfs -R "icheck $block" $device 2>/dev/null | awk '$1 !~ /^Block/ {print $2}')
for inode in $inodes; do
debugfs -R "ncheck $inode" $device 2>/dev/null | grep -v "Inode Pathname"
done
done < bad_sectors.txt > affected_files.txt
- Always work on unmounted filesystems or readonly mounts when possible
- Consider using
ddrescue
before attempting repairs on failing drives - For ext4 filesystems, the
e2fsck -c
option can map bad blocks automatically