通过前面两篇博文,我们对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读取文件内容的,但是我们自己通过一番摸索,对文件系统中的各种术语和名词以及它们的作用的认识和掌握又加深了一个层次。本文没啥技术含量,但对编程基本功底是个锻炼。感兴趣的朋友可以将我上面读取文件内容的“丑陋”代码优化一下吧。