rsync源码解析 (4) 文件列表 (File List)

在前一章 Rsync 进程角色 (Sender/Receiver/Generator)中,我们认识了 rsync 内部协同工作的"演员们"------生成器、发送者和接收者。我们知道了"谁"在做事。现在,是时候来了解他们工作的"蓝图"和"清单"了。这份清单,就是本章的主角------文件列表(File List)。

在开始任何实际的文件传输之前,rsync 必须先搞清楚一个根本问题:到底有哪些文件和目录需要被处理?

一场仓库搬迁的库存清单

想象一下,你正在指挥一次大型仓库搬迁。在搬运任何一个箱子之前,你首先需要一份详细的库存清单。这份清单上不仅列出了所有物品的名称(比如"A 型号螺丝刀"),还记录了它们的数量、重量、存放位置、是否易碎等重要属性。

后续的所有工作都依赖于这份清单:

  • 规划路线:根据清单决定哪些物品需要优先搬运。
  • 筛选物品:决定哪些物品需要搬走,哪些可以留在原地(比如已经过期的物品)。
  • 核对签收:新仓库的管理员根据这份清单核对收到的物品是否齐全、完好。

在 rsync 的世界里,文件列表 (File List) 就扮演着这份至关重要的"库存清单"的角色。它是在任何文件传输发生之前,由发送端(Sender)通过扫描源目录结构而创建的一份详细目录。这份列表是整个同步过程的基石和唯一事实来源,后续的所有决策,比如决定哪些文件需要传输(过滤)、传输的顺序(排序),以及如何处理目标端已存在的文件,都完全依赖于这份清单。

文件列表的核心:struct file_struct

这份"库存清单"在 rsync 的代码中,是由一个核心的数据结构 struct file_struct 来表示每一项的。这个结构体就像清单上的一行记录,包含了关于一个文件或目录的所有关键信息。

让我们来看一个 rsync.h 中这个结构体的简化版本:

c 复制代码
// 文件: rsync.h (简化版)

struct file_struct {
	const char *dirname;	// 所在目录的名称
	time_t modtime;		    // 文件最后修改时间
	uint32 len32;		    // 文件长度的低32位 (有标志位决定是否使用64位)
	uint16 mode;		    // 文件类型 (普通文件, 目录, 链接等) 和权限
	uint16 flags;		    // 描述文件状态的标志位 (例如, 是否是顶层目录)
	const char basename[];	// 文件的基本名称 (不含路径)
};
  • dirname: 指向一个字符串,表示这个文件所在的目录路径。
  • modtime: 记录文件的修改时间戳。这是 rsync 判断文件是否"过时"的关键依据之一。
  • len32flags: 组合起来可以表示文件的完整大小(支持64位大文件)。
  • mode: 一个非常重要的字段,它通过位操作存储了两种信息:
    • 文件类型 : 是普通文件 (S_IFREG)、目录 (S_ISDIR) 还是符号链接 (S_ISLNK)?
    • 文件权限 : 比如 rwx-r-xr-x 这样的读、写、执行权限。
  • basename: 文件的名字,不包含它所在的目录路径。比如对于 /home/user/doc/report.txtbasename 就是 report.txt

通过这个结构体,rsync 为每一个待处理的文件都创建了一个精确的"数字档案"。

文件列表的诞生与传递之旅

文件列表的生命周期可以分为两个主要阶段:生成 (在发送端)和接收(在接收端)。

sequenceDiagram participant Sender as 发送端 (flist.c) participant FS as 文件系统 participant Network as 网络 participant Receiver as 接收端 (flist.c) Sender->>FS: 递归扫描源目录 Note right of Sender: 对每个文件和目录... Sender->>FS: lstat("path/to/file") Note right of Sender: 获取文件的元数据(大小,时间,权限等) Sender->>Sender: make_file(): 创建一个 file_struct Note right of Sender: 将元数据填充到结构体中 Sender->>Network: send_file_entry(): 序列化并发送 Receiver->>Network: 接收序列化数据 Receiver->>Receiver: recv_file_entry(): 反序列化 Note right of Receiver: 根据数据重建 file_struct Receiver->>Receiver: 将新的 file_struct 存入本地文件列表

