ext4 recovery detail - hanyong/note GitHub Wiki

ext4 文件系统恢复详细记录

winpe 下操作恢复一个 ghost 文件到一个 windows 分区时, 网上的垃圾一键 ghost 软件逻辑不严谨, 误把 ghost 恢复到一个 linux 下的 lvm 分区。 我的 500G ext4 格式数据卷被恢复成 ntfs 分区, 并重写覆盖了约 10G 的空间。 这可是我从大学攒到现在的几乎所有资料, 赶紧研究下有没有办法恢复。

google 到一篇帖子, 按照其指引尝试操作: http://askubuntu.com/questions/533496/accidentally-formatted-ext4-partition

编辑 /etc/fstab 取消挂载 repo, 避免对 repo 卷做任何操作.

新建一个丢失分区两倍大小 + 100GiB 的物理卷 pv2, pv2 上新建一个相同大小逻辑卷 repo2 备用.

sudo vgextend vg /dev/sda8
sudo lvcreate -L 500g -n repo2 vg /dev/sda8

丢失数据卷创建快照, 尝试在快照上操作恢复数据, 避免对原 repo 卷做任何操作.

sudo lvcreate -s vg/repo -n repo0s0 -L 100g

testdisk

首先尝试使用 testdisk 恢复。

安装 testdisk:

sudo aptitude install testdisk

简单看了一下 man testdisk, 支持指定设备参数, 尝试对快照进行恢复.

sudo testdisk /dev/vg/repo0s0

显示设备信息, 确认处理:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org

  TestDisk is free software, and
comes with ABSOLUTELY NO WARRANTY.

Select a media (use Arrow keys, then press Enter):
>Disk /dev/vg/repo0s0 - 536 GB / 500 GiB


>[Proceed ]  [  Quit  ]

Note: Disk capacity must be correctly detected for a successful recovery.
If a disk listed above has incorrect size, check HD jumper settings, BIOS
detection, and install the latest OS patches and disk drivers.

选择分区表类型, 自动选择了 None。数据卷已经是一个分区, 没有分区表, 确认选择 None:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org


Disk /dev/vg/repo0s0 - 536 GB / 500 GiB

Please select the partition table type, press Enter when done.
 [Intel  ] Intel/PC partition
 [EFI GPT] EFI GPT partition map (Mac i386, some x86_64...)
 [Humax  ] Humax partition table
 [Mac    ] Apple partition map
>[None   ] Non partitioned media
 [Sun    ] Sun Solaris partition
 [XBox   ] XBox partition
 [Return ] Return to disk selection



Hint: None partition table type has been detected.
Note: Do NOT select 'None' for media with only a single partition. It's very
rare for a disk to be 'Non-partitioned'.

选择操作, 自动选择了 Advanced 操作文件系统。已确定的分区, 确认选择 Advanced:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org


Disk /dev/vg/repo0s0 - 536 GB / 500 GiB
     1048576000 sectors - sector size=512

 [ Analyse  ] Analyse current partition structure and search for lost partitions
>[ Advanced ] Filesystem Utils
 [ Geometry ] Change disk geometry
 [ Options  ] Modify options
 [ Quit     ] Return to disk selection


Note: Correct disk geometry is required for a successful recovery. 'Analyse'
process may give some warnings if it thinks the logical geometry is mismatched.

文件系统操作。由于已经被 ghost 格式化成 NTFS, 自动识别为 NTFS 文件系统:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org

Disk /dev/vg/repo0s0 - 536 GB / 500 GiB - 1048576000 sectors

     Partition                  Start        End    Size in sectors
>   P NTFS                           0 1048575999 1048576000


 [  Type  ] >[  Boot  ]  [  List  ]  [Undelete]  [Image Creation]  [  Quit  ]
                              Boot sector recovery

我们要恢复 ext4 分区数据, 操作 Type 修改为 ext4:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org

   P NTFS                           0 1048575999 1048576000
Please choose the partition type, press Enter when done.

 Unknown                   HFSX                      NTFS
 BeFS                      HPFS                      OpenBSD
 btrfs                     ISO                       OS2 Multiboot
 CramFS                    JFS                       ReiserFS 3.5
 exFAT                     Linux SWAP                ReiserFS 3.6
 ext2                      Linux SWAP 2              ReiserFS 3.x
 ext3                      Linux SWAP                ReiserFS 4
>ext4                      Linux SWAP 2              Sun
 FAT12                     Linux SWAP 2              SysV 4
 FAT16                     Linux LUKS                UFS
 FAT32                     Linux LVM                 UFS 2
 FreeBSD                   Linux LVM2                UFS - Little Endian
 GFS2                      Linux md 0.9 RAID         UFS 2 - Little Endian
 HFS                       Linux md 1.x RAID         VMFS
 HFS+                      Netware                   WBFS
                                                     Next
[ Proceed ]

切换到 ext4 操作选项, 自动选择定位 superblock:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org

Disk /dev/vg/repo0s0 - 536 GB / 500 GiB - 1048576000 sectors

     Partition                  Start        End    Size in sectors
>   P ext4                           0 1048575999 1048576000


 [  Type  ] >[Superblock]  [  List  ]  [Image Creation]  [  Quit  ]
                    Locate ext2/ext3/ext4 backup superblock

确认执行定位 superblock 操作, 显示结果如下:

TestDisk 7.0, Data Recovery Utility, April 2015
Christophe GRENIER <[email protected]>
http://www.cgsecurity.org

Disk /dev/vg/repo0s0 - 536 GB / 500 GiB - 1048576000 sectors

     Partition                  Start        End    Size in sectors

  ext4                           0 1048575999 1048576000
