绿联 Btrfs 阵列无损转换为飞牛原生存储空间

14 min

换 NAS 系统时,我最想解决的问题是:原来的硬盘和文件能不能接着用?

这次从绿联 UGOS 切换到飞牛 fnOS,我保留了原来的三组 Btrfs 阵列,没有重建 RAID、格式化,也没有把文件搬到另一组硬盘上。现在这三组阵列已经出现在飞牛的存储空间界面里,旧文件可以读取,新文件可以写入,SMB 共享也通过了测试。

完成转换后,日常使用就在飞牛界面中进行,不需要手动挂载,也不用进入测试时建立的临时目录。

设备是绿联 DXP6800 Plus。这次验证使用 fnOS 1.2.0701,内核为 6.18.18.c1107-trim,验证日期是 2026 年 10 月 11 日。

原来的阵列飞牛中的存储空间飞牛显示类型界面显示容量
MD linear存储空间 2JBOD约 1.8 TB
单成员 MD RAID1存储空间 3Basic约 3.62 TB
四成员 MD RAID6存储空间 4RAID6约 10.89 TB

飞牛原有的存储空间 1 保留。第二组在界面上显示为 Basic,底层仍是原来的单成员 MD RAID1。

这里说的“无损”,指旧数据留在原盘上,没有删除或迁移。转换过程中仍然写入了文件系统兼容标记、LVM 名称和飞牛存储登记。这是针对上述版本完成的一次实践,涉及真实硬盘的元数据修改。

同样是 Btrfs,为什么不能直接用

这三组阵列的结构是:

硬盘分区 → Linux MD 阵列 → LVM → 单设备 Btrfs

四盘 RAID6 在 MD 层完成,Btrfs 看到的是一个逻辑设备。它与 Btrfs 自身管理多块硬盘的 RAID6 不是同一种结构,后者没有在这次测试中验证。

最先遇到的是文件系统兼容问题。绿联的 Btrfs 超级块带有 UGACL 不兼容标记,目标内核无法直接使用。需要处理这个标记,再确认旧文件能读、新文件能写,以及权限行为有没有变化。

随后还有飞牛的识别问题。即便 Linux 已经能访问文件,飞牛的存储空间界面也不一定会出现这组阵列。它有自己的设备命名、存储登记、个人目录和共享机制,这部分也要接上。

所以我们先从只读访问开始,验证读写之后,再研究怎样让飞牛接管已有阵列。

先读原盘,把写入留在隔离环境

操作前先核对硬盘序列号和精确容量,并记录 MD、LVM、Btrfs 的身份与成员关系。设备名可能随启动变化,不能只认 /dev/sdX。系统盘、旧系统 SSD 和测试 SSD 单独列入保护清单;缺盘、降级、重建或设备关系无法解释时,工具会停止。

第一轮没有修改原盘的超级块。我们用只读映射读取原数据,仅把超级块对应的区域替换为独立文件中的兼容版本。最多三个超级块,每份 4096 字节,总共不超过 12 KiB,不必复制整卷。读取时也禁止日志重放。

接下来在独立飞牛虚拟机中,用合成样本验证文件、链接、子卷、权限和三种阵列结构。真实来源则使用独立 COW 区域:数据从原卷读取,测试写入留在另一处,不合并回原卷。这套机制可以参考 Linux device-mapper snapshot 文档。

测试 SSD 也按只读方式交给虚拟机,写入进入独立 qcow2 overlay。结束后复核了硬盘身份和首尾 GPT 元数据,没有格式化或合并测试写入。相关原理见 QEMU 磁盘镜像文档。

这些测试检查了写入、fsync、重命名、读回,以及重新激活后数据是否还在。权限测试则发现,清除 UGACL 标记后,绿联原来的拒绝访问、只读策略和配额并不会自动变成飞牛的对应规则。后续访问因此按飞牛账号重新建立,旧权限语义没有完整继承。

在这个阶段,我们建立过临时目录和兼容 SMB 共享,确认普通账号和客户端都能读写原数据。不过,那时文件能用了,飞牛还没有把阵列识别为自己的存储空间。这也是后面继续研究原生接管的原因。

原盘上究竟改了什么

与真实原卷身份、目标内核和代码匹配的 COW 验证通过后,才对原卷进行兼容转换。

Btrfs 部分只处理超级块中的 UGACL bit 62,并重新计算 CRC32C。写入前备份并同步到磁盘,再依次写次级和主超级块,逐份读回核对。RAID、文件数据区和旧文件位置都保留,旧文件的权限也没有被批量改写。

这里有一个必须保留的提醒:原卷开始读写后,不能直接写回转换前的超级块来“撤销”。新写入已经改变文件系统状态,旧超级块可能指向旧状态,返回 UGOS 也没有在这次实践中验证。

