发送端 ( btrfs send)

发送端 ( btrfs send)

我很快就会解释。我有两个单独的电影和音乐子卷,我将其备份在另一个带有发送/接收功能的磁盘上。我想将两个子卷合并为一个(带有cp --reflink)并将其发送到备份驱动器,从现有快照克隆重新链接的文件。

我尝试创建new_volume,然后cp -rx --reflink "music" "movies"进入它,然后:

btrfs send \
    -c music-snapshot \
    -c movies-snapshot \
    new_volume-snapshot | btrfs receive /path/to/backup

但它抱怨无法确定父级new_volume(由 id 标识):

ERROR: parent determination failed for <id_number>

当然music-snapshot并且movies-snapshot存在备份。我也尝试过快照到moviesnew_volume然后cp -rx --reflink "music"进入其中,然后快照并使用 发送它btrfs send -c music-snapshot -p movies-snapshot new_volume-snapshot,但似乎-c music-snapshot没有任何效果,无论如何都会发送数据。

我想做的事情可能吗?

编辑:我也尝试这样做:

btrfs sub snap -r movies movies-A
btrfs sub snap -r music music-A 
btrfs sub snap movies new_volume
btrfs sub snap -r new_volume new_volume-A

此时我有:

movies-A
music-A
new_volume-A (exact clone of movies-A)

然后:

cp -rx --reflink music/* new_volume/
btrfs sub snap -r new_volume new_volume-B

最后,当 和 都movies-A存在music-A于目的地时,我可以这样做:

btrfs send \
    -p movies-A \
    new_volume-A | btrfs receive /backup/

这总是完美地工作(发送的数据接近于零)。但...

btrfs send \
    -p new_volume-A \
    -c music-A \
    new_volume-B | btrfs receive /backup/

我不知道如何解释:对于它的“某些文件”,music它可以工作,而对于其他文件,它会不断发送数据,因为它忽略了源。我认为这种方式绝对应该有效,但我无法可靠地重现“工作”和“不工作”行为。我倾向于认为某处存在错误。有人可以试试这个吗?

答案1

(免责声明:lgaudino 的评论表明,提议的合并方法不再适用于 btrfs-progs >=v5.14.2由于改变了btrfs 属性更改快照的只读状态时的行为)

你想要的可以实现,但不能直接实现,因为btrfs send( 和btrfs receive) 是相当简单的工具,无法通过提供次要父级将快照“合并”为一个快照从工具内部。

发送端 ( btrfs send)

btrfs send输出文件系统级别操作(目录/文件创建、元数据传输、文件内容传输等),btrfs receive“接收方”可以使用这些操作在另一个 btrfs 文件系统上创建等效快照。这些操作可以通过执行“空运行”来查看btrfs receive --dump

一个增加的send(例如btrfs send -p <parent> <snapshot>)的工作方式与所关注的完全相同,除了仅记录通过修改(添加/删除目录/文件,更新元数据)btrfs send创建所需的文件系统操作。<snapshot><parent>

也没有进行特殊的“亲子”关系检查。假设该-p参数用于指定“父级”,btrfs send则只需生成从该快照到另一个快照(充当“子级”)所需的命令。如果两个快照完全不相关,这甚至可以工作。

在发送端,唯一的要求btrfs send是所有快照都是只读。只读子卷/快照通常是通过-r选项生成的btrfs subvolume snapshot -r <subvol> <snap>, 虽然btrfs property set <snap> ro true/false也可用于在创建后更改标志。

btrfs send完全不考虑接收端有什么或没有什么,这是不可避免的,因为双方之间没有双向通信,就像rsyncbtrfs 发送/接收甚至可能不同时运行,而是分别在不同的时间读取/写入文件。

只能有一个父母

手册页和(旧)维基常见问题解答不幸的是相当混乱。作者是btrfs-clone 断言btrfs发送和接收最多只能考虑增量传输的父级。父级可以通过-p一个或多个-c选项直接指定或间接指定:

btrfs-send来自 btrfs-tools 4.13 选择子卷 S 的父卷和给定克隆源集 C_i像这样:

  1. 如果-p指定了选项,则使用它
  2. 如果S没有parent_uuidset,或者找不到这个uuid,则放弃
  3. 如果存在C_iwith C_i->uuid == S->parent_uuid(S 是子卷(快照)的子卷,我们称其为“妈妈”),则使用它
  4. 如果没有C_i与S相同parent_uuid,则放弃
  5. 从所有“妈妈”的孩子 C_i 中,选择与“妈妈”最接近的一代(实际上,ctransid,与“一代”到底有什么区别?)。

注意维基百科有点误导,因为它表明 -cwithout与withp不同,尽管上面的算法通常暗示了这一点。唯一相关的例外是发送没有父卷的子卷。-c-p-p

总之,我建议始终明确指定一个父项 via-p并忽略-c,btrfs receive根本没有“两个父项”这样的概念。

接收端 ( btrfs receive)

btrfs-receive手册页将该程序的用途描述为:

接收更改流并复制先前由 btrfs send 生成的一个或多个子卷

在完全传输的情况下,将创建一个新的子卷,在增量传输的情况下,将<parent>创建快照(接收方等效)的新快照。无论哪种情况,新创建的子卷/快照最初都是读写btrfs send直到成功应用来自的“更改流”之后,才制作子卷/快照只读

显然,没有办法实现两个父级,因为“更改流”中首先没有写时复制/引用链接副本。快照<parent>只是提供了应用“变更流”的基础。

但是接收端如何确保自己的<parent>快照与<parent>发送端的快照相同呢?它从不检查/比较实际内容快照/子卷,而是使用通用唯一识别码与相关的元数据不变性父快照的假设。

btrfs subvolume show <subvolume>提供如下输出:

/mnt/btrfs/subvolume
        Name:                   subvolume
        UUID:                   5e076a14-4e42-254d-ac8e-55bebea982d1
        Parent UUID:            -
        Received UUID:          -
        Creation time:          2018-01-01 12:34:56 +0000
        Subvolume ID:           79
        Generation:             2844
        Gen at creation:        2844
        Parent ID:              5
        Top level ID:           5
        Flags:                  -
        Snapshot(s):

每个子卷/快照都有三个 UUID 插槽,其中两个可能为空。由 创建的每个快照btrfs subvolume snapshot始终有一个Parent UUID明确标识其父级的快照,并且 由 创建的每个快照btrfs receive始终都有一个Received UUID条目。如果是全量传输,则不会有任何Parent UUID条目,如果是增量传输,则会同时有条目Received UUIDParent UUID条目。用户无法手动更改 UUID 条目。

这些 ID 足以btrfs receive确定它是否拥有“接收方”<parent>快照应该与“发送方”快照完全相同<parent>,因为它Received UUID应该等于发送方父级的 UUID,从而确保它是从它创建的,并且涉及的所有快照的只读状态意味着存在不应该在此期间是否有任何变化。

(旁注:虽然两个父卷中只有一侧包含接收到的 UUID 条目,但 btrfs 发送/接收似乎足够智能,可以在“更改流”中提供此元数据,因此双方/文件系统都可以充当“发送”或“接收”端,根据用户需求)

如何合并两个子卷/快照 (< v5.14.2)

正如我们所看到的,使用 only 进行合并是不可能的,btrfs send/receive但手动进行合并相当容易,假设有人想要将movies和合并music到一个新的子卷下unified,并假设movies和 也music完全镜像在接收端:

  1. 在发送端创建一个新的(空)子卷:btrfs subvolume create unified
  2. 将其更改为只读:btrfs property set unified ro true
  3. 将其发送到备份:btrfs send /path/to/unified | btrfs receive /receiving/side/
  4. 禁用只读两侧,例如btrfs property set /path/to/unified ro false对于发送方
  5. 手动将和的--reflink内容musicmoviesunified 两侧 cp -a --reflink /path/to/music /path/to/movies /path/to/unified/
  6. 双方unified快照恢复为只读btrfs property set /path/to/unified ro true
  7. 创建一个新的读写快照unified进行交互,unified将作为未来增量传输的父级双方

music也可以从预先存在的子卷或movies快照从步骤 4 开始,而不是创建新的/空的子卷并发送它。从 btrfs 的角度来看,唯一重要的是两侧(未来)的父级通过一侧Received UUID指向另一侧而连接,并且两者都仅在未来传输时读取。

>= v5.14.2 做什么

最不坏的解决方案可能是遵循上一节中的方法,但另外Received UUID通过以下方式手动重置该值python-btrfs取消设置后btrfs-properties

没有好的仅使用标准btrfs-progs工具的解决方案。最接近“a”解决方案的可能是利用事实btrfs receive在传输完成之前创建的快照是可读可写的,并尝试--reflink在快照设置为只读之前进行复制btrfs-receive。换句话说,利用竞争条件是目前仅使用 btrfs-progs >=5.14.2 标准工具来执行此操作的唯一方法。

显然,真正需要的是有人向 btrfs 项目提出功能请求,至少允许合并,就像 5.14.2 之前的那样,对于那些知道自己在做什么的人来说是可能的。

无论如何,竞争条件的利用可能看起来像这样:由于我们仍然可以更改发送端快照的只读状态,因此我们可以更改所述快照,例如

使用movies并重新链接复制music到其中:

btrfs property set -f movies ro false
cp -a --reflink /path/to/music /path/to/movies/
btrfs property set -f /path/to/movies ro true   
btrfs subvolume snapshot /path/to/movies /path/to/unified_prep

如果有必要,我们现在可以移动其中的目录和文件unified_prep(但保留音乐目录),然后准备好后:

btrfs subvolume snapshot -r /path/to/unified_prep /path/to/unified
btrfs send -p /path/to/movies /path/to/unified | btrfs receive /path/to/backups/

btrfs send并且btrfs receive应该不是选择附加音乐目录,因为它同时存在于movies(发送端)和unified(客户端)中。现在,我们设法cp -a --reflink /bkp/to/music /bkp/to/unified/在接收端(!)同时unified传输新快照并因此可读可写,然后发送端子unified卷及其接收端快照应该相同,并且我们已成功合并子卷。

相关内容