摘要

  • draft-ietf-nfsv4-internationalization-17 记录了 NFSv4 处理国际化名称的多种现实路径:把名称当作不透明字节逐一比较,或按 Unicode 规范等价、规范化和大小写规则进行比较。客户端通常无法从成功响应反推出完整规则。
  • 备份、复制、故障切换或迁移之前,应形成一份“名称空间等价回执”,保存请求字节、返回名称、服务器与文件系统策略、能力证据、碰撞测试以及仍无法观察的边界。

屏幕上的同一个名字

带重音符号的一个词,可以由单个预组合字符构成,也可以由基础字符加组合符号构成。读者看到的字形可能相同,文件系统收到的字节却不同。反过来,大小写不同的两个字面名称,也可能被某台服务器判定为同一项。

当客户端用其中一种形式发出 LOOKUP,服务器返回了目标文件,这次操作当然成功了。但成功的原因并不唯一:服务器可能要求与目录项完全相同的字节;也可能先做规范化;可能保留原始名称、只在查找时承认规范等价;还可能由底层文件系统执行大小写折叠。只试中一个名称,无法分辨这些机制。

NFSv4 国际化草案第 17 版对这种不确定性没有粉饰。该版本于 2026 年 9 月 11 日提交,已完成 IETF Last Call 后的修订,目前仍是送交 IESG 的 Standards Track Internet-Draft,不是 RFC,也不能作为任何产品已经部署相同行为的证据。

更值得注意的是它对实现史的描述。文档负责人说明,NFSv4.1 原先写下的国际化机制并没有按设计得到实现;列举的主要实现普遍采用了接近 NFSv4.0 的“不理解 UTF-8”做法,理解 Unicode 的功能则多为有限或实验性质。当前文本因而很大程度上从实际实现出发,而不是假定规范性文字早已塑造了实现。这种诚实让标准化工作有价值:它首先界定真实存在的差异。

不透明字节是一种完整政策

对 UTF-8 不感知的文件系统而言,名称组件只是字节串。比较必须逐字节进行。它可以保留历史编码写入的名称,也可以容纳并非有效 UTF-8 的序列。两个字节串只要不同,就是两个不同的名称,即使 Unicode 认为它们的字符含义规范等价。

这种做法不是“少实现了一点文字显示”。它是一套明确的身份政策:原始字节拥有决定权。好处是旧名称不会因为 Unicode 库升级而自动合并;代价是用户看似输入同一个词,也可能因为输入法或应用选择了另一种规范形式而找不到文件。

UTF-8 感知型服务器的选择更多。它可以在存储前把名称映射到某种规范形式,也可以原样保存,却在查找时把规范等价形式视为相同。它还可以映射大小写变体,或在比较时忽略某些差异。实际行为可能由 NFS 服务器、底层文件系统和配置共同决定;同一产品的不同卷也不应被轻率地视为同一种政策。

“支持 Unicode”因此不是足够精确的说明。它可能只是验证输入,也可能涉及存储形式、比较方式或显示。国际化大小写映射还可能依赖语言与用户偏好。草案判断,目前并不存在一个已经适合标准化、能够把全部选择清楚告知客户端的通用机制。

能力位没有公开等价函数

NFSv4 的 fs_charset_cap 属性能提供部分线索。当前草案用它表示服务器是否只接受 UTF-8 文件名。历史上的 FSCHARSET_CAP4_CONTAINS_NON_UTF8 语义不够清楚,因此文档要求服务器应给出它的补集,并建议客户端忽略该标志。若服务器不支持此属性,客户端还可以用非 UTF-8 名称做受控探测,了解输入是否会被接受。

但“只接受 UTF-8”与“如何判断两个名称相等”是两回事。客户端通常仍无法确定是否进行了规范化、使用哪个 Unicode 版本、哪些大小写形式相等。能力属性描述了入口的一部分,不能充当名称空间契约。

多做几次成功查找,也只是增加样本。它们没有穷尽等价函数。尚未测试的组合字符、新增码点、语言相关大小写或底层文件系统例外,仍可能给出不同结果。供应商说明同样要与现场行为区分:说明书也许描述了服务器层,最终比较却可能发生在文件系统层。

目录列表不会列出所有可接受的问法