superblock 819200, blocksize=4096 []
superblock 884736, blocksize=4096 []
superblock 1605632, blocksize=4096 []
superblock 2654208, blocksize=4096 []
superblock 4096000, blocksize=4096 []
superblock 7962624, blocksize=4096 []
superblock 11239424, blocksize=4096 []
superblock 20480000, blocksize=4096 []
superblock 23887872, blocksize=4096 []
superblock 71663616, blocksize=4096 []

To repair the filesystem using alternate superblock, run
fsck.ext4 -p -b superblock -B blocksize device


>[  Quit  ]
                            Return to Advanced menu

阅读理解提示信息, 应该是要使用定位到的 superblock 和 blocksize 设置参数调用 fsck.ext4 执行恢复。 查了一下 man fsck.ext4, 确认支持这俩参数。

根据定位的 superblock 应执行 fsck.ext4 如下:

sudo fsck.ext4 -b 819200 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 884736 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 1605632 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 2654208 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 4096000 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 7962624 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 11239424 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 20480000 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 23887872 -B 4096 /dev/vg/repo0s0
sudo fsck.ext4 -b 71663616 -B 4096 /dev/vg/repo0s0

尝试执行恢复第一个 superblock:

$ sudo fsck.ext4 -b 819200 -B 4096 /dev/vg/repo0s0 
e2fsck 1.42.13 (17-May-2015)
/dev/vg/repo0s0 is in use.
e2fsck: 无法继续, 中止.

先退出 testdisk, 再尝试执行:

$ sudo fsck.ext4 -b 819200 -B 4096 /dev/vg/repo0s0
e2fsck 1.42.13 (17-May-2015)
超级块包含无效的 ext3 日志(inode 8).
清除<y>? 是
*** ext3 journal has been deleted - filesystem is now ext2 only ***

Resize inode not valid.  重建<y>? 是
第一步: 检查inode,块,和大小
The bad 块 inode looks 无效的.  清除<y>? 是
根inode不是一个目录.  清除<y>? 是
Quota inode is not in use, but contains data.  清除<y>? 是
Quota inode is not in use, but contains data.  清除<y>? 是
保留的inode 6 (<未删除的目录 inode>) 的模式无效.  清除<y>? 是
Inode 6, i_size is 562949983955569, 应为 0.  处理<y>? 是
日志 inode is not in use, but contains data.  清除<y>? 是
保留的inode 9 (<保留的 inode 9>) 的模式无效.  清除<y>? 是
... ...

提示存在各种异常情况, 应该是数据已经被破坏。

重新重建快照:

sudo lvremove -f vg/repo0s0
sudo lvcreate -s vg/repo -n repo0s0 -L 100g

看了下 man fsck.ext4, 可添加 -p 参数减少交互, 或者 -y/-n 全部回答. 尝试添加 -p 执行, 报错说只能手动恢复(?):

$ sudo fsck.ext4 -b 819200 -B 4096 /dev/vg/repo0s0 -p
/dev/vg/repo0s0: 超级块包含无效的 ext3 日志(inode 8).
已清除.
*** ext3 journal has been deleted - filesystem is now ext2 only ***

/dev/vg/repo0s0: Resize inode not valid.  

/dev/vg/repo0s0: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.
	(i.e., without -a or -p options)

分区前面的数据被破坏, 后面的数据应该没有被重写, 能否正常恢复(?)。 再次重建快照, 尝试先恢复最后一个 superblock, 还是一样报错。

看帖子上的说明, 应该是 root inode 被破坏, 使用 testdisk 不行, 尝试手动定位 superblock。

手动定位 superblock

重建快照尝试手动定位 superblock。

按照帖子指引找到这篇说明: Repair a broken Ext4 Superblock in Ubuntu

首先执行检查, 妥妥的说 "超级块无效":

sudo fsck.ext4 -v /dev/vg/repo0s0

然后文档说执行 mke2fs -n, man mkfs.ext4 看了一下, -n 是假装创建文件系统, 然后输出 backup superblocks 的位置, 这样我们就知道当初创建文件系统时 backup superblocks 在哪里了。 由于我是使用 mkfs.ext4 创建文件系统的, 执行:

sudo mkfs.ext4 -n /dev/vg/repo0s0

输出:

mke2fs 1.42.13 (17-May-2015)
/dev/vg/repo0s0 contains a ntfs file system labelled 'Windows10'
无论如何也要继续? (y,n) y
Creating filesystem with 131072000 4k blocks and 32768000 inodes
Filesystem UUID: b8acf54e-cd40-4129-964a-68426fe599f0
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
	4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, 
	102400000

对比了一下, 找到的 superblock 比 testdisk 多, 有部分是重叠的。 按照文档提示, 使用一个 superblock 执行恢复, 如果不行, 再尝试另外一个。 是否每个 superblock 都是完整的, 不用全部恢复(?)。

所有 superblock 试了一遍, 全部报错.

ext4 superblock 恢复测试

猜想是不是要先格式化成 ext4 格式才能恢复 superblock, 但是担心格式化 ext4 会破坏 superblock。 先研究下如何将 superblock 备份到文件。

了解了一下 ext4 将整个分区划为 n 个 block, 而 superblock 是其中一些元信息 block。 superblock 下标是从 0 开始还是从 1 开始呢, 创建了一个 test 卷进行测试。 test ext4 格式保存数据后创建快照 test0, 然后格式化为 ntfs, 创建快照 test0s0。

分别假设 superblock 下标为 0 或 1, dd 拷贝 superblock 出来计算 md5:

sudo bash -ec 'i0=1 ; for i in 32768 98304 163840 229376 294912 819200 884736 1605632 2654208 ; do dd if=/dev/vg/test0s0 bs=4096 skip=$(( i - $i0 )) count=1 of=$i0-$i ; done'