这个流程确保了在同步开始时,接收端能拿到一份与发送端完全一致的、关于源文件的"事实清单"。

1. 生成列表(发送端)

这个过程由 flist.c 文件中的 send_file_list 函数发起。它会遍历命令行中指定的所有源路径。

当遇到一个目录时,它会调用 send_directory 函数。这个函数就像一个侦察兵,负责打开目录,读取里面的每一个条目。

c 复制代码
// 文件: flist.c (简化逻辑)

static void send_directory(int f, struct file_list *flist, char *fbuf, ...)
{
    DIR *d;
    struct dirent *di;

    if (!(d = opendir(fbuf))) { // 打开目录
        // ... 错误处理 ...
        return;
    }

    // 遍历目录中的每一个条目
    while ((di = readdir(d))) {
        // ... 跳过 "." 和 ".." ...
        
        // 为找到的文件/目录条目调用 send_file_name
        send_file_name(f, flist, fbuf, NULL, ...);
    }

    closedir(d);
}

对于目录中的每一个条目,它都会调用 send_file_name,这个函数的核心又是调用 make_filemake_file 负责执行 lstat() 系统调用,获取文件的详细元数据,并用这些数据填充一个新的 struct file_struct 实例。

c 复制代码
// 文件: flist.c (简化逻辑)

struct file_struct *make_file(const char *fname, ..., STRUCT_STAT *stp, ...)
{
    struct file_struct *file;
    STRUCT_STAT st; // 用于存放 lstat 的结果

    // 1. 获取文件状态 (lstat 不会跟随符号链接)
    if (readlink_stat(fname, &st, ...) != 0) {
        // ... 错误处理, 文件可能不存在 ...
        return NULL;
    }

    // ... 检查文件是否被过滤规则排除 ...

    // 2. 分配内存给新的 file_struct
    file = /* ... 分配内存 ... */;

    // 3. 填充结构体
    file->basename = /* ... 从 fname 中提取文件名 ... */;
    file->dirname = /* ... 从 fname 中提取目录名 ... */;
    file->modtime = st.st_mtime;
    file->len32 = (uint32)st.st_size;
    file->mode = st.st_mode;
    // ... 填充其他字段,如 UID, GID 等 ...

    return file;
}

一旦 make_file 创建好了 file_structsend_file_name 就会调用 send_file_entry,将这个结构体中的信息序列化(转换成一串字节流),然后通过网络发送给接收端。序列化是一个压缩信息的过程,比如,如果一个文件的权限和上一个文件相同,它就只发送一个标志位,而不是完整的权限数据。

2. 接收列表(接收端)

接收端的 recv_file_list 函数则执行相反的操作。它在一个循环中不断从网络读取数据,直到收到一个特殊的"列表结束"标志。

在循环中,它调用 recv_file_entry 函数。这个函数负责反序列化 从网络收到的字节流,一步步地重建出 struct file_struct

c 复制代码
// 文件: flist.c (简化逻辑)

static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
{
    // 静态变量用于记住上一个文件的属性,以实现压缩
    static time_t modtime;
    static mode_t mode;
    // ...

    struct file_struct *file;

    // ... 根据 xflags 判断哪些属性是与上一个文件相同的 ...
    
    // 读取文件名
    char thisname[MAXPATHLEN];
    // ... 从网络流 f 中读取文件名到 thisname ...

    // 分配内存
    file = /* ... 分配内存 ... */;

    // 重建结构体
    file->basename = /* ... */
    file->dirname = /* ... */

    // 如果 xflags 中没有 "XMIT_SAME_TIME" 标志,就从网络读取新的时间
    if (!(xflags & XMIT_SAME_TIME)) {
        modtime = read_varlong(f, 4);
    }
    file->modtime = modtime;

    // 如果 xflags 中没有 "XMIT_SAME_MODE" 标志,就从网络读取新的模式
    if (!(xflags & XMIT_SAME_MODE)) {
        mode = from_wire_mode(read_int(f));
    }
    file->mode = mode;

    // ... 读取并设置其他属性 ...

    return file;
}

