tar(tape archive)是Unix和类Unix系统上文件打包工具,可以将多个文件合并为一个文件,使用tar工具打出来的包称为tar包。一般打包后的文件名后缀为”.tar”,也可以为其它。tar代表未被压缩的tar文件,已被压缩的tar文件则追加压缩文件的扩展名,如经过gzip压缩后的tar文件,扩展名为”.tar.gz”。在windows系统中用WinRAR也可以解压缩打开tar包。tar文件格式已经成为POSIX标准,最初是POSIX.1-1998,目前是POSIX.1-2001.

tar中的数据都是以512字节为单位。tar由两部分组成即头部+内容,其中头部是512字节的头部结构,内容是存放一个文件内容的地方。

tar文件格式的详细介绍可以参考:https://en.wikipedia.org/wiki/Tar_(computing)#File_header

通过执行以下命令生成测试tar包:

tar -cvf test.tar *

test.tar中包含两个文件blog_info.txt和github_info.txt.

其中blog_info.txt文件内容如下:

name: fengbingchun
address: http://blog.csdn.net/fengbingchun?viewmode=contents

github_info.txt文件内容如下:

name: fengbingchun
address: https://github.com/fengbingchun

实现代码tar.hpp:

#ifndef FBC_MESSY_TEST_TAR_HPP_
#define FBC_MESSY_TEST_TAR_HPP_

#include <vector>
#include <string>

/* reference:
	http://www.gnu.org/software/tar/manual/html_node/Standard.html
	http://stackoverflow.com/questions/2505042/how-to-parse-a-tar-file-in-c
	http://directory.fsf.org/wiki/Libtar
	http://work.freenet59.ru/svn/pkgsrc_haiku/trunk/archivers/libarchive/files/contrib/untar.c
	https://codeistry.wordpress.com/2014/08/14/how-to-parse-a-tar-file/
	http://stackoverflow.com/questions/17862383/how-to-know-the-files-inside-the-tar-parser
	https://en.wikipedia.org/wiki/Tar_(computing)
*/

/* tar Header Block, from POSIX 1003.1-1990.  */

/* POSIX header.  */

typedef 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 */
} tar_posix_header;

/*
	location  size  field
	0         100   File name
	100       8     File mode
	108       8     Owner's numeric user ID
	116       8     Group's numeric user ID
	124       12    File size in bytes
	136       12    Last modification time in numeric Unix time format
	148       8     Checksum for header block
	156       1     Link indicator (file type)
	157       100   Name of linked file
*/

#define TMAGIC   "ustar"        /* ustar and a null */
#define TMAGLEN  6
#define TVERSION "00"           /* 00 and no null */
#define TVERSLEN 2

/* Values used in typeflag field.  */
#define REGTYPE  '0'            /* regular file */
#define AREGTYPE '\0'           /* regular file */
#define LNKTYPE  '1'            /* link */
#define SYMTYPE  '2'            /* reserved */
#define CHRTYPE  '3'            /* character special */
#define BLKTYPE  '4'            /* block special */
#define DIRTYPE  '5'            /* directory */
#define FIFOTYPE '6'            /* FIFO special */
#define CONTTYPE '7'            /* reserved */

class TarFile {
public:
	TarFile(const char* tar_name);
	bool IsValidTarFile();
	std::vector<std::string> GetFileNames();
	bool GetFileContents(const char* file_name, char* contents);
	size_t GetFileSize(const char* file_name);
	size_t GetTarSize();
	~TarFile();
private:
	FILE* file;
	size_t size;
	std::vector<std::string> file_names;
	std::vector<size_t> file_sizes;
	std::vector<size_t> file_data_start_addrs;
};

int test_tar();

#endif // FBC_MESSY_TEST_TAR_HPP_

tar.cpp:

#include "tar.hpp"

TarFile::TarFile(const char* tar_name)
	: file(nullptr), size(0)
{
	file_names.clear();
	file_sizes.clear();
	file_data_start_addrs.clear();
	file = fopen(tar_name, "rb");
}

TarFile::~TarFile()
{
	if (file) {
		fclose(file);
		file = nullptr;
	}
	file_names.clear();
	file_sizes.clear();
	file_data_start_addrs.clear();
}

