Reverse lookup of inode/file from offset in raw device on linux and ext3/4?
Solution 1:
I just had to do a similar thing, so I thought I'd share my solution.
You can see which partition a drive byte offset belongs to by checking the 'offset' and 'size' elements of the udisks --show-info output; e.g.
user@host:~$ sudo udisks --show-info /dev/sda1 | grep -i 'offset'
offset: 1048576
alignment offset: 0
Subtract this offset from the disk offset to get the byte offset into the partition. So disk offset (10000000) in /dev/sda is partition offset (10000000 - 1048576) = 8951424 in /dev/sda1
You can find out how large blocks are in a partition using the following command:
user@host:~$ sudo tune2fs -l /dev/sda1 | grep -i 'block size'
Block size: 4096
Divide the partition byte offset by the block size to determine the block offset, in this case 8951424 / 4096 = 2185
Run the following command to find out what inode occupies that block:
user@host:~$ sudo debugfs -R "icheck 2185" /dev/sda1
debugfs 1.41.11 (14-Mar-2010)
Block Inode number
2185 123456
then the following command to find out what the filename is for that inode:
user@host:~$ sudo debugfs -R "ncheck 123456" /dev/sda1
debugfs 1.41.11 (14-Mar-2010)
Inode Pathname
123456 /tmp/some-filename.txt
There's a longer description of how this at http://www.randomnoun.com/wp/2013/09/12/determining-the-file-at-a-specific-vmdk-offset
Solution 2:
Greg Knox's answer is correct, but could be easier. I've written a shell script, lba2file, which performs all the arithmetic for you, source code below.
[Update: Script no longer depends on udisks
binary].
Example usage of lba2file
Solving the problem posed in the question (with address specified in bytes):
kremvax$ sudo lba2file -b 1000000 /dev/sda
Disk Byte 1000000 is at filesystem block 124744 in /dev/sda1
Block is used by inode 21762939
Searching for filename(s)...
Inode Pathname
21762939 /home/lilnjn/backups/adhumbla_pics_2.zip
Example usage with S.M.A.R.T.
If your hard drive has a bad sector, you may want to find out what file is corrupted before you remap the sector by writing zeros to it. You can do so easily using smartctl
and lba2file
.
kremvax$ sudo smartctl -C -t short /dev/sdd
kremvax$ sudo smartctl -a /dev/sdd | grep '^# 1'
# 1 Short captive Completed: read failure 90% 20444 1218783739
The final number 1218783739
is the disk address in sectors, not bytes:
kremvax$ sudo lba2file 1218783739 /dev/sdd
Disk Sector 1218783739 is at filesystem block 152347711 in /dev/sdd1
Block is used by inode 31219834
Searching for filename(s)...
Inode Pathname
31219834 /home/mryuk/2020-11-03-3045-us-la-msy.jpg
31219834 /home/mryuk/web/2020-11-03-3045-us-la-msy.jpg
Discussion
My script defaults to a sector address (often called "LBA") rather than bytes. This is because LBA is what tools like smartctl
will report when there is a bad block on the drive. However, if you want to specify bytes instead of sectors, just give the -b
flag.
Source Code
Cut and paste into a file or click here to download from https://github.com/hackerb9/lba2file/
#!/bin/bash
# lba2file: Given an LBA number and a drive in /dev/, print which
# filename(s), if any, use that sector.
# This is the opposite of `hdparm --fibmap /foo/bar`
# B9 May 2020
if [[ "$1" == "-b" ]]; then
BYTESFLAG=Byte
shift
fi
if [[ $# -lt 2 ]]; then
echo "Usage: lba2file [-b] <sector number> /dev/sdX"
echo " -b: Use byte address instead of sector"
exit 1
fi
if [[ $(id -u) -ne 0 ]]; then
echo "Please run as root using 'sudo $@'" >&2
exit 1
fi
lba=$1
drive=$2
drive=${drive#/dev/} # Remove /dev/ prefix, if any.
if [[ "$drive" =~ ^(.*)[0-9]$ ]]; then # Either user specified a partition.
searchparts="/sys/class/block/$drive"
drive=${BASH_REMATCH[1]}
else # Or user specified a drive.
shopt -s nullglob # Don't use '?' literally.
searchparts=$(eval echo /sys/class/block/${drive}?)
fi
for partition in $searchparts; do
device=/dev/${partition#/sys/class/block/}
cd "$partition" || continue
start=$(cat "$partition/start")
partitionsize=$(cat "$partition/size")
hwsectorsize=$(cat "/sys/class/block/$drive/queue/hw_sector_size")
# Typically: e2blocksize==4096, hwsectorsize==512
# Example: start=1048576, partitionsize=640133980160
# Do a sanity check.
if [[ -z "$start" || -z "$partitionsize" || -z "$hwsectorsize" ]]; then
echo "Error reading data for $device" >&2
continue
fi
# Scale everything to bytes since we'll use that for debugfs.
start=$((start * hwsectorsize))
partitionsize=$((partitionsize * hwsectorsize))
# If not using byte flag, scale the address, too.
if [[ -z "$BYTESFLAG" ]]; then
byteaddress=$((lba * hwsectorsize))
else
byteaddress=$lba
fi
if [[ $byteaddress -lt $start ||
$byteaddress -ge $((start+partitionsize)) ]]; then
#echo "Address $byteaddress is not within $partition"
continue # Not in this partition
fi
if ! e2blocksize=$(tune2fs -l $device 2>/dev/null |
grep '^Block size' | egrep -o '[0-9]+'); then
echo "Skipping $device, not an Ext2/3/4 partition"
continue
fi
# Scale address by filesystem blocksize to find filesystem block number
e2blockaddress=$(( (byteaddress - start) / e2blocksize))
Sector=${BYTESFLAG:-Sector}
echo "Disk $Sector $lba is at filesystem block $e2blockaddress in $device"
inode=$(debugfs -R "icheck $e2blockaddress" $device 2>/dev/null |
tail -1 | cut -f2)
if [[ "$inode" && "$inode" != "<block not found>" ]]; then
echo "$Sector is used by inode $inode"
echo "Searching for filename(s)..."
debugfs -R "ncheck $inode" $device 2>/dev/null
else
echo "$Sector is not in use."
fi
done