kristapsdz/openrsync
GitHub: kristapsdz/openrsync
基于 OpenBSD 的 rsync 实现,用于文件同步和数据备份。
Stars: 876 | Forks: 41
# 简介
**此系统已合并到 OpenBSD 基础中。如果您想为 openrsync 贡献,请将您的补丁发送到 tech@openbsd.org。**
此存储库只是 OpenBSD 版本和一些用于可移植性的粘合剂。
这是一个带有 BSD (ISC) 许可证的 [rsync](https://rsync.samba.org/) 实现。它与现代 rsync 兼容(用于测试的是 3.1.3,但任何支持协议 27 的版本都行),但只接受 rsync 命令行参数的一个子集。
它官方支持的操作系统是 OpenBSD,但它可以在其他 UNIX 系统上编译和运行。有关详细信息,请参阅 [可移植性](#Portability)。
openrsync 的官方文档是其手册页面。请参阅 [rsync(5)](https://github.com/kristapsdz/openrsync/blob/master/rsync.5) 和 [rsyncd(5)](https://github.com/kristapsdz/openrsync/blob/master/rsyncd.5) 以获取协议详细信息或 [openrsync(1)](https://github.com/kristapsdz/openrsync/blob/master/openrsync.1) 中的实用程序文档。
如果您想编写自己的 rsync 实现,协议手册页面应包含所需的所有信息。
本页上的 [架构](#Architecture) 和 [算法](#Algorithm) 部分旨在向开发者介绍源代码。它们是非官方的。
## 项目背景
openrsync 是作为 [rpki-client(1)](https://medium.com/@jobsnijders/a-proposal-for-a-new-rpki-validator-openbsd-rpki-client-1-15b74e7a3f65) 项目的组成部分编写的,该项目是 OpenBSD 的 [RPKI](https://en.wikipedia.org/wiki/Resource_Public_Key_Infrastructure) 验证器。openrsync 由 [NetNod](https://www.netnod.se), [IIS.SE](https://www.iis.se),[SUNET](https://www.sunet.se) 和 [6connect](https://www.6connect.com) 资助。
# 安装
在最新的 UNIX 系统上,只需下载并运行:
```
% ./configure
% make
# 安装
```
这将安装 openrsync 实用程序和手册页面。
同时安装 rsync 的安装是可以的:这两个程序在没有任何方式上会冲突。
如果您升级了源代码并想重新安装,只需运行相同的命令。
如果您想卸载源代码:
```
# 卸载
```
如果您想以服务器的方式与 openrsync 交互,您可以运行以下命令:
```
% rsync --rsync-path=openrsync src/* dst
% openrsync --rsync-path=openrsync src/* dst
```
如果您想让 openrsync 和 rsync 交互,使用两个都有的命令行标志是很重要的。
请参阅 [openrsync(1)](https://github.com/kristapsdz/openrsync/blob/master/openrsync.1) 以获取列表。
# 算法
有关 rsync 算法的详细描述,请参阅 Andrew Tridgell 和 Paul Mackerras 撰写的 "[The rsync algorithm](https://rsync.samba.org/tech_report/)"。Andrew Tridgell 的博士论文 "[Efficient Algorithms for Sorting and Synchronization](https://www.samba.org/~tridge/phd_thesis.pdf)" 更详细地涵盖了这些主题。
这提供了适合深入研究源代码的描述。
rsync 算法有两个组件:*发送器*和*接收器*。发送器管理源文件;接收器管理目标。
在以下调用中,首先发送器是主机 *remote*,接收器是本地主机,然后相反。
```
% openrsync -lrtp remote:foo/bar ~/baz/xyzzy
% openrsync -lrtp ~/foo/bar remote:baz/xyzzy
```
该算法依赖于组件之间共享的文件列表和元数据(例如,模式、mtime 等)。
文件列表描述了更新的所有源文件,并由发送器生成。
共享在 [flist.c](https://github.com/kristapsdz/openrsync/blob/master/flist.c) 中实现。
在共享此列表后,接收器和发送器独立按文件名的字典顺序对条目进行排序。
这使得文件列表可以无序地发送和接收。
排序保留了目录优先的顺序,因此目录在包含的文件之前处理。
此外,一旦排序,发送器和接收器都可以通过在排序数组中的位置来引用文件条目。
接收器读取列表后,它会遍历列表中的每个文件,将信息传递给发送器,以便发送器可以发送回更新文件的指令。
这被称为“块交换”,是 rsync 算法的核心。
在块交换期间,发送器等待接收更新请求或序列结束消息;一旦收到请求,它就会扫描要发送给接收器的新块。
一旦块交换完成,所有文件都将是最新的。
接收器在 [receiver.c](https://github.com/kristapsdz/openrsync/blob/master/receiver.c) 中实现;发送器在 [sender.c](https://github.com/kristapsdz/openrsync/blob/master/sender.c) 中实现。
大部分块交换都在 [blocks.c](https://github.com/kristapsdz/openrsync/blob/master/blocks.c) 中发生。
## 块交换
块交换序列对于文件是目录、符号链接还是常规文件而有所不同。
对于符号链接,接收器所需的信息已经编码在文件列表元数据中。
符号链接被更新为指向正确的目标。
不需要从发送器请求更新。
对于目录,如果目录不存在,则创建目录。
不需要从发送器请求更新。
常规文件的处理方式如下。
首先,检查文件是否是最新的。
如果文件大小和最后修改时间相同,则发生这种情况。
如果是这样,则不需要从发送器请求更新。
否则,接收器检查每个文件,按固定大小的块进行。
有关详细信息,请参阅 [块大小](#block-sizes)。
(如果文件大小不能被块大小整除,则终端块可能更小。)
如果文件为空或不存在,它将具有零个块。
每个块都进行两次哈希:首先,使用快速 Adler-32 类型 4 字节哈希;其次,使用较慢的 MD4 16 字节哈希。
这些哈希在 [hash.c](https://github.com/kristapsdz/openrsync/blob/master/hash.c) 中实现。
接收器将文件的块哈希发送给发送器。
一旦被接受,发送器就会检查具有给定块的相应文件。
对于源文件中的每个字节,发送器根据块大小计算一个快速哈希。
然后,它在发送的块信息中查找匹配的快速哈希。
如果找到匹配项,它然后计算并检查慢速哈希。
如果没有找到匹配项,它继续到下一个字节。
匹配(以及实际上所有的块操作)在 [block.c](https://github.com/kristapsdz/openrsync/blob/master/block.c) 中实现。
当找到匹配项时,首先将匹配之前的数据作为字节流发送给接收器。
然后是找到的块的标识符,如果没有更多数据,则为零。
接收器首先写入字节流,然后如果指定了块,则复制该块中的数据。
这继续到文件末尾,此时文件已完全重建。
如果接收器端不存在文件——这是基本案例——则整个文件作为字节流发送。
在此之后,整个文件使用 MD4 哈希进行哈希。
然后比较这些哈希;如果成功,则算法继续到下一个文件。
## 块大小
块大小算法在协议效率中起着至关重要的作用。
一般来说,块大小是总文件大小的平方根的舍入值。
然而,最小块大小为 700 B。
否则,平方根计算是 [sqrt(3)](https://man.openbsd.org/sqrt.3) 后跟 [ceil(3)](https://man.openbsd.org/ceil.3)
由于未知原因,平方根结果向上舍入到最接近的 8 的倍数。
# 架构
每个 openrsync 会话都分为一个运行的 *服务器* 和 *客户端* 进程。
客户端 openrsync 进程由用户执行。
```
% openrsync -rlpt host:path/to/source dest
```
服务器 openrsync 在远程主机上执行,要么是按需通过 [ssh(1)](https://man.openbsd.org/ssh.1),要么作为持久性网络守护进程。
如果通过 [ssh(1)](https://man.openbsd.org/ssh.1) 执行,服务器 openrsync 与客户端(用户启动的)openrsync 的区别在于 **--server** 标志。
一旦客户端或服务器 openrsync 进程启动,它就会检查命令行参数以确定它处于 *接收器* 或 *发送器* 模式。
(守护进程以协议特定的方式将命令行参数发送给守护进程,如 [rsyncd(5)](https://github.com/kristapsdz/openrsync/blob/master/rsyncd.5) 中所述,但除此之外做的是同样的事情。)
接收器是文件的目标;发送器是来源。
始终有一个接收器和发送器。
服务器进程明确地用 **--sender** 命令行标志指示它是一个发送器,否则它是一个接收器。
客户端进程隐式地通过查看命令行上传递的文件来确定其状态,以确定它们是本地还是远程。
```
openrsync path/to/source host:destination
openrsync host:source path/to/destination
```
在第一个示例中,客户端是发送器:它从自身向服务器 *发送* 数据。
在第二个示例中,情况相反,它 *接收* 数据。
客户端的命令行文件可以具有以下任何主机规范,这些规范确定本地性。
- 本地:*../path/to/source ../another*
- 远程服务器:*host:path/to/source :path/to/another*
- 远程守护进程:*rsync://host/module/path ::another*
主机规范必须一致:源必须在同一主机上本地或远程。两者不能都是远程。(**旁白**:技术上可以这样做。我不确定为什么 GPL rsync 限制为一种或另一种。)
如果源或目标在远程服务器上,客户端会 [fork(2)](https://man.openbsd.org/fork.2) 并在远程主机上通过 [ssh(1)](https://man.openbsd.org/ssh.1) 启动服务器 openrsync。
客户端和服务器随后通过 [socketpair(2)](https://man.openbsd.org/socketpair.2) 管道进行通信。
如果是在远程守护进程上,客户端不会 [fork(2)](https://man.openbsd.org/fork.2),而是通过网络 [socket(2)](https://man.openbsd.org/socket.2) 连接到独立的服务器。
服务器命令行,无论是通过 [ssh(1)](https://man.openbsd.org/ssh.1) 会话按需启动的 openrsync,还是传递给守护进程,都与客户端的命令行不同。
```
openrsync --server [--sender] . files...
```
提供的文件在接收器模式下是单个目标目录,在发送器模式下是源文件列表。
独立的全停止符对我来说是个谜。
本地性检测和路由到客户端和服务器运行时由 [main.c](https://github.com/kristapsdz/openrsync/blob/master/main.c) 处理。
服务器客户端在 [client.c](https://github.com/kristapsdz/openrsync/blob/master/client.c) 中实现,服务器在 [server.c](https://github.com/kristapsdz/openrsync/blob/master/server.c) 中实现。
网络守护进程的客户端在 [socket.c](https://github.com/kristapsdz/openrsync/blob/master/socket.c) 中实现。
远程服务器 openrsync 的调用由 [child.c](https://github.com/kristapsdz/openrsync/blob/master/child.c) 管理。
一旦客户端和服务器开始,它们就开始通过连接的套接字协商文件传输。
使用的协议在 [rsync(5)](https://github.com/kristapsdz/openrsync/blob/master/rsync.5) 中指定。
对于守护进程连接,[rsyncd(5)](https://github.com/kristapsdz/openrsync/blob/master/rsyncd.5) 协议也用于握手。
接收器端由 [receiver.c](https://github.com/kristapsdz/openrsync/blob/master/receiver.c) 管理,发送器端由 [sender.c](https://github.com/kristapsdz/openrsync/blob/master/sender.c) 管理。
接收器端实际上有两个功能:不仅必须将块元数据上传到发送器,还必须处理发送器发送的数据写入。
rsync 协议设计得使发送器接收块请求并持续向接收器发送数据。
为了实现这一点,接收器作为 *上传器* 和 *下载器* 进行多任务处理。这些角色分别在 [uploader.c](https://github.com/kristapsdz/openrsync/blob/master/uploader.c) 和 [downloader.c](https://github.com/kristapsdz/openrsync/blob/master/downloader.c) 中实现。
多任务处理是通过由来自发送器的数据和准备就绪的磁盘上的文件驱动的有限状态机来完成的。
上传器扫描文件列表,异步打开文件以处理块。
当它等待文件打开时,它将控制权交给事件循环。
当文件可用时,它对块进行哈希和校验和,并将其上传到发送器。
下载器等待来自发送器的数据。
当数据准备好(并且以将要更新的文件为前缀)时,下载器异步打开现有的文件以执行任何块复制。
当文件可用于读取时,它然后继续从发送器读取数据并从现有文件复制。
## 与 rsync 的区别
rsync 的设计涉及在接收器旁边运行另一个模式:生成器。
这作为从接收器 [fork(2)](https://man.openbsd.org/fork.2) 出来的另一个进程实现,并与接收器和发送器通信。
在 openrsync 中,生成器和接收器是同一个进程,并使用事件循环来快速响应读写请求。
# 安全性
除了常规的防御性编程之外,openrsync 还大量使用原生安全功能。
执行代码可用的系统操作首先受 OpenBSD 的 [pledge(2)](https://man.openbsd.org/pledge.2) 限制。给出的誓言取决于操作模式。例如,接收器需要写入磁盘的权限——但仅在非 dry-run 模式(**-n**)下。
[pledge(2)](https://man.openbsd.org/pledge.2) 允许在操作过程中限制可用资源。
第二个工具是 OpenBSD 的 [unveil(2)](https://man.openbsd.org/unveil.2),它限制对文件系统的访问。这可以防止恶意尝试“突破”目标。它是一个很有吸引力的替代方案 [chroot(2)](https://man.openbsd.org/chroot.2),因为它不需要 root 权限来执行。
在接收器端,文件系统在目标目录及其下方 [unveil(2)](https://man.openbsd.org/unveil.2)。
在创建目标目录之后,只能访问或修改该目录内的目标。
最后,MD4 哈希是用 [arc4random(3)](https://man.openbsd.org/arc4random.3) 而不是 [time(3)](https://man.openbsd.org/time.3) 进行种子化的。这仅适用于以服务器模式运行 openrsync 的情况,因为服务器生成种子。
# 可移植性
许多人询问了可移植性。
唯一官方支持的操作系统是 OpenBSD,因为它具有相当多的安全功能。然而,openrsync 使用 [oconfigure](https://github.com/kristapsdz/oconfigure) 在非 OpenBSD 系统上进行编译。这是为了鼓励移植。
它目前可以在 Linux(glibc 和 musl)、FreeBSD、NetBSD、
标签:6connect, BSD许可证, IIS.SE, NetNod, OpenBSD, RPKI, rsync, SUNET, UNIX, 内核驱动, 客户端加密, 数据备份, 文件同步, 文档, 算法, 系统工具, 网络协议, 软件安装, 软件架构