在 Linux 和 ext3/4 上的原始设备中从偏移量反向查找 inode/文件?

在 Linux 和 ext3/4 上的原始设备中从偏移量反向查找 inode/文件?

在 Linux 中,给定原始磁盘设备的偏移量,是否可以映射回分区 + inode?

例如,假设我知道字符串“xyz”包含在 /dev/sda 上的字节偏移量 1000000 处:(例如 xxd -l 100 -s 1000000 /dev/sda 显示以“xyz”开头的转储)

1)我如何确定偏移量 1000000 位于哪个分区(如果有的话)?(我想这很容易,但为了完整性我把它包括在内)

2)假设偏移量位于分区中,我该如何找到它属于哪个 inode(或确定它是可用空间的一部分)?据推测这是文件系统特定的,在这种情况下,有人知道如何对 ext4 和 ext3 执行此操作吗?

答案1

我刚好要做类似的事情,所以我想分享我的解决方案。

您可以通过检查 udisks --show-info 输出的“偏移量”和“大小”元素来查看驱动器字节偏移属于哪个分区;例如

user@host:~$ sudo udisks --show-info /dev/sda1 | grep -i 'offset'
    offset:                    1048576
    alignment offset:          0

从磁盘偏移量中减去此偏移量,即可得到分区中的字节偏移量。因此,/dev/sda 中的磁盘偏移量 (10000000) 是 /dev/sda1 中的分区偏移量 (10000000 - 1048576) = 8951424

您可以使用以下命令找出分区中的块有多大:

user@host:~$ sudo tune2fs -l /dev/sda1  | grep -i 'block size'
Block size:               4096

将分区字节偏移量除以块大小以确定块偏移量,在本例中为 8951424/4096 = 2185

运行以下命令来找出哪个 inode 占用了该块:

user@host:~$ sudo debugfs -R "icheck 2185" /dev/sda1
debugfs 1.41.11 (14-Mar-2010)
Block   Inode number
2185    123456 

然后使用以下命令找出该 inode 的文件名:

user@host:~$ sudo debugfs -R "ncheck 123456" /dev/sda1
debugfs 1.41.11 (14-Mar-2010)
Inode   Pathname
123456  /tmp/some-filename.txt

关于这一点,有更详细的描述http://www.randomnoun.com/wp/2013/09/12/determining-the-file-at-a-specific-vmdk-offset

答案2

Greg Knox 的答案是正确的,但可能更简单。我写了一个 shell 脚本,lba2文件,它为您执行所有算术运算,源代码如下。

[更新:脚本不再依赖于udisks二进制]。

lba2file 的使用示例

解决问题中提出的问题(以字节为单位指定地址):

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

SMART 使用示例

如果您的硬盘驱动器有坏扇区,您可能希望在通过向扇区写入零来重新映射扇区之前找出损坏的文件。您可以使用smartctl和轻松完成此操作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

最后一个数字1218783739是扇区数而不是字节数的磁盘地址:

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

讨论

我的脚本默认为扇区地址(通常称为“LBA”)而不是字节。这是因为 LBA 是smartctl当驱动器上有坏块时工具会报告的内容。但是,如果您想指定字节而不是扇区,只需给出标志即可-b

源代码

剪切并粘贴到文件中或单击这里下载自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

答案3

该脚本可以进一步完善为以下一行:

sudo tune2fs -l /dev/<disk>                                        \
| grep '^Block size:'                                              \
| sed 's/.* //'                                                    \
| xargs expr '(' <LBA> '*' 512 - <partition offset in bytes> ')' / \
| sudo xargs -I{} debugfs -R 'icheck {}' /dev/<partition>
sudo debugfs -R 'ncheck <inode from prev commend>' /dev/<partition>

但是如果使用 LVM,您可能还需要做一些额外的映射。

相关内容