READDIR 返回的是服务器选择展示的名称,不是这个名称的全部等价查询形式。服务器可以保留某个原始拼写,同时允许另一个规范等价拼写通过 LOOKUP 到达同一 filehandle。客户端把目录列表拉回本地再自行比较,并不能必然复现服务器的判断。

这会直接影响缓存。客户端若按自己的等价规则缓存“未找到”,可能在服务器其实会接受另一个等价形式时继续报告失败。客户端也可能因为 READDIR 中没有本地认为匹配的字符串而省略一次查找,从而漏掉服务器原本能够返回的对象。草案因此要求客户端准备面对规范化,又提醒它不能假定规范化必然发生。

时间也会改变条件。随着 Unicode 增加新字符,规范等价类可能出现新成员。草案倾向于在比较时兼容不同规范形式,而不是强迫所有长寿命文件名采用一种存储形式。这样可以减少对旧名称的改写,却也意味着升级后的环境可能对原有字节作出新的等价判断。

迁移是在更换裁判

存储项目常把迁移理解成搬运内容:复制数据块、保留元数据、核对数量与哈希,然后切换客户端。名称问题提醒我们,迁移还更换了判断身份的裁判。源端曾决定两次请求是否指向同一项,目标端会根据自己的文件系统、Unicode 库、导出选项和大小写政策重新裁决。

如果源端区分、目标端合并,两个目录项可能碰撞;如果源端合并、目标端区分,应用一直使用的变体可能突然失效;如果目标端拒绝历史字节,内容可以被完整备份,却无法用原名恢复。大小写政策变化还可能使过去和平共处的两个路径争夺同一名称。

抽样打开常用文件是最弱的一种验证。它偏向今天仍在使用、今天的键盘能直接输入、今天的库会自然生成的名称。真正危险的往往是旧系统写入的字节、罕见组合序列,或用户从未意识到服务器曾替他做过等价匹配的名称。

备份可能到恢复时才暴露问题,那时源端已不存在。复制可能在一次静默合并后仍显示“同步成功”。故障切换可能在最紧张的时刻发现:数据就在备用端,但工作负载不会再用同样的名字找到它。

名称空间等价回执

重要迁移需要一份能交给下一位负责人复核的证据。本文把它称为“名称空间等价回执”。这是治理建议,不是 IETF 草案新增的协议要求。

回执先确定测试环境:NFS 协议版本、服务器实现及版本、已知的底层文件系统、导出与挂载配置、操作系统、Unicode 库版本以及测试时间。每个名称同时保存安全的人类可读形式和精确字节。前者用于理解,后者防止记录工具在不知情的情况下先把证据规范化。

测试集应从真实名称清单出发,再补充与机构语言环境有关的成对样本:预组合与分解形式、相关大小写变体、确实存在的非 UTF-8 历史字节,以及在 Unicode 版本变化时值得关注的字符。所有创建与碰撞试验都应在隔离名称空间中进行,不能用生产目录做破坏性探测。

回执逐项记录:每种形式能否创建;LOOKUP 是否返回相同 filehandle;READDIR 展示什么;fs_charset_cap 提供什么;清空或绕过负缓存后结果是否变化。源端与目标端使用可比条件各测一次。结果要注明是现场观察、供应商声明还是尚未验证的推断。

碰撞、拒绝与模糊案例必须有责任人和处置决定:可逆重命名、转义编码、隔离、临时兼容层,或在明确影响下接受。任何名称变换都保存原字节、目标名称和内容标识之间的对应关系,而且这份映射不能随着源系统退役而消失。

回执的边界也要写清。符号链接内容是另一种不透明数据;非 ASCII 域名遵循 U-label 等专门规则;NFS 的不同字符串类型并不共享一条处理规范。回执不宣称普遍“兼容 Unicode”,只说明哪些名称关系在什么环境下经过了什么测试。

证据到哪里为止

RFC 6943 说明,标识符比较不一致可能造成误判、漏判、拒绝服务,在某些系统中甚至可能导致权限提升。这不等于草案报告了某起 NFS 安全事件,本文也不作此推断。它说明:一旦文件路径参与授权、策略、程序执行或证据保存,名称等价就应进入安全与连续性评审。

最终能作出的可靠陈述很窄:某个服务器端名称空间,在某个时刻,以当时生效的行为,把某段提交的字节解析到了某个对象。它不能证明字符具有跨系统的统一身份,也不能保证另一台服务器重复同一决定。成功是事实,但可移植性需要另一份事实。

资料来源