简介

这篇文章主要是为了配合另外一篇文章,内容是如何把存储在 btrfs 根目录的文件迁移到一个子卷里。

注意事项

仅适用于 btrfs 分区,不要试图迁移非 btrfs 分区的数据。
文中的许多命令需要 root 权限,请自行添加 sudo 或者直接 sudo -i
虽然迁移可以在系统正在运行的情况下进行,不过仍然推荐制作一个 Live USB,可以在误操作导致系统加载不到正确的分区的情况下进行抢救。

第一步:迁移文件

首先把要进行迁移的分区挂载到 /mnt,各个分区的 UUID 可以通过 lsblk -f 查看,这里以 71c631b0-e53f-40e4-aaac-22ae6fddbb96 为例。

mount -o subvol=/ -U 71c631b0-e53f-40e4-aaac-22ae6fddbb96 /mnt

然后我们需要在 /mnt 下新建一个子卷 (比如 root 分区建议起名 @ ,home 分区的话建议起名 @home),然后把根目录的文件迁移进去。幸运的是我们可以通过快照功能来方便快速(几乎不消耗时间和空间)的完成这项工作

btrfs subvol snapshot /mnt /mnt/@

由于快照会忽略子卷,所以还需要迁移分区里的子卷,首先用 btrfs subvol list /mnt 查看一下分区里面是否包含其它子卷

ID 6 gen 27199 top level 5 path data
ID 7 gen 33718 top level 5 path @

这里首先记一下自己刚健立的子卷的 ID(例中为 7),下一步会用到。然后注意到例中有一个名为 data 的子卷,把 data 也迁移到 @ 子卷里:

btrfs subvol snapshot /mnt/data /mnt/@/data

其实就是对 data 子卷也进行一次快照然后保存到 @ 子卷里,当然这里也可以选择在 @ 子卷里建立文件夹然后修改 fstab 进行挂载的方式。
最后确认一下刚刚创建的子卷的内容

ls /mnt/@

顺便在新的子卷里建立个文件做个标记(检查迁移是否成功的时候会用到)

touch /mnt/@/"$(date)"

最后取消挂载

umount /mnt

第二步:指定需要挂载的子卷

第一步完成了文件的迁移,而第二步需要做的是让系统在下次启动时挂载新的子卷。

如需挂载 root 分区

当 root 分区为 btrfs 格式时,系统在启动时会优先按照 kernel parameters 中 subvolid 或 subvol 所设定的子卷 ID 或者名称,来加载相应的子卷,当 subvolid 以及 subvol 均未设定时,会加载默认的子卷,所以这给我们提供了两种方案。而第二种方案,也就是设定默认加载的子卷,更为优雅,但是由于许多发行版为了体现自己很『智能』,在生成 boot loader 时会强行根据当前挂载在 root 的子卷向 kernel parameters 中添加相应的参数,这也让这种本可以很优雅的方案实现起来会多一些障碍。所以选取哪种方案基本上取决于你的发行版会不会多此一举的往 kernel parameters 里面加料,而判断方法如下:
首先运行

cat /proc/cmdline
  • 如果里面没有出现 subvolid=xx 以及 subvol=xxx,那么恭喜你,你使用的发行版还是比较老实的,可以使用第二种方案。
  • 如果 subvolid 或者 subvol 仅出现了一次,先检查一下自己设定的 kernel parameters 里有没有进行相应的设定:
    • 如果有的话可以删掉 subvolid=xx 以及 subvol=xxx 这两部分,然后更新一下 bootloader,同样可以使用第二种方案。
    • 如果没有则表示你的发行版比较有『自己的想法』,建议使用第一种方案。
  • 如果 subvolid 或者 subvol 出现了多次,这一般是由于你的 kernel parameters 里有相应的设定,并且发行版在生成 bootloader 时还会自行添加相应的参数,这时同样建议使用第一种方案。

方案一

既然系统会根据当前挂载到根目录的子卷 ID 或者名称自行设置 kernel parameters,我们可以把子卷挂载到相应的位置,然后 chroot 过去,最后更新 bootloader 即可。

# xx 为上一步中记下的子卷 ID
mount -o subvolid=xx -U 71c631b0-e53f-40e4-aaac-22ae6fddbb96 /mnt
mount -o bind /dev /mnt/dev                        
mount -o bind /proc /mnt/proc                      
mount -o bind /sys /mnt/sys
mount -o bind /boot /mnt/boot
chroot /mnt

chroot 到 /mnt 之后更新 bootloade (这里以 grub 为例):

grub-mkconfig -o /boot/grub/grub.cfg

退出 chroot 并且取消挂载:

exit
umount /mnt

方案二

通过 btrfs subvol set-default xx / 更改默认挂载的子卷,其中的 xx 为上一步中记下的 ID。完事,就是这么飘逸。

⚠️ 无论采取哪种方案都需要注意
如果需要挂载的子卷中的 kernel 与 /boot 中的 kernel image 版本不同,则需要更新一下 /boot 中的 kernel image,否则下次启动时会发生内核版本不匹配的问题从而导致无法启动。

方法仍旧是先 mount 然后 chroot:

# xx 为上一步中记下的子卷 ID
mount -o subvolid=xx -U 71c631b0-e53f-40e4-aaac-22ae6fddbb96 /mnt
mount -o bind /dev /mnt/dev                        
mount -o bind /proc /mnt/proc                      
mount -o bind /sys /mnt/sys
mount -o bind /boot /mnt/boot
chroot /mnt

更新一下 /boot 下的 kernel image:

mkinitcpio -P

最后退出 chroot 并且取消挂载:

exit
umount /mnt

另外如果之前使用第一种方其实可以顺便在更新 bootloader 的同时也更新一下 kernel image,就省得再挂载一次了。

如需挂载其它分区

直接修改 /etc/fstab 里相应的条目来指定需要加载的子卷 ID,例如

...    /home    btrfs    subvolid=xx,...

其中 xx 为上一步中记下的 ID。
当然也可以通过指定默认挂载的子卷来达成目标,不过这种方法有个缺陷,因为一个分区只能指定一个默认挂载的子卷,所以如果同时需要挂载其它子卷仍然需要修改 fstab。

第三步:检查

重启系统使修改生效,重启完后先去相应的文件夹里检查一下有没有我们在第一步里建立的『标记』文件,如果有就表明新的子卷已经正确的挂载了,再检查一下有没有遗落的文件就行。
如果一切都没问题,可以删除之前的旧文件:

mount -o subvol=/ -U 71c631b0-e53f-40e4-aaac-22ae6fddbb96 /mnt
cd /mnt
# 删除旧的文件
# rm ...
# 删除旧的子卷
# btrfs subvol delete ...
umount /mnt