· Arch

Btrfs 大小调整失败后……

将日常使用的所有软件和数据都迁移到了 Arch Linux(安装在 Btrfs 上)后,需要调整 Btrfs 的大小使之填满整个硬盘。

但是使用 KDE 分区编辑器进行操作,失败了……

症状

Btrfs 大小调整失败

我的失败操作本身没有截图保留下来,上面的图片来自这里。此时如果点击 [OK] 关闭对话框,会发现被调整的 Btrfs 分区变成了无法识别文件系统的分区。

不幸的是我的 Btrfs 位于磁盘末尾,调整操作是向前合并剩余空间,这样就无法读取 Btrfs 的 superblock,这就导致此时如果直接使用 btrfs checkbtrfs rescuebtrfs recover 等工具试图修复分区,都会因此失败。

更不幸的是我没有记录调节前的分区情况……由于着急使用电脑(第二天要开会),我先把整个硬盘 dd 了出来,然后重装了系统并用备份恢复了绝大多数数据。这份备份距离操作失败仅有 8 天不到,且我的工作文件都设置了云盘自动同步,因此只是损失了极少数工作无关的文件,自己手动重做了。不过丢失的数据中包含一些单机游戏的存档(没有备份),因此无论如何还是希望能恢复。

查找文件系统所在位置

Btrfs 文件系统的 superblock 位于 0x10000 位置,其中的 0x40 位置包含一个 8 字节的魔数 _BHRfS_M。因此首先要做的就是找到这个魔数。

$ strings -a -n 8 -t x backup.img | grep "_BHRfS_M"
...省略若干行
25926df14e _BHRfS_MI9H@t
25926e538f _BHRfS_MH9A@
25926fd63e _BHRfS_M1
2603210040 _BHRfS_M
2607200040 _BHRfS_M
268fcbfd98 _BHRfS_M
268fe0c438 _BHRfS_M
268ff4be58 _BHRfS_M
...省略若干行

这个扫描过程会耗费相当长的时间(每秒大约只能扫描 40 MiB),之所以不得不把硬盘 dump 出来后续处理,就是因为这个耗时再加上即时处理操作错误(应该扫全盘,但是当时扫的是分区,即便找出了魔数的位置,也得加上分区本身的 offset 才行)导致。

上述操作会产生大量输出。除了真正的 Btrfs 魔数以外,其他二进制文件也可能会恰好包含这个魔数(例如磁盘工具软件)。注意到其中 0x2603210040(约为 152.05 GiB)这个 offset 正好以 0x10040 (= 0x10000 + 0x40) 结尾,考虑到原分区大约确实在 150 GiB 左右的位置,因此直接猜测这就是真正的 Btrfs 魔数。

这样分区的起始位置就应该是 0x2603210040 - 0x10040 = 163261186048,除以 sector size (512 B) 得到正确的起始扇区 318869504。

那么终止扇区又应该是哪一个呢?安装 Arch Linux 时是将这个 Btrfs 分区直接划到了硬盘末尾,分区后面默认留出 40 个扇区,因此终止扇区号就应该是 1953525168 - 1 - 40 = 1953525127。

修改分区表

用 fdisk 打开硬盘:(如果像我一样操作的是硬盘镜像,那么需要先用 losetup 将其挂载为 loop device)

$ sudo fdisk /dev/loop0

Welcome to fdisk (util-linux 2.39.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

检查现有分区表:

Command (m for help): p
Disk /dev/loop0: 931.51 GiB, 1000204886016 bytes, 1953525168 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
Disklabel type: gpt
Disk identifier: 2BB82862-4819-41F6-836E-1C83ACAA383B

Device           Start        End    Sectors   Size Type
/dev/loop0p1        40    2097191    2097152     1G EFI System
/dev/loop0p4   2099200 1953523711 1951424512 930.5G Linux filesystem

删除损坏的分区,并按照计算出的起始/终止位置,重新建立分区。

此处建议保持分区号不变,以防止无法读取 fstab 导致系统无法启动(如果不再需要启动原系统或者 fstab 使用了 UUID 描述分区则无关紧要):

Command (m for help): d
Partition number (1,4, default 4): 4

Partition 4 has been deleted.

Command (m for help): n
Partition number (2-128, default 2): 4
First sector (2097192-1953525134, default 2099200): 318869504
Last sector, +/-sectors or +/-size{K,M,G,T,P} (318869504-1953525134, default 1953523711): 1953525127

Created a new partition 4 of type 'Linux filesystem' and of size 779.5 GiB.

此时 fdisk 已经可以识别到这个位置存在一个 Btrfs 分区了,它会询问是否要删除原有的 Btrfs 标记。因为我们要恢复原来的 Btrfs 分区,而不是重新格式化,所以此处请选择 No

Partition #4 contains a btrfs signature.

Do you want to remove the signature? [Y]es/[N]o: n

最后写入分区表,确认修改:

Command (m for help): w

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

检查文件系统状态,完全正常:

$ sudo btrfs check /dev/loop0p4
Opening filesystem to check...
Checking filesystem on /dev/loop0p4
UUID: e331a1e4-5420-4da8-bdb2-6d78648b79b4
[1/7] checking root items
[2/7] checking extents
[3/7] checking free space tree
[4/7] checking fs roots
[5/7] checking only csums items (without verifying data)
[6/7] checking root refs
[7/7] checking quota groups skipped (not enabled on this FS)
found 342436085760 bytes used, no error found
total csum bytes: 323865092
total tree bytes: 2177007616
total fs tree bytes: 1691320320
total extent tree bytes: 128712704
btree space waste bytes: 312925874
file data blocks allocated: 664760664064
 referenced 395543023616

这时候重新 mount 会发现分区已经可以挂载了,并且数据都可以正确读取。

后记

尽管某软件不支持 Btrfs,但它启动界面上的几个大字还是请各位刻进自己的 DNA 里:

数据无价 谨慎操作

(记得备份!)

其实我之前就因为用 KDE 分区编辑器修改 Windows 的 NTFS 分区翻过一次车,但是马上就用某软件直接扫盘找回了分区,就没当回事。这次其实是一样的问题,只是没有办法找到现成支持 Btrfs 文件系统找回的工具,不得不手动操作了。

手动修改分区表的风险非常大,因此强烈建议修改分区表之前对硬盘做一次完整镜像。(不是单个分区!只保留损坏分区的话,无法计算分区的正确位置,也没法使用 fdisk 了。虽然也能恢复文件系统,但是操作会变得极其繁琐)

至于为什么 KDE 分区编辑器能让我遇上两回这种事情,就真的不知道了……

Btrfs 大小调整失败后……