超级块处理逻辑见仓库中的兼容转换代码。

让三组阵列出现在飞牛存储空间中

文件系统能够读写之后,我们检查了这个版本飞牛的设备识别与登记方式:LVM 命名与 trim_ 有关,存储记录位于 PostgreSQL 的 trim 数据库,原生路径使用 /volN。

接管工具先校验版本,保存 LVM 元数据、配置和存储记录,再在原卷停止访问后调整已有 LVM 的名称,登记新的空间编号。原 LV UUID、容量和阵列成员保持不变,飞牛原有空间的记录也保留。

整个过程继续使用原来的 MD 阵列和 Btrfs,没有重新创建一套 RAID。飞牛网页里的普通“创建存储空间”流程也不能代替这次导入;那是 官方创建流程。

这部分依赖实测版本的内部规则,后续系统升级需要重新验证。

接入个人目录时还遇到过权限错误:直接创建数字账号目录返回 EPERM。我们在隔离环境复现后调整了初始化方式,只处理新建的个人目录,没有覆盖已有目录,也没有递归修改旧文件权限。

这次接管的卷,原来的顶层内容全部是目录。工具把它们关联到对应空间的原生个人文件入口,文件仍在原位置。后台服务维护这些关联,用户不需要手动操作。

随后由飞牛生成三组原生 SMB 共享。旧文件读取、新文件写入、重命名、上传和下载都通过了测试,网页中也能看到三组空间。到这里,日常访问才完整进入飞牛自己的存储空间和共享流程。

根部普通文件、符号链接等其他布局的自动接入还需要另行处理,不能直接套用这次全部为目录的结果。

性能会不会受影响

最终的原生入口使用内核 bind,直接访问原 Btrfs,文件数据不经过前期兼容入口使用的 bindfs 用户态转发层。测试用的 COW 和 qcow2 overlay 也没有留在生产阵列的数据路径中。

bind 只是让同一目录在另一处可见,不复制文件;bindfs 则通过 FUSE 处理访问。相关说明见 Linux mount 手册 和 bindfs 手册。

从数据路径看,最终方案没有额外引入本工具的用户态文件转发开销。不过,我还没有做同条件的性能基准,不能说它与飞牛新建空间完全一样。原 RAID 布局、硬盘组合、文件碎片、剩余容量、Btrfs 参数、SMB 和网络都会影响速度。

这次每组 16 MiB 的本地写入和 1 MiB 的 SMB 上传下载,是读写功能测试,不是速度测试。

已经测过的,以及还没测的

检查内容结果
飞牛网页与容量识别三组空间可见,原有空间 1 保留
旧文件读取抽样旧文件的首尾片段与基线一致
旧目录元数据服务重启前后,原顶层目录的所有者、原 POSIX 模式及可读取 xattr 保持一致
本地读写每组 16 MiB 写入、fsync、重命名、读回通过;原顶层目录内新增测试文件均通过
原生 SMB三组均完成上传、重命名、下载,后续实际使用再次确认读写正常
重启隔离系统整机重启通过,真实 NAS 普通服务重启通过

这些检查没有逐个校验全盘文件。真实 NAS 整机重启、全盘 scrub、断电恢复、飞牛扩容、修复和快照管理,也还没有验证。

旧目录之间的跨目录移动、应用扫描和 Btrfs 子卷操作同样需要补测。读写和共享正常,不能据此推断飞牛的全部存储管理功能都已兼容。

源码与安装包

这套应用叫 绿联阵列转换,项目名为 ugacltool,与 UGOS 自带的同名程序不同,也不属于仓库中的 ext4 恢复流程。

公开源码 将硬盘序列号、型号、容量和阵列身份改为参数,保留了防止选错硬盘的检查,不包含本机账号密码或私有运行配置。

后来又把这些步骤整理成了飞牛应用。现在管理员打开“绿联阵列转换”,选择阵列、核对硬盘序列号和容量,就可以点击接管。后台先做隔离验证,再完成原生登记和开机维护,成功后显示存储空间编号。

新版分别用 JBOD、单成员 RAID1 和四成员 RAID6 的虚拟阵列走完了整个流程,旧文件、普通账号读写、飞牛自动生成的 SMB 共享和虚拟机正常重启都通过了检查。Linux 上的 122 项自动检查也全部通过。这些都是隔离环境的结果,没有拿已经接管的三组真实阵列再转换一次。

GitHub 项目主页 提供源码,安装包在仓库的 Releases 页面获取。后续更新也会发布在那里,安装步骤和支持范围以仓库首页的说明为准。当前仍限定本文的飞牛版本,不能直接套用到其他版本或故障阵列。

对这台 NAS 而言,切换系统后原来的三组阵列和文件已经接着用起来了。日常读写和共享都在飞牛中完成,前期测试目录和临时共享留在探索记录里。