需要了解ISO9660文件系统的规范。
总体情况
首先我们先要有一个mkisofs这样的制作iso文件的工具,用它来制作一个iso文件,里面放上几个非空文件(至于为什么非空,最后会有介绍)
然后再有一个hexdump这样二进制文件查看器
先来看一下这个表格
先看第一行,说的是前 32768个字节被保留,十进制不好表示,换成十六进制就是0x8000
照此我们先用hexdump看一下我的mkisofs生成的iso文件
hexdump -C os.iso | less
正好符合,前0x8000字节都是0,不过貌似这是未使用而不是保留,所以如果你的工具往里面写点什么也应该可以
向下看是Data Area,先看第一个部分Volume Descriptor
Volume Descriptor
这是一个 Volume Descriptor的基本模板
第一个字节是Type,来看一下取值范围
255(0xff)表示这是最后一个Volume Descriptor,4-254都是保留,剩下的我们只打算看 Primary Volume Descriptor,其他的都无视掉
接下来是Identifier,五个字节,总是"CD001",我们写程序读取的时候就可以判断,不是就说明出错了
然后版本,一个字节,无视掉
剩下的就是最重要的数据区了,对于不同的Type有不同的数据,Volume Descriptor Set Terminator只是一个标记,它没有数据
所以我们就很清楚了,我们只用看一下Primary Volume Descriptor的数据区就好了
Primary Volume Descriptor
这个表格非常长,很多我们都用不到,我们只用到一个信息,我们也只贴出这一个
偏移是156,长度是34(0x22),根目录的入口,这是一个什么格式呢?这就是我们今天新出现的最后一个,也是最重要的一个格式
Directories
这个结构既可以代表目录,也可以代表文件
鉴于我们现在的这个结构来自Volume Descriptor中的,它代表ROOT目录,所以我们就不用判断Flags而直接认定就是文件夹了
看一下hexdump的输出:
开始地址是第一行的22那个地方,表明有0x22个字节,正好34个,与我们之前的描述相符
看一下"File identifier",就是这个文件的名称,这里是"/",前一个字节表示长度
然后再看一下"Location of extent (LBA) in both-endian format",有8个字节,前四个字节是小端的LBA号,后四个字节是大端的LBA号。注意:LBA号(逻辑扇区号)。
因为我们后面要用C语言读取,所以当然考虑小端而不是大端,尽管大端更符合人类思维.....
这里是18 00 00 00 00 00 00 18,表明LBA号是18
对于目录,这里指向的LBA里储存的数据是一个这个结构的数组,对于文件,当然储存的是文件内容了
计算一下偏移 24 * 2048 = 0x18 * 0x800 = 0xc000
再看一下:
第一个的大小是0x90,它的LBA指向是0x18,我没有仔细研究过这种指向的LBA反而小于等于自身LBA的这种结构
猜测可能是类似"/""."".."之类的"目录",由于我们并不需要把这些打印出来,而且打印如果不加判断可能会无限递归,我们先将其略过
说了这是一个数组,所以直接找到偏移0x90 + 0x00处的地方,发现指向的LBA仍然是0x18,继续略过,几次之后找到了一个文件
起始地址是第一行的84那里,然后往下找,找到"1b 00 00 00 00 00 00 1b",这就是刚刚的"回文"LBA号
由于这是一个文件,说明这个文件内容的LBA号就是0x1b
"00 02 00 00 00 00 02 00"是"回文"的大小,表明这个文件大小是0x200,就是512个字节
然后第三行倒数第二个字节,0a表示的是这个文件名的长度
往后的0a个字节都是这个文件名 "BOOT.BIN;1"
(ISO储存文件会自动把文件后面加上;1,并且小写转换成大写,这部分我们要自己处理,文件夹不会加上;1)
遍历所有文件并打印
接下来我们就要把以上讨论的问题转化为代码了,先来看第一部分,寻找Primary Volume Descriptor
static int parseISO9660FileSystem(IDEDevice *device)
{
/*See also http://wiki.osdev.org/ISO_9660.*/
/*And http://en.wikipedia.org/wiki/ISO_9660.*/
u8 *buf8 = (u8 *)ideIOBuffer;
/*System Area (32,768 B) Unused by ISO 9660*/
/*32786 = 0x8000.*/
u64 lba = (0x8000 / 0x800); /*从0x8000的地方开始寻找.*/
for(;;++lba)
{
if(ideRead(device,lba,ATAPI_SECTOR_SIZE,buf8))
return -1; /*It may not be inserted if error.*/
/*Identifier is always "CD001".*/
if(buf8[1] != 'C' ||
buf8[2] != 'D' ||
buf8[3] != '0' ||
buf8[4] != '0' ||
buf8[5] != '1' ) /*判断CD001 不符合直接返回.*/
return -1;
if(buf8[0] == 0xff) /*Volume Descriptor Set Terminator.*/
return -1; /*如果这是最后一个 也返回.*/
if(buf8[0] != 0x01 /*Primary Volume Descriptor.*/)
continue; /*不是Primary Volume Descriptor就继续*/
/*Directory entry for the root directory.*/
if(buf8[156] != 0x22 /*Msut 34.*/)
return -1; /*看前边,这里必须是34个字节.*/
/*Location of extent (LBA) in both-endian format.*/
lba = *(u32 *)(buf8 + 156 + 2);
break; /*读LBA,跳出.*/
}
return parseISO9660FileSystemDir(device,lba,buf8,0); /*分析ROOT目录里的文件.*/
}
再来看一下parseISO9660FileSystemDir,这是一个简单的递归函数
static int parseISO9660FileSystemDir(
IDEDevice *device,u64 lba,u8 *buf8,int depth)
{
u64 offset = 0; /*要读的文件(夹)的信息结构的偏移.*/
u8 isDir = 0; /*是否是文件夹.*/
u8 needRead = 0; /*需不需要重新读.*/
if(ideRead(device,lba,ATAPI_SECTOR_SIZE,buf8))
return -1; /*读取失败直接返回.*/
for(;;offset += buf8[offset + 0x0] /*Length of Directory Record.*/)
{
while(offset >= ATAPI_SECTOR_SIZE)
{
offset -= ATAPI_SECTOR_SIZE;
++lba;
needRead = 1;
/*Read again.*/
} /*偏移超出这个扇区的范围就修正一下.*/
if(needRead)
if(ideRead(device,lba,ATAPI_SECTOR_SIZE,buf8))
return -1; /*如果修正了就再读一次.*/
needRead = 0;
if(buf8[offset + 0x0] == 0x0) /*No more.*/
break; /*大小是0说明结束了,退出.*/
/*Location of extent (LBA) in both-endian format.*/
u64 fileLBA = *(u32 *)(buf8 + offset + 0x2);
if(fileLBA <= lba)
continue; /*获取文件LBA,小于就继续吧.*/
int __depth = depth;
while(__depth--)
printk("--"); /*形成一个视觉效果.*/
isDir = buf8[offset + 25] & 0x2; /*Is it a dir?*/
u64 filesize = *(u32 *)(buf8 + offset + 10);
/*Length of file identifier (file name).
* This terminates with a ';' character
* followed by the file ID number in ASCII coded decimal ('1').*/
u64 filenameLength = buf8[offset + 32];
if(!isDir) /*如果是文件就要删掉最后的";1".*/
filenameLength -= 2; /*Remove ';' and '1'.*/
char filename[filenameLength + 1]; /*Add 1 for '\0'.*/
memcpy((void *)filename,
(const void *)(buf8 + offset + 33),
filenameLength); /*把文件名复制过来.*/
if((!isDir) && (filename[0] == '_'))
filename[0] = '.';
if((!isDir) && (filename[filenameLength - 1] == '.'))
filename[filenameLength - 1] = '\0';
else
filename[filenameLength] = '\0'; /*做一些修正.*/
/*To lower.*/
for(u64 i = 0;i < filenameLength;++i)
if((filename[i] <= 'Z') && (filename[i] >= 'A'))
filename[i] -= 'A' - 'a'; /*全部转换成小写(好看一点).*/
if(isDir)
{
printk("LBA:%d.",(int)fileLBA);
printk("Dirname:%s\n",filename); /*打印出目录信息.*/
parseISO9660FileSystemDir(device,fileLBA,buf8,depth + 1); /*递归.*/
ideRead(device,lba,ATAPI_SECTOR_SIZE,buf8); /*上一个递归修改了buf8,重新读.*/
/*We must read again.*/
}
else
{
if(filesize != 0)
printk("LBA:%d.",(int)fileLBA);
else
printk("Null file,no LBA."); /*如果这个文件是空的,LBA地址无效.*/
/*这也就是刚开始时为什么要用非空文件的原因,空文件看不到正确的LBA地址.*/
/*LBA may not be right if this file is null!*/
printk("Filename:%s\n",filename);
}
}
return 0; /*返回......*/
}
参考资料
ISO9660 维基百科:http://en.wikipedia.org/wiki/ISO_9660
ISO9660 OSDEV百科:http://wiki.osdev.org/ISO_9660