每当一个 file_struct 被成功重建后,它就被添加到一个本地的 file_list 数组中。当整个列表接收完毕后,接收端就拥有了一份关于源文件的完整、精确的"数字蓝图"。

文件列表的威力:驱动后续一切

一旦发送和接收两端的 file_list 准备就绪,rsync 真正的威力才开始显现。这份清单是后续所有高级功能的"数据源":

  1. 文件过滤 :在同步开始前,rsync 会用过滤规则逐一检查文件列表中的每一个条目。如果一个文件的名字匹配了某个"排除"规则(比如 *.tmp),rsync 就会在它的 file_struct 中做一个标记,后续步骤就会直接跳过这个文件。

  2. 决策制定:生成器 (Generator) 进程会遍历这份文件列表。对于列表中的每一项,它会去检查目标位置是否存在同名文件。

    • 如果目标文件不存在:生成器会指示直接传输整个新文件。
    • 如果目标文件存在 :它会比较文件列表中的元数据(记录在 file_struct 中)和目标文件的元数据。如果大小或修改时间不同,它就会启动 增量传输算法,只传输差异部分。如果元数据完全相同,它就什么也不做,从而节省了大量时间和带宽。
  3. 排序与优化:rsync 会对文件列表进行排序。这种排序保证了目录总是在其内容之前被处理,这对于正确创建目录结构和设置权限至关重要。

总结

在本章中,我们揭开了 rsync 同步流程起点的秘密------文件列表。

  • 核心作用:文件列表是 rsync 的"库存清单",它在任何文件操作开始前被创建,详细记录了所有待处理项目的元数据。
  • 核心数据结构struct file_struct 是列表中的基本单元,它像一个文件的"身份证",包含了名称、路径、大小、时间、权限等所有关键信息。
  • 生成与传递:发送端通过扫描文件系统来构建文件列表,然后将其序列化后高效地传输给接收端。接收端再反序列化,重建一个一模一样的列表。
  • 驱动力:这份列表是 rsync 后续所有决策的唯一依据,无论是过滤、比较还是启动增量算法,都离不开它提供的数据。

现在我们知道了 rsync 是如何创建出这份详细的"作战地图"的。但是,如果用户只想同步目录下的 .c 文件,而不想同步 .o 文件,rsync 是如何在这份完整的地图上"划掉"不需要的部分呢?在下一章,我们将深入探讨 rsync 强大而灵活的 过滤规则系统,看看它是如何精确筛选文件列表的。

相关推荐
桦说编程1 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
科兽的AI小记1 天前
市面上的开源 AI 智能体平台使用体验
人工智能·源码·创业
阿兰哥6 天前
【调试篇5】TransactionTooLargeException 原理解析
android·性能优化·源码
小张课程7 天前
Dubbo 3 深度剖析 – 透过源码认识你|网盘无密分享
dubbo·源码
小张课程7 天前
dubbo3深度剖析透过源码认识你 dubbo源码分析
dubbo·源码
源码宝7 天前
中小企业智能云MES系统源码,实时采集生产现场数据,优化生产流程
源码·数据采集·可视化·源代码管理·生产制造·mes·生产管理
马尚道7 天前
Dubbo 3 深度剖析 – 透过源码认识你 | 更新完结
dubbo·源码
马尚道7 天前
Dubbo 3 深度剖析 – 透过源码认识你(完结)
dubbo·源码
马尚道7 天前
Dubbo 3 深度剖析 - 透过源码认识你
dubbo·源码
马尚道7 天前
Netty核心技术及源码剖析
源码·netty