总体情况
首先我们先要有一个makeisofs这样的制作iso文件的工具,用它来制作一个iso文件,里面放上几个非空文件(至于为什么非空,最后会有介绍)
然后再有一个hexdump这样二进制文件查看器
先来看一下这个表格
ISO 9660 File System | |
---|---|
System Area (32,768 B) | Unused by ISO 9660 |
Data Area | |
Volume Descriptor (2,048 B) | |
Volume Descriptor (2,048 B) | |
... | |
Volume Descriptor Set Terminator (2,048 B) | |
Optional space | |
Root directory | |
Directories and files |
先看第一行,说的是前32768个字节被保留,十进制不好表示,换成十六进制就是0x8000
照此我们先用hexdump看一下我的mkisofs生成的iso文件
hexdump -C os.iso | less
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00008000 01 43 44 30 30 31 01 00 4c 49 4e 55 58 20 20 20 |.CD001..LINUX |
00008010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00008020 20 20 20 20 20 20 20 20 43 44 52 4f 4d 20 20 20 | CDROM |
00008030 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00008040 20 20 20 20 20 20 20 20 00 00 00 00 00 00 00 00 | ........|
正好符合,前0x8000字节都是0,不过貌似这是未使用而不是保留,所以如果你的工具往里面写点什么也应该可以
向下看是Data Area,先看第一个部分Volume Descriptor
Volume Descriptor
Volume Descriptor | ← 2,048 bytes → | |||
---|---|---|---|---|
Parts | Type | Identifier (always 'CD001') | Version (always 0x01) | Data (depends on type), can be subdivided into several fields |
Sizes | 1 byte | 5 bytes | 1 byte | 2,041 bytes |
这是一个Volume Descriptor的基本模板
第一个字节是Type,来看一下取值范围
Value | Description |
---|---|
0 | Boot Record |
1 | Primary Volume Descriptor |
2 | Supplementary Volume Descriptor |
3 | Volume Partition Descriptor |
4-254 | Reserved |
255 | Volume Descriptor Set Terminator |
255(0xff)表示这是最后一个Volume Descriptor,4-254都是保留,剩下的我们只打算看Primary Volume Descriptor,其他的都无视掉
接下来是Identifier,五个字节,总是"CD001",我们写程序读取的时候就可以判断,不是就说明出错了
然后版本,一个字节,无视掉
剩下的就是最重要的数据区了,对于不同的Type有不同的数据,Volume Descriptor Set Terminator只是一个标记,它没有数据
所以我们就很清楚了,我们只用看一下Primary Volume Descriptor的数据区就好了
Primary Volume Descriptor
这个表格非常长,很多我们都用不到,我们只用到一个信息,我们也只贴出这一个
Offset | Length (bytes) | Field name | Datatype | Description |
---|---|---|---|---|
156 | 34 | Directory entry for the root directory | - | Note that this is not an LBA address, it is the actual Directory Record, which contains a zero-length Directory Identifier, hence the fixed 34 byte size. |
偏移是156,长度是34(0x22),根目录的入口,这是一个什么格式呢?这就是我们今天新出现的最后一个,也是最重要的一个格式
Directories
Offset | Size | Description |
---|---|---|
0 | 1 | Length of Directory Record. |
1 | 1 | Extended Attribute Record length. |
2 | 8 | Location of extent (LBA) in both-endian format. |
10 | 8 | Data length (size of extent) in both-endian format. |
18 | 7 | Recording date and time (see format below). |
25 | 1 | File flags (see below). |
26 | 1 | File unit size for files recorded in interleaved mode, zero otherwise. |
27 | 1 | Interleave gap size for files recorded in interleaved mode, zero otherwise. |
28 | 4 | Volume sequence number - the volume that this extent is recorded on, in 16 bit both-endian format. |
32 | 1 | Length of file identifier (file name). This terminates with a ';' character followed by the file ID number in ASCII coded decimal ('1'). |
33 | (variable) | File identifier. |
(variable) | 1 | Padding field - zero if length of file identifier is odd, otherwise, this field is not present. This means that a directory entry will always start on an even byte number. |
(variable) | (variable) | System Use - The remaining bytes up to the maximum record size of 255 may be used for extensions of ISO 9660. The most common one is the System Use Share Protocol (SUSP) and its application, the Rock Ridge Interchange Protocol (RRIP). |
这个结构既可以代表目录,也可以代表文件
鉴于我们现在的这个结构来自Volume Descriptor中的,它代表ROOT目录,所以我们就不用判断Flags而直接认定就是文件加了
看一下hexdump的输出:
00008090 00 00 00 00 00 00 00 16 00 00 00 00 22 00 18 00 |............"...|
000080a0 00 00 00 00 00 18 00 08 00 00 00 00 08 00 71 0c |..............q.|
000080b0 09 01 04 2f 20 02 00 00 01 00 00 01 01 00 20 20 |.../ ......... |
开始地址是第一行的22那个地方,表明有0x22个字节,正好34个,与我们之前的描述相符
看一下"File identifier",就是这个文件的名称,这里是"/",前一个字节表示长度
然后再看一下"Location of extent (LBA) in both-endian format",有8个字节,前四个字节是小端的LBA号,后四个字节是大端的LBA号
因为我们后面要用C语言读取,所以当然考虑小端而不是大端,尽管大端更符合人类思维.....
这里是18 00 00 00 00 00 00 18,表明LBA号是18
对于目录,这里指向的LBA里储存的数据是一个这个结构的数组,对于文件,当然储存的是文件内容了
计算一下偏移 24 * 2048 = 0x18 * 0x800 = 0xc000
再看一下:
0000c000 90 00 18 00 00 00 00 00 00 18 00 08 00 00 00 00 |................|
0000c010 08 00 71 0c 09 01 04 2f 20 02 00 00 01 00 00 01 |..q..../ .......|
0000c020 01 00 53 50 07 01 be ef 00 52 52 05 01 81 50 58 |..SP.....RR...PX|
0000c030 2c 01 ed 41 00 00 00 00 41 ed 02 00 00 00 00 00 |,..A....A.......|
0000c040 00 02 e8 03 00 00 00 00 03 e8 e8 03 00 00 00 00 |................|
0000c050 03 e8 18 00 00 00 00 00 00 18 54 46 1a 01 0e 71 |..........TF...q|
0000c060 0c 09 01 04 2f 20 71 0a 06 03 39 2e 20 71 0c 09 |..../ q...9. q..|
0000c070 01 04 2f 20 43 45 1c 01 19 00 00 00 00 00 00 19 |../ CE..........|
第一个的大小是0x90,它的LBA指向是0x18,我没有仔细研究过这种指向的LBA反而小于等于自身LBA的这种结构
猜测可能是类似"/""."".."之类的"目录",由于我们并不需要把这些打印出来,而且打印如果不加判断可能会无限递归,我们先将其略过
说了这是一个数组,所以直接找到偏移0x90 + 0x00处的地方,发现指向的LBA仍然是0x18,继续略过,几次之后找到了一个文件
0000c0f0 0a 06 03 39 2e 20 71 0c 09 01 04 2f 20 00 84 00 |...9. q..../ ...|
0000c100 1b 00 00 00 00 00 00 1b 00 02 00 00 00 00 02 00 |................|
0000c110 71 0c 09 01 04 2e 20 00 00 00 01 00 00 01 0a 42 |q..... ........B|
0000c120 4f 4f 54 2e 42 49 4e 3b 31 00 52 52 05 01 89 4e |OOT.BIN;1.RR...N|
起始地址是第一行的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
转自 https://blog.csdn.net/goodqt/article/details/17202109