相信你對 linux 的?.tar.gz?有點熟悉,這就是先 tar 打包(.tar 后綴),再對此?tar?文件用?gzip?壓縮(.tar.gz)的后綴名。
值得注意的是,?tar?不是壓縮軟件,它只做把一堆文件/文件夾打包到一個文件(tar 文件)里的事情,而文件聯系,文件權限,相對的路徑等都會給你保存好。一開始設計是?tar?跟?gzip?只做一件事情,各司其事,后來發現太麻煩了,于是就把壓縮功能整合到 tar 里了。
-?Create?a?gzipped?archive:
tar?czf?target.tar.gz?file1?file2?file3
最近學習 OS 時寫了一個類似?tar?的項目,那么今天就趁熱打鐵簡單說一下如何寫一個打包軟件,這個軟件會將重復的文件內容通過 md5 比較,復用舊的內容。
基本單位 block
block?可以理解為文件系統的最小單位,分別有以下類型:
?directory block,文件夾 block,存儲文件夾 meta 信息;
?file block,文件 block,存儲文件 meta 信息;
?data block,只用來存文件內容;
Directory block,注意的是 entry 里要有 fileindex 來存儲重復文件的 name 的下標。同時,給 項目一個 root dir。
typedef?struct?{
char????????name[SIFS_MAX_NAME_LENGTH];?//?name?of?the?directory
time_t????????modtime;????//?time?last?modified?
uint32_t????????nentries;//?文件夾內的文件/文件夾數量
struct?{
SIFS_BLOCKID????blockID;????//?subdirectory?或者?file?的?blockID
uint32_t????fileindex;????//?重復文件的不同名字
}?entries[SIFS_MAX_ENTRIES];
}?SIFS_DIRBLOCK;
文件 Block,length?就是有多少 bytes 的文件內容,之后用來算有多少個?data block,firstblockID?記錄第一個數據 block 的 id,nfiles?記錄有多少重復內容的文件數量了,filenames?就是重復此文件 block 的文件內容的文件名字。
typedef?struct?{
time_t????????modtime;????//?time?first?file?added?
size_t????????length;????????//?length?of?files'?contents?in?bytes
unsigned?char????md5[MD5_BYTELEN];//the?MD5?cryptographic?digest?(a?summary)?of?the?files'?contents
SIFS_BLOCKID????firstblockID;//?the?block?number?(blockID)?of?the?files'?first?data-block
uint32_t????????nfiles;????????//?n?files?with?identical?contents
char????????filenames[SIFS_MAX_ENTRIES][SIFS_MAX_NAME_LENGTH];//?an?array?of?each?same?file's?name?and?its?modification?time.
}?SIFS_FILEBLOCK;
bitmaps數組,記錄了每個 block 的類型,有:文件、文件夾以及data block 三種類型。
通用函數
就讓大家看看關鍵函數好了:
讀 tar 后的文件的 meta 頭,記錄了 block 的大小( blocksize) 以及多少個 blocks。
void?read_vol_header(FILE?*vol,?SIFS_VOLUME_HEADER?*header){
fread(header,?sizeof(SIFS_VOLUME_HEADER),?1,?vol);
printf("header->blocksize?%zu,?header->nblocks?%u\n",?header->blocksize?,?header->nblocks);
}
bitmap,每次操作 tar 文件都要讀的。
void?read_bitmap(FILE?*vol,?SIFS_BIT?*bitmap,?int?nblocks){
int?size?=?nblocks?*?sizeof(SIFS_BIT);
fread(bitmap,?size,?1,?vol);
}
root_block?同理,讀和寫啥東西都要從 root block、root dir 出發。
void?read_root_block(FILE?*vol,?SIFS_DIRBLOCK?*dirblock){
fread(dirblock,?sizeof(SIFS_DIRBLOCK),?1,?vol);
printf("read_root_block?finish,?dirblock.name:?%s,?dirblock.entrieds:?%d,?dirblock.modtime?%ld\n",?dirblock->name,?dirblock->nentries,dirblock->modtime);
}
路徑嘛,你懂的,./sifs_put volumn ~/res.txt /dirB/subdirB/subsubdir/newfileB,要讀的內容可以靠 read 函數解決,但是寫到 tar 文件里的就要手動解析遞歸查路徑了。
void?read_route_names(char*?pathname,?char**?route_names,?int?*route_cnt)?{
char?*dir;
char?*pathname_to_split?=?copyStr(pathname);
strcpy(pathname_to_split,?pathname);
while?((dir?=?strsep(&pathname_to_split,?"/"))?!=?NULL)?{
route_names[*route_cnt]?=?copyStr(dir);
(*route_cnt)++;
}
}
以上幾乎是?mkdir,rmdir,writefile,readfile,putfile?等等操作都要做的。
實現
然后,應該舉一個 readfile 的例子就可以做代表了。
int?recursive_dirinfo(SIFS_DIRBLOCK?*cur_dir_block,?char?**route_names,?int?route_name_p,?int?route_cnt);
實現:
int?recursive_dirinfo(SIFS_DIRBLOCK?*cur_dir_block,?char?**route_names,?int?route_name_p,?int?route_cnt){
for(int?i=0;?inentries?;?i++)?{
int?blockid?=?cur_dir_block->entries[i].blockID;
if(bitmap[blockid]==SIFS_DIR)?{
SIFS_DIRBLOCK?dirblock;
int?start?=?sizeof(SIFS_VOLUME_HEADER)?+?header.nblocks*sizeof(SIFS_BIT);
read_dir_block(vol,?&dirblock,?blockid?*?blocksize,?start);
if(strcmp(dirblock.name,?route_names[route_name_p])?==?0)?{
if(route_name_p+2?==?route_cnt)?{
return?do_read_file(cur_dir_block,?route_names[route_name_p+1],?blockid);
}
return?recursive_dirinfo(&dirblock,?route_names,?route_name_p+1,?route_cnt);
}
}
}
return?1;
}
以``./sifs_put volumn ~/res.txt /dirB/subdirB/subsubdir/newfileB?為例子,如果遞歸找到?subsubdir`這個文件夾 block,進行相應操作:
?寫文件就往 bitmap 一直找沒有用過的 block,夠寫文件就寫進去,文件夾更新一下信息。
?讀文件就是根據此文件夾 block,找里面的?newfileB
int?do_read_file(SIFS_DIRBLOCK?*parent_dir,?char?*filename,??int?parent_dir_block){
printf("do_find_file_info,?filename?%s\n",?filename);
for(int?i=1;?i
SIFS_FILEBLOCK?fileblock;
if(bitmap[i]==SIFS_FILE)?{
int?start?=?sizeof(SIFS_VOLUME_HEADER)?+?header.nblocks*sizeof(SIFS_BIT);
read_file_block(vol,?&fileblock,?i?*?blocksize,?start);
*nbytes?=?fileblock.length;
int?need_data_blocks?=?*nbytes?/?header.blocksize;
if(strcmp(fileblock.filenames[0],??filename)?==?0)?{
for(int?d_block_id?=?fileblock.firstblockID;?d_block_id?-?i?-1?
read_data_block(vol,?(char*)(*data)+(d_block_id?-?i?-1),?blocksize,?d_block_id?*?header.blocksize,?start);
}
return?0;
}
}
}
return?1;
}
而真實的 tar 自然更復雜,還要記錄用戶權限、用戶、group文件等等:
struct?posix_header
{???????????????????????/*?byte?offset?*/
char?name[100];???????/*???0?*/???文件名
char?mode[8];?????????/*?100?*/???用戶權限
char?uid[8];??????????/*?108?*/???user?id
char?gid[8];??????????/*?116?*/???group?id
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?*/???user?name
char?gname[32];???????/*?297?*/???group?name
char?devmajor[8];?????/*?329?*/???設備文件?major
char?devminor[8];?????/*?337?*/???設備文件?minor
char?prefix[155];?????/*?345?*/
/*?500?*/
};
文件類型標志定義,包含了所有?Unix?系統中的文件類型
#define?REGTYPE??'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?*/
概覽如此,寫起來其實有點煩 - = -,有興趣的讀者可以寫寫。