单一来源
当前只有一个可用来源,尚未完成多源交叉核验最难的压缩包?
最难的 tarball?
2026年11月7日
这是一个有趣的现象:一个可以通过单次 GNU tar 调用构建的归档文件,但根据你使用的工具不同,它有 8 种截然不同的解压方式。
$ printf hello > file $ touch empty $ tar --format=pax --pax-option='delete=?time,size:=512' \ --transform='s,^\./file$,café,' \ -cf hardest.tar file ./file empty
它按顺序从三个文件 file ./file empty 创建了归档文件 hardest.tar,其中 file 被重复,以便 GNU tar 将第二个作为硬链接存储,而 empty 是空的。
利用第二个条目的唯一路径,硬链接被重命名为 café。这触发了 tar 创建一个 pax 头部,以 UTF-8 存储文件名。我们在 pax 头部添加了一个 size=512 记录,这覆盖了硬链接的默认 size=0。数值 512 并非巧合:它恰好是一个 tar 头部的大小。
从视觉上看,这个归档具有以下结构,基于硬链接是否可以拥有大小,有两种基本解释。
现在,POSIX 规定硬链接的数据块“可以包含”在 pax 归档中,但这其中的含义并不完全清楚。那么,硬链接之后的 512 字节是它的数据,而不是我们归档时的 empty 的头部?硬链接怎么能与其前一个对应文件 file 拥有不同的内容?
解压它
使用知名的 tar 工具和库解压这个归档时,我迄今为止遇到的结果最丰富、也最令人惊讶。下面的结果是在撰写本文时,使用所有软件包的最新版本生成的。
GNU tar, BusyBox, Python, Go 和 Perl
这一组在原则上讲得通:它们忽略了硬链接上的大小,并解压出 file、它的硬链接 café 和 empty。
bsdtar
这是 macOS 的系统 tar 工具,在硬链接大小这一概念上,它在精神上更接近 POSIX,并将 512 字节写入硬链接。最终结果是 file 现在包含了 empty 的 tar 头部。
7-Zip, node-tar, tar-fs 和 Rust 的 tar & astral-tokio-tar
这一组同意 bsdtar 的观点,即 512 字节是硬链接的数据,但并没有将它们写入硬链接。相反,它们跳过这些数据,结果只剩下 file 和 café,没有 empty。
Java 的 Commons Compress
这就需要你发挥更多想象力了。因为 café 包含数据,所以创建了一个单独的文件,其中包含 empty 的头部作为数据,而 file 保持其原始内容。
PHP 的 PharData
让 café 成为单独的空文件,同时也解压出 empty。为什么不呢?
RubyGems
根本不解压 café,但为了弥补这一点,它在 file 和 empty 旁边创建了一个空目录 PaxHeaders。
R 采用了完全不同的方法:它解压出 empty,其中包含 512 个空字节。它错误地将大小应用到了 empty 而不是硬链接上,而这 512 字节来自一个归档结束标记。
附赠:Haskell
Haskell 的 tar 程序如果不存在字符串解码问题,就会和 GNU tar 完全一样,否则它会把文件 café 变成 café 存储在磁盘上(在 2026 年,这并不是你浏览器的渲染怪癖)。
.NET、Julia 和 uv 的 tar-codec
这些实现会拒绝这个 tarball。我相信这是你能做的最好的事情,即使这在技术上不符合 POSIX 标准。
More of this
这个 tarball 是我正在我的测试套件 haampie/tar-minefield 中收集和分类的 134 个棘手 tarball 之一,该测试套件自上一篇文章以来一直在开发。
其网站 https://stoppels.ch/tar 有一个巨大的表格,展示了实现如何处理所有这些情况,以及每个测试用例的详细信息。本文的 tarball 的一个变体被包含为一个测试用例。
如果你想讨论这个问题,请给我发送电子邮件至 firstname[at]lastname.ch。
Evidence
来源与证据
当前仅有一个可用来源,尚未完成多源交叉核验。
- 发布时间
- 内容处理
- AI 翻译 / 摘要,需结合原文核对
相关资讯
AI 翻译、摘要和解读仅用于辅助判断;涉及重要决策时,请回到上方来源核实。