在 C 中创建我自己的归档工具
Creating my own archive tool in C
我刚刚被分配了一个项目来为 unix 创建一个归档工具。所以在创建程序后我会做类似
"./bar -c test_archive.bar file.1"
它将创建一个 test_archive.bar,其中包含 file.1。然后我可以执行一些命令,列出其中的文件等。但是我无法理解制作 test_archive.bar 的概念,我意识到它本质上只是一个文件,但如果你要说打开一个 .tgz "vi file.tgz" 它会在里面给出 directories/files 的列表,
那么,有什么好的方法可以创建一个 archive/directory,我可以在其中推断一些文件并列出它们的名称等。
注意:我查看了 tar.c 以及其中包含的所有文件,但每个文件都非常抽象,很难理解。
注意:我知道如何阅读命令行标志等
使用旧的(但仍然有效)tar 格式实际上很容易做到。 Wikipedia has a nice explanation of the format here.您需要做的就是:
对于每个文件:
- 填写并发出 header 到 tar 文件
- 发出文件内容
- 将文件大小填充为 512 字节的倍数
tar 文件最基本的有效 header 是:(基本上从维基百科复制)
- 100 字节:文件名
- 8 字节:文件模式
- 8 字节:所有者的数字 ID
- 8 字节:组的数字 ID
- 12 字节:文件大小
- 12字节:最后修改时间的时间戳
- 8 字节:校验和
- 1字节:文件类型
- 100 字节:linked 文件的名称
文件类型可以是 0(普通文件)、1(硬 link)或 2(symlink)。 linked 文件的名称是link 指向的文件的名称。如果我没记错的话,如果你有硬link或符号link,文件内容应该是空的。
引用维基百科:
"Numeric values are encoded in octal numbers using ASCII digits, with leading zeroes. For historical reasons, a final NUL or space character should be used."
"The checksum is calculated by taking the sum of the unsigned byte values of the header record with the eight checksum bytes taken to be ascii spaces (decimal value 32). It is stored as a six digit octal number with leading zeroes followed by a NUL and then a space."
这是一个简单的 tar 球发生器。创建提取器、处理自动文件馈送等,留作 reader.
的练习。
#include<stdio.h>
#include<string.h>
struct tar_header{
char name[100];
char mode[8];
char owner[8];
char group[8];
char size[12];
char modified[12];
char checksum[8];
char type[1];
char link[100];
char padding[255];
};
void fexpand(FILE* f, size_t amount, int value){
while( amount-- ){
fputc( value, f );
}
}
void tar_add(FILE* tar_file, const char* file, const char* internal_name){
//Get current position; round to a multiple of 512 if we aren't there already
size_t index = ftell( tar_file );
size_t offset = index % 512;
if( offset != 0 ){
fexpand( tar_file, 512 - offset, 0);
}
//Store the index for the header to return to later
index = ftell( tar_file );
//Write some space for our header
fexpand( tar_file, sizeof(struct tar_header), 0 );
//Write the input file to the tar file
FILE* input = fopen( file, "rb" );
if( input == NULL ){
fprintf( stderr, "Failed to open %s for reading\n", file);
return;
}
//Copy the file content to the tar file
while( !feof(input) ){
char buffer[2000];
size_t read = fread( buffer, 1, 2000, input );
fwrite( buffer, 1, read, tar_file);
}
//Get the end to calculate the size of the file
size_t end = ftell( tar_file );
//Round the file size to a multiple of 512 bytes
offset = end % 512;
if( end != 0 ){
fexpand( tar_file, 512 - offset, 0);
}
//Fill out a new tar header
struct tar_header header;
memset( &header, 0, sizeof( struct tar_header ) );
snprintf( header.name, 100, "%s", internal_name );
snprintf( header.mode, 8, "%06o ", 0777 ); //You should probably query the input file for this info
snprintf( header.owner, 8, "%06o ", 0 ); //^
snprintf( header.group, 8, "%06o ", 0 ); //^
snprintf( header.size, 12, "%011o", end - 512 - index );
snprintf( header.modified, 12, "%011o ", time(0) ); //Again, get this from the filesystem
memset( header.checksum, ' ', 8);
header.type[0] = '0';
//Calculate the checksum
size_t checksum = 0;
int i;
const unsigned char* bytes = &header;
for( i = 0; i < sizeof( struct tar_header ); ++i ){
checksum += bytes[i];
}
snprintf( header.checksum, 8, "%06o ", checksum );
//Save the new end to return to after writing the header
end = ftell(tar_file);
//Write the header
fseek( tar_file, index, SEEK_SET );
fwrite( bytes, 1, sizeof( struct tar_header ), tar_file );
//Return to the end
fseek( tar_file, end, SEEK_SET );
fclose( input );
}
int main( int argc, char* argv[] ){
if( argc > 1 ){
FILE* tar = fopen( argv[1], "wb" );
if( !tar ){
fprintf( stderr, "Failed to open %s for writing\n", argv[1] );
return 1;
}
int i;
for( i = 2; i < argc; ++i ){
tar_add( tar, argv[i], argv[i] );
}
//Pad out the end of the tar file
fexpand( tar, 1024, 0);
fclose( tar );
return 0;
}
fprintf( stderr, "Please specify some file names!\n" );
return 0;
}
So, are there any good ways to go about creating a archive/directory
in which i can extrapolate some files within and list their names
etc..
基本上有两种方法:
一个接一个地复制文件内容,每个文件内容都以 "header" 块为前缀,包含有关文件名、大小和(可选)其他属性的信息。 Tar 就是一个例子。示例:
一个接一个地复制文件内容并放在某处(在开头或结尾)"index",其中包含文件名及其大小和(可选)其他属性的列表。当您查看文件大小时,您可以计算单个文件的位置 begin/end.
大多数现实世界的存档器都使用这些组合,并添加其他功能,例如校验和、压缩和加密。
例子
假设我们有两个名为 hello.txt
的文件包含 Hello, World!
(12 字节)和 bar.txt
包含 foobar
(6 字节)。
在第一种方法中,存档看起来像这样
[hello.txt,12][Hello, World!][bar.txt,6][foobar]
^- fixed size ^- 12 bytes ^- fixed size ^- 6 bytes
header 块的长度必须是常数,或者您必须在某处编码它们的长度。
秒:
[Hello, World!foobar][hello.txt,12,bar.txt,6]
^- 12+6 bytes
我刚刚被分配了一个项目来为 unix 创建一个归档工具。所以在创建程序后我会做类似
"./bar -c test_archive.bar file.1"
它将创建一个 test_archive.bar,其中包含 file.1。然后我可以执行一些命令,列出其中的文件等。但是我无法理解制作 test_archive.bar 的概念,我意识到它本质上只是一个文件,但如果你要说打开一个 .tgz "vi file.tgz" 它会在里面给出 directories/files 的列表,
那么,有什么好的方法可以创建一个 archive/directory,我可以在其中推断一些文件并列出它们的名称等。
注意:我查看了 tar.c 以及其中包含的所有文件,但每个文件都非常抽象,很难理解。
注意:我知道如何阅读命令行标志等
使用旧的(但仍然有效)tar 格式实际上很容易做到。 Wikipedia has a nice explanation of the format here.您需要做的就是:
对于每个文件:
- 填写并发出 header 到 tar 文件
- 发出文件内容
- 将文件大小填充为 512 字节的倍数
tar 文件最基本的有效 header 是:(基本上从维基百科复制)
- 100 字节:文件名
- 8 字节:文件模式
- 8 字节:所有者的数字 ID
- 8 字节:组的数字 ID
- 12 字节:文件大小
- 12字节:最后修改时间的时间戳
- 8 字节:校验和
- 1字节:文件类型
- 100 字节:linked 文件的名称
文件类型可以是 0(普通文件)、1(硬 link)或 2(symlink)。 linked 文件的名称是link 指向的文件的名称。如果我没记错的话,如果你有硬link或符号link,文件内容应该是空的。
引用维基百科:
"Numeric values are encoded in octal numbers using ASCII digits, with leading zeroes. For historical reasons, a final NUL or space character should be used."
"The checksum is calculated by taking the sum of the unsigned byte values of the header record with the eight checksum bytes taken to be ascii spaces (decimal value 32). It is stored as a six digit octal number with leading zeroes followed by a NUL and then a space."
这是一个简单的 tar 球发生器。创建提取器、处理自动文件馈送等,留作 reader.
的练习。#include<stdio.h>
#include<string.h>
struct tar_header{
char name[100];
char mode[8];
char owner[8];
char group[8];
char size[12];
char modified[12];
char checksum[8];
char type[1];
char link[100];
char padding[255];
};
void fexpand(FILE* f, size_t amount, int value){
while( amount-- ){
fputc( value, f );
}
}
void tar_add(FILE* tar_file, const char* file, const char* internal_name){
//Get current position; round to a multiple of 512 if we aren't there already
size_t index = ftell( tar_file );
size_t offset = index % 512;
if( offset != 0 ){
fexpand( tar_file, 512 - offset, 0);
}
//Store the index for the header to return to later
index = ftell( tar_file );
//Write some space for our header
fexpand( tar_file, sizeof(struct tar_header), 0 );
//Write the input file to the tar file
FILE* input = fopen( file, "rb" );
if( input == NULL ){
fprintf( stderr, "Failed to open %s for reading\n", file);
return;
}
//Copy the file content to the tar file
while( !feof(input) ){
char buffer[2000];
size_t read = fread( buffer, 1, 2000, input );
fwrite( buffer, 1, read, tar_file);
}
//Get the end to calculate the size of the file
size_t end = ftell( tar_file );
//Round the file size to a multiple of 512 bytes
offset = end % 512;
if( end != 0 ){
fexpand( tar_file, 512 - offset, 0);
}
//Fill out a new tar header
struct tar_header header;
memset( &header, 0, sizeof( struct tar_header ) );
snprintf( header.name, 100, "%s", internal_name );
snprintf( header.mode, 8, "%06o ", 0777 ); //You should probably query the input file for this info
snprintf( header.owner, 8, "%06o ", 0 ); //^
snprintf( header.group, 8, "%06o ", 0 ); //^
snprintf( header.size, 12, "%011o", end - 512 - index );
snprintf( header.modified, 12, "%011o ", time(0) ); //Again, get this from the filesystem
memset( header.checksum, ' ', 8);
header.type[0] = '0';
//Calculate the checksum
size_t checksum = 0;
int i;
const unsigned char* bytes = &header;
for( i = 0; i < sizeof( struct tar_header ); ++i ){
checksum += bytes[i];
}
snprintf( header.checksum, 8, "%06o ", checksum );
//Save the new end to return to after writing the header
end = ftell(tar_file);
//Write the header
fseek( tar_file, index, SEEK_SET );
fwrite( bytes, 1, sizeof( struct tar_header ), tar_file );
//Return to the end
fseek( tar_file, end, SEEK_SET );
fclose( input );
}
int main( int argc, char* argv[] ){
if( argc > 1 ){
FILE* tar = fopen( argv[1], "wb" );
if( !tar ){
fprintf( stderr, "Failed to open %s for writing\n", argv[1] );
return 1;
}
int i;
for( i = 2; i < argc; ++i ){
tar_add( tar, argv[i], argv[i] );
}
//Pad out the end of the tar file
fexpand( tar, 1024, 0);
fclose( tar );
return 0;
}
fprintf( stderr, "Please specify some file names!\n" );
return 0;
}
So, are there any good ways to go about creating a archive/directory in which i can extrapolate some files within and list their names etc..
基本上有两种方法:
一个接一个地复制文件内容,每个文件内容都以 "header" 块为前缀,包含有关文件名、大小和(可选)其他属性的信息。 Tar 就是一个例子。示例:
一个接一个地复制文件内容并放在某处(在开头或结尾)"index",其中包含文件名及其大小和(可选)其他属性的列表。当您查看文件大小时,您可以计算单个文件的位置 begin/end.
大多数现实世界的存档器都使用这些组合,并添加其他功能,例如校验和、压缩和加密。
例子
假设我们有两个名为 hello.txt
的文件包含 Hello, World!
(12 字节)和 bar.txt
包含 foobar
(6 字节)。
在第一种方法中,存档看起来像这样
[hello.txt,12][Hello, World!][bar.txt,6][foobar]
^- fixed size ^- 12 bytes ^- fixed size ^- 6 bytes
header 块的长度必须是常数,或者您必须在某处编码它们的长度。
秒:
[Hello, World!foobar][hello.txt,12,bar.txt,6]
^- 12+6 bytes