看到设下标为 0 时 md5 各不相同, 而下标为 1 时若干 block md5 相同 (sort 的 key 内字符默认从空白开始计算) :

$ sudo md5sum 1-* | sort -k 2.5n
620f0b67a91f7f74151bc5be745b7110  1-32768
a0b877693086b9411c7c93b418ec5c23  1-98304
6a8622cf8436e44ca7ca9cdb79337635  1-163840
30dbe08fecfdd5565e1faef666480d62  1-229376
147b2a1962092a32e04db42a2269a688  1-294912
620f0b67a91f7f74151bc5be745b7110  1-819200
620f0b67a91f7f74151bc5be745b7110  1-884736
620f0b67a91f7f74151bc5be745b7110  1-1605632
620f0b67a91f7f74151bc5be745b7110  1-2654208

再用 od -t x1 检查下文件内容:

sudo bash -xc 'for i in 0-* ; do od -t x1 $i | head ; done'

发现下标为 0 的都有内容, 而下标为 1 相同 md5 的 block 实际内容都为 0, 包括第一个主 block 32768。 最后一个不为 0 的 block 294912 在 294912.0 * 4 / 1024 = 1152MiB 的位置, 分区内测试数据 1.5g 多, 应该正好是分区内的测试数据。 故下标应是从 0 开始。 而头疼的是各个 block 的内容各不相同。

对比 ext4 和 ntfs block 数据差异:

sudo bash -ec 'i0=0 ; for i in 32768 98304 163840 229376 294912 819200 884736 1605632 2654208 4096000 ; do dd if=/dev/vg/test0 bs=4096 skip=$(( i - $i0 )) count=1 of=a-$i ; done'
sudo bash -ec 'i0=0 ; for i in 32768 98304 163840 229376 294912 819200 884736 1605632 2654208 4096000 ; do dd if=/dev/vg/test0s0 bs=4096 skip=$(( i - $i0 )) count=1 of=b-$i ; done'
sudo md5sum a-* b-* | sort -k 2.5n -k 2.3,2.3

发现如果只是简单的重新格式化而没有重写数据, 那么各 block 的数据还是一样的, 没有被覆盖。

ace0dfaed93aad5d3ab63b131627ed03  a-32768
ace0dfaed93aad5d3ab63b131627ed03  b-32768
b49e0d5a6012cc9f1fd6a1338e7e5b2b  a-98304
b49e0d5a6012cc9f1fd6a1338e7e5b2b  b-98304
7081e4935c35304fa421742806555d7e  a-163840
7081e4935c35304fa421742806555d7e  b-163840
8776e844b77dc2af5eb959fcab9f32b7  a-229376
8776e844b77dc2af5eb959fcab9f32b7  b-229376
3859e3a519c0dc464c3969eb775ed384  a-294912
3859e3a519c0dc464c3969eb775ed384  b-294912
d5cb215dedb5f67832cc4d05ee53049b  a-819200
d5cb215dedb5f67832cc4d05ee53049b  b-819200
b87deb764927aacb0fce3a527b7aebc3  a-884736
b87deb764927aacb0fce3a527b7aebc3  b-884736
a4e117986fc375bc25fe3e61d1eac682  a-1605632
a4e117986fc375bc25fe3e61d1eac682  b-1605632
92d9362366e7d7b0dfeccda1aca54378  a-2654208
92d9362366e7d7b0dfeccda1aca54378  b-2654208
a18a725e980dfce052e74165dfa0e48c  a-4096000
a18a725e980dfce052e74165dfa0e48c  b-4096000

重新 ext4 格式化后果然所有 block 内容都变化, superblock 数据被破坏。

恢复 backup superblock:

sudo bash -ec 'i0=0 ; for i in 32768 98304 163840 229376 294912 819200 884736 1605632 2654208 4096000 ; do dd of=/dev/vg/test0s0 bs=4096 seek=$(( i - $i0 )) count=1 if=a-$i ; done'

再用 fsck.ext4 检查:

$ sudo fsck.ext4 -v /dev/vg/test0s0 
e2fsck 1.42.13 (17-May-2015)
/dev/vg/test0s0 primary superblock features different from backup, 强制检查.
第一步: 检查inode,块,和大小
第二步: 检查目录结构
第3步: 检查目录连接性
Pass 4: Checking reference counts
第5步: 检查簇概要信息

          11 inodes used (0.00%, out of 1310720)
           0 non-contiguous files (0.0%)
           0 non-contiguous directories (0.0%)
             # of inodes with ind/dind/tind blocks: 0/0/0
             Extent depth histogram: 3
      126289 blocks used (2.41%, out of 5242880)
           0 bad blocks
           1 large file

           0 regular files
           2 directories
           0 character device files
           0 block device files
           0 fifos
           0 links
           0 symbolic links (0 fast symbolic links)
           0 sockets
------------
           2 files

然后挂载卷, 发现并没有数据:

$ sudo mount /dev/vg/test0s0 /mnt/
$ ls /mnt/
lost+found
$ df -h /mnt
文件系统                容量  已用  可用 已用% 挂载点
/dev/mapper/vg-test0s0   20G   44M   19G    1% /mnt
$ sudo umount /mnt 

可见 fsck.ext4 应该是认为 backup superblock 有问题, 然后进行订正。 要使用 backup superblock, 应该指定 -b 参数。

重新格式化 test0s0, 恢复最后一个 superblock, 添加 -b 检查, 报错, 大概是说不能自动恢复, 跟之前的检查报错类似。

sudo mkfs.ext4 /dev/vg/test0s0
sudo bash -ec 'i0=0 ; for i in 4096000 ; do dd of=/dev/vg/test0s0 bs=4096 seek=$(( i - $i0 )) count=1 if=a-$i ; done'
sudo fsck.ext4 -v /dev/vg/test0s0 -b 4096000 -p

