通過前面兩篇博文,我們對ext2fs應該有了一個宏觀上的認識。但是這些所謂的superblock、block、group、group descriptor和ionde等等,它們到底有什麼用呢?今天我們簡單熱個身,來研究一下在一個磁盤分區上如何根據文件的inode號來訪問文件的內容?
在我們將某個分區格式化成ext2/ext3文件系統時,block的大小一定是確定的,即使用戶沒有手工指定,block也會有個缺省值。在分區總大小一定的情況,每個分區所包含的block數也就固定了,通過superblock我麼可以知道分區中一共有多個少group以及每個group中所包含的block數。
當我們拿到一個inode號時,首先要確定該inode屬於哪個group,即計算出該inode所在的group號,然後在組描述符數組中找到該group的描述信息,進而就可以獲取該inode所表示的文件內容了。
我們設計的結構體信息如下:
struct fdata { unsigned long inode_num; //用戶輸入的inode號 unsigned long i_blk_size; //block大小,從superblock裏獲取 unsigned int i_grp_num; //該inode 所在group號 unsigned long i_nt_blk_num; //每個group中inode table所在的第一個block號 struct ext2_super_block sb; //superblock的拷貝 struct ext2_group_desc gd; //group descriptor的拷貝 struct ext2_inode i_data; //inode的拷貝 }; |
最終的測試代碼如下rd_file_by_inode.c:
#include #include #include #include #include #include #include #include
#define EXT2_SB_SIZE 1024 //ext2文件系統superblock的大小
struct fdata { unsigned long inode_num; //inode number unsigned long i_blk_size; //block size unsigned int i_grp_num; //group number to which inode belongs unsigned long i_nt_blk_num; struct ext2_super_block sb; struct ext2_group_desc gd; struct ext2_inode i_data; };
//獲取文件系統的block大小,並計算inode所在的group號 int get_blk_size(int fd,int offset,struct fdata* ret){ if(-1 == fd){ printf("file descriptor invalid!\n"); return 0; }
char *buf=(char*)malloc(EXT2_SB_SIZE);
if(NULL == buf){ printf("can't alloc memory!\n"); return 0; } memset(buf,0, EXT2_SB_SIZE); if(-1 == lseek(fd,offset,0)){ printf("lseek error!\n"); return 0; }
if(read(fd,buf, EXT2_SB_SIZE)<0){ printf("read file error!\n"); return 0; } //將superblock的信息複製一份到fdata.sb裏。 memcpy((char*)&(ret->sb),buf, EXT2_SB_SIZE); //還記得如何根據superblock計算block的真實大小麼? ret->i_blk_size = 1<<(ret->sb.s_log_block_size+10); /*我們知道inode號在一個分區上是全局唯一且順序增長的,每個group中所包含的inode總數也是固定, 所以根據inode號就可以求得其所在group號。group可是從0開始編號的哦。*/ ret->i_grp_num = ret->inode_num/ret->sb.s_inodes_per_group;
free(buf); buf=NULL;
return 1; }
//獲取inode所在group的組描述符。 int get_grp_descriptor(int fd,int offset,struct fdata* ret){ int ds_size = sizeof(struct ext2_group_desc);
char *buf=(char*)malloc(ds_size); lseek(fd,offset+ret-> i_grp_num *ds_size,0);
read(fd,buf,ds_size); //將inode所在的組的組描述符複製一份到fdata.gd裏。 memcpy((char*)&(ret->gd),buf,ds_size); //inode所在組中,inode在該組裏所佔的第一個block號。注意,inode有可能會連續佔據好幾個block,這一點我們前面也瞭解到。 ret->i_nt_blk_num=ret->gd.bg_inode_table;
free(buf); buf=NULL; return 1; }
//這裏纔是根據inode號讀取該inode本身的128字節的數據。 int get_inode(int fd,int offset,struct fdata* ret){ int inode_size = sizeof(struct ext2_inode); /*知道了block大小,以及inode所佔block的起始號,inode大小也知道,那麼根據inode號就可以直接定位到所要操作的inode在 磁盤分區上的偏移量,如下。而inode是從1開始編號,所以計算偏移量要留心。*/ offset=ret->i_nt_blk_num*ret->i_blk_size+(ret->inode_num-1)*inode_size;
char *buf=(char*)malloc(inode_size); lseek(fd,offset,0); read(fd,buf,inode_size);
memcpy((char*)&(ret->i_data),buf,inode_size);
free(buf); buf=NULL; return 1; }
/*拿到inode裏的信息後,我們就可以獲取其所表示的文件的內容數據了。 這裏我用了遞歸算法來讀取各級block數據指針所指向的block中的內容。 算法有待優化,呵呵。*/ int read_data(int fd,int block_s,int dblock_num,int fsize,int level,char* dbuf){ int offset=0,rdbytes,left; char *buf=(char*)malloc(block_s); lseek(fd,block_s*dblock_num,0); read(fd,buf,block_s); unsigned int *pdblk=(unsigned int *)buf; while(fsize>0 && ((char*)pdblk-buf) if(level > 2){ rdbytes=read_data(fd,block_s,*pdblk,fsize,level-1,dbuf+offset); }else{ rdbytes=(fsize >= block_s? block_s:fsize); lseek(fd,(*pdblk)*block_s,0); read(fd,dbuf+offset,rdbytes); } offset += rdbytes; fsize -= rdbytes; pdblk++; } free(buf); return offset; }
//對上面讀取inode中數據block的函數進行一次封裝,如下: int get_data(int fd,struct fdata* ret,char* output_fileName){ int rdbytes,offset=0,i=0,file_size = ret->i_data.i_size,Blk; int blk_size = ret->i_blk_size; char *buf=(char*)malloc(file_size); memset(buf,0,file_size);
while(file_size>0){ rdbytes=(file_size >= blk_size? blk_size:(file_size)); if(i<12){ //讀取數據的直接block指針所指向的數據塊裏的內容 lseek(fd,ret->i_data.i_block[i]*blk_size,0); Blk=read(fd,buf+offset,rdbytes); }else if(i == 12){ //兩級block數據指針 Blk=read_data(fd,blk_size,ret->i_data.i_block[i],file_size,2,buf+offset); }else if(i == 13){//三級block數據指針 Blk=read_data(fd,blk_size,ret->i_data.i_block[i],file_size,3,buf+offset); }else{//四級block數據指針 Blk=read_data(fd,blk_size,ret->i_data.i_block[i],file_size,4,buf+offset); } offset += Blk; file_size -= Blk; i++; }
int fdo = open(output_fileName,O_CREAT|O_WRONLY,0777); write(fdo,buf,ret->i_data.i_size); close(fdo); return 1; } int main(int argc,char** argv){ int fd = -1; struct fdata mf_data;
if(4 != argc){ printf("Usage: %s /dev/partationLabel inode_number output_fileName \n"); return 0; }
if(-1 == (fd=open(argv[1],O_RDONLY,0777))){ printf("open file error!\n"); return 1; }
mf_data.inode_num = atol(argv[2]); if(!get_blk_size(fd, EXT2_SB_SIZE,&mf_data)){ printf("get superblock failed!\n"); close(fd); return 1; }
get_grp_descriptor(fd,mf_data. i_blk_size,&mf_data); get_inode(fd,0,&mf_data); get_data(fd,&mf_data,argv[3]);
printf("inode : %d\n",mf_data.inode_num); printf("block size: %d\n",mf_data.i_blk_size); printf("file size : %d Byte(s)\n",mf_data.i_data.i_size);
close(fd); return 0; } |
編譯:
我的一塊虛擬硬盤/dev/hdd1只有一個分區,格式是ext2掛載在/mnt/ided目錄下。該分區裏有幾個文件,如下所示。
然後我們依次執行如下命令:
根據文件的inode號分別讀取/mnt/ided目錄下的bzImage(大小bzImage字節),klinux-2.6.18.tar.gz(大小156992521字節)和VMwareTools-7.8.6-185404.i386.rpm(大小102939982字節)的內容。驗證一下我們讀取到的文件內容是否正確:
md5sum算出來的摘要一模一樣。有些童鞋可能心裏犯嘀咕:“MD5摘要加密算法不是早在2005年就已經被山東大學王小云教授的團隊破解了,證明是不可靠的麼?那麼md5sum命令的輸出結果會不會是忽悠人呢?”
懷疑纔會使人進步和成長。那麼下面這個操作肯定100%值得信賴:
我們把源文件和我們通過inode讀取到的文件內容,以十六進制形式輸出,然後再比較看其是否差異。理論和實際均證明我們的算法是正確的。
雖然文件系統不是這樣來根據inode讀取文件內容的,但是我們自己通過一番摸索,對文件系統中的各種術語和名詞以及它們的作用的認識和掌握又加深了一個層次。本文沒啥技術含量,但對編程基本功底是個鍛鍊。感興趣的朋友可以將我上面讀取文件內容的“醜陋”代碼優化一下吧。