bool TarFile::IsValidTarFile()
{
	if (!file) return false;

	const int block_size{ 512 };
	unsigned char buf[block_size];
	tar_posix_header* header = (tar_posix_header*)buf;
	memset(buf, 0, block_size);

	fseek(file, 0, SEEK_END);
	size = ftell(file);
	fseek(file, 0, SEEK_SET);
	if (size % block_size != 0) {
		fprintf(stderr, "tar file size should be a multiple of 512 bytes: %d\n", size);
		return false;
	}

	size_t pos{ 0 };

	while (1) {
		size_t read_size = fread(buf, block_size, 1, file);
		if (read_size != 1) break;
		if (strncmp(header->magic, TMAGIC, 5)) break;

		pos += block_size;
		size_t file_size{0};
		sscanf(header->size, "%lo", &file_size);
		size_t file_block_count = (file_size + block_size - 1) / block_size;

		switch (header->typeflag) {
			case '0': // intentionally dropping through
			case '\0':
				// normal file
				file_sizes.push_back(file_size);
				file_names.push_back(std::string(header->name));
				file_data_start_addrs.push_back(pos);
				break;
			case '1':
				// hard link
				break;
			case '2':
				// symbolic link
				break;
			case '3':
				// device file/special file
				break;
			case '4':
				// block device
				break;
			case '5':
				// directory
				break;
			case '6':
				// named pipe
				break;
			default:
				break;
		}

		pos += file_block_count * block_size;
		fseek(file, pos, SEEK_SET);
	}

	fseek(file, 0, SEEK_SET);

	return true;
}

std::vector<std::string> TarFile::GetFileNames()
{
	return file_names;
}

bool TarFile::GetFileContents(const char* file_name, char* contents)
{
	bool flag = false;
	for (int i = 0; i < file_names.size(); i++) {
		std::string name_(file_name);

		if (file_names[i].compare(name_) == 0) {
			int file_size = file_sizes[i];
			flag = true;
			fseek(file, file_data_start_addrs[i], SEEK_SET);
			fread(contents, file_size, 1, file);
			fseek(file, 0, SEEK_SET);

			break;
		}
	}

	return flag;
}

size_t TarFile::GetFileSize(const char* file_name)
{
	size_t file_size{0};

	for (int i = 0; i < file_names.size(); i++) {
		std::string name_(file_name);

		if (file_names[i].compare(name_) == 0) {
			file_size = file_sizes[i];
			break;
		}
	}

	return file_size;
}

size_t TarFile::GetTarSize()
{
	return size;
}

//////////////////////////////////////////////
int test_tar()
{
	const std::string tar_file_path{ "E:/GitCode/Messy_Test/testdata/test.tar" };
	TarFile tarfile(tar_file_path.c_str());

	bool is_valid_tar_file = tarfile.IsValidTarFile();
	if (!is_valid_tar_file) {
		fprintf(stderr, "it is not a valid tar file: %s\n", tar_file_path.c_str());
		return -1;
	}

	fprintf(stderr, "tar file size: %d byte\n", tarfile.GetTarSize());

	std::vector<std::string> file_names = tarfile.GetFileNames();
	fprintf(stderr, "tar file count: %d\n", file_names.size());
	for (auto name : file_names) {
		fprintf(stderr, "=====================================\n");
		size_t file_size = tarfile.GetFileSize(name.c_str());
		fprintf(stderr, "file name: %s,  size: %d byte\n", name.c_str(), file_size);

		char* contents = new char[file_size + 1];
		tarfile.GetFileContents(name.c_str(), contents);
		contents[file_size] = '\0';

		fprintf(stderr, "contents:\n%s\n", contents);
		delete[] contents;
	}

	return 0;
}

测试结果如下:

GitHubhttps://github.com/fengbingchun/Messy_Test

