Tar打包格式浅析
Tar是Unix平台下非常流行的打包格式,该格式不负责压缩,只负责打包。简而言之,Tar就是将各个文件和文件夹,简单的组合成一个文件,使之可以按照标准还原成原来的多个文件。
文件结构
Tar的文件结构十分简单,前边提到,Tar只是简单的将多个文件或文件夹组合,除了文件本身的数据,Tar还会使用“标头”来记录文件名、文件属性、大小等信息。拥有了这些信息,再加上文件本身的数据,便可以将打包前的文件及其目录结构还原出来。
所以,Tar的结构如图所示:
image.png
上图中是一个内包含两个文件的Tar包。
如何解析他?
Tar包有一个标准,其中的所有数据大小都是512字节的倍数,其中标头规定为512个字节,文件数据规定必须为512的倍数,若不足,则在末尾补0。知道了这一规范,我们就至少能得到第一个文件的标头了,在此标头中记录了该文件的大小,也就是数据部分的长度。根据这个长度,在标头后就可以取出该文件的数据,此时第一个文件就可以正常取出了。
第二个文件呢?
当第一个文件正常取出后,我们已经知道了第一个文件数据的末尾在整个Tar包的什么位置,但别忘了,所有文件数据都应当是512字节的倍数,若不足则补0,那么我们得到第一个文件数据的末尾后,还要根据此规则计算,得出文件数据真正的末尾是什么,然后从此位置开始,便可以读取第二个文件的标头。
以此类推,我们便可以解析出整个Tar文件的所有内容。大家可能对这种结构非常熟悉,其实这就是类似的链表结构,但不同的是它的每个节点在存储上也是连续的。
文件名过长的问题
以上是Tar包的基本结构,目前我们已知的一个重要内容就是标头的长度为512个字节。
让我们单独聊聊这部分。在GNU的代码实现中,标头的结构如下:
struct posix_header
{ /* byte offset */
char name[100]; /* 0 */
char mode[8]; /* 100 */
char uid[8]; /* 108 */
char gid[8]; /* 116 */
char size[12]; /* 124 */
char mtime[12]; /* 136 */
char chksum[8]; /* 148 */
char typeflag; /* 156 */
char linkname[100]; /* 157 */
char magic[6]; /* 257 */
char version[2]; /* 263 */
char uname[32]; /* 265 */
char gname[32]; /* 297 */
char devmajor[8]; /* 329 */
char devminor[8]; /* 337 */
char prefix[155]; /* 345 */
/* 500 */
};
我们暂时不必了解其中的每一项代表什么含义,只需要关注以下几项:
name:文件名(路径)。
size:文件大小(文件数据部分的尺寸)。
typeflag:类型标志,标志该节点是一个普通文件,还是软连接抑或是目录等。
你可能注意到,name占用了100个字节,这意味着文件路径将被限制在100个英文字符,若其中包含中文,那更少。但操作系统所支持的文件名长度要远远大于这个限制。所以不得不面对的问题是,超出的话怎么办?
我找了很久发现了GNU方案对该问题的解决办法:当遇到超长文件名时,在文件节点(包括标头和数据)之前,加入一个特殊节点,该节点标志了下一个节点存在一个超长的文件名,并且该节点的数据部分就是下一个节点的文件名。这么描述可能有点乱,我们画个图:
image.png
图中表示了一个拥有超长文件名的文件在Tar中的存储方式。简而言之就是将文件名以文件数据的形式存储在上一个节点中。
这样就解决了文件名长度限制的问题,其中linkname是软连接的目标路径,同样也是用这种方案解决。
最后
其实思考后可以发现,该标准中似乎存在一些不必要的地方,例如,除了要求标头长度为512字节之外,数据长度为什么也必须是512的倍数,如果不做此要求,程序照样可以通过size来定位到下一个节点。
目前来说这种规范确实没必要,但是最初tar并不是设计给计算机系统的,而是设计给磁带使用,正如他的名字(Tar:磁带),这些古老的硬件设备由他们本身的限制,这些问题也被历史保留了下来。
本篇文章中唯一的代码段,此段代码是 Paul Eggert eggert@cs.ucla.edu 在26年前提交的,那时候我应该还不叫这个名字(还在上辈子)。