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. MySQL慢日志简介及Anemometer工具介绍

    作者:王航威 - fordba.com 来源:http://fordba.com/box-anemometer-visual-mysql-slow.html,叶师傅对原文内容略有调整 备注:王航威是知 ...

  2. Python项目生成requirements.txt的多种方式

    我相信任何软件程序都会有依赖的类库,尤其现在开源如此的火爆,因为一个项目可能会有无很多的依赖的包 这个时候难道我们都要一个一个的去找到安装吗?即使你找到了依赖的包 但是呢模块的版本又有很多难道你都要装 ...

  3. DES加密(支持ARC与MRC)

    DES加密(支持ARC与MRC) 源文件: YXCrypto.h 与 YXCrypto.m // // YXCrypto.h // 用秘钥给字符串加密或者解密 // // Created by You ...

  4. 原生 JS 的 Base64 转码

    JavaScript 原生提供两个 Base64 相关的方法: btoa():任意值转为 Base64 编码 atob():Base64 编码转为原来的值 注意:这两个方法不适合非 ASCII 码的字 ...

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

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

  6. Java多线程和并发基础面试总结

    多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.收藏起来,希望给予即将找 ...

  7. openssl生成证书

    数字证书: 第三方机构使用一种安全的方式把公钥分发出去 证书格式:x509,pkcs家族 x509格式: 公钥和有效期限: 持有者的个人合法身份信息:(主机名,域名) 证书的使用方式 CA的信息 CA ...

  8. mac 安装npm

    npm是什么 NPM的全称是Node Package Manager ,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准. 如何安装 一:如果你安装了Homebrew ...

  9. 20145203JAVA课程总结

    20145203盖泽双 <Java程序设计>课程总结 课程总结 (按顺序)每周读书笔记链接汇总 调查问卷:http://www.cnblogs.com/GZSdeboke/p/524832 ...

  10. 关于wordpress主题、插件上传和下载问题及其上传图片权限问题解决方案

    主题官方下载地址:https://wordpress.org/themes/ 插件官方下载地址: https://wordpress.org/plugins/ 主题的上传下载,无疑是需要ftp服务器的 ...