再次重新格式化, 恢复最后一个 superblock, 使用 -y 检查。 输出若干问题, 等了一会儿后最终检查成功。

$ sudo fsck.ext4 -v /dev/vg/test0s0 -b 4096000 -y
... ...
Free inodes count wrong (1441781, counted=1310709).
处理? 是


/dev/vg/test0s0: ***** 文件系统已修改 *****

          11 inodes used (0.00%, out of 1310720)
           0 non-contiguous files (0.0%)
           0 non-contiguous directories (0.0%)
             # of inodes with ind/dind/tind blocks: 0/0/0
             Extent depth histogram: 3
      126289 blocks used (2.41%, out of 5242880)
           0 bad blocks
           1 large file

           0 regular files
           2 directories
           0 character device files
           0 block device files
           0 fifos
           0 links
           0 symbolic links (0 fast symbolic links)
           0 sockets
------------
           2 files
hanyong@han2015:~/tmp$ sudo fsck.ext4 -v /dev/vg/test0s0
e2fsck 1.42.13 (17-May-2015)
/dev/vg/test0s0: clean, 11/1310720 files, 126289/5242880 blocks

但挂载数据卷后发现依然没有数据。

再次格式化恢复所有 superblock, 使用 -b 32768 -p 检查, 发现检查直接通过, 但是文件并没有恢复。

$ sudo fsck.ext4 -v /dev/vg/test0s0 -b 32768 -p
/dev/vg/test0s0 was not cleanly unmounted, 强制检查.

          11 inodes used (0.00%, out of 1310720)
           0 non-contiguous files (0.0%)
           0 non-contiguous directories (0.0%)
             # of inodes with ind/dind/tind blocks: 0/0/0
             Extent depth histogram: 3
      126289 blocks used (2.41%, out of 5242880)
           0 bad blocks
           1 large file

           0 regular files
           2 directories
           0 character device files
           0 block device files
           0 fifos
           0 links
           0 symbolic links (0 fast symbolic links)
           0 sockets
------------
           2 files

重新恢复, 去掉 -p 检查, 有一个问题回答 y, 这次提示 "文件系统已修改"。

$ sudo fsck.ext4 -v /dev/vg/test0s0 -b 32768
e2fsck 1.42.13 (17-May-2015)
/dev/vg/test0s0 was not cleanly unmounted, 强制检查.
第一步: 检查inode,块,和大小
第二步: 检查目录结构
第3步: 检查目录连接性
Pass 4: Checking reference counts
第5步: 检查簇概要信息
Inode位图差异:  -(655361--786432)
处理<y>? 是

/dev/vg/test0s0: ***** 文件系统已修改 *****

          11 inodes used (0.00%, out of 1310720)
           0 non-contiguous files (0.0%)
           0 non-contiguous directories (0.0%)
             # of inodes with ind/dind/tind blocks: 0/0/0
             Extent depth histogram: 3
      126289 blocks used (2.41%, out of 5242880)
           0 bad blocks
           1 large file

           0 regular files
           2 directories
           0 character device files
           0 block device files
           0 fifos
           0 links
           0 symbolic links (0 fast symbolic links)
           0 sockets
------------
           2 files

mount 看了一下, 文件还是没有恢复。 推测结论: (1) superblock 保留越多越容易恢复。 (2) 使用 -y 才会尽可能采纳 backup superblock 的信息执行修改。

另外发现, 随着反复格式化和恢复, 快照 test0s0 占据的空间逐渐增加, 不知为何, 暂忽略:

hanyong@han2015:~/tmp$ sudo lvs -a
  LV         VG   Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  test       vg   owi-a-s---  20.00g                                                    
  test0      vg   swi-a-s---   2.00g      test   3.19                                   
  test0s0    vg   swi-a-s---   2.00g      test   22.20                                  

重建快照, 尝试直接使用 -y 恢复, 输出一堆问题, 经过一段时间的等待后修复完成:

$ sudo lvremove -f vg/test0s0 && sudo lvcreate -s vg/test -n test0s0 -L 2g
  Logical volume "test0s0" successfully removed
  Logical volume "test0s0" created.
$ sudo fsck.ext4 /dev/vg/test0s0 -b 32768 -y
... ...
/dev/vg/test0s0: ***** 文件系统已修改 *****
/dev/vg/test0s0: 13/1310720 files (7.7% non-contiguous), 547718/5242880 blocks

检查发现卷已经修复成 ext4 文件系统, mount 发现文件也恢复了:

$ sudo blkid /dev/vg/test0s0
/dev/vg/test0s0: UUID="a58307c0-79bf-47ff-8457-5a8989695b91" TYPE="ext4"
$ sudo mount /dev/vg/test0s0 /mnt/
$ ls -Alh /mnt/
总用量 1.7G
-rwxr-xr-x 1 root root 1.6G 8月   7 15:20 linuxmint-18-cinnamon-64bit.iso
drwx------ 2 root root  16K 8月   7 15:18 lost+found
-rwxr-xr-x 1 root root  27M 8月   7 15:20 smplayer-portable-16.4.0.0-x64.7z
$ sudo md5sum /mnt/linuxmint-18-cinnamon-64bit.iso /d/software/linuxmint-18-cinnamon-64bit.iso 
8e9dba7e5ae538e06a13c7135bf457f2  /mnt/linuxmint-18-cinnamon-64bit.iso
8e9dba7e5ae538e06a13c7135bf457f2  /d/software/linuxmint-18-cinnamon-64bit.iso

可见, 如果只是简单的重新格式化, 数据没有被覆盖, 数据是可以恢复的。

