前面贴了2篇关于FAT文件系统和硬盘物理结构的文章,下面我是我在那2篇文章的基础上总结出来的关于U盘的读写的程序的要点,这个程序实现这样的功能,把U盘里的DBR,保留扇区,加上FAT1,FAT2以及其根目录全部保存起来,然后格式化U盘后,可以恢复过来,并且文件所在的物理位置不变,也就是说,程序就相当于一个GHOST备份软件。
整体思路是这样的,把U盘里面的数据结构都读出来,存放到一个文件中。分析FAT表,当1簇有数据时就存储该簇数据,没数据时就不要存储。程序中具体的实现时用一个另一文件来存储FAT表,这样做的好处是不需要在以后来回折腾/dev/sdb1。
首先,必须获得U盘的数据结构呢,很显然在linux下面可以打开设备文件,一般是/dev/sdb,这里要注意的是当用fdisk -l 查看U盘时,我们发现的是/dev/sdb1,而使用/dev/sdb的原因是打开/dev/sdb打开的是整个U盘,而/dev/sdb1是对应的分区,由于U盘都只有一个区(想不通还会有人去把U盘分区的)。所以
if ((devp = fopen("/dev/sdb1", "r")) == NULL)
{
PDEBUG("cann't read /dev/sdb1");
return -1;
}
有3种方法判断是不是一个FAT32程序,1个是从DPT中查看系统ID来判断,这个需要打开/dev/sdb。另一个是从扩展BPB的系统ID来判断,还一个就是根据数据区有多少簇来判断,当小于4085为FAT12,小于65525为FAT16,剩下的就是FAT32了。严格来说,最后一种方法是前面2中方法的依据。也就是是说前面的系统ID都是根据后面来设置的。
其次要注意的是读写必须以一个扇区为单位,扇区大小也是根据磁盘的容量来确定的,前面的文章中已经说过。对于U盘来说,一般都是512。还需注意的是对于U盘里面的数据,都必须用无符号数据类型来表示,当遇到数据时,虽然单位改成了簇,但是读写还是以扇区来的,也就是一次512个字节。
比如定义unsigned char buffer[512]用来读写数据。
第二个需要注意的,这里面都是用小端表示法来表示数据的。尤其是分析读出来数据时候要特别注意。
当按照顺序一路存储了DBR,保留扇区,FAT1, FAT2后就是正式的数据了(目录和文件)。到了这里以后,就要考虑以簇为单位存储了。因为FAT表存储了各个簇之间的关系。因此程序遍历FAT表中数据开始处到结束处(也就是磁盘末端)的所有簇,如果该簇有数据则先存储其簇号,再存储其簇的数据。核心代码如下,其中i代表的是簇号
/*we come to the data which stored in a clustor at a time */
i = 2; j = 0;
for(i = 2; i < CountofClustor; i++){
FATsecNum = (i * 4) / 512;
FATEntoffset = (i * 4) % 512;
fseek(FAT_table_p, FATsecNum * 512 + FATEntoffset, SEEK_SET);
if ((num = fread(&int_buffer, sizeof(int), 1, FAT_table_p)) != 1)
return -1;
if (int_buffer == 0x0 || (int_buffer <= 0x0FFFFFF7 && int_buffer >= 0x0FFFFFF0))
{
// PDEBUG("clustor%d need not to save!/n", i);
continue;
}
/*we need to store which clustor is need to stored*/
fwrite(&i, sizeof(int), 1, saveP);
PDEBUG("the int_buffer is %x/n", int_buffer);
/*we store the data of the cluster*/
for (j = 0; j < Sector_perCluster; j++){
if ((num = fread(buffer, sizeof(char), 512, devp)) < 512)
return -1;
if ((num = fwrite(buffer, sizeof(char), 512, saveP)) < 512)
return -1;
}
}
这段代码需要注意的是
if (int_buffer == 0x0 || (int_buffer <= 0x0FFFFFF7 && int_buffer >= 0x0FFFFFF0))
在前面我贴上来的 《硬盘物理结构和FAT分析(二)》中有个错误,FAT记录项的取值错了。32位实际有效的只有28位。0x0代表未分配的簇,0x0FFFFFF0-0x0FFFFFF6为系统保留,0x0FFFFFF7表示坏簇,0x0FFFFFF7-0x0FFFFFFF是文件结束标志。
至于恢复数据则只要逆行就可以了,需要注意的是也就是数据区的存储,根据我们的存储,先存储簇号,再存储簇的数据,所以当我们恢复时,先读到的时簇号,这时就需要找到簇号所对应的物理位置,而这个只是一个简单的小学计算而已。代码如下:
/*now we come to the data area*/
index = pre_sectors + Reserved_Sector + sector_perFAT * Number_ofFAT;
while ((num = fread(&int_buffer, sizeof(int), 1, savep)) != 0){
fseek(devp, (index + (int_buffer - 2) * Sector_perCluster )* 512, SEEK_SET);
for (j = 0; j < Sector_perCluster; j++){
if ((num = fread(buffer, sizeof(char), 512, savep)) < 512)
return -1;
if ((num = fwrite(buffer, sizeof(char), 512, devp)) < 512)
return -1;
}
}