本文介绍一个开源的轻量级读写 tar 文件的 C 库 —— microtar,作者是 rxi,该开源代码是在 MIT License
下发布的。
该库只包含一个头文件 microtar.h
和一个源文件 microtar.c
,要使用该库只需把这两个文件直接放进项目里面。
tar 文件结构
tar 文件的结构如下:
tar 文件结构 |
---|
file_1 metadata header (512 bytes) |
file_1 data |
padding |
file_2 metadata header (512 bytes) |
file_2 data |
padding |
… |
file_n metadata header (512 bytes) |
file_n data |
padding |
tar final padding |
这里的文件 file 不仅指普通文件,还包括目录、链接等各种文件。
tar 文件会依顺序写入每个文件的 metadata header 以及该文件原原本本的二进制数据 data,每个 metadata header 的大小是 512 个字节,如果该文件的二进制数据字节数不是 512 的整数倍,会在该文件 data 后填充内容为 0
的字节,使得 data 和 padding 的总字节数恰好是 512 的整数倍。如果 data 大小为 0 字节(比如说目录),那么有的 tar 打包软件会填充 512 字节的 padding,但本文介绍的 microtar 不会进行填充,也就是说 data 和 padding 的总字节数会是 0。
当所有需要打包的文件已被依次写入,tar 文件结尾会填充数量不等的内容全为 0
的大小为 512 字节的数据块,比如本文介绍的 microtar 会填充两个这样的数据块,其他 tar 软件实现可能会选择不同的填充数量。
metadata header 结构
name (100 bytes)
文件路径字符串,比如 “path/file_name”,如果路径长度超过 100 个字符,则字段 prefix (155 bytes) 存储文件路径前一部分,name 字段存储文件路径后一部分,总共 255 个字符。mode (8 bytes)
文件权限,比如读、写、执行等。假设权限是 rw-rw-r–,那么 mode 字段最终存储的是权限的八进制形式字符串 “664”,至于该八进制形式字符串是左对齐(“664\0\0\0\0\0”)还是左边补数字 0(“0000664\0”),抑或是其它格式,则可能不同软件有不同实现。本文介绍的 microtar 采取的是左对齐。owner (8 bytes)
用户 ID,可选。group (8 bytes)
组 ID,可选。size (12 bytes)
文件字节大小。同 mode 字段一样,最终存储的是八进制形式字符串。mtime (12 bytes)
文件最后修改时间。同 mode 字段一样,最终存储的是八进制形式字符串。checksum (8 bytes)
metadata header 的校验值,该值是 metadata header 除了 checksum 字段以外其余 504 个字节的值的总和。同 mode 字段一样,最终存储的是八进制形式字符串。本文介绍的 microtar 采取的是 %06o 形式,即字符串长度为 6,不足的左边补上数字 0,因为 checksum 最大值的八进制形式是 373010。type (1 bytes)
文件类型。0 = 普通文件;1 = 硬链接;2 = 软链接;3 = 字符设备;4 = 块设备;5 = 目录;6 = FIFO。linkname (100 bytes)
链接名,可选。other (255 bytes)
其他字段比如 prefix (155 bytes),在本文介绍的 microtar 中并没有实现,而是全部放一起当作一个 255 字节的 padding,因此本文也不进行介绍。
API
metadata header
typedef struct {
unsigned mode;
unsigned owner;
unsigned size;
unsigned mtime;
unsigned type;
char name[100];
char linkname[100];
} mtar_header_t;
struct mtar_t
struct mtar_t {
int (*read)(mtar_t *tar, void *data, unsigned size);
int (*write)(mtar_t *tar, const void *data, unsigned size);
int (*seek)(mtar_t *tar, unsigned pos);
int (*close)(mtar_t *tar);
void *stream;
unsigned pos;
unsigned remaining_data;
unsigned last_header;
};
读写函数
const char* mtar_strerror(int err);
int mtar_open(mtar_t *tar, const char *filename, const char *mode);
int mtar_close(mtar_t *tar);
int mtar_seek(mtar_t *tar, unsigned pos);
int mtar_rewind(mtar_t *tar);
int mtar_next(mtar_t *tar);
int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h);
int mtar_read_header(mtar_t *tar, mtar_header_t *h);
int mtar_read_data(mtar_t *tar, void *ptr, unsigned size);
int mtar_write_header(mtar_t *tar, const mtar_header_t *h);
int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size);
int mtar_write_dir_header(mtar_t *tar, const char *name);
int mtar_write_data(mtar_t *tar, const void *data, unsigned size);
int mtar_finalize(mtar_t *tar);
例子
writing.c
// gcc microtar.c writing.c -o writing
#include <stdio.h>
#include <stdlib.h>
#include "microtar.h"
int main() {
mtar_t tar;
const char *str1 = "Hello world";
const char *str2 = "Goodbye world";
/* Open archive for writing */
mtar_open(&tar, "test.tar", "w");
/* Write strings to files `test1.txt` and `test2.txt` */
mtar_write_file_header(&tar, "test1.txt", strlen(str1));
mtar_write_data(&tar, str1, strlen(str1));
mtar_write_file_header(&tar, "test2.txt", strlen(str2));
mtar_write_data(&tar, str2, strlen(str2));
/* Finalize -- this needs to be the last thing done before closing */
mtar_finalize(&tar);
/* Close archive */
mtar_close(&tar);
return 0;
}
reading.c
// gcc microtar.c reading.c -o reading
#include <stdio.h>
#include <stdlib.h>
#include "microtar.h"
int main() {
mtar_t tar;
mtar_header_t h;
char *p;
/* Open archive for reading */
mtar_open(&tar, "test.tar", "r");
/* Print all file names and sizes */
while ( (mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) {
printf("%s (%d bytes)\n", h.name, h.size);
mtar_next(&tar);
}
/* Load and print contents of file "test1.txt" */
mtar_find(&tar, "test1.txt", &h);
p = calloc(1, h.size + 1);
mtar_read_data(&tar, p, h.size);
printf("%s\n", p);
free(p);
/* Close archive */
mtar_close(&tar);
return 0;
}
分别编译 writing.c 和 reading.c,先运行 writing,再运行 reading,最终结果如下:
test1.txt (11 bytes)
test2.txt (13 bytes)
Hello world