copy 一个 500 多 MiB 的文件到 ntfs 分区, 比较发现 superblock 还是没有被覆盖, 再次尝试恢复, 还是恢复成功, 数据 md5 也对, 猜测 ntfs 文件读写错开了 ext4 的数据区, 数据没有被覆盖(?)。

把 test 卷挂进虚拟机, 再用 ghost 恢复写入一个 700 多 MiB 的镜像, 再检查 superblock, 这回发现前 3 个被破坏了, 第 4 个开始是好的。 第 4 个是从 229376.0 * 4 / 1024 = 896MiB 开始的位置。

$ sudo md5sum a-* b-* | sort -k 2.5n -k 2.3,2.3
ace0dfaed93aad5d3ab63b131627ed03  a-32768
7964e860bad427d7ccf3fa9548ae4665  b-32768
b49e0d5a6012cc9f1fd6a1338e7e5b2b  a-98304
168a5dca0c1859f2f8d9b6131b61ba2d  b-98304
7081e4935c35304fa421742806555d7e  a-163840
aef55fe67f3f75bf2e7cbfd77c56424a  b-163840
8776e844b77dc2af5eb959fcab9f32b7  a-229376
8776e844b77dc2af5eb959fcab9f32b7  b-229376

被破坏的 superblock 应该无法恢复了, 果然尝试从前 3 个 superblock 恢复都直接报错:

$ sudo fsck.ext4 /dev/vg/test0s0 -b 32768 -y 
e2fsck 1.42.13 (17-May-2015)
fsck.ext4: Bad magic number in super-block 当尝试打开 /dev/vg/test0s0 时

The 超级块 could not be read or does not describe a valid ext2/ext3/ext4
文件系统.  If the 设备 is valid and it really contains an ext2/ext3/ext4
文件系统 (and not swap or ufs or something else), then the 超级块
is corrupt, and you might try running e2fsck with an alternate 超级块:
    e2fsck -b 8193 <设备>
 or
    e2fsck -b 32768 <设备>

尝试从第 4 个 superblock 恢复, 恢复过程中磁盘 IO 较高, 整个系统很卡, 经过较长时间的等待恢复完成。 根据之前的经验, 输出日志刷太多, 直接重定向到文件。

$ sudo fsck.ext4 /dev/vg/test0s0 -b 229376 -y > a.log
e2fsck 1.42.13 (17-May-2015)
e2fsck: 已中止

$ head a.log 
超级块包含无效的 ext3 日志(inode 8).
清除? 是

*** ext3 journal has been deleted - filesystem is now ext2 only ***

Resize inode not valid.  重建? 是

fsck.ext4: Illegal doubly indirect block found while reading bad blocks inode
This doesn't bode well, but we'll try to go on...
第一步: 检查inode,块,和大小

$ tail a.log 
Error storing 目录 块 information (inode=22818, 块=0, num=76668584): Memory allocation failed

/dev/vg/test0s0: ***** 文件系统已修改 *****
重建日志? 是

Creating journal (32768 blocks): 完成.

*** journal has been re-created - filesystem is now ext3 again ***

/dev/vg/test0s0: ***** 文件系统已修改 *****

看控制台输出说 "e2fsck: 已中止", 看日志又是已恢复成 ext3。 blkid 和 parted 看似乎文件系统还是 ntfs。

$ sudo blkid /dev/vg/test0s0
/dev/vg/test0s0: LABEL="test" UUID="BA98DDC598DD7FF5" TYPE="ntfs" PTUUID="00000001" PTTYPE="dos"

hanyong@han2015:~/tmp$ sudo parted /dev/vg/test0s0
GNU Parted 3.2
使用 /dev/dm-16
欢迎使用 GNU Parted! 输入 'help'可获得命令列表.
(parted) print free                                                       
Model: Linux 设备映射器 (snapshot) (dm)
磁盘 /dev/dm-16: 21.5GB
Sector size (logical/physical): 512B/4096B
分区表:loop
Disk Flags: 

数字  开始:  End     大小    文件系统  标志
 1    0.00B   21.5GB  21.5GB  ntfs

(parted) unit s                                                           
(parted) print free                                                       
Model: Linux 设备映射器 (snapshot) (dm)
磁盘 /dev/dm-16: 41943040s
Sector size (logical/physical): 512B/4096B
分区表:loop
Disk Flags: 

数字  开始:  End        大小       文件系统  标志
 1    0s      41943039s  41943040s  ntfs

(parted) quit                                                             

检查前 3 个 superblock 也更新了:

hanyong@han2015:~/tmp$ sudo md5sum a-* b-* | sort -k 2.5n -k 2.3,2.3
ace0dfaed93aad5d3ab63b131627ed03  a-32768
eb4542a7c70c809ea5379e1e1fd8884e  b-32768
b49e0d5a6012cc9f1fd6a1338e7e5b2b  a-98304
168a5dca0c1859f2f8d9b6131b61ba2d  b-98304
7081e4935c35304fa421742806555d7e  a-163840
aef55fe67f3f75bf2e7cbfd77c56424a  b-163840
... ...
92d9362366e7d7b0dfeccda1aca54378  a-2654208
620f0b67a91f7f74151bc5be745b7110  b-2654208
... ...

注意前 3 个 superblock 因为是破坏后重建, 所以跟之前不一样, 并不奇怪。 其他的是倒数第 2 个 superblock 也变的不一样了, 恢复前是一样的。

mount 卷发现自动识别为 fuseblk, 与指定 -t ntfs 相同, 数据没有恢复:

$ sudo mount /dev/vg/test0s0 /mnt
$ mount
... ...
/dev/mapper/vg-test0s0 on /mnt type fuseblk (rw,relatime,user_id=0,group_id=0,allow_other,blksize=4096)
$ ls /mnt/
System Volume Information
$ sudo umount /mnt

