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. mysqlcilent的安装

    这软件包是贼的气 首先是windows的安装上你必须要指定版本,linux的安装你就不需要指定版本了 windos上的指定安装必须要   一.下载包的时候需要指定版本, 比如python2的和mysq ...

  2. 使用Instruments中的CoreAnimation分析动画

    使用Instruments中的CoreAnimation分析动画 1. 打开Instruments中的CoreAnimation 2. 运行前的准备工作 要注意勾选以下选项,便于调试 3. 运行与调试 ...

  3. [翻译] VENCalculatorInputView

    VENCalculatorInputView https://github.com/venmo/VENCalculatorInputView VENCalculatorInputView is the ...

  4. win中使用cmd杀端口

    最近在win开发时,总是遇到端口占用的情况...可能是跑的程序太多了吧 当你测试一个demo时遇到这个就很恶心.. 记一下 netstat -ano | findstr 80 //列出进程极其占用的端 ...

  5. ZT 查找字符串中连续最长的数字串

    查找字符串中连续最长的数字串 有俩方法,1)比较好理解一些.2)晦涩 1) /* 功能:在字符串中找出连续最长的数字串,并把这个串的长度返回, 并把这个最长数字串付给其中一个函数参数outputstr ...

  6. 01-urllib库添加headers的一般方法

    2018-08-23 13:07:57 对于请求一些网站,我们需要加上请求头才可以完成网页的抓取,不然会得到一些错误,无法返回抓取的网页.下面,介绍两种添加请求头的方法. 方法一:借助build_op ...

  7. UI(三)

    1. 2.经常用到的loadmap函数 void CTopology::LoadMap() { //m_map.RemoveAllLayers(); AddLayersBasemap(); AddLa ...

  8. 【C#】#100 调用摄像头

    需求:由于项目需要获得用户的头像,所以需要用C#调用摄像头获取头像. 下面写一个调用摄像头的方法 案例:调用摄像头的一个DEMO[效果图] 使用的类库:AForge.dll   [Demo下载,Dem ...

  9. angular 生命周期钩子 ngOnInit() 和 ngAfterViewInit() 的区别

    angular 生命周期钩子的详细介绍在 https://angular.cn/guide/lifecycle-hooks  文档中做了介绍. ngOnInit() 在 Angular 第一次显示数据 ...

  10. iOS: 聊聊 Designated Initializer(指定初始化函数):NS_DESIGNATED_INITIALIZER

    总结:指定函数的调用规则: 初始化函数的调用顺序与初始化顺序相反. 上面关于指定初始化的规则讲了那么多,其实可以归纳为两点: 便利初始化函数只能调用自己类中的其他初始化方法 指定初始化函数才有资格调用 ...