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 强大而灵活的 过滤规则系统,看看它是如何精确筛选文件列表的。

相关推荐
重启的码农6 小时前
rsync源码解析 (3) 进程角色 (Sender/Receiver/Generator)
源码
重启的码农6 小时前
rsync源码解析 (6) 文件属性与元数据处理
源码
重启的码农6 小时前
rsync源码解析 (5) 文件过滤规则系统
源码
重启的码农6 小时前
rsync源码解析 (7) 客户端/服务器通信协议
源码
前端双越老师7 小时前
为何前端圈现在不关注源码了?
面试·前端框架·源码
RPA+AI十二工作室20 小时前
影刀RPA_抖音评价获取_源码解读
运维·机器人·自动化·源码·rpa·影刀
RPA+AI十二工作室1 天前
影刀RPA_Temu关键词取数_源码解读
大数据·自动化·源码·rpa·影刀
重启的码农1 天前
rsync源码解析 (2) 增量传输算法
源码
重启的码农1 天前
rsync源码解析 (1) 选项与配置解析
源码