指定 -t ext3 挂载则报错, 指定 ext2/3/4 均报错:

$ sudo mount -t ext3 /dev/vg/test0s0 /mnt
mount: wrong fs type, bad option, bad superblock on /dev/mapper/vg-test0s0,
       missing codepage or helper program, or other error

       In some cases useful info is found in syslog - try
       dmesg | tail or so.

再次重试从第 4 个 superblock 起恢复, 同样卡了会儿, 等了会儿。日志信息有点不一样。

$ sudo fsck.ext4 /dev/vg/test0s0 -b 229376 -y > a.log
e2fsck 1.42.13 (17-May-2015)
e2fsck: 已中止

$ head a.log 
Backing up 日志 inode 块 information.

/dev/vg/test0s0 was not cleanly unmounted, 强制检查.
Resize inode not valid.  重建? 是

第一步: 检查inode,块,和大小
根inode不是一个目录.  清除? 是

Special (设备/socket/fifo) inode 66 has non-zero size.  处理? 是

$ tail a.log 
Inode 32663 中包含非法块.  清除? 是

非法的 块 #2 (18223814) in inode 32663.  已清除.
非法的 块 #4 (18223797) in inode 32663.  已清除.
非法的 块 #89132278 (1346801131) in inode 32663.  已清除.
Error storing 目录 块 information (inode=32663, 块=0, num=79846000): Memory allocation failed

/dev/vg/test0s0: ***** 文件系统已修改 *****

/dev/vg/test0s0: ***** 文件系统已修改 *****

检查第一个 superblock 32768 再次变化, 其他没有变:

$ sudo md5sum a-* b-* | sort -k 2.5n -k 2.3,2.3
ace0dfaed93aad5d3ab63b131627ed03  a-32768
08b91135db037987f1a29c7e8e1fc1fb  b-32768

再用 blkid 和 parted 看还是 ntfs 格式, 但尝试 mount 挂载时用 ntfs 或 ext2/3/4 都报错。

再测试发现即使对一个正常的分区, 指定 -b 从一个靠后的 superblock 检查, 也会每次检查都有问题。 随后即使再指定第一个 backup superblock -b 32768 也会反复检查都有问题。 如果不指定 -b, 第一次检查修复后, 后面再检查就没问题了。 检查 ok 后 mount 挂载看数据是 ok 的。

尝试不加 -b 检查 test0s0, 报 "Bad magic number in super-block", 超级块无效, 尝试指定第 1/2/3 个 backup superblock, 依然报错。

重试从第 4 个 superblock 恢复, 这次不重定向日志, 注意到最后有一个报错:

$ sudo fsck.ext4 -v /dev/vg/test0s0 -b 229376 -y
... ...
非法的 块 #89132397 (3968068747) in inode 39480.  已清除.
Error storing 目录 块 information (inode=39480, 块=0, num=79842416): Memory allocation failed

/dev/vg/test0s0: ***** 文件系统已修改 *****
e2fsck: 已中止

/dev/vg/test0s0: ***** 文件系统已修改 *****

"Memory allocation failed", 这个报错之前也有, 但是没注意到。 应该是这个错误导致 fsck.ext4 未执行完就终止了。 为什么会内存分配失败, 是内存不够吗, 重试一次恢复并 top 看了一下, fsck.ext4 果然会占用大量内存, 这时系统也几乎卡死:

top - 21:03:19 up  8:26,  6 users,  load average: 7.19, 4.61, 2.71
Tasks: 320 total,   1 running, 318 sleeping,   0 stopped,   1 zombie
%Cpu(s):  1.5 us,  1.7 sy,  0.0 ni, 84.1 id, 12.7 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 16379800 total,   199912 free, 16016944 used,   162944 buff/cache
KiB Swap: 16777212 total,  7278440 free,  9498772 used.    80712 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                         
19567 root      20   0 23.724g 0.014t   2416 D   5.6 94.1   0:32.86 fsck.ext4                                                       
16491 hanyong   20   0 1504920  91816  19660 D   1.3  0.6   5:02.74 chromium-browse                                                 
 5108 hanyong   20   0 1676952  41972   4668 D   0.0  0.3   5:41.30 chromium-browse                                                 
10309 hanyong   20   0 1285248  32756   4668 D   0.3  0.2   1:28.47 chromium-browse                                                 

可看到 swap 也用了大半, 因为系统几乎卡死, 不知道 swap 有没有用完。 不知道系统卡是不是因为 swap 用多了。 swapoff -a 禁用掉 swap 再次尝试恢复, 这次果然系统没有卡了, 磁盘 IO 也不高了, 并且 fsck.ext4 竟然也执行成功了。

$ sudo fsck.ext4 -v /dev/vg/test0s0 -b 229376 -y
... ...
/dev/vg/test0s0: ***** 文件系统已修改 *****

        3809 inodes used (0.29%, out of 1310720)
          95 non-contiguous files (2.5%)
         209 non-contiguous directories (5.5%)
             # of inodes with ind/dind/tind blocks: 31601/24021/31515
             Extent depth histogram: 1/1
      127120 blocks used (2.42%, out of 5242880)
           0 bad blocks
           2 large files

         120 regular files
         219 directories
         831 character device files
         690 block device files
        1232 fifos
  4294966859 links
           0 symbolic links (0 fast symbolic links)
         731 sockets
------------
  4294967078 files

看来 fsck.ext4 并不是 "内存杀手", 都是 swap 害了系统。

随后看 blkid 未识别到文件系统, mount -t ext4 挂载磁盘成功, 但查看数据都丢失了。

