无厘头

凡是不确定的东西,你都会觉得难,但其实它可能根本就是无厘头!今天就遇到两件无厘头的事:

第一件是个很无厘头的数据损坏的bug。说它是无厘头,是因为还没搞清楚它为什么会发生,虽然找到了解决方案并且解决方案非常简单。问题大体如下:针对cached i/o,如果写数据超出文件大小,我们会为此文件再分配空间,然后直接调用CcCopyWrite交给Cache Manager来处理就行。这里会涉及文件大小的更改,问题就出在这里。以前的版本是在CcCopyWrite之前就直接将文件大小改好了,数据写入一切正常。后来觉得不妥当,遂改到调用CcCopyWrite之后根据返回结果来更改文件大小,结果就发生了数据的损坏。但研究来研究去,并没有发现CcCopyWrite函数有对文件大小有什么判断。

数独

第二件是一个数独游戏。以前也摆过这道题,但因为不确定性太多,无法下子,然后就不了了之。今天又摆上了,到后面只剩下了1,3,5和8,可能的摆法 很多,就随便摆了摆,结果就通了。呵呵,原来是怎么摆都成,而不是就只有唯一的一个确定的方案!这道题实际上是第一题,应该是最容易的。但我当初玩时却在想,为什么第一题这么难,倒是后面的比较容易,难就难在太纠结于不确定上面了。

FastIo

针对FastIoRead及FastIoWrite的实现,很多文件系统驱动都简单地调用系统提供的File System Runtime Routine: FsRtlCopyRead和FsRtlCopyWrite。

但这样做会有一个隐患,以Ext2Fsd来说,实际的文件大小是存储在inode.i_size中的,不是在FSRTL_COMMON_FCB_HEADER中(或FSRTL_ADVANCED_FCB_HEADER,前者超集)。Cache Manager内部只会处理FSRTL_COMMON_FCB_HEADER中的FileSize,从而导致Fcb中的FileSize和内部的inode.i_size大小不一致。这个 bug还是测试在EXT2卷上用DDK编译/build程序时发现的。

解决办法就是实现自己的FastIoWrite,如果写请求超出文件大小则拒绝此请求,然后走正常的IRP流程。其实,结合上篇blog: AdvanceOnly- FileEndOfFileInformation,还有另外一种方案,即在处理FileEndOfFileInformation w/ AdvanceOnly 时来更新内部的inode.i_size至最新大小,但此方法有待验证。

AdvanceOnly – FileEndOfFileInformation

IRP_MJ_SET_INFORMATION有个特殊的参数:AdvanceOnly,DDK中说明如下:

IrpSp->Parameters.SetFile.AdvanceOnly

Specifies conditions for the setting of each information value. If TRUE, each information value should be set only if greater than the current value. If FALSE, each information value should be set only if less than the current value.

实际实现中,此参数只针对FileEndOfFileInformation才有意义。FileEndOfFileInformation case是用来处理文件数据大小的,即更改文件数据长度。但当AdvanceOnly为TRUE的时候,情形并不如此,此时的真实目的是更新ValidDataLength (VDL),并不是EndOfFile,这是最容易让人误解的地方。

下面是一个典型的调用实例,堆栈分析如下 (XP, i386):

0: kd> kb
ChildEBP RetAddr  Args to Child             
ba4e3bbc b16dfd58 89a462c0 899f8ad8 ba4e3c0c Ext2Fsd!Ext2SetFileInformation+0x2a2
ba4e3bcc b16dfe67 89a462c0 0b2b27d2 899f8ad8 Ext2Fsd!Ext2DispatchRequest+0x9a
ba4e3c0c 804f0095 898d4af8 88b66c40 88b66c40 Ext2Fsd!Ext2BuildRequest+0x89
ba4e3c1c b9ed709e 8988b008 899de848 00000000 nt!IopfCallDriver+0x31
ba4e3c48 804f0095 899f8ad8 88b66c40 88b66c40 fltMgr!FltpDispatch+0x152
……
ba4e3cb4 804f0095 89a8ead8 88b66c40 89a8ead8 fltMgr!FltpDispatch+0x11f
ba4e3cc4 804e2f43 00000000 88cbd008 806e59f0 nt!IopfCallDriver+0x31
ba4e3cf8 804e59aa 88ccd4f8 ba4e3d20 80559690 nt!CcSetValidData+0xa5
ba4e3d34 804e8081 89e31098 80564720 89e31998 nt!CcWriteBehind+0x258
ba4e3d7c 805389bd 89e31098 00000000 89e31998 nt!CcWorkerThread+0x12f
ba4e3dac 805cf84c 89e31098 00000000 00000000 nt!ExpWorkerThread+0xef
ba4e3ddc 8054632e 805388ce 00000000 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

可以看出,调用者是Lazy-writer线程。CcWriteBehind将dirty cache写入磁盘后会调用CcStValidData来更新当前文件的ValidDataLength。Cache Manager在每个文件的SHARED_CACHE_MAP中在保存着此文件最大的ValidDataLength (变量ValidDataGoal,由脏页的最大偏移计算出),并在每次写入cache后告知文件系统。

NTFS文件系统中有VDL的概念,但FAT及EXT2都没有,只有AllocationSize及FileSize描述。Ext2Fsd是直接将VDL等同于FileSize来处理的。