C++实现tar包解析的更多相关文章

  1. Caffe实战三(依赖包解析及环境配置)

    前面的文章使用的软件环境是开始时通过apt-get命令所安装的,本文将通过编译源码的方式重新配置一个可迁移的软件环境.(参考:<深度学习 21天实战Caffe> 第五天 Caffe依赖包解 ...

  2. Linux RPM、TAR包管理

    一.RPM软件包命令的使用 RPM主要有5种基本操作模式:安装.卸载.刷新.升级及查询.下面分别介绍. 1.安装软件包 命令语法: rpm -ivh [RPM包文件名称] 命令中各参数的含义如下: - ...

  3. tar 只解压tar包中某个文件

    sh-4.1# ls test.tar sh-4.1# tar -tf test.tar ./ecs20161207.png ./ecs.png ./ecs.xml ./rds.png ./Scree ...

  4. find 查找文件 -exec 然后压缩 查看tar包的内容

    [root@cs Downloads]# find ./ -name "banner*" -exec tar -cvf k.tar "{}" \; ./bann ...

  5. java jar包解析:打包文件,引入文件

    java jar包解析:打包文件,引入文件 cmd下: jar命令:package包打包 javac命令:普通类文件打包 Hello.java: package org.lxh.demo; publi ...

  6. Unix系统解压tar包时出现@LongLink错误

    Unix系统上使用tar命令解压tar包后,多了一个@LongLink的文件,并且原来的tar包解压后不完整.网上查了下,原因是AIX系统上tar命令自身的一个缺陷.解决办法:把该tar包上传到lin ...

  7. Android做法说明(3)---Fragment使用app袋或v4包解析

    Android做法说明(3)---Fragment使用app袋或v4包解析 1)问题简述 相信非常多的朋友在调用Fragment都会遇到以下的情况: watermark/2/text/aHR0cDov ...

  8. linux tar包追加问题【转】

    只能已归档的文件才能追加文件. 如果tar.gz文件是如此生成:#tar -zcvf test.tar.gz  a.txt即tar.gz是压缩(-z)和归档(-c)文件,则无法给它追加文件:若果tar ...

  9. Spring (3.2.4) 常用jar 包解析

    Spring (3.2.4) 常用jar 包解析 基本jar包 spring-aop-3.2.4.RELEASE.jar spring-aspects-3.2.4.RELEASE.jar spring ...

随机推荐

  1. 【Kettle】1、简单介绍

    本人为转载,感谢作者的辛勤劳作:http://blog.csdn.net/rotkang/article/details/20810921 ------------------------------ ...

  2. 用jQuery Validate+layer插件实现好看的表单提交效果

    作为初学者,以前做表单验证都是自己写的,目的是让自己更好的了解代码,加深自己对javascript的理解,但是其实在很久都知道有一个很好用的表单验证插件:jQuery Validate.js,一直都没 ...

  3. 关于easyUI一些标签的使用

    ①table: 1.class="easyui-datagrid":指定为easyUI的表格布局 2.pagination="true":带分页的表格 3.ro ...

  4. [翻译] CNPPopupController

    CNPPopupController CNPPopupController is a simple and versatile class for presenting a custom popup ...

  5. 【matlab】 幂法 求解最大特征值

    一. 算法: 1.输入矩阵A,初始向量x误差限ep,最大迭代次数N 2.置 k = 1, m1 = 0; 3.求Xr-> norm(x)   abs(Xr)=max[Xi] 1<=i< ...

  6. java判断字符串内容

    java判断字符串是否全为数字 String str = "032";boolean isNum = str.matches("[0-9]+"); java判断 ...

  7. Centos7 Nginx 443端口反向代理springboot项目

    开发微信小程序需要部署项目到服务器.要求必须是443端口.但是一个443端口只能监听一个服务器.所以就出现了一个问题就是每次开发一个小程序就需要买一个服务器.觉得特别多余.后来查到了有一种方式就是通过 ...

  8. jQuery: 刨根问底 attr and prop两个函数的区别

    In this short post I will explain the difference between attributes and properties in HTML. The .pro ...

  9. 【原创】Python 对象创建过程中元类, __new__, __call__, __init__ 的处理

    原始type: type是最原始的元类,其__call__方法是在你使用" t_class = type(classname_string, base_classes_tuple, attr ...

  10. loj6119 「2017 山东二轮集训 Day7」国王

    题目描述 在某个神奇的大陆上,有一个国家,这片大陆的所有城市间的道路网可以看做是一棵树,每个城市要么是工业城市,要么是农业城市,这个国家的人认为一条路径是 exciting 的,当且仅当这条路径上的工 ...