$ sudo blkid /dev/vg/test0s0
$ sudo mount -t ext4 /dev/vg/test0s0 /mnt/
hanyong@han2015:~/tmp$ ls /mnt
lost+found
hanyong@han2015:~/tmp$ df -h
文件系统                   容量  已用  可用 已用% 挂载点
... ...
/dev/mapper/vg-test0s0      20G   48M   19G    1% /mnt

重建快照重试恢复, 这次系统没有卡死的症状, 但 fsck.ext4 再次报内存分配失败终止了。 fsck.ext4 还是需要大量内存(?)。

google 了一下 e2fsck 确实需要很多内存: http://serverfault.com/questions/9218/running-out-of-memory-running-fsck-on-large-filesystems 。 可以配置 /etc/e2fsck.conf 使用磁盘文件, 但这样执行会非常慢。没办法只能配置。 看了下 man fsck.ext4man e2fsck.conf, scratch_files 包含 directory, dirinfo, icount 3 个配置。 后两个是元信息, 应该不用落盘, 尝试将这两个设置为 false。 第 2 个物理卷创建一个 500GiB 的卷, 挂载到 /fsck, /etc/e2fsck.conf 添加如下配置:

[scratch_files]
directory=/fsck
dirinfo=false
icount=false

重建 test0s0 快照再重试恢复, e2fsck 依然内存不足后终止, 注意到:

Error storing 目录 块 information (inode=11456, 块=0, num=22414330): Memory allocation failed

修改后两个设置为 true, 或者删掉这俩设置, 还是一样占用很多内存, 然后内存不足终止。

google "e2fsck scratch_files not work", 找到一篇帖子: e2fsck - out of memory despite [scratch_files] set in /etc/e2fsck.conf . 提到 e2fsck 可能确实需要很多内存, 并且 e2fsck 会区分对待 swap, 给 swap 不要 (不会充分利用 swap), 只要真实内存。 一个技巧就是在宿主机上分配很大的 swap, 然后创建一个 vm 占用 swap 空间作为内存, vm 内运行的 e2fsck 就会以为系统有很大的真实内存了。 不过这样将会变得非常非常慢, 但至少能执行了。

这时在想另一个问题, ext4 的文件系统是不是应该时分散在分区各处的, 而不是集中在分区头部(?)。 创建一个 10G 的卷写入一些文件测试, 发现即使将头部 1G 多的内容抹零, 只丢失了顶层目录数, 二级目录树及部分文件仍然能够恢复, 并且恢复很快, 也不会占很多内存。 猜想只有破坏的错误数据才会导致恢复困难及占用大量内存, 直接抹零反而恢复简单。 但直接抹零会破坏抹零区域的所有数据, 而错误恢复会尝试恢复未被破坏的数据, 在只夹杂部分坏数据时可能有用。 ghost 恢复应该将分区头部的数据全部破坏了, 并不能恢复有效数据, 可能还会误导 e2fsck (占用大量内存进行碎片分析并可能产生错误结果), 不如将分区被完全破坏的头部抹零, 再用 e2fsck 恢复。

重建快照 test0s0, 将其前 750 多 MiB 抹零, 然后执行恢复:

sudo lvremove -f vg/test0s0 && sudo lvcreate -s vg/test -n test0s0 -L 2g
sudo dd if=/dev/zero of=/dev/vg/test0s0 bs=8M count=$(( 750 / 8 + 1 ))
sudo fsck.ext4 -v /dev/vg/test0s0 -b 229376 -y > a.log

由于发现有点慢, 直接禁用掉了 scratch_files 配置, fsck 较快完成, 并且内存占用都很少, 可惜的是文件内容并没有恢复成功, 可能是有效索引都被覆盖了(?)。

$ sudo blkid /dev/vg/test0s0
/dev/vg/test0s0: UUID="a58307c0-79bf-47ff-8457-5a8989695b91" TYPE="ext4"
$ sudo mount /dev/vg/test0s0 /mnt
$ df -h /mnt
文件系统                容量  已用  可用 已用% 挂载点
/dev/mapper/vg-test0s0   20G   44M   19G    1% /mnt
$ sudo find /mnt
/mnt
/mnt/lost+found

尝试只抹掉前 128MiB 内容, 恢复时依然只占用少量内存, 文件依然没有恢复成功。 推测 e2fsck 应该只会检查利用 superblock 和前 128MiB 保存的关键元数据。 为什么测试 128MiB ? 这是第一个 backup superblock 32768.0 * 4 / 1024 = 128MiB 的位置。

恢复 repo 数据

回顾 testdisk 找到的最靠前 superblock 是 819200 在 3200MiB 位置。

而查看 repo0s0 被覆盖了 9576MiB 空间:

$ sudo mount /dev/vg/repo0s0 /mnt
hanyong@han2015:~$ df -m /mnt
文件系统                1M-块  已用   可用 已用% 挂载点
/dev/mapper/vg-repo0s0 512000  9576 502425    2% /mnt

相对可靠的 superblock 应该是 2654208 在 10368MiB 位置。

repo0s0 将前 128MiB 抹零, 尝试从最靠前 superblock 819200 恢复。 其中出现大量报错, 经过漫长的等待 (29分钟) 后恢复完成。 恢复期间 top 观察 CPU 占用最多 100%, 内存占用不多。 mount 检查恢复了 327GiB 数据。

$ sudo umount /mnt
$ sudo mount /dev/vg/repo0s0 /mnt
$ df -h /mnt
文件系统                容量  已用  可用 已用% 挂载点
/dev/mapper/vg-repo0s0  493G  327G  141G   70% /mnt
$ ls /mnt
lost+found

恢复的文件在 lost+found 文件夹下, 由于丢失了顶层目录, 看到的都是类似 #29097985 这样的文件名, 但二级目录起文件名恢复 OK。 发现 lost+found 下存在大量各种文件, ls 命令直接狂刷屏, 还有大量报错。

