制作 tar(或其他)存档,并像原始文件一样将数据块对齐,以实现更好的块级重复数据删除?

制作 tar(或其他)存档,并像原始文件一样将数据块对齐,以实现更好的块级重复数据删除?

如何生成 tar 文件,以便 tarred 文件的内容像原始文件一样是块对齐的,这样就可以从块级重复数据删除中受益(https://unix.stackexchange.com/a/208847/9689)?

(我是否正确,tar 格式没有任何内在的东西阻止我们获得这样的好处?否则,如果不是 tar,是否可能有另一个存档器内置了这样的功能?)

PS 我的意思是“未压缩的 tar” - 不是 tar+gz 或其他东西 - 未压缩的 tar 和问题要求一些允许对齐文件块级别的技巧。 AAFAIRecall tar 是为与磁带机一起使用而设计的,因此也许在文件格式中添加一些额外的位来进行对齐是可能且容易的?我希望甚至有工具可以实现它;)。据我记得 tar 文件可以连接,所以也许会有填充空间进行对齐的技巧。

答案1

理论上是可以做到的。但它非常丑陋,并且本质上涉及手动构建我们的档案。

我们面临的挑战

格式tar在 512 字节块上运行。该大小是固定的,旨在与传统磁盘扇区大小相匹配。将文件存储在存档中时,第一个 512 字节块是包含文件元数据(名称、大小、类型等)的标头,后面的块包含文件内容。因此我们的存档数据将错位 512 字节。

btrfs 的块大小(“--sectorsize”)通常为 4096 字节。理论上我们可以选择这个,但实际上它看起来必须与我们的 CPU 的页面大小相匹配。所以我们无法缩小 btrfs 的块。

tar程序有一个更大的“记录”大小的概念,定义为块大小的倍数,这几乎看起来很有用。事实证明,这是为了指定给定磁带驱动器的扇区大小,这样就tar可以避免写入部分磁带记录。但是,数据仍然以 512 字节为单位构建和打包,因此我们不能tar像您希望的那样使用它来增长块。

最后一点要知道的数据tar归档结束标记是两个连续的全零块,除非这些块位于文件数据内部。因此任何类型的幼稚填充块可能都不会被接受。

黑客攻击

我们能做的就是插入填充文件。在存档的开头,在添加要删除重复的文件(称为dup)之前,我们添加一个文件pad,其大小为

pad's header + pad's data + dup's header = 4096 bytes.

这样,dup的数据从块边界开始并且可以进行重复数据删除。

然后,对于每个后续文件,我们还必须跟踪前一个文件的大小,以便计算正确的填充。我们还必须预测是否需要某种标头扩展:例如,基本 tar 标头只有 100 字节的文件路径空间,因此较长的路径使用结构上特殊命名的文件进行编码,其数据是完整路径。一般来说,预测标头大小存在很多潜在的复杂性——tar文件格式在多个历史实现中存在很多缺陷。

一线希望是所有填充文件都可以共享相同的名称,因此当我们解压时,我们最终只会得到一个大小小于 4096 字节的额外文件。

可靠地创建这样的档案的最干净的方法可能是修改 GNUtar程序。但是,如果您想以牺牲 CPU 和 I/O 时间为代价来实现快速和脏操作,您可以对每个文件执行如下操作:

#!/bin/bash

# Proof of concept and probably buggy.
# If I ever find this script in a production environment,
# I don't know whether I'll laugh or cry.

my_file="$2"
my_archive="$1"

file_size="$(wc -c <"$my_file")"
arch_size="$(tar cb 1 "$my_file" | wc -c)"  # "b 1": Remember that record size I mentioned?  Set it to equal the block size so we can measure usefully.
end_marker_size=1024  # End-of-archive marker: 2 blocks' worth of 0 bytes

hdr_size="$(( (arch_size - file_size - end_marker_size) % 4096 ))"
pad_size="$(( (4096 - 512 - hdr_size) % 4096 ))"
(( pad_size < 512 )) && pad_size="$(( pad_size + 4096 ))"

# Assume the pre-existing archive is already a multiple of 4096 bytes long
# (not including the end-of-archive marker), and add extra padding to the end
# so that it stays that way.
file_blocks_size="$(( ((file_size+511) / 512) * 512 ))"
end_pad_size="$(( 4096 - 512 - (file_blocks_size % 4096) ))"
(( end_pad_size < 512 )) && end_pad_size="$(( end_pad_size + 4096 ))"

head -c $pad_size /dev/zero > _PADDING_
tar rf "$my_archive" _PADDING_ "$my_file"
head -c $end_pad_size /dev/zero > _PADDING_
tar rf "$my_archive" _PADDING_

相关内容