$ sudo su -l
# cd /mnt/lost+found/
# find -maxdepth 2 -name 'Bolt.rmvb'
find: ‘./#4328233’: Permission denied
... ...
./#29097985/Bolt.rmvb

可以通过检查文件夹内容确定原顶层文件夹名字, 如通过查找 Bolt.rmvb 可知 #29097985film 文件夹。 尝试播放该文件, 发现可以播放, 恢复成功。

新建快照 repo0s1 抹零, 从可靠 superblock 2654208 恢复。

sudo lvcreate -s vg/repo -n repo0s1 -L 100g
sudo dd if=/dev/zero of=/dev/vg/repo0s1 bs=8M count=$(( 128 / 8 ))
time sudo fsck.ext4 -v /dev/vg/repo0s1 -b 2654208 -y > a.log

依然有大量报错, 31 分钟后恢复完成, 查看恢复后的分区 repo0s1 似乎与之前 repo0s0 是一样的。 恢复数据大小一样, 顶层文件个数一样, film 等文件夹恢复后的编号都一样。

另外发现恢复数据只占用极少的快照空间:

$ sudo lvs -a
  LV         VG   Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  ... ...
  repo       vg   owi-a-s--- 500.00g                                                    
  repo0s0    vg   swi-aos--- 100.00g      repo   2.83                                   
  repo0s1    vg   swi-aos--- 100.00g      repo   1.14                                   

怀疑 lost+found 下的大量垃圾数据是恢复被 ghost 复写的垃圾数据产生, 再建一个快照, 抹零前 9576MiB (df -m 看到的 ntfs 分区占用空间, 应为 ghost 恢复后的头部) 再尝试恢复。 机械磁盘较慢, 抹零花了 3 分钟 (写快照速度下降一倍?)。 抹零后最靠前 (也是较可靠) 的 superblock 是 2654208。 结果 10 分钟后内存不够报错。

$ sudo lvcreate -s vg/repo -n repo0s2 -L 20g
  Logical volume "repo0s2" created.
$ time sudo dd if=/dev/zero of=/dev/vg/repo0s2 bs=8M count=$(( 9576 / 8 ))
记录了1197+0 的读入
记录了1197+0 的写出
10041163776 bytes (10 GB, 9.4 GiB) copied, 217.848 s, 46.1 MB/s

real	3m37.860s
user	0m0.004s
sys	0m14.400s
$ time sudo fsck.ext4 -v /dev/vg/repo0s2 -b 2654208 -y > a.log
e2fsck 1.42.13 (17-May-2015)
e2fsck: 已中止

real	10m21.034s
user	0m54.888s
sys	1m29.352s

增加到抹零 10GiB, 或可靠superblock 2654208 之前的 10368MiB, 还是内存不足报错。 看来抹零也不能瞎抹, 要有技巧或者看运气的。

repo0s2 抹零 128MiB 从 testdisk 识别到的最后一个 superblock 71663616 恢复, 依然恢复成功。 而且看数据大小和文件夹内容结构都跟之前恢复的结构一样。 由此可推测 ext4 的索引分散在整个文件系统, 从任意一个有效 superblock 都可以恢复文件系统。

重建 repo0s3 抹零 128MiB 从可靠 superblock 2654208 恢复, 比较两个恢复快照的 superblock:

sudo bash -ec 'i0=0 ; for i in 32768 98304 163840 229376 294912 819200 884736 1605632 2654208 4096000 7962624 11239424 20480000 23887872 71663616 78675968 102400000 ; do dd if=/dev/vg/repo0s3 bs=4096 skip=$(( i - $i0 )) count=1 of=a-$i ; done'
sudo bash -ec 'i0=0 ; for i in 32768 98304 163840 229376 294912 819200 884736 1605632 2654208 4096000 7962624 11239424 20480000 23887872 71663616 78675968 102400000 ; do dd if=/dev/vg/repo0s2 bs=4096 skip=$(( i - $i0 )) count=1 of=b-$i ; done'
sudo md5sum a-* b-* | sort -k 2.5n -k 2.3,2.3

发现虽然不同 superblock 都能恢复文件系统, 恢复之后的 superblock 列表却是不一样的。 为保险起见, 决定使用从最靠前可靠 superblock 恢复的快照 repo0s3 找回数据, 其他快照可以删除。 而从实际恢复数据看 (文件和文件夹结构), 从哪个 superblock 恢复的结果似乎都是一样的。

找回数据

受垃圾数据的影响, 恢复文件里有大量无用的垃圾文件。 repo 卷顶层只有几个重要的文件夹, 除了通过能记住名字的文件进行定位, 还可以尝试从这堆垃圾文件中只把正常文件夹过滤出来。

mkdir ../tmp
# 只保留执行 ls 不报错的文件夹. 还有很多文件夹 ls 不报错但是 mv 失败, 也一同忽略.
find -maxdepth 1 -name '.' -o \( -type d -exec bash -c 'ls {} >/dev/null 2>/dev/null' \; -exec mv {} ../tmp/ \; \)
# 删除空文件夹
find -maxdepth 1 -empty -exec rm -rf {} \;

过滤之后剩下的文件夹已经不多了, 再 ls 过一遍相关文件夹内容, 就能将各个文件夹都识别出来了:

root@han2015:/mnt/tmp# ls
#11272193  #131083  #131087  #131091  #17563649  #17566454  #22937601  #23724033  #24248322  #25165825  #30146561
#131081    #131085  #131089  #131093  #17566420  #22806529  #23068673  #24248321  #24903681  #27525121  #4528911
root@han2015:/mnt/tmp# ls *
... ...